Skip to content

chore(router): regenerate per-module route type files and gate them in CI - #1127

Merged
ivogt merged 3 commits into
mainfrom
chore/regenerate-gen-files
Oct 13, 2026
Merged

ivogt merged 3 commits into
mainfrom
chore/regenerate-gen-files

Conversation

@ivogt

@ivogt ivogt commented Oct 12, 2026

Copy link
Copy Markdown
Collaborator

devcorner: dc-t21

Per-module <module>.gen.ts files were committed but never kept current: only the rango generate CLI writes them, and nothing in CI or the hooks ran it. At 8a843aa, 16 of 60 files were stale (test-app type-stress-test.gen.ts had 214 lines against 1,114 generated; pages/articles.gen.ts lacked a route) and 131 modules had no file. A second problem hid underneath: the route names in *.gen.ts were sorted with a bare localeCompare, so the order followed the machine's locale (Czech sorts "ch" after "h"). Two machines wrote different files from the same routes, so a freshness check on one would report the other's files as stale.

Before and after

# before: nothing noticed a stale per-module file
git push                  # CI green with urls/blog.gen.ts out of date

# after: the same push
pnpm check:route-gen      # exit 1: "stale: apps/.../urls/blog.gen.ts"
node tools/generate-route-types.mjs   # rewrites it; commit the result

Consumers of @rangojs/router see one change: generated route files list names in the same order on every machine. Nothing to change in app code.

End-to-end example

A contributor adds path("/archive", ..., { name: "archive" }) to src/urls/blog.tsx and forgets the gen file.

  1. Pre-commit (lefthook route-gen job, gated on app sources, src/build/route-types/** and the tool) runs node tools/generate-route-types.mjs --check and fails, listing blog.gen.ts as stale.
  2. They run node tools/generate-route-types.mjs, commit the rewritten file, and the check passes.
  3. CI lint runs pnpm check:route-gen as well, so --no-verify does not get around it.

Semantics

Situation --check write mode
File matches generator output current untouched
File differs stale, exit 1 rewritten
Source module has no file missing, exit 1 created
File without a source module, or an empty placeholder orphan, exit 1 reported, never deleted by the tool alone (it aborts if a run would delete more files than it generates)
Module whose includes the static parser cannot resolve (4: test-app urls/dev-routes, urls/revalidation-contract; stress-demo groups/hub, site-admin-patterns) skipped, reason in the tool header; --verbose lists the skipped includes skipped
apps/docs/src/content.gen.ts driven through its own writer, which gains --check own writer

Code changes

  • tools/generate-route-types.mjs (new): bundles the router's per-module writer from src with esbuild (in memory, packages: "external", no router build), runs it over every app that depends on @rangojs/router. --check takes about 0.45 s.
  • src/build/route-types/per-module-writer.ts: split into a pure planPerModuleRouteTypesForFile and the unchanged write wrapper (placeholder only if missing, diff-before-write, catch-and-warn), so the tool needs no fs shim. Not a package export.
  • src/build/route-types/codegen.ts: generatePerModuleTypesSource and generateRouteTypesSource sort with localeCompare(b, "en"). Why here: both generators feed every writer, so the pin covers the CLI, Vite and the tool.
  • apps/docs/scripts/gen-content.mjs: --check flag, "en" sort.
  • Wiring: package.json (check:route-gen, generate:route-types), .github/workflows/e2e.yml lint job, lefthook.yml pre-commit job, AGENTS.md Hard rule 2 clause.
  • 167 files: 139 new and 18 refreshed *.gen.ts, plus the above.

Locale fix, key lines:

// codegen.ts
names.sort((a, b) => a.localeCompare(b, "en"));

Test plan

  • packages/rangojs-router/src/build/__tests__/generate-route-types.test.ts (new): stubs localeCompare to default to Czech; ["hat", "charts"] comes out hat, charts without the pin and charts, hat with it. Red before the pin, green after.
  • pnpm check:route-gen on the rebased tree: 198 per-module files, 198 current, 0 stale, 0 missing, 0 orphan (16 apps, 1,028 source files).
  • Red-before-green for the gate (dc-c150): a stale file makes --check exit 1; a broken writer exits non-zero without deleting anything; write mode on a clean tree gives zero diff.
  • pnpm run typecheck 16/16
  • pnpm run test:unit:all: router 8,716 passed (5 skipped, 6 todo), rsc 830 passed (2 expected fail), plus consumer suites 1, 3, 20, 25, 26, 27, 85 passed
  • pnpm run lint: 0 warnings, 0 errors
  • pnpm run format: clean
  • pnpm check:e2e-bucketing: OK
  • Route-types e2e, dev and production, 12/12, and route-types HMR 21 (run on the pre-rebase tree, dc-c158); CI runs e2e for this tree.

Notes

@pkg-pr-new

pkg-pr-new Bot commented Oct 12, 2026 •

Copy link
Copy Markdown

Open in StackBlitz

npm i https://pkg.pr.new/@rangojs/router@1127

commit: 2792db0

ivogt added 3 commits October 13, 2026 05:32
…very locale

generatePerModuleTypesSource and generateRouteTypesSource sorted route names
with a bare `localeCompare`, so the order in `*.gen.ts` followed the machine's
default locale (Czech sorts "ch" after "h"). Two machines could write
different files from the same routes, and a freshness check on one reported
the other's files as stale. Both sorts pin the "en" locale.

The new test stubs `localeCompare` to default to Czech: without the pin
`["hat", "charts"]` comes out as hat, charts; with it, charts, hat.
The per-module route-type files (`<module>.gen.ts`) are written only by the
`rango generate` CLI, which nothing in CI or the hooks ran, so the committed
copies drifted from their sources.

tools/generate-route-types.mjs runs the router's per-module writer over every
app that depends on @rangojs/router and rewrites the files; `--check` compares
the generator output to the committed files and exits 1 listing stale, missing,
placeholder and orphan files. apps/docs/src/content.gen.ts stays under its own
writer, which gains a `--check` flag the tool drives and sorts its pages with a
pinned "en" locale.

The tool aborts instead of touching the disk when its fs shim over the writer
does not apply (no matching import, `fs/promises`, a write moved to a helper),
when the writer produces no files, or when a write run would delete more files
than it generates. `--verbose` lists the modules whose includes the static
parser skipped (the three committed partial maps). The writer's placeholder
comment no longer claims runtime discovery fills placeholders in.

Wired into the CI lint job (`pnpm check:route-gen`) and a lefthook pre-commit
job gated on app sources, the generator and its inputs. AGENTS.md Hard rule 2
names the command.
Ran tools/generate-route-types.mjs: 18 stale files refreshed (including
apps/docs/src/content.gen.ts, from its own writer) and 139 added (every
urls()/clientUrls() module in the apps now has its `<module>.gen.ts`). Four
modules whose includes the static parser cannot resolve (test-app
urls/dev-routes and urls/revalidation-contract, cloudflare-stress-demo
groups/hub and site-admin-patterns) get no file; the tool header explains why.
@ivogt
ivogt force-pushed the chore/regenerate-gen-files branch from 4c04a7e to 2792db0 Compare October 13, 2026 02:38
@ivogt
ivogt merged commit f579921 into main Oct 13, 2026
59 checks passed
@ivogt
ivogt deleted the chore/regenerate-gen-files branch October 13, 2026 03:03
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