diff --git a/changelog.d/features/13021-fast-build-skip-standalone.md b/changelog.d/features/13021-fast-build-skip-standalone.md new file mode 100644 index 0000000000..83ce2a49b7 --- /dev/null +++ b/changelog.d/features/13021-fast-build-skip-standalone.md @@ -0,0 +1 @@ +- **feat(build):** add build:fast and start:fast to bypass standalone tracing ([#13021](https://github.com/diegosouzapw/OmniRoute/pull/13021)) — thanks @tuandinh0801 diff --git a/next.config.mjs b/next.config.mjs index 36c0282e0f..55caf90f38 100644 --- a/next.config.mjs +++ b/next.config.mjs @@ -10,6 +10,7 @@ import { nonPageRoutePrefixes, resolveDashboardEmbedMode, } from "./scripts/build/dashboardEmbed.mjs"; +import { shouldBuildStandalone } from "./scripts/build/backendOnlyPages.mjs"; const withNextIntl = createNextIntlPlugin("./src/i18n/request.ts"); const distDir = process.env.NEXT_DIST_DIR || ".build/next"; @@ -109,8 +110,6 @@ function filterKnownInfrastructureWarnings(baseConsole) { // The resulting artifact is intended to be published as `omniroute-secure` // for security-sensitive environments. See docs/security/SOCKET_DEV_FINDINGS.md. const isMinimalBuild = process.env.OMNIROUTE_BUILD_PROFILE === "minimal"; -// Contributor builds validate compilation only and do not need a shippable standalone bundle. -const isContributorBuild = process.env.OMNIROUTE_BUILD_PROFILE === "contributor"; // #10273: `null` unless the operator opts in with DASHBOARD_ALLOW_EMBED=vscode. Read at build // time like every other knob in this file (OMNIROUTE_BASE_PATH, OMNIROUTE_BUILD_PROFILE, …), @@ -220,7 +219,7 @@ const nextConfig = { }, ], }, - ...(isContributorBuild ? {} : { output: "standalone" }), + ...(shouldBuildStandalone(process.env) ? { output: "standalone" } : {}), compress: true, productionBrowserSourceMaps: false, // Issue #67: enable React Compiler — automates memoization, removes manual useCallback/useMemo debt. diff --git a/package.json b/package.json index 92f7c65a79..9bae4ca04a 100644 --- a/package.json +++ b/package.json @@ -105,7 +105,9 @@ "eval:router:trends": "node --import tsx scripts/router-eval/trends.ts", "release:sync-changelog-i18n": "node scripts/release/sync-changelog-i18n.mjs", "prebuild": "npm run check:native-deps", + "prebuild:fast": "npm run check:native-deps", "build": "node scripts/build/build-next-isolated.mjs", + "build:fast": "cross-env OMNIROUTE_SKIP_STANDALONE=1 node scripts/build/build-next-isolated.mjs", "build:secure": "OMNIROUTE_BUILD_PROFILE=minimal node scripts/build/build-next-isolated.mjs", "build:backend": "cross-env OMNIROUTE_BUILD_BACKEND_ONLY=1 node scripts/build/build-next-isolated.mjs", "build:contributor": "cross-env OMNIROUTE_BUILD_PROFILE=contributor OMNIROUTE_USE_TURBOPACK=0 node scripts/build/build-next-isolated.mjs", @@ -114,6 +116,7 @@ "build:release": "rm -rf .build dist && OMNIROUTE_BUILD_SHA=$(git rev-parse --short HEAD) npm run build && npm run build:cli && node scripts/build/write-build-sha.mjs", "build:native:tproxy": "cd src/mitm/tproxy/native && npx --yes node-gyp rebuild", "start": "node scripts/dev/run-next.mjs start", + "start:fast": "cross-env OMNIROUTE_SKIP_STANDALONE=1 node scripts/dev/run-next.mjs start", "homolog": "node scripts/homolog/run.mjs", "lint": "eslint . --cache --cache-location .eslintcache --suppressions-location config/quality/eslint-suppressions.json", "lint:json": "node scripts/quality/run-eslint-json.mjs", diff --git a/scripts/build/backendOnlyPages.mjs b/scripts/build/backendOnlyPages.mjs index 58f1922d7a..f045762945 100644 --- a/scripts/build/backendOnlyPages.mjs +++ b/scripts/build/backendOnlyPages.mjs @@ -59,7 +59,8 @@ const ERROR_STUB = `${HEADER}"use client";\nexport default function BackendOnlyE // global-error replaces the root layout on a root error, so it must render /. const GLOBAL_ERROR_STUB = `${HEADER}"use client";\nexport default function BackendOnlyGlobalErrorStub() {\n return (\n \n \n \n );\n}\n`; -const UI_BASENAME_RE = /^(page|layout|template|loading|error|global-error|not-found|default)\.(tsx|jsx|ts|js)$/; +const UI_BASENAME_RE = + /^(page|layout|template|loading|error|global-error|not-found|default)\.(tsx|jsx|ts|js)$/; const ROUTE_FILE_RE = /[\\/]route\.(ts|js|tsx|jsx)$/; /** @@ -103,6 +104,11 @@ export function isContributorBuild(env = process.env) { return env.OMNIROUTE_BUILD_PROFILE === "contributor"; } +/** True when standalone output should be packaged (default true; skipped for contributor or fast build). */ +export function shouldBuildStandalone(env = process.env) { + return !isContributorBuild(env) && env.OMNIROUTE_SKIP_STANDALONE !== "1"; +} + /** Replace the build-only instrumentation entrypoint to avoid pulling the startup graph. */ export function stubContributorInstrumentation(rootDir = process.cwd(), log = console) { const stubbed = []; diff --git a/scripts/build/build-next-isolated.mjs b/scripts/build/build-next-isolated.mjs index 499649f21b..2503581cbe 100644 --- a/scripts/build/build-next-isolated.mjs +++ b/scripts/build/build-next-isolated.mjs @@ -14,11 +14,14 @@ import { import { isBackendOnlyBuild, isContributorBuild, + shouldBuildStandalone, stubContributorInstrumentation, stubDashboardPages, restoreDashboardPages, } from "./backendOnlyPages.mjs"; +export { shouldBuildStandalone } from "./backendOnlyPages.mjs"; + /** * Layer 1: `app/` has been renamed to `dist/` and the App-Router collision is gone. * The only transient paths remaining are `.tmp/wine32` (Wine prefix used by some @@ -310,7 +313,7 @@ export async function main() { const result = await runNextBuild(); const standaloneDir = path.join(distDir, "standalone"); - if (result.code === 0 && (await exists(standaloneDir)) && !isContributorBuild()) { + if (result.code === 0 && (await exists(standaloneDir)) && shouldBuildStandalone()) { try { await fs.cp(path.join(projectRoot, "docs"), path.join(standaloneDir, "docs"), { recursive: true, @@ -377,9 +380,9 @@ export async function main() { } catch (assembleErr) { console.warn("[build-next-isolated] Non-fatal error assembling standalone:", assembleErr); } - } else if (result.code === 0 && isContributorBuild()) { + } else if (result.code === 0 && !shouldBuildStandalone()) { console.log( - "[build-next-isolated] Contributor profile: skipped standalone packaging (compile-only validation)" + "[build-next-isolated] Skipped standalone packaging (standalone disabled for fast compile)" ); } process.exitCode = result.code; diff --git a/tests/unit/build-next-isolated.test.ts b/tests/unit/build-next-isolated.test.ts index 70806b13eb..c1ecf693ca 100644 --- a/tests/unit/build-next-isolated.test.ts +++ b/tests/unit/build-next-isolated.test.ts @@ -9,6 +9,7 @@ import { movePath, pruneStandaloneArtifacts, resolveNextBuildEnv, + shouldBuildStandalone, syncStandaloneExtraModules, syncStandaloneNativeAssets, } from "../../scripts/build/build-next-isolated.mjs"; @@ -223,3 +224,18 @@ test("syncStandaloneExtraModules copies the complete wreq-js runtime", async () assert.match(logs[0] ?? "", /wreq-js TLS runtime/); }); }); + +test("shouldBuildStandalone honors OMNIROUTE_SKIP_STANDALONE and contributor profile", () => { + assert.equal(shouldBuildStandalone({}), true); + assert.equal(shouldBuildStandalone({ OMNIROUTE_SKIP_STANDALONE: "0" }), true); + assert.equal(shouldBuildStandalone({ OMNIROUTE_SKIP_STANDALONE: "1" }), false); + assert.equal(shouldBuildStandalone({ OMNIROUTE_BUILD_PROFILE: "contributor" }), false); + assert.equal( + shouldBuildStandalone({ + OMNIROUTE_SKIP_STANDALONE: "1", + OMNIROUTE_BUILD_PROFILE: "minimal", + }), + false + ); + assert.equal(shouldBuildStandalone({ OMNIROUTE_BUILD_PROFILE: "minimal" }), true); +}); diff --git a/tests/unit/build/contributor-build-script.test.mjs b/tests/unit/build/contributor-build-script.test.mjs index 52367b3c40..aa6c8ce292 100644 --- a/tests/unit/build/contributor-build-script.test.mjs +++ b/tests/unit/build/contributor-build-script.test.mjs @@ -4,6 +4,7 @@ import fs from "node:fs"; import path from "node:path"; import { isContributorBuild, + shouldBuildStandalone, stubContributorInstrumentation, } from "../../../scripts/build/backendOnlyPages.mjs"; @@ -51,10 +52,18 @@ test("contributor instrumentation stubs are reversible", async () => { await fs.rm(tempRoot, { recursive: true, force: true }); }); -test("contributor profile disables standalone output while default keeps it", () => { - assert.match(nextConfigSource, /isContributorBuild/); +test("shouldBuildStandalone disables standalone output for contributor and fast build while default keeps it", () => { + assert.equal(shouldBuildStandalone({}), true); + assert.equal(shouldBuildStandalone({ OMNIROUTE_BUILD_PROFILE: "backend" }), true); + assert.equal(shouldBuildStandalone({ OMNIROUTE_BUILD_PROFILE: "minimal" }), true); + assert.equal(shouldBuildStandalone({ OMNIROUTE_BUILD_PROFILE: "contributor" }), false); + assert.equal(shouldBuildStandalone({ OMNIROUTE_SKIP_STANDALONE: "1" }), false); + assert.match(packageJson.scripts["build:fast"], /OMNIROUTE_SKIP_STANDALONE=1/); + assert.match(packageJson.scripts["prebuild:fast"], /check:native-deps/); + assert.match(packageJson.scripts["start:fast"], /OMNIROUTE_SKIP_STANDALONE=1/); + assert.match(nextConfigSource, /shouldBuildStandalone/); assert.match( nextConfigSource, - /\.\.\.\(isContributorBuild \? \{\} : \{ output: "standalone" \}\)/ + /\.\.\.\(shouldBuildStandalone\(process\.env\) \? \{ output: "standalone" \} : \{\}\)/ ); });