diff --git a/CHANGELOG.md b/CHANGELOG.md index 234d26150c..808fd81e6f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,14 @@ --- +## [3.8.24] — TBD + +### 🐛 Fixed + +- fix(cli): `ServerSupervisor.handleExit` now coerces the exit code to a number before calling `process.exit()` — Node.js v24 throws `TypeError [ERR_INVALID_ARG_TYPE]` when `process.exit()` receives a string (e.g. `'ENOENT'` from a spawn `error` event's `err.code`). The `error` callback also now passes `-1` instead of the raw `err.code`, which is an OS error string rather than a meaningful exit code. ([#3748](https://github.com/diegosouzapw/OmniRoute/issues/3748)) + +--- + ## [3.8.23] — TBD ### ✨ New Features diff --git a/bin/cli/runtime/processSupervisor.mjs b/bin/cli/runtime/processSupervisor.mjs index 135514cfbd..b16de9670e 100644 --- a/bin/cli/runtime/processSupervisor.mjs +++ b/bin/cli/runtime/processSupervisor.mjs @@ -42,17 +42,20 @@ export class ServerSupervisor { }); } - this.child.on("error", (err) => this.handleExit(err.code ?? -1, err)); + this.child.on("error", (err) => this.handleExit(-1, err)); this.child.on("exit", (code) => this.handleExit(code)); return this.child; } handleExit(code) { + // Node.js v24+ requires process.exit() to receive a number. Spawn-error events + // deliver err.code (a string like 'ENOENT') via the 'error' listener; normalise here. + const exitCode = typeof code === "number" ? code : null; cleanupPidFile("server"); - if (this.isShuttingDown || code === 0) { - process.exit(code || 0); + if (this.isShuttingDown || exitCode === 0) { + process.exit(exitCode ?? 0); return; } @@ -71,7 +74,7 @@ export class ServerSupervisor { } } this.dumpCrashLog(); - process.exit(code ?? 1); + process.exit(exitCode ?? 1); return; } diff --git a/tests/unit/cli-process-supervisor.test.ts b/tests/unit/cli-process-supervisor.test.ts index 8185d874a3..99012ad82e 100644 --- a/tests/unit/cli-process-supervisor.test.ts +++ b/tests/unit/cli-process-supervisor.test.ts @@ -195,6 +195,36 @@ test("ServerSupervisor reseta restartCount após processo viver >=30s", async () assert.equal(supervisor.restartCount, 1); // reset p/ 0, depois incrementado p/ 1 }); +// --- Node.js v24 compat: process.exit() must receive a number (#3748) --- + +test("ServerSupervisor.handleExit com string code não passa string para process.exit (#3748)", async () => { + const { ServerSupervisor } = await import("../../bin/cli/runtime/processSupervisor.mjs"); + + const exits: Array = []; + const origExit = process.exit.bind(process); + // @ts-ignore + process.exit = (code?: number | string) => exits.push(code); + + const supervisor = new ServerSupervisor({ + serverPath: "/fake/server.js", + env: {}, + maxRestarts: 0, + }); + // Simulates the 'error' event on child spawn failure: err.code = 'ENOENT' (string, not number). + // maxRestarts=0 → restartCount(0) >= maxRestarts(0) → process.exit() is called immediately. + supervisor.startedAt = Date.now() - 100; + supervisor.handleExit("ENOENT" as any); + + // @ts-ignore + process.exit = origExit; + assert.equal(exits.length, 1, "process.exit deve ser chamado exatamente 1 vez"); + assert.equal( + typeof exits[0], + "number", + `process.exit deve receber number, recebeu: ${typeof exits[0]} (${exits[0]})` + ); +}); + // --- pid.mjs multi-service --- test("writePidFile/readPidFile/cleanupPidFile operam por service", async () => {