Skip to content

test(scripts): the #3944 alias-target gate now covers the 24 package-level vite.config.ts tables - #5381

Merged
os-support-ai merged 1 commit into
mainfrom
claude/issue-5168-package-vite-alias-targets
Aug 20, 2026
Merged

test(scripts): the #3944 alias-target gate now covers the 24 package-level vite.config.ts tables#5381
os-support-ai merged 1 commit into
mainfrom
claude/issue-5168-package-vite-alias-targets

Conversation

@os-support-ai

Copy link
Copy Markdown
Collaborator

Fixes#5168

What was missing

scripts/__tests__/vitest-config-alias-targets-3944.test.ts enforces "a declared alias target must exist on disk" — for two root tables: vitest.config.mts's resolve.alias (#3944) and tsconfig.json's compilerOptions.paths, folded into the same file by #4804. The package-levelvite.config.ts tables were covered by nothing.

The only other gate that reads those tables, side-effects-declaration-consistency.test.ts, does not go red on a dead entry — resolveAliasModule() returns undefined and resolvableEntryPaths()continues. That is correct behaviour for its own question (a dead alias contributes no entry form, so no sideEffects declaration can conflict with it), not an oversight. Target existence and sideEffects consistency are two contracts, so this lands in the file whose contract already is the former — the same absorption #4804 performed — rather than making that gate health-check tables it only reads incidentally.

Counts — measured here, not adopted

Three prior methods gave three numbers (card: 24 files / 196 entries; triage: 24 of 26; a crude git grep -l: 26 files). Measured by this PR's own parse on main@aff10e283:

