Skip to content

fix(site): fix tsconfig and sync-spec-docs.mts drift breaking verify - #372

Merged
tnunamak merged 3 commits into
mainfrom
fix/site-generated-docs
Sep 23, 2026
Merged

tnunamak merged 3 commits into
mainfrom
fix/site-generated-docs

Conversation

@tnunamak

@tnunamak tnunamak commented Sep 23, 2026 •

Copy link
Copy Markdown
Contributor

What

Fixes two failures found while verifying a dependency bump on origin/main, both reproducible on a fresh checkout:

  1. pnpm --dir apps/site run verify (this repo's combined typecheck/test/lint/build command for the public documentation site under apps/site/) failed with TS2307: Cannot find module '@generated-docs/dynamic.ts'.
  2. pnpm generated-artifacts:check (a script that recompiles a small set of TypeScript sources and asserts the checked-in compiled .js/.mjs output is byte-identical, so no one hand-edits generated files) failed with drift in apps/site/scripts/sync-spec-docs.mjs.

Why

Bug 1 is a stale path mapping. apps/site generates its documentation-page source (via the fumadocs-mdx library) into a directory controlled by the PDPP_SITE_SOURCE_DIR environment variable, isolated per run mode (.source-dev for pnpm dev, .source-verify for pnpm verify) so concurrent dev/verify runs cannot clobber each other's output. apps/site/next.config.mjs's webpack and Turbopack (Next.js's alternate, opt-in bundler) aliases both read that variable correctly. apps/site/tsconfig.json's paths entry for the same @generated-docs/* import alias does not; it was hardcoded to the default ./.source/* and never updated when the per-mode isolation was added. TypeScript's tsc, invoked directly in types:check, has no way to read an environment variable from paths, so it always looked in .source, which a verify run never populates. No CI workflow in this repository ran apps/site's verify or types:check, so nothing caught this.

The first fix here listed all three mode directories (.source, .source-verify, .source-dev) in paths as fallbacks, on the assumption that tsc would resolve whichever one the current run populated. That is unsound: tsc resolves the first listed directory that exists, not the one the current run generated, so a .source left behind by an unrelated next build/next dev run would mask a real type error in verify's own .source-verify output. Reproduced by seeding a valid .source, then breaking docs-source.ts's import against a freshly regenerated .source-verify: tsc --noEmit against the fallback list still exited 0. Fixed by giving verify a dedicated tsconfig.verify.json that resolves @generated-docs/* against .source-verify only, selected by a new PDPP_SITE_TSCONFIG env var. next build's own internal type-check pass also reads tsconfig.json directly, separately from the bundler aliases, so next.config.mjs now sets typescript.tsconfigPath from the same variable. The base tsconfig.json keeps a single .source path for the editor and a plain types:check. Re-ran the repro against the fix: with the same stale .source present, the per-mode tsc pass correctly fails on the broken .source-verify.

Bug 2 is real generated-artifact drift, not a tooling gap. apps/site/scripts/sync-spec-docs.mjs is compiled output; its source is sync-spec-docs.mts, and scripts/check-generated-artifacts.ts is the check that keeps them in sync. PR #366 added stale-file cleanup logic (removing a leftover .md page that an earlier revision of this script used to write, which otherwise collides with the .mdx page of the same name and fails the site build) by editing the compiled .mjs file directly, without making the matching change in the .mts source. generated-artifacts:check only runs in .github/workflows/semantic-release.yml, gated on semantic-release deciding a new version will actually publish, so it was not a required PR check and this drift shipped unnoticed. Fixed by porting the same cleanup logic into sync-spec-docs.mts and regenerating sync-spec-docs.mjs from it; the regenerated file is byte-identical to what was already committed, confirming the port is faithful.

Neither bug was caught by the CI check named "root/site spec drift" (.github/workflows/spec-check.yml, job spec-check): that job runs pnpm spec:check, which only diffs generated markdown against the root specs and never invokes tsc or generated-artifacts:check.

Fixed the remaining verify failure. ultracite check (the linter step inside apps/site's check/verify; it runs Biome, a Rust-based JS/TS linter, under the hood) failed on scripts/style-differ.mjs's import { chromium } from "playwright", flagged as an unresolved import. playwright is a real, declared dependency, and the import resolves cleanly under both TypeScript and Node at runtime. The failure is Biome's own documented resolver gap: playwright re-exports from playwright-core, an indirect (not directly declared) dependency here, which pnpm's isolated node_modules layout hides from Biome's resolver even though Node itself resolves it fine. apps/site/biome.jsonc already carries a scoped waiver for this exact upstream gap (biomejs/biome#9143, #6464) covering src/**; style-differ.mjs sits outside src/, so a second, narrowly scoped override was added for this one file. style-differ.mjs itself is unmodified: it's a real, actively used computed-style diff tool, not dead code.

Added a PR-time gate. 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 scoped. It runs types:check in verify mode (PDPP_SITE_SOURCE_DIR=.source-verify PDPP_SITE_TSCONFIG=tsconfig.verify.json) and generated-artifacts:check, the two commands that would have caught both bugs above. It runs those two commands specifically, not the full verify script: verify also runs the test suite and a production next build, real coverage but roughly double the wall time for no extra coverage against these two bug classes.

Verification

  • pnpm --dir apps/site run verify: passes end to end (exit 0), covering types:check, 250/250 tests (3 new, covering the per-mode tsconfig selection), ultracite check (0 errors, 4 pre-existing unrelated warnings), generated-artifacts:check (7 byte-identical artifacts), and next build (log confirms Using tsconfig file: tsconfig.verify.json).
  • pnpm generated-artifacts:check and pnpm spec:check: both pass, unaffected.
  • The new workflow's two steps, run standalone from the repo root with the same env it sets, both pass.

Not verified: whether the pre-existing scope of the playwright/ultracite resolver gap extends to other files beyond style-differ.mjs; not audited beyond the one file this PR touches.

Assisted-by: AI

@vercel

vercel Bot commented Sep 23, 2026 •

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated
pdpp Ready Ready Preview Sep 23, 2026 3:35pm UTC

Request Review

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 <tnunamak@gmail.com>
…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 <tnunamak@gmail.com>
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 <tnunamak@gmail.com>
@tnunamak
tnunamak merged commit a1856cc into main Sep 23, 2026
6 checks passed
@tnunamak
tnunamak deleted the fix/site-generated-docs branch September 23, 2026 15:41

This branch was successfully deployed

1 active deployment
Preview — 60ef31e2 Deployed Sep 23, 2026 by vercel[bot]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant