Skip to content

ci(types): run the root Vitest config's type program directly - #8004

Merged
baozhoutao merged 1 commit into
mainfrom
claude/issue-7328-root-vitest-config-type-program
Sep 6, 2026
Merged

ci(types): run the root Vitest config's type program directly#8004
baozhoutao merged 1 commit into
mainfrom
claude/issue-7328-root-vitest-config-type-program

Conversation

@baozhoutao

Copy link
Copy Markdown
Contributor

Fixes #7328

The repo-root vitest.config.mts had no runnable gate of its own. This gives it one that costs two seconds, and proves it goes red on the exact defect that cost a CI cycle in PR #7291.

What was actually missing — a runner, not a program

The card and the triage both read the gap as "no type program contains this file". Measured on origin/main 15789d5, that is not quite it. apps/console/tsconfig.node.json has listed the file outright since objectui#3476:

"include": [
  "vite.config.ts",
  "vitest.config.ts",
  "../../vitest.config.mts",
  "../../scripts/vite-*.ts"
]

What was missing was any way to RUN that program short of the console's own type-check script — tsc --noEmit && tsc -b tsconfig.node.json --force — reached through the task runner, whose type-check task waits on ^build. So the cheapest compiler in the repo that reads the root Vitest config was reachable only behind a full workspace build, and no local gate union short of that could go red on it.

Measured cost of the two routes, same box, same worktree:

Route Tasks Wall
the console's type-check through the task runner (35 builds + 1 type-check) 36 successful, 36 total 2m24s
tsc -b apps/console/tsconfig.node.json --force, on a worktree with no dist/ anywhere 1 2.0s

Why not a new root program (the two readings the triage asked for)

The .mts glob trap — confirmed. Neither existing root program's globs admit the extension: tsconfig.scripts.json includes only scripts/**/*.ts, tsconfig.vitest-setup.json includes ./vitest.setup*.ts and ./vitest.setup*.tsx. An include entry alone was never going to be the fix.

@types/node — confirmed, and the setup program is ruled out. One correction to the assumption: the root config imports path and url as BARE specifiers, not node:path (grep -n 'node:' vitest.config.mts finds nothing). The consequence is the same — the program needs @types/node — and tsconfig.vitest-setup.json deliberately ships without it, at a documented cost of 12 errors inside third-party declarations, with vitest.setup.network-escape-guard.ts written against that constraint. Untouched, per the triage boundary.

A NEW root program was measured, and it is strictly worse. A minimal tsconfig.vitest-config.json including exactly ./vitest.config.mts and ./apps/console/vitest.config.ts compiles green in 1.1s — and --listFiles shows its non-node_modules source set is these nine files:

vitest.config.mts
apps/console/vitest.config.ts
apps/console/vite.config.ts
scripts/vite-crypto-stub.ts          scripts/vite-maplibre-worker.ts
scripts/vite-objectstack-client-dist.ts   scripts/vite-objectstack-spec-dist.ts
scripts/vite-ineffective-dynamic-imports.ts   scripts/vite-declared-lazy-views.ts

