Repository navigation
Add published-type checks (attw + publint) to publish-check CI #403
Description
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— validatespackage.jsonpackaging 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
-
publintruns per publishable package inpublish-check.yml -
attw --packruns per publishable package inpublish-check.yml, with an.attw.jsondocumenting 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
verifyscript for local pre-push - Confirm current tool versions at implementation time (latest at time of writing:
@arethetypeswrong/cli0.18.3,publint0.3.21)
Depends on #402.
Activity
phillipc commentedon Jun 7, 2026
Good point, but I think we can also fix it. node16 (ESM) can be relevant in only tsc scenarios