test: guard exports targets against having no source entry - #740
Merged
sroussey merged 1 commit intoAug 10, 2026
Conversation
`ExportTypesPairing.test.ts` only compared a branch's `types` string to the implementation string beside it, so a branch could be internally consistent and still name files nothing builds. Copying another package's `browser` block into a manifest that has no `*.browser.ts` declares a matching `.d.ts`/`.js` pair for a build that does not exist, and every existing assertion passes. Two repo-wide assertions close that: - every `types` and implementation target must derive to a `src/<stem>.ts` (or `.tsx`) that exists; a target whose layout the derivation cannot describe is reported rather than skipped. - `types` must precede the implementation key in the same object, since Node stops at the first matching condition. The `src` correlation is a proxy for "the build emits this", not a check of each package's build-script entry list: a source file that exists but was never added to `build-code` still passes. It catches the copy-paste case. All 163 branches across the workspace pass both today, so no manifest changes. Also corrects three comments: a branch with no `types` is not typed by an outer one (resolution stops at the matched branch and TypeScript looks beside the resolved file), and `ALLOWED_MISMATCHES` silences missing declarations too, not just mismatched ones. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PDvMMv78PuEw4T5atLeJeS
Coverage Report
File CoverageNo changed files found. |
sroussey
merged commit Aug 10, 2026
d86050a
into
claude/wonderful-turing-rjtcnx-ai-types
10 checks passed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
The regression class the existing guard cannot see
packages/test/src/test/util/ExportTypesPairing.test.tspairs eachexportscondition branch'stypesstring against the implementation string beside it. That comparison is purely textual —declarationFor()is a string transform andisViolation()compares two strings. Nothing ever touches the filesystem for a declared target, and nothing correlates a target to a source entry.So a branch can be perfectly self-consistent and still name files that nothing builds. The concrete case:
providers/anthropichas nosrc/*.browser.tsand nobrowsercondition. Pasteproviders/openai's (correct) block into it —— and the
.d.tscorrectly describes the.js, so every existing assertion passes while a browser consumer resolves to a module that is never emitted. The same hole is open forgoogle-gemini,huggingface-inferenceandhuggingface-transformers.What this adds
Two repo-wide assertions in the same file:
declares only targets a source entry file can emit— everytypestarget and every implementation target must derive to asrc/<stem>.ts(or.tsx) that exists../dist/storage/bun.js→src/storage/bun.ts;.d.cts/.d.mts/.cjs/.mjssuffixes strip whole. A target whose layout the derivation cannot describe (anything outside./dist/) is reported, not skipped — silently skipping it would reopen the hole this closes.declares \types` before the implementation in the same branch** — Node stops at the first matching condition, so atypeskey declared after the implementation key is never reached. Recorded at collection time asDeclaredBranch.typesBeforeImplementation`.Plus pure unit fixtures for the new derivation (nested stems,
.d.ctsstripping, the underivable case), sofindViolationsstays filesystem-free and the existing synthetic-manifest tests are unaffected.Comment corrections (no behavior change)
types"falls through to an outer one". It does not: exports resolution stops at the branch that matched, and TypeScript then looks for a declaration beside the resolved implementation (.js→.d.ts). The bug fix(providers): point each export condition's types at its own declaration #717 fixed was an explicitly wrongtypes, not a fall-through.ALLOWED_MISMATCHES' JSDoc described only the mismatch case, butfindViolationsfilters the allowlist over all violations includingreason: "missing". Prose fixed; name and behavior unchanged.Honest caveat
The
srccorrelation is a proxy for "the build emits this", not a check of the build script's entry list. The true emitter is each package'sbuild-code/build-browserentry array — a package that hassrc/ai.browser.tsbut forgot to add it tobuild-browserstill passes this test. Parsing build scripts is the stronger check and is out of scope here; thesrccheck catches the copy-paste case that actually occurs..tsxis accepted as a candidate extension although the repo has zero.tsxfiles today — one array element, so a future React entry does not trip the guard.Verification
Baseline (unmodified tree): 163 branches across 38 manifests contributing branches (39 manifests carry an
exportskey;examples/webhasexports: {}and contributes none). All 163 declare atypeskey, so all 163 exercise both new assertions, and all 163 pass — zero manifest churn.Four deliberate breakages, each reverted afterwards (
git statusclean before commit):browserblock added toproviders/anthropicexports["./ai"]declares only targets a source entry file can emitfails with exactly two entries naming that branch — while the pre-existingpairs every \types` target…` assertion still passes. That contrast is the whole justification."types"moved below"import"inproviders/openaiexports["./ai"]declares \types` before the implementation in the same branch` fails naming that branchproviders/xaiimportrepointed to./lib/ai.jsunrecognizedassertion fails naming the branch and targetfinds condition branches to checkpasses withbranches.length= 163 (> 50)ExportTypesPairing.test.ts: 13/13 pass.bun scripts/test.ts util vitest: 50 files / 735 pass, 10 skipped.bun run test:vitest:integration: 49 files / 1615 pass, 42 skipped.npx tsc -b: error set byte-identical with and without this change (all pre-existing, none in the touched file).Test-file-only change — no source or manifest files are touched.
🤖 Generated with Claude Code
https://claude.ai/code/session_01PDvMMv78PuEw4T5atLeJeS
Generated by Claude Code