That is the SAME nine files apps/console/tsconfig.node.json already resolves. A new program would therefore add nothing but a second option set over seven shared files — the "green in one project, red in the other" hazard tsconfig.scripts.json's own header documents at length, and the reason that file matches the console node project's options deliberately. It would also re-check scripts/*.ts under a third option set, which the dispatch ruled out.

So: one program, one option set, one new runner.

The change

  • package.json"type-check:vitest-config": "tsc -b apps/console/tsconfig.node.json --force". --force so an up-to-date .tsbuildinfo can never turn the gate into a silent no-op.
  • .github/workflows/ci.yml — one step in the existing Type Check job, Type-check the root Vitest config, placed in the CHEAP half beside pnpm type-check:scripts (after the install, before the Turbo cache restore and pnpm type-check). The dispatch allows a workflow edit when a new script must be wired into an existing job; this is that edit and nothing else. No timeout changed, no job added.
  • content/docs/guide/ci-cd-pipeline.md — forced, not optional: scripts/__tests__/ci-cd-pipeline-doc.test.ts fails when the workflow runs a first-party command the job table does not name. The new command is added to the type-check row in ci.yml order, with the rationale.
  • scripts/__tests__/vitest-config-type-check.test.ts — new pin, the sibling of scripts-type-check.test.ts and vitest-setup-type-check.test.ts. It asserts behaviour, never spelling: TypeScript's own resolution of the project, not a string search for a filename.

vitest.config.mts is not edited. The program forced no change to it.

Done criterion — red WITHOUT the console's type-check

The planted defect is the #7291 shape itself: drop the as const from extends: true as const in the conditionally spread dist project. Mutation proven on disk by marker counts and a moved blob hash, never by an editor's exit code; restore by git checkout HEAD -- vitest.config.mts under a trap with absolute paths.

PRE  target-count=1 inject-count=0 PRE_HASH=77a40ce4… HEAD_BLOB=77a40ce4…
POST target-count=0 inject-count=1 POST_HASH=122c8e5d…
MUTATION PROVEN ON DISK (target 1->0, inject 0->1, blob hash moved)
Leg Command Exit error TS lines
planted defect pnpm type-check:vitest-config 1 3
restored (HEAD ddd41ba) pnpm type-check:vitest-config 0 0

The three errors, verbatim from the mutated run:

apps/console/vitest.config.ts(11,15): error TS2345: Argument of type 'UserConfig & …'
    is not assignable to parameter of type 'never'.
vitest.config.mts(346,7): error TS2769: No overload matches this call.
vitest.config.mts(367,7): error TS2769: No overload matches this call.

Three errors, two of them at vitest.config.mts — the same shape PR #7291's repair round recorded from CI job 100117481182, reproduced here in 2 seconds by a command that runs no task-runner build and no tsc --noEmit over the console app.

Restore proven by state, not by an exit code: RESTORED_HASH == HEAD_BLOB == 77a40ce4…, and git diff HEAD / git status --short both empty.

Gates — at commit ddd41ba, exit codes captured by redirect-then-capture

Gate Verdict
pnpm type-check:vitest-config (new) exit 0 — 0 error TS lines, 2.0s
pnpm type-check:scripts exit 0
pnpm type-check:vitest-setup exit 0
turbo run type-check --filter=@object-ui/console exit 0 — Tasks: 36 successful, 36 total
pnpm exec vitest run scripts/ exit 0 — Test Files 108 passed (108) / Tests 3255 passed (3255)
node scripts/check-type-check-coverage.mjs exit 0 — "45/46 via type-check … 1 not compiled"
pnpm lint:root exit 0 — 32 problems (0 errors, 32 warnings), all pre-existing, none in the new file
pnpm check:control-bytes exit 0 — "OK (scanned 6439 tracked text file(s); skipped 85 binary)"
pnpm check:doc-fences exit 0 — "every TypeScript block in 227 document(s) is fenced…"
pnpm docs:check-links exit 0 — "Links are valid across 17 scan roots."
node scripts/check-changeset-presence.mjs exit 0 — "4 file(s) changed, 0 of them published source … no changeset is owed"
node scripts/check-governed-queue-guard.mjs --test (all 4 changed paths) exit 0 — "NOT GOVERNED — 4 path(s) checked against 5 governed surface(s); none matched."

turbo ls --affected against the branch point reports 0 no packages — this diff touches no workspace package, so no package test or type-check is owed beyond the console run above (kept because the console must still compile the merged config).

⚠️ Live E2E (informational) is red on every branch today for an upstream reason (objectui#7990 / objectstack#16186). It is not this diff's.

Not in this PR

scripts/check-type-check-coverage.mjs is untouched. Its subject is workspace PACKAGES — it iterates the workspace and asks per package whether type-check reaches its sources and its tests — so it models no repo-root file and makes no claim, true or false, about vitest.config.mts. The triage's fallback ("if the mechanical route fails, the coverage report must stop calling the root config covered") is conditional on a failure that did not happen.

The docs-only answer is not taken either: the lane fact stays true, but it is no longer the only thing standing between this file and a CI-only red.

🤖 Generated with Claude Code

https://claude.ai/code/session_01MM7kaS4dPpYHV5BsMyu4tQ


Generated by Claude Code

The repo-root `vitest.config.mts` had no runnable gate of its own
(objectui#7328). A program did already contain it —
`apps/console/tsconfig.node.json` has listed `../../vitest.config.mts` and
`vitest.config.ts` since objectui#3476 — but the only invocation of that
program was the console's own `type-check` script, reached through the task
runner, whose `type-check` task waits on `^build`. So the cheapest compiler in
the repo that reads the root Vitest config was reachable only behind a full
workspace build, and no local gate union short of that could go red on it.

PR #7291 is the measured cost: a conditionally spread `dist` project whose
literal `extends: true` widened to `boolean` degraded the whole `projects`
array to `never[]`; `type-check:scripts`, `type-check:vitest-setup` and the
vitest runs were all green, and CI answered with three errors, two of them
reported at `../../vitest.config.mts`.

Adding the file to an existing root program was measured and is not available:
`tsconfig.scripts.json` includes only `scripts/**/*.ts` and
`tsconfig.vitest-setup.json` only `./vitest.setup*.ts(x)`, neither glob admits
`.mts`, and the setup program deliberately ships no `@types/node` while the
root config imports `path` and `url`. A NEW root program was measured too: its
resolved source set is the same nine files as
`apps/console/tsconfig.node.json`'s, so it would only add a second option set
over seven shared files — the "green in one project, red in the other" hazard
`tsconfig.scripts.json`'s header documents.

So the program keeps its single home and gains a second, cheap runner:
`type-check:vitest-config` invokes it directly, in the cheap half of the
`Type Check` job beside `pnpm type-check:scripts`. Nothing in its program
imports an `@object-ui/*` package (the 40 `@object-ui/*` occurrences in
`vite.config.ts` are alias-map strings), so it needs the install and nothing
built: 2.0s on a worktree with no `dist/` anywhere.

`scripts/__tests__/vitest-config-type-check.test.ts` pins the behaviour — that
every root `vitest.config.*` is in the resolved program, that the merging
consumer is too, that `allowJs` stays off so the root config's
`@ts-expect-error` remains live, and that CI runs the script before the build.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MM7kaS4dPpYHV5BsMyu4tQ

Copy link
Copy Markdown
Contributor Author

Standing down on Live E2E (informational) — red on the base branch too, not this PR's. domain:devx @ objectui execution seat, PM session session_01MM7kaS4dPpYHV5BsMyu4tQ, R45, 2026-09-06T08:13Z. Same signature as main's scheduled run 34017174769 (job 101442890465): the published backend boots without its auth core (objectstack#16186); consumer-side anchor #7990. This diff is a root script, one ci.yml type-check step, a doc table row and a scripts/__tests__/ pin — no backend pin, no e2e/ path. No fix to port, no re-run spent. Flip and arming wait on the remaining shards (ACCEPT on #7328).


Generated by Claude Code

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

2 participants