Repository navigation
Commit 57ceb9d
feat(scripts): check-dts-references — catch a partially-emitted DTS pass at the build (#19434)
Part of #19402
Clause-②: no
`#19402` is a **composition gap**, not a defect in either guard, and
this PR does not change either one. It adds the third reading that was
missing between them.
## The seam, and the measurement that chose it
The card named the gap and deliberately declined to name the fix, so the
three candidate seams were measured rather than preferred.
**Candidate A — tsup's own emission record. Ruled out: there is none.**
On the installed tsup 8.5.1, `metafile` occurs **0** times in
`dist/rollup.js`, the DTS worker. Lit control on the same grep over the
same file: **16** `rollup`, **45** `dts` — so the zero is an absence the
instrument could see, not a grep that missed the file. `metafile` is an
esbuild-only option, written from `result.metafile` in `dist/index.js`
as `metafile-FORMAT.json`, and the JS pass is not the pass that can die
mid-emission. A record written by the DTS pass would in any case be
evidence from the run whose message-less death is the thing being
guarded: a pass that stops mid-emission does not write its closing
record either.
**Candidate B — derive the chunk set from the entry declarations' own
imports. Chosen.** rollup-dts writes every chunk reference into the file
that needs it, as an ordinary relative specifier (`import { X } from
'./view.zod-CIU5lVgU.js'`), so the set of files a build was supposed to
emit is the transitive closure of those references from the
manifest-declared declarations. Derived on every run, no number to
maintain. Measured on `packages/spec` at `a0e62e69` (2026-09-20T19:09Z):
| reading | value |
|---|---|
| manifest-declared roots | 34 |
| transitive closure of their relative references | **124** |
| declaration files actually on disk under `dist/` | **124** |
| emitted declarations OUTSIDE the closure (orphans) | **0** |
| relative references, all resolved | 366 |
| AST positions carrying a specifier | 318 import, 164 export, 0 of the
other four |
⭐ 0 orphans is the load-bearing number: on this package the closure is
not a sample of the emission, it **is** the emission — so a constant
like "there should be 46 chunks" buys nothing the derivation does not
already give, and costs a maintenance obligation.
**Candidate C — make the build's last step (`check-dev-prereqs.mjs
--stamp`) the owner. Ruled out on two measurements.** Population: **68**
package build scripts run `check-dts-emitted.mjs`; exactly **4** of them
are freshness AMPLIFIERS, the only packages where `--stamp` runs at all;
**12** of the 68 declare more than one entry point, which is the
chunk-capable set. So the stamp's population is the wrong one by
measurement. Subject: `--stamp` records which **inputs** a dist was
built from, and answering an emission question with an input digest is
the direction `#7122` already had rejected one artifact over.
**Why a build step and not the post-build sweep.**
`check-dts-closure.mjs` already sweeps the built workspace after the
closure build — and it shares `check-dts-emitted.mjs`'s manifest
criterion (it imports `declaredDeclarationPaths` from it), so it is
blind to chunks in exactly the same way, one instant later. Later is too
late for the reason `#11907` recorded: turbo caches only **successful**
tasks, so a build that exits non-zero never becomes a cache entry, while
a build that exits 0 over a partial emission is cached under the
ordinary hash and replayed by every later run in that worktree.
## Both directions, proved
Measured 2026-09-20T19:17–19:19Z in one worktree of `origin/main`
`a0e62e69`, every exit code captured before any pipe, `dist/` being
gitignored so sha256 is the restore proof.
**RED — 2 emitted chunk declarations moved aside**
(`dist/view.zod-CIU5lVgU.d.ts` 497,743 B and
`dist/object.zod-ThXvwL_J.d.ts` 401,806 B; mutation confirmed on disk,
62 `.d.ts` down to 60):
| instrument | reading |
|---|---|
| `node scripts/check-dts-emitted.mjs` | exit **0** — "34/34 declared
declaration file(s) present" |
| `distIsStale()` | **false** · `declarationStamp()` → **match** |
| `pnpm --filter @objectstack/spec check:api-surface` | exit **1** —
**233 breaking (removed/narrowed)**, 227 added, **229 removal lines** |
| `node scripts/check-dts-references.mjs` (this PR) | exit **1**, naming
every referrer and the path it looked for |
⭐ **Lit control on the same instrument and subject**: removing one
**declared entry** instead (`dist/meta-spelling/index.d.ts`) makes
`check-dts-emitted.mjs` exit **1** and name it. So its zero above is a
real coverage gap, not a dead instrument.
1 parent 23f1de0 commit 57ceb9d
3 files changed
Lines changed: 638 additions & 1 deletion
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
641 | 641 | | |
642 | 642 | | |
643 | 643 | | |
| 644 | + | |
| 645 | + | |
| 646 | + | |
| 647 | + | |
| 648 | + | |
| 649 | + | |
| 650 | + | |
| 651 | + | |
| 652 | + | |
| 653 | + | |
| 654 | + | |
| 655 | + | |
| 656 | + | |
| 657 | + | |
| 658 | + | |
644 | 659 | | |
645 | 660 | | |
646 | 661 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
243 | 243 | | |
244 | 244 | | |
245 | 245 | | |
246 | | - | |
| 246 | + | |
247 | 247 | | |
248 | 248 | | |
249 | 249 | | |
| |||
0 commit comments