diff --git a/package.json b/package.json index e939e1658980..1482a1026177 100644 --- a/package.json +++ b/package.json @@ -23,6 +23,7 @@ "src/shared/utils/nodeRuntimeSupport.ts", ".env.example", "scripts/postinstall.mjs", + "scripts/postinstallSupport.mjs", "scripts/check-supported-node-runtime.ts", "scripts/sync-env.mjs", "scripts/native-binary-compat.mjs", diff --git a/scripts/pack-artifact-policy.ts b/scripts/pack-artifact-policy.ts index b2530be4dae7..8e3711de092e 100644 --- a/scripts/pack-artifact-policy.ts +++ b/scripts/pack-artifact-policy.ts @@ -72,6 +72,7 @@ export const PACK_ARTIFACT_ROOT_ALLOWED_EXACT_PATHS: string[] = [ "scripts/check-supported-node-runtime.ts", "scripts/native-binary-compat.mjs", "scripts/postinstall.mjs", + "scripts/postinstallSupport.mjs", "scripts/sync-env.mjs", "src/shared/utils/nodeRuntimeSupport.ts", ]; @@ -89,6 +90,7 @@ export const PACK_ARTIFACT_REQUIRED_PATHS: string[] = [ "package.json", "scripts/native-binary-compat.mjs", "scripts/postinstall.mjs", + "scripts/postinstallSupport.mjs", "src/shared/utils/nodeRuntimeSupport.ts", ]; diff --git a/scripts/postinstall.mjs b/scripts/postinstall.mjs index 1e3f1e67427c..87cd21a65944 100644 --- a/scripts/postinstall.mjs +++ b/scripts/postinstall.mjs @@ -22,6 +22,7 @@ import { dirname, join } from "node:path"; import { fileURLToPath } from "node:url"; import { PUBLISHED_BUILD_ARCH, PUBLISHED_BUILD_PLATFORM } from "./native-binary-compat.mjs"; +import { hasStandaloneAppBundle } from "./postinstallSupport.mjs"; const __filename = fileURLToPath(import.meta.url); const __dirname = dirname(__filename); @@ -174,6 +175,10 @@ async function fixBetterSqliteBinary() { } async function ensureSwcHelpers() { + if (!hasStandaloneAppBundle(ROOT)) { + return; + } + const swcHelpersApp = join(ROOT, "app", "node_modules", "@swc", "helpers"); const swcHelpersRoot = join(ROOT, "node_modules", "@swc", "helpers"); diff --git a/scripts/postinstallSupport.mjs b/scripts/postinstallSupport.mjs new file mode 100644 index 000000000000..bb4f2837f74f --- /dev/null +++ b/scripts/postinstallSupport.mjs @@ -0,0 +1,16 @@ +#!/usr/bin/env node + +import { existsSync } from "node:fs"; +import { join } from "node:path"; + +/** + * Detect whether the current install tree contains the published standalone app bundle. + * Source checkouts should not create `app/` during postinstall because Next.js would + * mis-detect it as a competing App Router root and serve 404s for the real `src/app` routes. + * + * @param {string} rootDir + * @returns {boolean} + */ +export function hasStandaloneAppBundle(rootDir) { + return existsSync(join(rootDir, "app", "server.js")); +} diff --git a/tests/unit/pack-artifact-policy.test.ts b/tests/unit/pack-artifact-policy.test.ts index c1eb9911dbce..032138e85bac 100644 --- a/tests/unit/pack-artifact-policy.test.ts +++ b/tests/unit/pack-artifact-policy.test.ts @@ -45,7 +45,13 @@ test("findUnexpectedArtifactPaths flags app pack files outside the allowlist", ( test("findMissingArtifactPaths flags missing root runtime files in the tarball", () => { const missingPaths = findMissingArtifactPaths( - ["app/server.js", "bin/omniroute.ts", "package.json", "scripts/postinstall.mjs"], + [ + "app/server.js", + "bin/omniroute.ts", + "package.json", + "scripts/postinstall.mjs", + "scripts/postinstallSupport.mjs", + ], PACK_ARTIFACT_REQUIRED_PATHS ); diff --git a/tests/unit/postinstall-support.test.ts b/tests/unit/postinstall-support.test.ts new file mode 100644 index 000000000000..65b972ef9264 --- /dev/null +++ b/tests/unit/postinstall-support.test.ts @@ -0,0 +1,30 @@ +import test from "node:test"; +import assert from "node:assert/strict"; +import { mkdtempSync, mkdirSync, writeFileSync, rmSync } from "node:fs"; +import { join } from "node:path"; +import { tmpdir } from "node:os"; + +import { hasStandaloneAppBundle } from "../../scripts/postinstallSupport.mjs"; + +test("hasStandaloneAppBundle returns false for source checkout without standalone app", () => { + const root = mkdtempSync(join(tmpdir(), "omniroute-postinstall-src-")); + + try { + mkdirSync(join(root, "src", "app"), { recursive: true }); + assert.equal(hasStandaloneAppBundle(root), false); + } finally { + rmSync(root, { recursive: true, force: true }); + } +}); + +test("hasStandaloneAppBundle returns true for published standalone app bundle", () => { + const root = mkdtempSync(join(tmpdir(), "omniroute-postinstall-standalone-")); + + try { + mkdirSync(join(root, "app"), { recursive: true }); + writeFileSync(join(root, "app", "server.js"), "export {};\n"); + assert.equal(hasStandaloneAppBundle(root), true); + } finally { + rmSync(root, { recursive: true, force: true }); + } +});