measured
configs walked with a vite.config.* basename26
configs declaring a resolve.alias table24
total entries in those tables227
of those, @object-ui/*200, across 20 configs
dead @object-ui/* targets0
dead targets overall1 (see the carve-out)

24 matches the card and triage. 200 is not 196: the card measured main@cdac3cc20, and apps/console (34 to 35), examples/console-starter (29 to 30) and packages/runner (14 to 15) have each gained an entry since, plus one more elsewhere. The 26-vs-24 spread is the looser predicate — two of the 26 configs match on aliasesExclude / Object.assign, not on a resolve.alias table.

Why an AST read, and why borrowed

Across 24 files the table is spelled four ways: bare resolve(, path.resolve(, the array alias: [{ find, replacement }] form, and double-quoted keys. #5158 measured the cost of a regex here — a reader requiring path.resolve( after a single-quoted key saw 70 of 196 entries, 15 configs contributing zero: not checked-and-passed, never looked at. The parser is borrowed from side-effects-declaration-consistency.test.ts (:397-502, resolveAliasModule at :524) rather than written a fifth time. Borrowed, not moved or edited — that file is another gate's, and its copy answers its own question.

One deliberate divergence from the borrowed copy: entries whose key is a RegExp matcher are kept. There it is right to drop them (a RegExp names no single specifier, so it yields no entry form); here the question is about the replacement, and a RegExp-keyed replacement is a path like any other — packages/components has three, all pointing at real shim files.

Two things that do not transfer from the root halves

  1. Existence is not existsSync alone. A package table legitimately aliases an extension-less file: packages/runner's "@/lib/utils" points at packages/components/src/lib/utils, which is utils.tsx on disk and resolves fine, because Vite probes resolve.extensions after substituting an alias. The predicate is "exists, or exists with one of those extensions appended".
  2. The "extension-less target must be a directory" case is deliberately not ported. Up there it is right — every root entry but one is a package src/ root. Down here, landing on a file is the intended spelling, and porting the case would have reddened a correct config. Named in the header rather than silently dropped.

The gate found one live dead entry

packages/runner/vite.config.ts:25"@app": path.resolve(__dirname, "./src/app-data"). packages/runner/src/app-data does not exist in any form, and the declaration is the only occurrence of @app in the repo. Its comment reads DX: App Data Symlink, so the fix is either "drop the line" or "restore the symlink it was written for" — a maintainer call, not a rider on a gate that is only supposed to start looking. It is carved out as a shrink-only ratchet (KNOWN_MISSING_PACKAGE_ALIAS_TARGETS), the same shape #4820 has in the paths half: the list may not grow, and a pin asserts the entry is still declared and still missing, so resolving it either way turns this file red until the list is emptied with it. Filed unassigned as #5380 — that issue remains open and is not addressed here.

Worth noting for scope: "@app" is not an @object-ui/* key. A gate scoped to the 196-entry @object-ui/* framing would have shipped green over it. That is why the range is "every entry in the table", matching both root halves, which also check every key regardless of prefix. This is also the second time this one config has outlived a target — its own comment records #3593, where a 2>/dev/null kept a dead data-objectql entry quiet.

Reverse verification — the whole difficulty, since the gate lands green

There are zero dead @object-ui/* entries today, so passing proves nothing. Direction predicted before running: plain RED, and +1 case as well as +1 failure. The case count is the load-bearing half — a spelling the parser skipped would add zero cases and stay green, which is exactly the "did not look" green this card exists to close.

No build artifact sits between any edit below and the thing under test: the gate reads node:fs and parses source text, so nothing is compiled, bundled or emitted in between. Nothing was rebuilt, and nothing needed to be. All runs from the repo root (pnpm exec vitest run scripts/…), never pnpm --filter.

legprobepredictedobserved
baselinenone289 pass / 0 fail289 pass / 0 fail
Athe card's exact probe — '@object-ui/ghost-5158': resolve(__dirname, '../ghost-5158/src') in packages/plugin-editor (single-quoted key, bare resolve)290 total, 1 fail290 total, 1 fail
Bsame shape with path.resolve( in packages/fields290 total, 1 fail290 total, 1 fail
Csame shape with a double-quoted key in packages/runner290 total, 1 fail290 total, 1 fail
Dthe array form, { find, replacement }, in packages/components290 total, 1 fail290 total, 1 fail
Ea value the parser cannot read (ghostHelper('…'))differs from A–D: no new case, coverage case red289 total, 1 failparses EVERY entry in every table
Fablation: parser blinded to path.resolveat least 2 fail; total decreases195 total, 5 fail — 94 rows vanished
Gmkdir packages/runner/src/app-datacarve-out pin red, it.each count unchanged289 total, 1 failA carved-out target now exists…

Legs A–D confirm all four spellings are reachable, one probe each, in the config that natively uses that spelling. Leg E confirms the different shape the no-silent-skip case has. Leg G confirms the ratchet's obsolescence half.

Prediction vs observation differed on leg F, and the gap is the finding. I predicted at least two red cases (the spelling-coverage case and the parse-gap case). Observed five: the plausibility floor, the per-config coverage case and the carve-out pin catch the shrink too, because 94 of the 227 entries are written with path.resolve and their disappearance drops the total under the floor, empties three configs entirely, and takes @app out of the parsed set. Five independent cases stand between a silently-narrowed parser and a green run — stronger than designed for, not weaker.

The contrast that shows the hole was real, on probe A: with the dead entry in place, the two pre-existing gates were run against the gate file as it stands on origin/mainTests 88 passed (88), i.e. #3944 at 58/58 and side-effects at 30/30, reproducing exactly what #5168 measured. The same tree with this PR's gate goes red.

Verification

Gate union re-derived against the actual diff (one file under scripts/__tests__/, one changeset) and run on the final commit deb273f91:

  • pnpm exec vitest run scripts/57 files, 1522 tests, all passing (the gate file itself contributes 289).
  • pnpm run type-check:scripts (tsc -p tsconfig.scripts.json) — clean.
  • pnpm exec eslint scripts/__tests__/vitest-config-alias-targets-3944.test.ts — clean.
  • node scripts/check-control-bytes.mjs — OK, 4762 tracked text files scanned.
  • node scripts/check-changeset-presence.mjs — "No source of a released package changed in this range, so no changeset is owed"; check-changeset-no-major.mjs — no major declared.

objectui has no skip-changeset label, so the declaration form is an empty-frontmatter changeset — added, and correct here because the diff is one file under scripts/__tests__/ with no package src/ touched, which is precisely what the presence check reports.

Not in scope


Generated by Claude Code

…level vite.config.ts tables (#5168)
`vitest-config-alias-targets-3944.test.ts` enforced "a declared alias target
must exist" for the two ROOT tables — `vitest.config.mts`'s `resolve.alias`
(#3944) and `tsconfig.json`'s `compilerOptions.paths` (folded in by #4804).
The package-level `vite.config.ts` tables were covered by nothing: measured on
this tree, 24 configs declaring 227 entries, 200 of them `@object-ui/*`.
The only other gate that reads those tables, `side-effects-declaration-
consistency.test.ts`, does not go red on a dead entry, and that is correct for
its own question — an unresolvable target contributes no entry form, so no
`sideEffects` declaration can conflict with it. Target existence and
`sideEffects` consistency are two contracts, so this lands in the file whose
contract already is the former, the same absorption #4804 performed.
The parse is borrowed from that gate's AST reader rather than written a fifth
time: across 24 files the table is spelled four ways (bare `resolve(`,
`path.resolve(`, the array `{ find, replacement }` form, double-quoted keys),
and #5158 measured a regex reader that understood one of them reading 70 of 196
entries with 15 configs contributing zero — green because it never looked. A
spelling-coverage case asserts each of the four is reachable, so dropping one
goes red rather than silently shrinking the surface.
Two adjustments the root tables did not need, both named rather than silent:
existence probes Vite's `resolve.extensions` after the substitution, because an
extension-less target naming a file is live here (`packages/runner`'s
`@/lib/utils` is `utils.tsx`); and the root half's "an extension-less target
must be a directory" case is deliberately NOT ported, because down here landing
on a file is the intended spelling and porting it would redden a correct config.
The new gate found one live dead entry on its first run: `packages/runner`'s
`"@app"` points at `packages/runner/src/app-data`, absent in every form, with
the declaration as its only occurrence in the repo. Carved out as a shrink-only
ratchet with a pin asserting it is still declared AND still missing, because the
fix — drop the line, or restore the DX symlink its comment names — is a
maintainer call. Filed as #5380, unassigned. Note it is not an `@object-ui/*`
key: a gate scoped to that prefix would have shipped green over it.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RV6yuVCxymHYE16PL9vQkE
@os-support-ai
os-support-ai marked this pull request as ready for review August 20, 2026 03:31
@os-support-ai
os-support-ai added this pull request to the merge queueAug 20, 2026
Merged via the queue into main with commit cadc5b1Aug 20, 2026
21 checks passed
@os-support-ai
os-support-ai deleted the claude/issue-5168-package-vite-alias-targets branch August 20, 2026 03:32
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

finding: 24 个包级 vite.config.ts 的 196 条 alias 目标存在性无人把关 —— #3944 的门只覆盖根 vitest.config.mts 与 tsconfig.json

2 participants

@os-support-ai@claude