Uh oh!
There was an error while loading. Please reload this page.
test(cli): externalise packages/types/dist so the host importer keeps Node's resolution anchor - #12357
Conversation
… Node's resolution anchor `packages/cli/vitest.config.ts` gains a `test.server.deps.external` entry for `/packages[\/]types[\/]dist/`. Vitest's default `server.deps.external` is `[/\/node_modules\//]`, evaluated against a module's realpath. A pnpm-linked workspace package's realpath is the package directory itself (`packages/types/dist/node.mjs`), which has no `/node_modules/` segment — so every workspace dependency is inlined, and Vite rewrites the `import()` inside it to `__vite_ssr_dynamic_import__`, re-anchoring it at the vitest root. `createHostImporter` exists to resolve against a specific base, so under vitest it was measuring a base that had been flattened out from under it (#11412). The entry hands that call back to Node. The pin is the inverted M1 block of `test/vitest-resolution-base-collapse.e2e.test.ts`, which previously asserted the collapse. It now asserts the restored anchor as an envelope — `code`, `hostImportFailureKind`, and the message naming `packages/types` as the base — so the in-process reading equals the real-Node reading the same file already measures in a spawned child. The absence-of-marker assertion carries an in-file positive control (a dynamic import written in this still-inlined test file), so a renamed marker fails loudly instead of silently confirming the line under it. M1 is not gone as a platform fact: the entry names one dist path in one package's config, and every other workspace dependency here is still inlined. M2 (NODE_PATH into a spawned child, honoured by CJS) is untouched. No changeset: `packages/cli` publishes only `dist`, README and CHANGELOG, and neither edited file reaches it. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UjM2ia8Av1v5NqfqQEQmC6
…i-vitest-deps-external
📓 Docs Drift Check
What this run could not see
Coarse fallback — 23 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): |
yinlianghui
commented
Aug 25, 2026
ACCEPT — PM review of #11775. The inverted pin — the one thing here that could hide a regression, checkedInverting an existing assertion is exactly the shape that silences a test while looking like a fix, so I checked both halves of your account. The file's own header sanctions it, verbatim on And the timing is exact. You said it landed ~40 minutes after the card was filed, which is why the card's "11 files / 103 tests, identical both ways" could not have included it: So the card's reading was honest and incomplete for a reason nobody could have avoided, and ⭐ The tension question, answered better than the card asked itZone 2d told you to argue with the card's structural claim rather than inherit it. You did, and the answer is stronger than the claim:
⭐ The gate assumed to be in tension with this entry is the gate that keeps the two mechanisms disjoint. That converts #11412's "may be in tension" from an open worry into a structural guarantee with a named enforcer — and you measured it directly besides (marker in Zone 2b's silent trap, handled with the control it needs
Three rows, and the third explains the defect rather than just failing to match. A negative here would have been indistinguishable from "externalising does not help" — that is the card's own origin story, and you closed it before landing anything. ⭐ The in-file positive control on the absence-of-marker assertion is the detail I want on the record: a dynamic import in the still-inlined test file, so a vitest upgrade that renames The rest
Landing conditions
Generated by Claude Code |
yinlianghui
commented
Aug 25, 2026
Two notes on the report, neither changing the ACCEPT. The scope judgement you asked me to confirm is confirmed, and I checked it independently rather than agreeing: the file's own header at ⭐ Your rebuild-leg reasoning is better decomposed than most, and I want the shape on the record. You did not answer "no rebuild leg applies" and stop; you separated two different claims:
"A rebuild leg does not apply to this mutation" and "staleness cannot fake this green" are genuinely independent, and folding the second into the first is how a stale-artifact green survives an honest-looking disclaimer.
A whole-set The outcome is fine — Generated by Claude Code |
Uh oh!
There was an error while loading. Please reload this page.
Fixes#11775
Lands
test.server.deps.externalinpackages/cli/vitest.config.tsper the shape the card measured, and pins the restored throw behaviour.What changed
Vitest's default
server.deps.externalis[/\/node_modules\//], evaluated against a module's realpath. A pnpm-linked workspace package's realpath is the package directory itself —packages/types/dist/node.mjs— which contains no/node_modules/segment, so every workspace dependency is inlined, including one reached throughexportstodist/. Vite then rewrites theimport()inside it to__vite_ssr_dynamic_import__, which re-anchors it at the vitest root.createHostImporterexists to resolve against a specific base, so under vitest it was measuring a base flattened out from under it (#11412). The entry hands that call back to Node.Re-measured, not inherited
Every number the card reported was re-measured on this branch.
The pattern trap, with a positive control. The card warns a name-shaped pattern matches nothing silently. Confirmed against the real realpath before anything was landed:
packages/types/dist/node.js?/packages[\/]types[\/]dist/(measured shape)/@objectstack[\/]types/(natural-looking)/\/node_modules\//(vitest default)The anchor is restored. The in-process call now fails with the same sentence real Node produces, naming the base:
Cannot find package 'chalk' … imported from …/packages/types/dist/node.mjs.check:test-source-aliasstays green, with readings identical before and after — 72 packages scanned, 61 registered, 45 subpaths. The ledger moved in neither direction.Test cost, on the whole suite. The card ran 11 of the package's files; this ran all 183 (2090 tests), in four chunks: 444 / 532 / 677 / 437, all passing. Two reds surfaced and neither was this change: one was
packages/cli's owndist/not being built (that e2e deliberately spawns the shippedbin/run.js), and one was anECONNREFUSEDboot race in a spawned-server e2e under container load — both green when re-run.Second-order costs (the card's own "not measured" list).
packages/clihas novi.mockof@objectstack/typesanywhere — its only mock targets are../utils/optional-package.js,node:fs/promisesand@objectstack/cloud-connection. Coverage instrumentation of the externalised package is genuinely lost; the config header now records both costs for whoever extends the list.The tension the card left open, now measured rather than argued
The card offered its structural claim "for argument": an external pattern anchored on
/dist/cannot match a package aliased to source. It holds, and the reason is stronger than stated.resolve.aliasacts in the resolve phase, so an aliased specifier is already an absolutesrc/…path before the externalise predicate is consulted — andcheck:test-source-aliasis precisely the gate that fails any alias whose winning entry does not land undersrc/. The gate assumed to be in tension with this entry is the same gate that keeps the two mechanisms disjoint.Measured directly: with a source alias and a
/service-cache[\/]dist/external both present for the same package, a marker planted only insrc(0 occurrences indist) was the one that loaded —SRC. Both mutations were restored under atrapand proved byte-identical withgit hash-object.The pin
The pin is the inverted M1 block of
test/vitest-resolution-base-collapse.e2e.test.ts— the file that previously asserted the collapse. Note it landed 40 minutes after this card was filed, which is why the card's "11 files / 103 tests, identical both ways" reading did not include it; 103 + its 9 cases = the 112 measured here.Inverting it is the fix landing, not a conflict: the two cases asserted the absence of the anchor this card exists to restore, and the file's own header says "if this ever reads false, M1 is gone and the two cases above should be re-measured". They were re-measured, not deleted — "the anchor is restored" is only meaningful beside the real-Node baseline the file already measures in a spawned child.
It is asserted as an envelope, never a bare
toThrow():code,hostImportFailureKind, and the message namingpackages/typesas the base. And the absence-of-marker assertion carries an in-file positive control — a dynamic import written in the still-inlined test file itself — so a vitest upgrade that renamed the marker fails loudly instead of silently confirming the line under it.Falsifiable, and shown failing. Removing the config entry turns exactly those 2 cases red and leaves the other 7 green; the config was restored byte-identically afterwards.
M1 is not gone as a platform fact — the entry names one dist path in one package's config, and every other workspace dependency here is still inlined. M2 (
NODE_PATHinto a spawned child, honoured by CJS) is untouched by any vitest config. Both are stated where they will be read.Gates
Green at
77a2741, exit codes captured before any pipe:check:test-source-alias,cli-test-child-env,cross-package-test-inputs,type-source-resolution,published-files,page-declaration-shape,slot-lookup,nul-bytes,engine-double-contract,where-matcher,query-options-erasure,type-check-coverage,i18n,i18n-coverage,check-ci-filter-parity,check-cross-package-test-inputs,check-plugin-teardown-shape, bothdocs-auditgates, pluspnpm --filter @objectstack/cli typecheck.check:type-check-debtis NOT MEASURED, not green: its--re-measureleg exceeded this container's ~10-minute foreground cap (exit 143). CI runs it.No changeset
packages/clipublishes onlydist,README.mdandCHANGELOG.md. Neither edited file reachesdist/— a vitest config and a test file — so nothing user-visible ships.skip-changesetapplied.Generated by Claude Code