From fa8937eb7eb535d77b34bc033275f423c635e78f Mon Sep 17 00:00:00 2001 From: innolove-dev Date: Thu, 16 Jul 2026 21:02:33 +0100 Subject: [PATCH] fix(native): don't flag P0_TRANSFORMS pages as uncovered server routes The anti-rot scan only consulted ITEMS_TO_DISABLE, so a page whose server-only exports are already stripped by a P0_TRANSFORMS stub still failed the native build. This is live on main: (mobile-ui)/claim/page.tsx has force-dynamic and a matching stub, and the scan rejects it. Skip files handled by a transform, and cover the scan with a unit test. --- scripts/__tests__/native-build-scan.test.js | 57 +++++++++++++++++++++ scripts/native-build.js | 10 +++- 2 files changed, 66 insertions(+), 1 deletion(-) create mode 100644 scripts/__tests__/native-build-scan.test.js diff --git a/scripts/__tests__/native-build-scan.test.js b/scripts/__tests__/native-build-scan.test.js new file mode 100644 index 0000000000..cff8347b5d --- /dev/null +++ b/scripts/__tests__/native-build-scan.test.js @@ -0,0 +1,57 @@ +const fs = require('fs') +const path = require('path') +const Module = require('module') + +const SCRIPT_PATH = path.join(__dirname, '..', 'native-build.js') + +// native-build.js is a script, not a module: it calls main() at import time and +// exports nothing. Load the real source with that call stripped so the scan +// helpers can be asserted against the actual app tree. +function loadScriptInternals() { + const source = fs.readFileSync(SCRIPT_PATH, 'utf-8') + const withoutEntrypoint = source.replace(/\nmain\(\)\s*$/, '\n') + expect(withoutEntrypoint).not.toBe(source) + + const exposed = + withoutEntrypoint + + '\nmodule.exports = { isHandledByTransform, detectUncoveredServerRoutes, P0_TRANSFORMS, APP_DIR }\n' + + const mod = new Module(SCRIPT_PATH, null) + mod.filename = SCRIPT_PATH + mod.paths = Module._nodeModulePaths(path.dirname(SCRIPT_PATH)) + mod._compile(exposed, SCRIPT_PATH) + return mod.exports +} + +const { isHandledByTransform, detectUncoveredServerRoutes, P0_TRANSFORMS, APP_DIR } = loadScriptInternals() + +const toPosix = (p) => p.split(path.sep).join('/') + +describe('native build server-route scan', () => { + it('treats every P0_TRANSFORMS entry as handled', () => { + for (const transform of P0_TRANSFORMS) { + expect(isHandledByTransform(transform.path)).toBe(true) + } + }) + + // The scan passes `path.relative(APP_DIR, full)` into the predicate. If the + // predicate compared a different path shape it would silently never match. + it('matches the relative path shape the scan actually computes', () => { + for (const transform of P0_TRANSFORMS) { + const absolute = path.join(APP_DIR, transform.path) + expect(isHandledByTransform(path.relative(APP_DIR, absolute))).toBe(true) + } + }) + + it('does not suppress routes that are not transformed', () => { + expect(isHandledByTransform('some/other/page.tsx')).toBe(false) + expect(isHandledByTransform('api/foo/route.ts')).toBe(false) + expect(isHandledByTransform('(mobile-ui)/claim/layout.tsx')).toBe(false) + }) + + it('does not flag transformed pages as uncovered server routes', () => { + const transformed = new Set(P0_TRANSFORMS.map((t) => t.path)) + const offenders = detectUncoveredServerRoutes().filter((o) => transformed.has(toPosix(o.rel))) + expect(offenders).toEqual([]) + }) +}) diff --git a/scripts/native-build.js b/scripts/native-build.js index 09934ec733..31946721dd 100644 --- a/scripts/native-build.js +++ b/scripts/native-build.js @@ -248,6 +248,14 @@ function isCoveredByDisableList(relPath) { }) } +// P0_TRANSFORMS files are replaced with static-export-safe stubs before `next +// build`, so their server-only exports (generateMetadata, force-dynamic) never +// reach the export — the scan must not flag them. +function isHandledByTransform(relPath) { + const normalized = relPath.split(path.sep).join('/') + return P0_TRANSFORMS.some((t) => t.path === normalized) +} + function detectUncoveredServerRoutes(dir = APP_DIR, found = []) { for (const entry of fs.readdirSync(dir, { withFileTypes: true })) { if (entry.name.includes('.disabled') || entry.name.startsWith('_')) continue @@ -258,7 +266,7 @@ function detectUncoveredServerRoutes(dir = APP_DIR, found = []) { detectUncoveredServerRoutes(full, found) continue } - if (isCoveredByDisableList(rel)) continue + if (isCoveredByDisableList(rel) || isHandledByTransform(rel)) continue if (entry.name === 'route.ts' || entry.name === 'route.js') { found.push({ rel, reason: 'route handler (cannot be statically exported)' }) continue