Repository navigation
fix(docs): type-check apps/docs with next typegen, and delete the dead types:check - #10879
Conversation
…e dead `types:check`
`apps/docs` declared two near-identical type-check scripts:
"types:check": "fumadocs-mdx && next typegen && tsc --noEmit"
"typecheck": "tsc --noEmit"
`types:check` -- the thorough one -- was invoked by nothing (`git grep`
over `.github/`, `scripts/`, `turbo.json` and the root `package.json`
returns only its own declaration). The one CI actually runs is
`typecheck`, reached through `turbo run typecheck --filter='./apps/*'`.
That mattered because `tsconfig.json` includes `.next/types/**/*.ts`,
which only `next typegen` produces. So the program CI type-checked was
set by whether some earlier, unrelated command had populated `.next` --
and on a fresh checkout it had not. Measured with `.next` deleted, the
bare script exits 0 while compiling none of the generated route types.
Wiring the thorough command under the name CI already runs makes the
check a function of source: it generates `.next/types/**` and
`next-env.d.ts` itself before tsc reads them, and short-circuits loudly
if typegen fails. Typegen costs ~1s and needs no build.
Measured coverage delta (empty `.next`, `tsc --listFiles`): 1225 -> 1231
files. The six are `.next/types/validator.ts` (160 lines validating 13
route entry points), `routes.d.ts`, `root-params.d.ts`,
`cache-life.d.ts`, `next-env.d.ts` and, through it,
`next/image-types/global.d.ts` -- so the bare run was also type-checking
a Next app without Next's own ambient declarations.
Ablation, to show the added coverage is real: giving
`app/[lang]/docs/layout.tsx` a `params` shape the route cannot supply
leaves the old script green (exit 0, 0 errors) and makes the new one
fail with TS2344 at `.next/types/validator.ts`, naming the layout and
the missing param.
`include` is deliberately left alone. Next writes both `.next` globs
itself and re-adds either one on the next `next dev` / `next build`;
running `writeConfigurationDefaults` against a narrowed copy put the
`dev` glob straight back and rewrote the file. The rationale is recorded
in `tsconfig.json` where the next reader would try to tidy it.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DdCnBGcHeufjrq7drTD3wt
✅ ACCEPT — reviewer of record:
|
| leg | result |
|---|---|
bare tsc --noEmit — today's CI |
EXIT=0, 0 error lines — green over a broken route signature |
| typegen'd | EXIT=2 — .next/types/validator.ts(139,31): error TS2344 … Property 'nonexistentParam' is missing in type '{ lang: string; }' |
That is not "typegen adds files", it is "today's CI passes a broken route." The coverage delta measured it (1225 → 1231 program files, 6 named), and the ablation showed what those six are for. Cost: 1 second, no build.
Card's required proof also run: rm -rf .next next-env.d.ts then typecheck ⇒ exit 0 in 4s with .next/types regenerated — with a negative control (OLD_BARE_TSC_EXIT=0 on the same tree), so the difference is the script and not the tree. Filter verified per #10853.
#10880 — filed with the right reason for not riding along
deliberately kept out of the PR because it would edit the 3790-line gate that #10756 is already dispatched against (same file, different invariant; needs sequencing, not parallel work)
⭐ That is a serial constraint you worked out yourself from the state of the lane, not from anything in your brief.
Flipping ready and arming.
Generated by Claude Code
⛔ merge queue 构建失败 — 先分诊,再决定要不要重排队列构建 32497999764 红了。队列跑的是全量套件(PR 侧 CI 只跑 affected 子集), 失败的 job(日志抽取,best effort):
跨 PR 相同签名(24h,按失败测试文件聚合):
历史信号:
分诊清单:
Generated by Claude Code · merge-queue-triage workflow (#4859) |
Fixes #10871
apps/docsdeclared two near-identical type-check scripts, and the thorough one was dead:git grep types:checkover.github/,scripts/,turbo.jsonand the rootpackage.jsonreturns only its own declaration. CI reaches the other one throughturbo run typecheck --filter='./apps/*'(lint.yml,Type Check · workspace).That mattered because
apps/docs/tsconfig.jsonincludes.next/types/**/*.ts, which onlynext typegenproduces. The program CI type-checked was therefore set by whether some earlier, unrelated command had populated.next— and on a fresh CI checkout nothing had.The judgement: typegen materially changes coverage, so the thorough command wins
Measured on this branch's base,
.nextdeleted, comparingtsc --noEmit --listFileswith and without typegen:tsc --noEmit(what CI ran)fumadocs-mdx && next typegen && tsc --noEmitThe six added files are
.next/types/validator.ts(160 lines validating 13 route entry points — every page, layout and route handler against the route's real param map),routes.d.ts,root-params.d.ts,cache-life.d.ts,next-env.d.ts, and through that last onenext/image-types/global.d.ts. So the bare run was also type-checking a Next app without Next's own ambient declarations.Both were green, so the delta is coverage rather than a live bug. To show the added coverage is real rather than decorative, an ablation — giving
app/[lang]/docs/layout.tsxaparamsobject with one extra required key the route cannot supply, an edit that is internally consistent and so invisible to a plain compile:1 file changed, 1 insertion(+), 1 deletion(-)tsc, empty.next)TS2344at.next/types/validator.ts(139,31), naming the layout and the missing paramgit status --porcelainempty for the file, re-run exit 0⛔ Both scripts are not left standing.
types:checkis deleted and its command becomestypecheck— the nameturbo run typecheckandcheck:type-check-coveragealready require, so no turbo or CI wiring moves.How the empty-
.nextcase becomes honestThe check now produces the generated inputs it reads, from source, on every run:
fumadocs-mdxwrites.source/,next typegenwrites.next/types/**andnext-env.d.ts, and only then doestscrun. It can no longer pass because someone else populated.next, because it populates.nextitself; and if typegen ever fails,&&short-circuits andtscnever runs, so the failure is loud rather than a quietly smaller program.Proof, run on this commit with
.nextandnext-env.d.tsdeleted first:and the negative control on the same tree — the old command is still green over an empty
.next, which is the trap being removed:The cost of the trade, named rather than absorbed
next typegenneeds no build. Measured at ~1 second; it reads theapp/directory and writes route types, it does not compile the app. The turbotypechecktask keepsdependsOn: ["^build"]exactly as before (that builds@objectstack/spec, not this app), andpnpm buildstill excludes docs. So this buys a whole class of route-signature checking for about a second, and does not turn the type check into something that requires a full build.Why
includeis left alone — the other option in the card is not availableThe card floated narrowing
includeinstead. Measured: that change is self-reverting. Next owns this array —writeConfigurationDefaultswrites both.nextglobs and re-adds either one that is missing. Running Next's own routine against a narrowed copy of the file, exactly asnext buildandnext devcall it:So a hand-narrowed
includecomes back as an unrequested modification to a tracked file on the nextnext devornext build. The rationale is recorded as a comment intsconfig.json, at the spot where the next reader would try to tidy the glob away.The residual is named there too, and it is the harmless direction:
.next/dev/types/**is written only bynext dev, and Next's own build-mode type check filters that directory out of the program (getDevTypesPath,lib/typescript/runTypeCheck.js) "to prevent stale dev types from causing errors when routes have been deleted since the last dev session". Plaintschas no such filter, so a leftover dev session can only add a local false red — never a false green, which is the failure this card is about. Remedy:rm -rf apps/docs/.next.No changeset
skip-changeset.apps/docsisprivate: trueand the diff is two files — a package script and a config comment. Nothing user-visible ships and no published package changes;privatePackages.tagisfalse, so there is no tag or release to describe either.Gates
Derived with
node scripts/pm/dispatch-gates.mjs(2 paths vs merge base7d483e1e5), run at8140f71b39— the final commit — each quoting its own verdict line:check:override-consistency✓ 8 published-manifest declaration(s) covered by pnpm-workspace.yaml overrides all resolve to their override targets.check:test-source-aliascheck-test-source-alias OK — 72 packages with tests scannedcheck:type-source-resolutioncheck-type-source-resolution OK — 76 packages with a tsconfig.json scannedscripts/check-changeset-fixed.mjs✓ .changeset/config.json "fixed" group is in sync with 69 public workspace packages.scripts/check-osv-exemptions.mjs✓ osv-scanner.toml holds zero OSV exemptions (the intended steady state).check:nul-bytescheck-nul-bytes: OK (scanned 6294 text file(s) ... no raw ASCII control bytes).check:type-check-coveragecheck-type-check-coverage: OK — 64/77 workspace packages type-checked (plus the root)check:nul-bytesis the any-edit gate;check:type-check-coverageis not path-derived but reads exactly the two files this diff changes (itsREALinvariant is about whether a declaredtypecheckscript means anything), so it was run deliberately.Also run:
turbo run typecheck --filter='./apps/*' --force—Tasks: 2 successful, 2 total— which is the CI lane's own command over this diff.Generated by Claude Code