Skip to content

Add published-type checks (attw + publint) to publish-check CIΒ #403

Description

@brianmhunt

Context

PR #402 makes TKO publish per-package .d.ts declarations for the first time. Once that lands, the published type/packaging surface becomes something we can regress silently β€” a future package could ship without a types entry, with a broken exports ordering, or with a declaration that doesn't resolve. We should guard it in CI.

This is a follow-up to #402 β€” the checks validate the types that #402 introduces, so this only makes sense once #402 is merged.

Proposal

Add two standard tools to the publish-check.yml workflow (which already runs on every PR to verify packages are publishable):

  • publint β€” validates package.json packaging correctness (exports condition ordering, files, etc.). Resolution-mode-agnostic, near-zero false positives. Straightforward win.
  • @arethetypeswrong/cli (attw --pack) β€” validates that published declarations actually resolve.

Optionally also wire both into the local verify script so they're caught before a PR is even pushed.

Important: scope attw to TKO's actual audience

TKO is a browser MVVM framework; its consumers use bundlers (Vite/webpack/esbuild/Rollup β†’ moduleResolution: bundler). The node16/nodenext TypeScript resolution mode is out of audience β€” a no-bundler Node+TS consumer is not a realistic TKO user.

Out of the box, attw fails on the node16/nodenext columns. We should add an .attw.json that ignores the node16-specific rules (e.g. false-cjs, false-esm, false-export-default, unexpected-module-syntax, internal-resolution-error, cjs-resolves-to-esm β€” exact names to be confirmed against attw --list-rules at implementation time) so the gate fails only on genuinely-relevant problems for bundler consumers (e.g. no-resolution, untyped-resolution). The ignored rules should be documented in .attw.json so the decision is explicit, not a silent suppression.

Evidence β€” current attw matrix (run locally on #402's branch)

Package node10 node16 (CJS) node16 (ESM) bundler
@tko/observable (CJS-default leaf) 🟒 🟒 🚭 UnexpectedModuleSyntax 🟒
@tko/utils (type:module) 🟒 πŸ‘Ί FalseESM + πŸ₯΄ InternalResolutionError πŸ₯΄ InternalResolutionError 🟒
@tko/build.knockout (build, .mjs) 🟒 ❗️ FalseExportDefault 🎭 FalseCJS 🟒

bundler is 🟒 everywhere β€” the audience that matters. The reds are confined to node16/nodenext, which is why they should be ignored rather than fixed.

Related (separate, pre-existing β€” not blocking this)

The 🚭 UnexpectedModuleSyntax on @tko/observable is a pre-existing JS packaging issue independent of types: dist/index.js contains ESM syntax but the package has no "type": "module", so Node treats it as CommonJS. It would affect a node16 ESM consumer at runtime. Orthogonal to the types work and to bundler consumers; worth its own issue if we ever decide to care about the Node-resolution path.

Acceptance

  • publint runs per publishable package in publish-check.yml
  • attw --pack runs per publishable package in publish-check.yml, with an .attw.json documenting the ignored node16/nodenext rules
  • The gate is green on the current package set (per the matrix above) and would fail a package that ships no/broken types
  • (optional) Both mirrored in the verify script for local pre-push
  • Confirm current tool versions at implementation time (latest at time of writing: @arethetypeswrong/cli 0.18.3, publint 0.3.21)

Depends on #402.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions