From 0789fec46e0f18f521b76fe41cdeda59aba52a13 Mon Sep 17 00:00:00 2001 From: Tim Nunamaker Date: Wed, 23 Sep 2026 10:12:46 -0500 Subject: [PATCH 1/3] fix(site): fix tsconfig and sync-spec-docs.mts drift breaking verify Two independent bugs, both hidden because neither ran as a required PR gate. apps/site's tsconfig.json maps `@generated-docs/*` to a single hardcoded `./.source/*`. `types:check`/`verify` set `PDPP_SITE_SOURCE_DIR` to `.source-verify` (and `dev` to `.source-dev`), so fumadocs-mdx writes each mode's output to its own directory instead. #340 isolated these directories, and kept matching webpack/turbopack aliases in `next.config.ts` in sync, so dev and verify runs stop clobbering each other's generated files. `tsc` has no way to read that env var, so it always resolved `.source`, which `verify` never populates: `tsc --noEmit` failed with `TS2307: Cannot find module '@generated-docs/dynamic.ts'`. `next build` never hit this, because Next's own bundler uses the parameterized alias, not tsconfig's `paths`, so the bug was invisible to any build/deploy path and to every CI job, none of which invoke `apps/site`'s `verify` or `types:check` at all. List all three mode directories in `paths` as fallbacks; tsc resolves whichever one the run populated. `apps/site/scripts/sync-spec-docs.mjs` is a generated artifact compiled from `sync-spec-docs.mts` (`scripts/check-generated-artifacts.ts` asserts they're byte-identical). #366 added stale-`.md` cleanup logic by editing the compiled `.mjs` directly and never touched the `.mts` source, so the two diverged. Port the same logic into `.mts` and regenerate `.mjs` from it. `generated-artifacts:check` only runs in semantic-release.yml, gated on a version actually publishing, so it's absent from the PR-required checks and this went uncaught since #366 merged. Root/site spec drift (spec-check.yml) stayed green throughout because it only runs `spec:check`, which diffs generated markdown against the root specs and never invokes tsc or generated-artifacts:check. Verified: `pnpm --dir apps/site run verify` now gets past types:check, test (247/247), and build. `pnpm generated-artifacts:check` reports 7 byte-identical artifacts. `ultracite check` still fails on an unrelated, pre-existing `playwright` import in `apps/site/scripts/style-differ.mjs`, present unmodified on main and out of scope here. Assisted-by: AI Signed-off-by: Tim Nunamaker --- apps/site/scripts/sync-spec-docs.mts | 16 +++++++++++++++- apps/site/tsconfig.json | 14 ++++++++++++-- 2 files changed, 27 insertions(+), 3 deletions(-) diff --git a/apps/site/scripts/sync-spec-docs.mts b/apps/site/scripts/sync-spec-docs.mts index 0467a0c0d..95dc6cd03 100644 --- a/apps/site/scripts/sync-spec-docs.mts +++ b/apps/site/scripts/sync-spec-docs.mts @@ -31,7 +31,7 @@ // Runs from `predev` and `prebuild`. Vercel builds from apps/site with the // monorepo root available, so the relative path to the repo root resolves. -import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs"; +import { existsSync, mkdirSync, readFileSync, rmSync, writeFileSync } from "node:fs"; import path from "node:path"; import { fileURLToPath } from "node:url"; @@ -155,6 +155,7 @@ function checkStatusDateDrift(header, root, spec) { } let generated = 0; +let staleRemoved = 0; for (const spec of SPECS) { const rootPath = path.join(repoRoot, `${spec}.md`); const headerPath = path.join(headerDir, `${spec}.header.md`); @@ -174,6 +175,16 @@ for (const spec of SPECS) { // header (frontmatter + Callout) + blank line + normative body. const out = `${header}\n\n${root.body.replace(/\s*$/, "")}\n`; writeFileSync(outPath, out); + // An earlier revision of this script wrote `.md` here. A leftover from one + // of those runs is not overwritten by the `.mdx` write above, and fumadocs + // globs both, so the two resolve to the same slug and fail the build with + // "Duplicated slugs". Clearing it makes a stale checkout self-heal instead + // of failing a build that looks unrelated to anything the author changed. + const stalePath = path.join(contentDir, `${spec}.md`); + if (existsSync(stalePath)) { + rmSync(stalePath); + staleRemoved += 1; + } generated += 1; } @@ -455,6 +466,9 @@ export const PRINCIPLES_LIST = ${JSON.stringify(principles, null, 2)} as const; ); console.log(`[sync-spec-docs] generated ${generated} spec page(s) from root spec-*.md`); +if (staleRemoved > 0) { + console.log(`[sync-spec-docs] removed ${staleRemoved} stale .md page(s) left by an earlier revision of this script`); +} console.log( `[sync-spec-docs] wrote src/generated/spec-front-matter.ts (${versionMatch[1]}, ${status}, ${date}, ${editors.length} editor(s); ` + `governance: ${governanceFrontMatter.status}; principles: v${principlesFrontMatter.version})` diff --git a/apps/site/tsconfig.json b/apps/site/tsconfig.json index 4940cad8d..62c7e4551 100644 --- a/apps/site/tsconfig.json +++ b/apps/site/tsconfig.json @@ -25,10 +25,20 @@ "name": "next" } ], + // fumadocs-mdx writes into PDPP_SITE_SOURCE_DIR (default ".source"; see + // scripts/site-runtime.ts), isolated per run mode so dev, verify, and + // build never clobber each other's generated output (see next.config.ts's + // matching webpack/turbopack aliases). tsc has no env-var substitution in + // "paths", so list every mode's directory; tsc resolves the first one that + // exists. "paths": { "@/*": ["./src/*"], - "@generated-docs/*": ["./.source/*"], - "collections/*": ["./.source/*"] + "@generated-docs/*": [ + "./.source/*", + "./.source-verify/*", + "./.source-dev/*" + ], + "collections/*": ["./.source/*", "./.source-verify/*", "./.source-dev/*"] } }, "include": [ From ab4a2d5a6ec725fc71f96cfb0fcea87017a34065 Mon Sep 17 00:00:00 2001 From: Tim Nunamaker Date: Wed, 23 Sep 2026 10:29:23 -0500 Subject: [PATCH 2/3] fix(site): isolate verify's tsc pass per mode, add generated-docs PR gate The previous commit's tsconfig fix listed `.source`, `.source-verify`, and `.source-dev` as fallback `paths` for `@generated-docs/*`, so `tsc` would resolve whichever directory exists. That is unsound: `tsc` picks the first entry that exists, not the one the current run generated, so a `.source` left behind by an unrelated `next build`/`next dev` run masks a real type error in `verify`'s own `.source-verify` output. Reproduced: with a valid `.source` present and a `docs-source.ts` import renamed to break against a freshly-regenerated `.source-verify` (the same shape a real fumadocs-mdx or source-config change could produce), `tsc --noEmit` against the fallback list still exited 0. Fix: give `verify` a dedicated `tsconfig.verify.json` that resolves `@generated-docs/*` against `.source-verify` only, selected by a new `PDPP_SITE_TSCONFIG` env var alongside the existing `PDPP_SITE_SOURCE_DIR`. `next build` has its own internal type-check pass that reads `tsconfig.json` directly, separate from the webpack/turbopack aliases its bundler uses, so it needs the same fix: `next.config.mjs` now sets `typescript.tsconfigPath` from the same env var. The base `tsconfig.json` goes back to a single `.source` path, for the editor and a plain `types:check`. Re-ran the same masking repro against the fix: with the same stale, valid `.source` present, `tsc -p tsconfig.verify.json --noEmit` correctly fails on the broken `.source-verify`. `ultracite check` (the linter step inside `apps/site`'s `check`/`verify`) failed on `scripts/style-differ.mjs`'s `import { chromium } from "playwright"`, flagged as an unresolved import. `playwright` is a real dependency (`apps/site/package.json`); the import resolves under TypeScript and at runtime. The failure is Biome's documented resolver gap with re-exports (`export * from 'playwright-core'` in `playwright`'s `index.d.ts`): `playwright-core` is only an indirect dependency here, so pnpm's isolated `node_modules` gives Biome's resolver nothing to follow. `apps/site/biome.jsonc` already carries a scoped waiver for this exact upstream gap (biomejs/biome#9143, #6464) for `src/**`; `style-differ.mjs` sits outside `src/`, so it wasn't covered. Added a second, narrowly scoped override for this one file with its own rationale, rather than widening the `src/**` waiver's stated scope. `style-differ.mjs` itself is unmodified: it is a real, actively-referenced visual-regression diagnostic (used to prove CSS-migration behavior preservation, per its own header comment), not dead code. Both original bugs (the tsconfig path drift and the hand-edited `sync-spec-docs.mjs` generated-artifact drift) reached `main` because no PR check ran `apps/site`'s `types:check`/`verify` or the root `generated-artifacts:check`; the latter only runs post-merge in `semantic-release.yml`, gated on an actual version publish. Added `.github/workflows/site-generated-docs-check.yml`, path-scoped to `apps/site/**` the same way `react-doctor.yml` (this repo's existing apps/site-only PR check, a React-health regression scan) is, running `types:check` (in verify mode, via the same `PDPP_SITE_SOURCE_DIR`/ `PDPP_SITE_TSCONFIG` pair `verify` sets) and `generated-artifacts:check`. Deliberately not the full `verify` script: `verify` also runs the test suite and a production `next build`, real coverage but not what closes this gap, and roughly doubles wall time for no additional defect coverage here. Verified: `pnpm --dir apps/site run verify` passes end to end (exit 0): types:check, 250/250 tests, `ultracite check` (0 errors, 4 pre-existing unrelated warnings), `generated-artifacts:check` (7 byte-identical artifacts), and `next build` (log shows `Using tsconfig file: tsconfig.verify.json`). The new workflow's two steps (`PDPP_SITE_SOURCE_DIR=.source-verify PDPP_SITE_TSCONFIG=tsconfig.verify.json pnpm --dir apps/site run types:check` and `pnpm generated-artifacts:check`) both pass when run standalone from the repo root, matching how the workflow invokes them. Assisted-by: AI Signed-off-by: Tim Nunamaker --- .../workflows/site-generated-docs-check.yml | 84 +++++++++++++++++++ apps/site/biome.jsonc | 26 ++++++ apps/site/next.config.mjs | 13 ++- apps/site/package.json | 5 +- apps/site/scripts/site-runtime.test.ts | 32 +++++++ apps/site/scripts/site-runtime.ts | 7 ++ apps/site/tsconfig.json | 25 +++--- apps/site/tsconfig.verify.json | 20 +++++ 8 files changed, 199 insertions(+), 13 deletions(-) create mode 100644 .github/workflows/site-generated-docs-check.yml create mode 100644 apps/site/tsconfig.verify.json diff --git a/.github/workflows/site-generated-docs-check.yml b/.github/workflows/site-generated-docs-check.yml new file mode 100644 index 000000000..94659e42c --- /dev/null +++ b/.github/workflows/site-generated-docs-check.yml @@ -0,0 +1,84 @@ +name: site-generated-docs-check + +# PR gate for apps/site's generated-docs pipeline: verify's own `tsc` pass +# (types:check) and the root generated-artifacts drift check +# (generated-artifacts:check), both of which currently run ONLY post-merge +# (generated-artifacts:check in semantic-release.yml, gated on a version +# actually publishing) or not at all in CI (types:check/verify are never +# invoked by any workflow). That gap let two separate defects reach main +# undetected: a stale tsconfig path mapping that broke `pnpm --dir apps/site +# run verify` on a fresh checkout, and hand-edited generated output +# (apps/site/scripts/sync-spec-docs.mjs) that drifted from the TypeScript +# source it's compiled from. Neither is caught by spec-check.yml's "root/site +# spec drift" job, which only diffs generated markdown against the root +# specs. +# +# Deliberately NOT the full `verify` script: `verify` also runs the test +# suite and a production `next build`, which is real coverage but not what +# closes this specific gap and would roughly double this job's wall time for +# no additional defect coverage here. `types:check` alone reproduces the +# tsconfig defect (TS2307/TS2305-class failures); generated-artifacts:check +# alone reproduces the compiled-output drift. Both run in well under the +# `pnpm install` time this job already pays. + +on: + push: + branches: [main] + paths: + - ".github/workflows/site-generated-docs-check.yml" + - "apps/site/**" + - "docs/design-system/ink-carbon/project/**" + - "scripts/check-generated-artifacts.ts" + - "package.json" + - "pnpm-lock.yaml" + pull_request: + paths: + - ".github/workflows/site-generated-docs-check.yml" + - "apps/site/**" + - "docs/design-system/ink-carbon/project/**" + - "scripts/check-generated-artifacts.ts" + - "package.json" + - "pnpm-lock.yaml" + workflow_dispatch: {} + +concurrency: + group: site-generated-docs-check-${{ github.ref }} + cancel-in-progress: true + +jobs: + check: + name: site types:check + generated-artifacts:check + runs-on: ubuntu-latest + timeout-minutes: 10 + + steps: + - name: Checkout + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + + - name: Install pnpm + uses: pnpm/action-setup@ea17c68df8912ef543352723c149a84f56e3d413 # v6.1.0 + + - name: Install Node.js + uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0 + with: + node-version: "24" + cache: "pnpm" + + - name: Install dependencies + env: + PATCHRIGHT_SKIP_BROWSER_DOWNLOAD: "1" + PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD: "1" + run: pnpm install --frozen-lockfile + + # Runs with the same PDPP_SITE_SOURCE_DIR/PDPP_SITE_TSCONFIG isolation + # as `pnpm --dir apps/site run verify`, so this job exercises the exact + # tsconfig this gate exists to protect (tsconfig.verify.json), not the + # default tsconfig.json a plain `types:check` would use. + - name: Site types:check (verify mode) + env: + PDPP_SITE_SOURCE_DIR: .source-verify + PDPP_SITE_TSCONFIG: tsconfig.verify.json + run: pnpm --dir apps/site run types:check + + - name: Generated artifacts are not drifted + run: pnpm generated-artifacts:check diff --git a/apps/site/biome.jsonc b/apps/site/biome.jsonc index 67d9bb4b2..9a295dd85 100644 --- a/apps/site/biome.jsonc +++ b/apps/site/biome.jsonc @@ -89,6 +89,32 @@ } } }, + { + // Upstream Biome resolver false-positive waiver — NOT a doctrine change. + // + // scripts/style-differ.mjs does `import { chromium } from "playwright"`. + // playwright's index.d.ts is `export * from 'playwright-core'`, and + // playwright-core is an indirect dependency here (pulled in + // transitively, not listed in this package's own package.json), so + // under pnpm's isolated node_modules it has no symlink inside + // apps/site/node_modules for Biome's resolver to follow. The import + // resolves cleanly under TypeScript (moduleResolution: "bundler") and + // at runtime (verified: `node -e "require('playwright').chromium"` + // and the ESM equivalent both return the launcher). Same upstream gap + // as the src/** waiver above (biomejs/biome#9143, #6464); this file + // sits outside src/** so that override does not reach it. + // + // Exit condition: same as the src/** override above — drop once + // Biome's resolver follows re-exports through transitive dependencies. + "includes": ["scripts/style-differ.mjs"], + "linter": { + "rules": { + "correctness": { + "noUnresolvedImports": "off" + } + } + } + }, { // Architectural shim waiver — NOT a barrel-file pattern. // diff --git a/apps/site/next.config.mjs b/apps/site/next.config.mjs index 61d499abd..55c629e91 100644 --- a/apps/site/next.config.mjs +++ b/apps/site/next.config.mjs @@ -9,7 +9,7 @@ import { collectAllowedDevOrigins } from "./scripts/dev-origins.ts"; import { resolveSiteRuntime } from "./scripts/site-runtime.ts"; const __dirname = fileURLToPath(new URL(".", import.meta.url)); -const { buildWorkers, distDir, isProduction, sourceDir } = resolveSiteRuntime(); +const { buildWorkers, distDir, isProduction, sourceDir, tsconfigPath } = resolveSiteRuntime(); const withMDX = createMDX({ outDir: sourceDir }); const allowedDevOrigins = isProduction ? [] : collectAllowedDevOrigins(); @@ -27,6 +27,17 @@ const nextConfig = { }, output: "standalone", serverExternalPackages: ["fumadocs-mdx", "satteri"], + // `next build`'s own internal type-check pass reads tsconfig.json's + // "paths" directly — it does not go through the webpack/turbopack + // aliases below, which the bundler itself uses instead. Point it at the + // same per-mode tsconfig apps/site/package.json's "types:check" script + // selects via PDPP_SITE_TSCONFIG (see scripts/site-runtime.ts), so a + // `verify` build's typecheck resolves "@generated-docs/*" against + // ".source-verify" like `verify`'s own separate `tsc -p` pass does, not + // against the base config's ".source". + typescript: { + tsconfigPath, + }, turbopack: { resolveAlias: { "@generated-docs/*": `./${sourceDir}/*`, diff --git a/apps/site/package.json b/apps/site/package.json index 2f81691ba..72f380bb1 100644 --- a/apps/site/package.json +++ b/apps/site/package.json @@ -8,6 +8,7 @@ "//dev-node-options": "NODE_OPTIONS adds Node.js diagnostic reports on fatal V8 errors and uncaught exceptions. Reports land in tmp/node-reports/ at the repo root (gitignored), excluding env/network sections so reports do not persist secrets.", "//dev-port": "Public site defaults to :3001 (design.md §5). PDPP_SITE_PORT overrides; PDPP_WEB_PORT kept as a fallback for existing contributor env files.", "//sync-specs": "Generates content/docs/spec-*.md from the normative root spec-*.md (single source). The generated files are gitignored; predev/prebuild regenerate them. See scripts/sync-spec-docs.mjs.", + "//types-check-tsconfig": "PDPP_SITE_TSCONFIG selects which tsconfig types:check's tsc pass uses; default tsconfig.json resolves @generated-docs/* against .source only. A mode whose PDPP_SITE_SOURCE_DIR points elsewhere (currently just verify, via .source-verify) must also set PDPP_SITE_TSCONFIG to a config whose paths match that directory, or tsc can silently resolve a stale .source left by an unrelated dev/build run instead of the output this run just generated. See tsconfig.verify.json.", "prebuild": "node scripts/sync-spec-docs.mjs", "build": "next build --webpack", "check": "ultracite check && pnpm --dir ../.. generated-artifacts:check", @@ -19,8 +20,8 @@ "start": "next start", "sync:specs": "node scripts/sync-spec-docs.mjs", "test": "node --import tsx ../../scripts/test-scratch/run-command.ts -- node ../../scripts/run-node-tests.mjs --test --import tsx --import ../../packages/operator-ui/css-stub-register.ts \"scripts/*.test.ts\" \"src/**/*.test.ts\"", - "types:check": "node scripts/sync-spec-docs.mjs && fumadocs-mdx source.config.ts \"${PDPP_SITE_SOURCE_DIR:-.source}\" && next typegen && tsc --noEmit", - "verify": "sh -c 'export PDPP_SITE_NEXT_DIST_DIR=.next-verify PDPP_SITE_SOURCE_DIR=.source-verify; pnpm types:check && pnpm test && pnpm check && pnpm build'" + "types:check": "node scripts/sync-spec-docs.mjs && fumadocs-mdx source.config.ts \"${PDPP_SITE_SOURCE_DIR:-.source}\" && next typegen && tsc -p \"${PDPP_SITE_TSCONFIG:-tsconfig.json}\" --noEmit", + "verify": "sh -c 'export PDPP_SITE_NEXT_DIST_DIR=.next-verify PDPP_SITE_SOURCE_DIR=.source-verify PDPP_SITE_TSCONFIG=tsconfig.verify.json; pnpm types:check && pnpm test && pnpm check && pnpm build'" }, "dependencies": { "@base-ui/react": "^1.8.0", diff --git a/apps/site/scripts/site-runtime.test.ts b/apps/site/scripts/site-runtime.test.ts index b9567b1c4..19336506e 100644 --- a/apps/site/scripts/site-runtime.test.ts +++ b/apps/site/scripts/site-runtime.test.ts @@ -11,6 +11,7 @@ const DEV_DIST_DIR = /PDPP_SITE_NEXT_DIST_DIR=\.next-dev/; const DEV_SOURCE_DIR = /PDPP_SITE_SOURCE_DIR=\.source-dev/; const VERIFY_DIST_DIR = /PDPP_SITE_NEXT_DIST_DIR=\.next-verify/; const VERIFY_SOURCE_DIR = /PDPP_SITE_SOURCE_DIR=\.source-verify/; +const VERIFY_TSCONFIG = /PDPP_SITE_TSCONFIG=tsconfig\.verify\.json/; test("development omits the production worker limit", () => { assert.deepEqual(resolveSiteRuntime({ NODE_ENV: "development", PDPP_SITE_NEXT_DIST_DIR: ".next-dev" }), { @@ -18,6 +19,7 @@ test("development omits the production worker limit", () => { distDir: ".next-dev", isProduction: false, sourceDir: ".source", + tsconfigPath: "tsconfig.json", }); }); @@ -26,6 +28,20 @@ test("production defaults to one worker and accepts an explicit override", () => assert.equal(resolveSiteRuntime({ NODE_ENV: "production", PDPP_WEB_BUILD_WORKERS: "8" }).buildWorkers, 8); }); +// The default sourceDir/tsconfigPath pairing must stay consistent: a mode +// that does not set PDPP_SITE_SOURCE_DIR resolves "@generated-docs/*" via +// tsconfig.json's own ".source" paths entry, so a plain resolveSiteRuntime() +// call must also default to that same tsconfig, not verify's. +test("default source directory pairs with the base tsconfig, not a per-mode override", () => { + const runtime = resolveSiteRuntime({}); + assert.equal(runtime.sourceDir, ".source"); + assert.equal(runtime.tsconfigPath, "tsconfig.json"); +}); + +test("PDPP_SITE_TSCONFIG overrides the default tsconfig path", () => { + assert.equal(resolveSiteRuntime({ PDPP_SITE_TSCONFIG: "tsconfig.verify.json" }).tsconfigPath, "tsconfig.verify.json"); +}); + test("supported development and verification commands select distinct output directories", async () => { const packageJson = JSON.parse(await readFile(new URL("../package.json", import.meta.url), "utf8")) as { scripts: Record; @@ -40,3 +56,19 @@ test("supported development and verification commands select distinct output dir assert.match(verify, VERIFY_SOURCE_DIR); assert.notEqual(dev, verify); }); + +// verify's own tsc pass (types:check) and next build's internal type-check +// must resolve "@generated-docs/*" against .source-verify, not the base +// tsconfig's ".source" — see tsconfig.verify.json and next.config.mjs's +// typescript.tsconfigPath wiring. A stale .source left by an unrelated +// dev/build run must never be able to mask a type error in verify's own +// freshly generated output. +test("verify selects its own tsconfig, distinct from the base config", async () => { + const packageJson = JSON.parse(await readFile(new URL("../package.json", import.meta.url), "utf8")) as { + scripts: Record; + }; + + const { verify } = packageJson.scripts; + assert.ok(verify); + assert.match(verify, VERIFY_TSCONFIG); +}); diff --git a/apps/site/scripts/site-runtime.ts b/apps/site/scripts/site-runtime.ts index e7cfdb183..175f35c61 100644 --- a/apps/site/scripts/site-runtime.ts +++ b/apps/site/scripts/site-runtime.ts @@ -5,6 +5,7 @@ interface SiteRuntimeEnvironment { NODE_ENV?: string; PDPP_SITE_NEXT_DIST_DIR?: string; PDPP_SITE_SOURCE_DIR?: string; + PDPP_SITE_TSCONFIG?: string; PDPP_WEB_BUILD_WORKERS?: string; } @@ -27,5 +28,11 @@ export function resolveSiteRuntime(environment: SiteRuntimeEnvironment = process distDir: environment.PDPP_SITE_NEXT_DIST_DIR ?? ".next", isProduction, sourceDir: environment.PDPP_SITE_SOURCE_DIR ?? ".source", + // Same PDPP_SITE_TSCONFIG that package.json's "types:check" script + // passes to `tsc -p`, so `next build`'s own internal type-check pass + // (which reads tsconfig.json directly, not through the webpack/ + // turbopack "@generated-docs" aliases) resolves against the same + // directory as the rest of the current run. See next.config.mjs. + tsconfigPath: environment.PDPP_SITE_TSCONFIG ?? "tsconfig.json", }; } diff --git a/apps/site/tsconfig.json b/apps/site/tsconfig.json index 62c7e4551..2c396f120 100644 --- a/apps/site/tsconfig.json +++ b/apps/site/tsconfig.json @@ -27,18 +27,23 @@ ], // fumadocs-mdx writes into PDPP_SITE_SOURCE_DIR (default ".source"; see // scripts/site-runtime.ts), isolated per run mode so dev, verify, and - // build never clobber each other's generated output (see next.config.ts's - // matching webpack/turbopack aliases). tsc has no env-var substitution in - // "paths", so list every mode's directory; tsc resolves the first one that - // exists. + // build never clobber each other's generated output (see + // next.config.mjs's matching webpack/turbopack aliases and its + // typescript.tsconfigPath, which points next build's own internal + // type-check at the right tsconfig for the same reason this file exists). + // tsc has no env-var substitution in "paths", so this default config + // resolves only ".source" — the editor/IDE and a plain `types:check` (no + // PDPP_SITE_SOURCE_DIR set) both use that directory. A mode that isolates + // its output elsewhere (currently just "verify", via .source-verify) + // needs its own tsconfig with "paths" pointed at that directory instead + // of a fallback list here: listing every mode's directory would let tsc + // silently resolve a stale leftover directory ahead of the one the + // current run actually generated, masking a real type error in fresh + // output. See tsconfig.verify.json. "paths": { "@/*": ["./src/*"], - "@generated-docs/*": [ - "./.source/*", - "./.source-verify/*", - "./.source-dev/*" - ], - "collections/*": ["./.source/*", "./.source-verify/*", "./.source-dev/*"] + "@generated-docs/*": ["./.source/*"], + "collections/*": ["./.source/*"] } }, "include": [ diff --git a/apps/site/tsconfig.verify.json b/apps/site/tsconfig.verify.json new file mode 100644 index 000000000..102448927 --- /dev/null +++ b/apps/site/tsconfig.verify.json @@ -0,0 +1,20 @@ +{ + // Used only by `types:check`/`verify` (PDPP_SITE_SOURCE_DIR=.source-verify; + // see scripts/site-runtime.ts and package.json's "verify" script). Extends + // the base tsconfig but resolves "@generated-docs/*" against + // ".source-verify" ONLY, not the multi-directory fallback list in the base + // config. That fallback let tsc silently resolve a stale ".source" left + // over from an unrelated `next build`/`next dev` run instead of the + // ".source-verify" output verify just generated, so a type error in fresh + // output could pass unnoticed. The editor/IDE and a plain `types:check` + // (no PDPP_SITE_SOURCE_DIR set) keep using tsconfig.json's default + // ".source" directly. + "extends": "./tsconfig.json", + "compilerOptions": { + "paths": { + "@/*": ["./src/*"], + "@generated-docs/*": ["./.source-verify/*"], + "collections/*": ["./.source-verify/*"] + } + } +} From 60ef31e284ea0bf42482d77373564e1d3c0ef235 Mon Sep 17 00:00:00 2001 From: Tim Nunamaker Date: Wed, 23 Sep 2026 10:33:48 -0500 Subject: [PATCH 3/3] docs(site): correct and shorten the per-mode tsconfig comments tsconfig.verify.json still described a fallback list in the base config that the previous commit removed. Assisted-by: AI Signed-off-by: Tim Nunamaker --- apps/site/tsconfig.json | 19 ++++--------------- apps/site/tsconfig.verify.json | 14 ++++---------- 2 files changed, 8 insertions(+), 25 deletions(-) diff --git a/apps/site/tsconfig.json b/apps/site/tsconfig.json index 2c396f120..f6263dcbe 100644 --- a/apps/site/tsconfig.json +++ b/apps/site/tsconfig.json @@ -25,21 +25,10 @@ "name": "next" } ], - // fumadocs-mdx writes into PDPP_SITE_SOURCE_DIR (default ".source"; see - // scripts/site-runtime.ts), isolated per run mode so dev, verify, and - // build never clobber each other's generated output (see - // next.config.mjs's matching webpack/turbopack aliases and its - // typescript.tsconfigPath, which points next build's own internal - // type-check at the right tsconfig for the same reason this file exists). - // tsc has no env-var substitution in "paths", so this default config - // resolves only ".source" — the editor/IDE and a plain `types:check` (no - // PDPP_SITE_SOURCE_DIR set) both use that directory. A mode that isolates - // its output elsewhere (currently just "verify", via .source-verify) - // needs its own tsconfig with "paths" pointed at that directory instead - // of a fallback list here: listing every mode's directory would let tsc - // silently resolve a stale leftover directory ahead of the one the - // current run actually generated, masking a real type error in fresh - // output. See tsconfig.verify.json. + // Resolves generated docs from ".source", the default PDPP_SITE_SOURCE_DIR. + // tsc cannot read env vars in "paths", so a mode that writes elsewhere + // needs its own tsconfig; see tsconfig.verify.json. Do not list several + // directories here: tsc picks the first that exists, which can be stale. "paths": { "@/*": ["./src/*"], "@generated-docs/*": ["./.source/*"], diff --git a/apps/site/tsconfig.verify.json b/apps/site/tsconfig.verify.json index 102448927..e40f00baa 100644 --- a/apps/site/tsconfig.verify.json +++ b/apps/site/tsconfig.verify.json @@ -1,14 +1,8 @@ { - // Used only by `types:check`/`verify` (PDPP_SITE_SOURCE_DIR=.source-verify; - // see scripts/site-runtime.ts and package.json's "verify" script). Extends - // the base tsconfig but resolves "@generated-docs/*" against - // ".source-verify" ONLY, not the multi-directory fallback list in the base - // config. That fallback let tsc silently resolve a stale ".source" left - // over from an unrelated `next build`/`next dev` run instead of the - // ".source-verify" output verify just generated, so a type error in fresh - // output could pass unnoticed. The editor/IDE and a plain `types:check` - // (no PDPP_SITE_SOURCE_DIR set) keep using tsconfig.json's default - // ".source" directly. + // Used by `verify` (PDPP_SITE_SOURCE_DIR=.source-verify, selected through + // PDPP_SITE_TSCONFIG). Resolves generated docs only from the output that + // verify just wrote, so a stale ".source" from a dev or build run cannot + // hide a type error. "extends": "./tsconfig.json", "compilerOptions": { "paths": {