From e136fbc56a214286a0ce639037bfa352ceebabf7 Mon Sep 17 00:00:00 2001 From: Ajay Ghanwat Date: Tue, 4 Aug 2026 20:09:16 +0530 Subject: [PATCH] fix(router): quietly skip permissions.gen.{ts,js} in authz scan Task 10 fix round 1: the coordinator's plan doc (41fb82b9) recorded that generated authz type files should be skipped before the isSafeIslandName check, but the code change never landed. isSafeIslandName rejects the dot in the stripped basename "permissions.gen", so every app running Task 12's codegen would warn on every boot. Add a quiet skip for *.gen.ts / *.gen.js immediately after the extension guard, before the name check. Add tests: a .gen.ts file is skipped without a console.warn (spied), and a .gen.js file is skipped the same way while a legitimately named .js declaration is still discovered. Also corrects the scanDir extraExtensions doc comment, which incorrectly implied app/schemas passes it too (only app/authz does). Co-Authored-By: Claude Opus 5 --- packages/router/src/index.ts | 4 ++++ packages/router/src/scan.ts | 9 +++---- packages/router/test/authz-discovery.test.ts | 25 +++++++++++++++++++- 3 files changed, 33 insertions(+), 5 deletions(-) diff --git a/packages/router/src/index.ts b/packages/router/src/index.ts index 5923b5e3..9060e178 100644 --- a/packages/router/src/index.ts +++ b/packages/router/src/index.ts @@ -290,6 +290,10 @@ export function buildRouter(appDir: string, opts: RouterOptions = {}): Router { const authz: ComponentRef[] = []; for (const f of scanDir(join(appDir, "authz"), [".js"])) { if (!/\.(ts|js)$/.test(f.file)) continue; + // Generated type files (permissions.gen.ts) live here too. Skip them quietly: + // they export types only, and isSafeIslandName would otherwise reject the dot + // and warn on every boot. + if (/[.]gen[.](ts|js)$/.test(f.file)) continue; const name = basename(f.file).replace(/\.(ts|js)$/, ""); if (!isSafeIslandName(name)) { console.warn(`[wrnexus] skipping authz declaration with unsafe name: ${name}`); diff --git a/packages/router/src/scan.ts b/packages/router/src/scan.ts index 7d6b7650..dce4d52d 100644 --- a/packages/router/src/scan.ts +++ b/packages/router/src/scan.ts @@ -33,10 +33,11 @@ function isIgnored(name: string): boolean { * Recursively collect allowed route files under `baseDir`. * Returns [] if the directory does not exist (a route kind may be unused). * - * `extraExtensions` widens the allow-list for callers that scan non-route - * directories (e.g. `app/schemas`, `app/authz`) and accept plain `.js` - * modules; it defaults to empty so route scanning (`app/pages`, `app/api`, - * `app/realtime`, ...) is unaffected. + * `extraExtensions` widens the allow-list for a caller that scans a non-route + * directory and accepts plain `.js` modules (currently only `app/authz`); it + * defaults to empty so every other caller — route scanning (`app/pages`, + * `app/api`, `app/realtime`, ...) as well as `app/schemas`, which does not + * pass it and so still only sees `.ts`/`.tsx`/`.wrn` — is unaffected. */ export function scanDir(baseDir: string, extraExtensions: readonly string[] = []): ScannedFile[] { if (!existsSync(baseDir)) return []; diff --git a/packages/router/test/authz-discovery.test.ts b/packages/router/test/authz-discovery.test.ts index 438e7fbe..196fdd47 100644 --- a/packages/router/test/authz-discovery.test.ts +++ b/packages/router/test/authz-discovery.test.ts @@ -1,4 +1,4 @@ -import { describe, expect, test } from "bun:test"; +import { describe, expect, spyOn, test } from "bun:test"; import { mkdirSync, mkdtempSync, writeFileSync } from "node:fs"; import { tmpdir } from "node:os"; import { join } from "node:path"; @@ -41,4 +41,27 @@ describe("app/authz discovery", () => { mkdirSync(join(root, "app", "pages"), { recursive: true }); expect(buildRouter(join(root, "app")).authz).toEqual([]); }); + + test("quietly skips generated permissions.gen.ts without warning", () => { + const appDir = appWithAuthz({ + "permissions.gen.ts": "export type Foo = 1;", + "blog.ts": "export default {};", + }); + const warnSpy = spyOn(console, "warn").mockImplementation(() => {}); + try { + const router = buildRouter(appDir); + expect(router.authz.map((entry) => entry.name)).toEqual(["blog"]); + expect(warnSpy).not.toHaveBeenCalled(); + } finally { + warnSpy.mockRestore(); + } + }); + + test("skips permissions.gen.js too, while a legitimately named declaration is still discovered", () => { + const appDir = appWithAuthz({ + "permissions.gen.js": "export const x = 1;", + "billing.js": "export default {};", + }); + expect(buildRouter(appDir).authz.map((entry) => entry.name)).toEqual(["billing"]); + }); });