Skip to content

fix(build): scope standalone type:module to worker dirs so server.js stays CJS - #10936

Merged
diegosouzapw merged 2 commits into
cheaper-inference:release/v3.8.50from
arminanton:fix/standalone-server-cjs-esm-scope
Aug 21, 2026
Merged

diegosouzapw merged 2 commits into
cheaper-inference:release/v3.8.50from
arminanton:fix/standalone-server-cjs-esm-scope

Conversation

@arminanton

Copy link
Copy Markdown
Contributor

Summary

The official diegosouzapw/omniroute:next Docker image crashes immediately on boot with:

file:///app/server.js:1
const path = require('path')
             ^
ReferenceError: require is not defined in ES module scope

This is a CommonJS/ESM module-system conflict in the build, and it is a regression introduced by #10836. This PR fixes it at the source so the standalone server.js boots while the ESM worker bundles keep working.

Fixes #10933.

Root cause

Two build steps make contradictory claims about the standalone package.json:

  1. scripts/build/assembleStandalone.mjs → patchStandalonePackageJson() removes "type":"module" from the standalone root package.json, on purpose, because the Next.js standalone entrypoint server.js is CommonJS (require(), __dirname).
  2. scripts/build/colocate-standalone.mjs (the npm postbuild hook, added in fix(build): ensure standalone package.json declares module type for Node 24 worker compatibility #10836) re-adds "type":"module" to that same root so its esbuild'd ESM worker bundles (callLogArtifactWorker.js, onnxWorker.js, both emitted with --format=esm) load as ESM.

postbuild runs after build, so step 2 wins. The standalone root ends up with "type":"module", Node then parses the CommonJS server.js as ESM, and it throws on the first require(). run-standalone.mjs → server-ws.mjs → await import("./server.js") surfaces the crash at boot.

Fix

Node resolves a file's module system from the nearest package.json. Instead of flipping the whole standalone tree to ESM (which breaks server.js), scope {"type":"module"} to each worker's own directory:

  • standalone root package.json → stays CommonJS (no type), so server.js runs
  • src/lib/usage/package.json → {"type":"module"}, so callLogArtifactWorker.js runs as ESM
  • open-sse/services/compression/engines/llmlingua/package.json → {"type":"module"}, so onnxWorker.js runs as ESM (only when the optional SLM deps are installed)

This preserves #10836's intent (the ESM workers still load without a warning) while keeping server.js CommonJS. No worker format change, no root edit. The write is no-clobber, so it never overwrites a package.json that Next's trace already placed in a worker directory.

The issue suggested renaming server.js → server.cjs; that works too, but it touches the Next standalone entry contract (server-ws.mjs, run-standalone.mjs, dev/run-standalone.mjs) and several build/pack scripts. Scoping the worker dirs is the smaller, more contained change and leaves the entrypoint untouched.

Changes

  • scripts/build/colocate-standalone.mjs
    • Replace the root type:module re-add with writeEsmWorkerScopes(), which writes a scoped {"type":"module"} into each worker directory.
    • Refactor the script body into main() + an exported writeEsmWorkerScopes() helper, guarded by the import.meta.url === argv[1] run-check (same idiom as build-next-isolated.mjs), so the scoping logic is importable and unit-testable without a full next build.
    • Add an OMNIROUTE_STANDALONE_DIR override seam (mirrors write-build-sha.mjs / write-build-base-path.mjs).
  • tests/unit/build/colocate-standalone-esm-scope.test.ts (new)
    • Asserts scoped type:module lands in each worker dir, the root package.json is never touched, and the write is no-clobber.
    • End-to-end guard: actually runs a CommonJS server.js and an ESM worker.js side by side under the scoped layout to prove the two module systems coexist (this is exactly what the bug broke).

Testing

$ node --import tsx/esm --test tests/unit/build/colocate-standalone-esm-scope.test.ts
# tests 4
# pass 4
# fail 0

Sibling build tests stay green (tests/unit/build/assemble-standalone.test.ts, tests/unit/colocate-optionals.test.ts: 13/13). I also rebuilt the runner-base image from this branch and confirmed the container boots past the previous crash and serves the dashboard.

Notes

  • Base branch is release/v3.8.50 per CONTRIBUTING.md.
  • Changelog fragment added under changelog.d/fixes/.

…stays CJS

The Next.js standalone entrypoint server.js is CommonJS (require/__dirname),
so assembleStandalone.mjs::patchStandalonePackageJson deliberately strips
"type":"module" from the standalone root package.json. The postbuild step
colocate-standalone.mjs then re-added "type":"module" to that same root to
make its esbuild'd ESM worker bundles (callLogArtifactWorker.js, onnxWorker.js)
load as ESM. Because postbuild runs last, the root ends up with type:module and
the container crashes on boot:

  file:///app/server.js:1
  const path = require('path')
  ReferenceError: require is not defined in ES module scope

Node resolves module type from the nearest package.json, so instead of forcing
the whole standalone tree to ESM (which breaks server.js), write a scoped
{"type":"module"} package.json into each worker's own directory. The root stays
CommonJS for server.js while each worker keeps its ESM scope. No worker format
change, no root edit, and the no-clobber guard never overwrites a traced
package.json.

Refactors the script's body into main() + an exported writeEsmWorkerScopes()
helper (mirroring build-next-isolated.mjs's main-guard idiom) so the scoping
logic is unit-testable without a full next build.

Fixes cheaper-inference#10933. Regression from cheaper-inference#10836.
Copilot AI lite review requested due to automatic review settings August 21, 2026 02:26

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Fixes a Docker boot regression where the Next.js standalone server.js (CommonJS) was being parsed as ESM due to "type":"module" being re-added to the standalone root package.json during postbuild. The fix scopes {"type":"module"} to the specific worker directories so ESM worker bundles load correctly without forcing the entire standalone tree into ESM.

Changes:

  • Refactors scripts/build/colocate-standalone.mjs to write per-worker package.json ESM scopes (writeEsmWorkerScopes) instead of mutating the standalone root manifest.
  • Adds an OMNIROUTE_STANDALONE_DIR override seam and makes the script importable for unit testing (entrypoint guard + exported helper).
  • Adds a focused unit/regression test that proves CJS server.js and ESM worker .js can coexist via nearest-package.json resolution.

Testing / Coverage (custom):

  • Commands (reported in PR): node --import tsx/esm --test tests/unit/build/colocate-standalone-esm-scope.test.ts
  • Changed/added tests: tests/unit/build/colocate-standalone-esm-scope.test.ts
  • Coverage result: not provided in the PR description (custom)

Reviewed changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated no comments.

File Description
scripts/build/colocate-standalone.mjs Stops re-adding "type":"module" at the standalone root; instead writes no-clobber ESM scopes in worker directories and adds a test seam/entry guard.
tests/unit/build/colocate-standalone-esm-scope.test.ts New regression test covering scoped ESM manifests, no root mutation, no-clobber behavior, and an end-to-end CJS+ESM coexistence check.
changelog.d/fixes/10936-standalone-server-cjs-esm-scope.md Changelog entry documenting the Docker boot crash fix and the worker-dir scoping approach.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

@diegosouzapw
diegosouzapw merged commit 10deb5c into cheaper-inference:release/v3.8.50 Aug 21, 2026
3 checks passed
backryun added a commit to backryun/OmniRoute that referenced this pull request Aug 22, 2026
…NDALONE_DIR vars

Three merged features read env vars that were never added to
.env.example / ENVIRONMENT.md, breaking the env-doc contract gate
(check-env-doc-sync + issue-7793 repro test) on release/v3.8.50:

- KIMI_WEB_BASE_URL / KIMI_WEB_CHAT_URL — Kimi Web executor overrides
  (cheaper-inference#10944 moved the default to www.kimi.ai but never documented the
  override knobs)
- OMNIROUTE_OIDC_DISABLE_PASSWORD_LOGIN (+ bare alias
  OIDC_DISABLE_PASSWORD_LOGIN) — OIDC-only login flag (cheaper-inference#10889)
- OMNIROUTE_STANDALONE_DIR — build-time standalone output override
  (scripts/build/colocate-standalone.mjs, cheaper-inference#10936)

check-env-doc-sync.mjs now reports "✓ Env / docs contract is in sync"
and both sync tests pass 15/15.
muhamadgalihsaputra pushed a commit to niyatna/NiyatnaRoute that referenced this pull request Sep 27, 2026
…stays CJS (cheaper-inference#10936)

Validado no worktree combinado: typecheck:core, changelog-integrity, complexity, cognitive-complexity, file-size, lint e teste focado (colocate-standalone-esm-scope) todos verdes. Fix real, correção de regressão introduzida por cheaper-inference#10836 (server.js CommonJS quebrando com type:module reintroduzido). CI vermelho é o base-red já rastreado em cheaper-inference#9985. Obrigado!
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Docker image :next crashes on boot: ReferenceError: require is not defined in ES module scope (server.js + "type":"module")

3 participants