mirror of
https://github.com/diegosouzapw/OmniRoute.git
synced 2026-08-05 14:52:09 +03:00
The Docker builder stage ran `npm run build` with V8's default heap ceiling (~2 GB). After #4052 forced the heavier webpack engine (Turbopack panics on this Next.js version), the production optimization pass exceeded that ceiling and the build died with "FATAL ERROR: ... JavaScript heap out of memory" at [builder] npm run build. The builder stage now sets NODE_OPTIONS=--max-old-space-size (default 4096 MB, overridable via --build-arg OMNIROUTE_BUILD_MEMORY_MB) before the build; the value propagates to the spawned next build (resolveNextBuildEnv spreads process.env). Build-only — the runtime heap on the runner stage is unchanged, and CI/local builds (which invoke npm run build directly) are unaffected. Regression guard: tests/unit/dockerfile-build-heap-4076.test.ts asserts the builder stage sets the heap ceiling, before npm run build, at >= 4096 MB.
This commit is contained in:
committed by
GitHub
parent
4941a0d462
commit
42cf06e4af
@@ -16,6 +16,7 @@ _In development — bullets added per PR; finalized at release._
|
||||
|
||||
- **fix(ws): start the LiveWS sidecar with `cwd` at the package root (global/systemd installs)** — the standalone LiveWS launcher (`scripts/start-ws-server.mjs`) re-spawns itself with `node --import tsx <self>` but did not set `cwd`. When the WebSocket sidecar was launched from outside the package directory — a global npm/homebrew install, or a `systemd`/`launchd` unit started from `$HOME` — Node could not resolve the `tsx` package (`ERR_MODULE_NOT_FOUND: Cannot find package 'tsx'`), and even from the package directory `tsx` could not resolve the tsconfig `@/*` path aliases (e.g. `@/types/databaseSettings`), so the sidecar never booted. The spawn now pins `cwd` to the package root (the directory above `scripts/`, where `package.json` + `tsconfig.json` live), which resolves both `tsx` discovery and the `@/*` aliases regardless of launch directory. ([#4055](https://github.com/diegosouzapw/OmniRoute/issues/4055) — thanks @Rahulsharma0810)
|
||||
- **fix(dashboard): Logs page auto-refresh now works in embedded/proxied dashboards** — the Request Logger gated each auto-refresh tick on a static `document.visibilityState === "visible"` read. Hosts that report a permanent non-`"visible"` state without ever firing a `visibilitychange` event (Docker dashboard wrappers, embedded webviews) froze auto-refresh entirely — only the manual Refresh button worked, a regression from 3.8.24's unconditional polling. The pause is now event-driven and fail-open: polling starts enabled and only pauses after a real `visibilitychange` → hidden transition (still preserving the backgrounded-tab optimization for normal browser tabs). ([#4054](https://github.com/diegosouzapw/OmniRoute/issues/4054) — thanks @tjengbudi)
|
||||
- **fix(docker): raise the build-stage Node heap to stop the production-build OOM** — the Docker `builder` stage ran `npm run build` with V8's default heap ceiling (~2 GB). After #4052 forced the heavier webpack engine (Turbopack panics on this Next.js version), the production optimization pass exceeded that ceiling and the build died with `FATAL ERROR: … JavaScript heap out of memory` at `[builder] npm run build`. The builder stage now sets `NODE_OPTIONS=--max-old-space-size` (default 4096 MB, overridable via `--build-arg OMNIROUTE_BUILD_MEMORY_MB=…`) before the build; the value propagates to the spawned `next build`. Build-only — the runtime heap (`OMNIROUTE_MEMORY_MB` on the runner stage) is unchanged. ([#4076](https://github.com/diegosouzapw/OmniRoute/issues/4076) — thanks @kamenkadmitry)
|
||||
|
||||
---
|
||||
|
||||
|
||||
11
Dockerfile
11
Dockerfile
@@ -46,6 +46,17 @@ RUN --mount=type=cache,target=/root/.npm \
|
||||
# See docs/ops/QUALITY_GATE_PLAYBOOK.md Parte 6.
|
||||
ENV OMNIROUTE_USE_TURBOPACK=0
|
||||
|
||||
# Raise the V8 heap ceiling for the build. The webpack production optimization
|
||||
# pass (forced above since Turbopack panics) needs more than V8's default ceiling
|
||||
# (~2 GB) for a codebase this size; a memory-constrained Docker build otherwise
|
||||
# dies with "FATAL ERROR: ... JavaScript heap out of memory" at `[builder] npm run
|
||||
# build` (#4076). NODE_OPTIONS propagates to the spawned `next build` child
|
||||
# (build-next-isolated.mjs → resolveNextBuildEnv spreads process.env). Build-only;
|
||||
# the runtime heap is set separately on the runner stage (OMNIROUTE_MEMORY_MB).
|
||||
# Override for hosts with more/less RAM: `--build-arg OMNIROUTE_BUILD_MEMORY_MB=6144`.
|
||||
ARG OMNIROUTE_BUILD_MEMORY_MB=4096
|
||||
ENV NODE_OPTIONS="--max-old-space-size=${OMNIROUTE_BUILD_MEMORY_MB}"
|
||||
|
||||
COPY . ./
|
||||
RUN --mount=type=cache,target=/app/.build/next/cache \
|
||||
mkdir -p /app/data && npm run build
|
||||
|
||||
74
tests/unit/dockerfile-build-heap-4076.test.ts
Normal file
74
tests/unit/dockerfile-build-heap-4076.test.ts
Normal file
@@ -0,0 +1,74 @@
|
||||
/**
|
||||
* #4076 — Docker build fails with "JavaScript heap out of memory" during the
|
||||
* `[builder] npm run build` step. The webpack production optimization pass (forced
|
||||
* since #4052 replaced the panicking Turbopack engine) needs more heap than V8's
|
||||
* default ceiling, which a memory-constrained Docker build does not provide.
|
||||
*
|
||||
* Fix: the `builder` stage must raise the heap ceiling via NODE_OPTIONS
|
||||
* (`--max-old-space-size`) BEFORE running `npm run build`, so the value propagates
|
||||
* to the spawned `next build` child (build-next-isolated.mjs → resolveNextBuildEnv
|
||||
* spreads process.env). This is a Docker-only change — CI/local builds invoke
|
||||
* `npm run build` directly and are unaffected.
|
||||
*
|
||||
* This guards the mechanism (the heap setting is present and ordered correctly);
|
||||
* the end-to-end "the OOM is gone" proof is a successful `docker build`.
|
||||
*/
|
||||
import test from "node:test";
|
||||
import assert from "node:assert/strict";
|
||||
import fs from "node:fs";
|
||||
import path from "node:path";
|
||||
import { fileURLToPath } from "node:url";
|
||||
|
||||
const repoRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "../..");
|
||||
const dockerfile = fs.readFileSync(path.join(repoRoot, "Dockerfile"), "utf-8");
|
||||
const lines = dockerfile.split("\n");
|
||||
|
||||
/** Line indices that bound the `builder` stage (from its FROM to the next FROM). */
|
||||
function builderStageRange(): { start: number; end: number } {
|
||||
const start = lines.findIndex((l) => /^FROM\s+\S+\s+AS\s+builder\b/i.test(l.trim()));
|
||||
assert.ok(start >= 0, "Dockerfile must declare a `builder` stage");
|
||||
const after = lines.slice(start + 1).findIndex((l) => /^FROM\s+/i.test(l.trim()));
|
||||
const end = after === -1 ? lines.length : start + 1 + after;
|
||||
return { start, end };
|
||||
}
|
||||
|
||||
test("#4076 builder stage raises the Node heap ceiling via NODE_OPTIONS", () => {
|
||||
const { start, end } = builderStageRange();
|
||||
const stage = lines.slice(start, end);
|
||||
const heapLineIdx = stage.findIndex(
|
||||
(l) => /NODE_OPTIONS/.test(l) && /--max-old-space-size/.test(l)
|
||||
);
|
||||
assert.ok(
|
||||
heapLineIdx >= 0,
|
||||
"builder stage must set NODE_OPTIONS with --max-old-space-size to avoid the #4076 build OOM"
|
||||
);
|
||||
});
|
||||
|
||||
test("#4076 the heap ceiling is set BEFORE `npm run build` so it reaches `next build`", () => {
|
||||
const { start, end } = builderStageRange();
|
||||
const stage = lines.slice(start, end);
|
||||
const heapLineIdx = stage.findIndex(
|
||||
(l) => /NODE_OPTIONS/.test(l) && /--max-old-space-size/.test(l)
|
||||
);
|
||||
const buildLineIdx = stage.findIndex((l) => /npm run build\b/.test(l));
|
||||
assert.ok(buildLineIdx >= 0, "builder stage must run `npm run build`");
|
||||
assert.ok(heapLineIdx >= 0, "builder stage must set the heap ceiling");
|
||||
assert.ok(
|
||||
heapLineIdx < buildLineIdx,
|
||||
"NODE_OPTIONS heap ceiling must be set before the `npm run build` step"
|
||||
);
|
||||
});
|
||||
|
||||
test("#4076 the build heap default is at least 4096 MB (the V8 default ~2 GB OOMed)", () => {
|
||||
const { start, end } = builderStageRange();
|
||||
const stage = lines.slice(start, end).join("\n");
|
||||
// Match the literal default in either `--max-old-space-size=N` or an ARG default
|
||||
// referenced by the ENV (e.g. ARG OMNIROUTE_BUILD_MEMORY_MB=4096).
|
||||
const direct = stage.match(/--max-old-space-size=(\d+)/);
|
||||
const argDefault = stage.match(/ARG\s+\w*MEMORY\w*\s*=\s*(\d+)/i);
|
||||
const value = Number(direct?.[1] ?? argDefault?.[1] ?? 0);
|
||||
assert.ok(
|
||||
value >= 4096,
|
||||
`build heap default must be >= 4096 MB to clear the #4076 OOM (found ${value})`
|
||||
);
|
||||
});
|
||||
Reference in New Issue
Block a user