Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion module/services/frontend/code/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
"check:plugin-allowlist": "npm run build:plugin-packages && tsx scripts/generate-plugin-service-allowlist.ts --check",
"generate:plugin-codefly-dependencies": "npm run generate:plugin-allowlist && cd ../../accounts/code && go run ./cmd/frontend-service-manifest -catalog ../generated/service-catalog.json -topology ../../../deployment/topology.bindings.codefly.yaml -allowlist ../../frontend/code/server/plugin-service-allowlist.generated.json -output ../../frontend/service.codefly.yaml",
"check:plugin-codefly-dependencies": "npm run check:plugin-allowlist && tsx scripts/check-plugin-service-dependencies.ts",
"dev": "npm run prepare:frontend && next dev",
"dev": "npm run prepare:frontend && NODE_OPTIONS=\"$NODE_OPTIONS --max-old-space-size=8192\" next dev",
"build": "npm run prepare:frontend && next build",
"start": "next start",
"lint": "npm run build:plugin-packages && eslint",
Expand Down
56 changes: 56 additions & 0 deletions module/services/frontend/code/scripts/dev-heap-ceiling.test.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import { execFileSync } from "node:child_process";
import { readFileSync } from "node:fs";
import { join } from "node:path";

import { describe, expect, it } from "vitest";

// The nextjs service agent starts the dev server with `npm run dev` under a
// POSIX shell, so the `dev` script is the only in-repo layer that governs the
// dev process heap. Under Turbopack + reactCompiler + transpiled plugin
// workspaces the dev compiler grows past Node's inherited ~2 GB old-space
// default and OOMs within minutes, taking the port with it. The ceiling must
// therefore be pinned to an explicit, above-default value here rather than
// inherited from the platform — and pinned by APPENDING to NODE_OPTIONS so any
// options the runtime already injected into the dev process survive.
const packageJson = JSON.parse(
readFileSync(join(import.meta.dirname, "..", "package.json"), "utf8"),
);

const INHERITED_DEFAULT_MB = 2048;

const dev = packageJson.scripts?.dev ?? "";
// The dev server is the last `&&`-chained command; its inline env prefix is what
// reaches the `next dev` process.
const devSegment = dev.split("&&").at(-1).trim();

describe("frontend dev heap ceiling", () => {
it("pins an explicit old-space ceiling above the inherited default", () => {
const match = dev.match(/--max-old-space-size=(\d+)/);
expect(
match,
`dev script must set --max-old-space-size: ${dev}`,
).not.toBeNull();
expect(Number(match[1])).toBeGreaterThan(INHERITED_DEFAULT_MB);
});

it("preserves inherited NODE_OPTIONS and appends the ceiling for the dev server", () => {
expect(devSegment, `dev server command must be next dev: ${dev}`).toMatch(
/\bnext dev\b/,
);

// Run the real env prefix from the dev script, substituting a probe for
// `next dev` that reports the NODE_OPTIONS the dev process would see. A
// replacing (non-appending) prefix would drop the inherited value below.
const probe = devSegment.replace(
/\bnext dev\b.*$/,
"node -e \"process.stdout.write(process.env.NODE_OPTIONS ?? '')\"",
);
const effective = execFileSync("sh", ["-c", probe], {
encoding: "utf8",
env: { ...process.env, NODE_OPTIONS: "--enable-source-maps" },
});

expect(effective).toContain("--enable-source-maps");
expect(effective).toMatch(/--max-old-space-size=\d+/);
});
});
5 changes: 3 additions & 2 deletions module/tools/base-manifest.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"note": "Base-file integrity manifest for the saas-starter module. Generated FROM canonical by `node tools/base-integrity.mjs gen`. Consumers MUST NOT hand-edit base files — only add files on the side. Regenerated on every codefly sync from canonical.",
"fileCount": 1471,
"fileCount": 1472,
"files": {
"AUTHORIZATION_CATALOG.md": "b2ea5d41f0098e80315e02e7c4bdbda03b65c8eef463426242552fd3c047ca0a",
"CONTRACT_VERSIONING.md": "02031aa87922ab5ec573ea582cf584271873481de6ceabbda52b1bfda005ffbd",
Expand Down Expand Up @@ -684,7 +684,7 @@
"services/frontend/code/instrumentation-client.ts": "5c590de3453dbba68945c4584164b37636ce56bf93bb8df37f8147a382c8a381",
"services/frontend/code/instrumentation.ts": "41c169de2e928d30bdbc4ea559c2d6d6e4d95695854e35351412137420cc492a",
"services/frontend/code/next.config.mjs": "01287ae79d02e9faa47b93ac793482860436a9c29bdda893145334e8879a695e",
"services/frontend/code/package.json": "31ca41248ec4c4d463f0c40e9fcc4d456284a57b4b6df60244e3b7560a7509a4",
"services/frontend/code/package.json": "22dede932e8f0a79f1591429804743f2d97b6c8f2df7c5b794565d3a6290320b",
"services/frontend/code/packages/saas-plugin-contract/README.md": "a2763de59eeb823a9e8a3cb22d4ed6330ae54fd5397f2cedac157b6a040cf120",
"services/frontend/code/packages/saas-plugin-contract/buf.gen.local.yaml": "8b234b5eddfe02f93327fbb4cecfa30a4ed7d57660b27bb0c1a6af6839b3f2d7",
"services/frontend/code/packages/saas-plugin-contract/buf.gen.yaml": "104d34fbc5f80d9f63edf4399d2a211f8bcd16b5467c77bcdaf9e7ce7e94f06b",
Expand Down Expand Up @@ -730,6 +730,7 @@
"services/frontend/code/scripts/build-plugin-workspaces.mjs": "f8fdaffb8b8dfd6541c2af8bcc2500f1f8ff57280d80914bea509bd15e0001ea",
"services/frontend/code/scripts/build-plugin-workspaces.test.mjs": "1bc0a6180a551c8ed91a1e7adcb6e3d2124846dda21908950d32b359403f3f87",
"services/frontend/code/scripts/check-plugin-service-dependencies.ts": "600af5097296618b92f7e4c49aae2fb4b1191059883d35944ebe80ec424c9a45",
"services/frontend/code/scripts/dev-heap-ceiling.test.mjs": "06021337003b281b508e54b59fdf86c4131b4f3b99f2dfc903589dc515deed6b",
"services/frontend/code/scripts/generate-plugin-service-allowlist.ts": "bfb2d3aa6af85439e9b51d90f2c53f5827c00f6a9f8c0286ca55ba2fe705d14e",
"services/frontend/code/server/accounts-bindings.d.mts": "dd9bdf14ecdbe5fdb6241c6ac5f9e09272c4ef41e5e3a465dbc71508e2c7e21d",
"services/frontend/code/server/accounts-bindings.mjs": "3e9ffa25910286ace824db95a093e70249e32ccc3ab572503689085cc31ae299",
Expand Down
Loading