mirror of
https://github.com/Pandipipas/scoreko-electron-dev.git
synced 2026-06-05 21:22:07 +00:00
fix(nodecg): include actionable diagnostics when process exits before readiness (#27)
This commit is contained in:
@@ -49,6 +49,8 @@ export function createNodecgProcessManager({
|
||||
|
||||
let nodecgProcess: ChildProcess | null = null;
|
||||
let stopNodecgPromise: Promise<void> | null = null;
|
||||
let lastExit: { code: number | null; signal: NodeJS.Signals | null } | null = null;
|
||||
let lastStderrLine: string | null = null;
|
||||
|
||||
const startNodecgProcess = async (): Promise<ChildProcess> => {
|
||||
validateNodecgInstall(
|
||||
@@ -85,16 +87,21 @@ export function createNodecgProcessManager({
|
||||
});
|
||||
|
||||
child.stderr?.on("data", (chunk) => {
|
||||
resolvedDeps.stderrWrite(String(chunk));
|
||||
const line = String(chunk);
|
||||
lastStderrLine = line.trim().length > 0 ? line.trim() : lastStderrLine;
|
||||
resolvedDeps.stderrWrite(line);
|
||||
});
|
||||
|
||||
log(`NodeCG started with pid=${child.pid} using ${NODE_RUNTIME_NAME}`);
|
||||
|
||||
child.on("exit", (code, signal) => {
|
||||
log(`NodeCG exited code=${code} signal=${signal ?? "none"}`);
|
||||
lastExit = { code, signal };
|
||||
nodecgProcess = null;
|
||||
});
|
||||
|
||||
lastExit = null;
|
||||
lastStderrLine = null;
|
||||
nodecgProcess = child;
|
||||
return child;
|
||||
};
|
||||
@@ -102,7 +109,19 @@ export function createNodecgProcessManager({
|
||||
const waitForNodecgReady = async (startTime: number): Promise<void> => {
|
||||
while (Date.now() - startTime < appConfig.startupTimeoutMs) {
|
||||
if (!nodecgProcess) {
|
||||
throw new Error("NodeCG terminó antes de estar listo.");
|
||||
const exitDetails = lastExit
|
||||
? `Última salida registrada: code=${lastExit.code ?? "null"}, signal=${lastExit.signal ?? "none"}.`
|
||||
: "No se registró código de salida del proceso NodeCG.";
|
||||
const stderrDetails = lastStderrLine ? `Último stderr: ${lastStderrLine}` : "Sin salida stderr capturada.";
|
||||
throw new Error(
|
||||
[
|
||||
"NodeCG terminó antes de estar listo.",
|
||||
exitDetails,
|
||||
stderrDetails,
|
||||
`Ruta NodeCG: ${nodecgRootPath}`,
|
||||
"Revisa que lib/nodecg tenga dependencias instaladas y que el bundle exista.",
|
||||
].join("\n"),
|
||||
);
|
||||
}
|
||||
|
||||
try {
|
||||
|
||||
@@ -236,3 +236,46 @@ test("startNodeCG falla si el puerto ya está ocupado", async () => {
|
||||
await manager.startNodecgProcess();
|
||||
}, /ya está en uso/);
|
||||
});
|
||||
|
||||
test("waitForNodeCGReady expone diagnóstico cuando NodeCG sale antes de readiness", async () => {
|
||||
const child = new MockChildProcess(4242);
|
||||
const manager = createNodecgProcessManager({
|
||||
isDev: true,
|
||||
nodecgRootPath: "/fake/nodecg",
|
||||
nodecgBaseUrl: "http://127.0.0.1:9090",
|
||||
appConfig: getBaseConfig(),
|
||||
log: () => undefined,
|
||||
deps: {
|
||||
pathExists: () => true,
|
||||
platform: "linux",
|
||||
spawnProcess: () => child as unknown as import("node:child_process").ChildProcess,
|
||||
fetchUrl: async () => {
|
||||
child.emit("exit", 1, null);
|
||||
throw new Error("still starting");
|
||||
},
|
||||
setTimer: (handler) => {
|
||||
handler();
|
||||
return 0;
|
||||
},
|
||||
stdoutWrite: () => undefined,
|
||||
stderrWrite: () => undefined,
|
||||
probePortAvailable: async () => true,
|
||||
hasReadWriteAccess: () => true,
|
||||
},
|
||||
});
|
||||
|
||||
await manager.startNodecgProcess();
|
||||
|
||||
await assert.rejects(
|
||||
async () => {
|
||||
await manager.waitForNodecgReady(Date.now());
|
||||
},
|
||||
(error: unknown) => {
|
||||
assert.ok(error instanceof Error);
|
||||
assert.match(error.message, /NodeCG terminó antes de estar listo/);
|
||||
assert.match(error.message, /Última salida registrada/);
|
||||
assert.match(error.message, /Ruta NodeCG: \/fake\/nodecg/);
|
||||
return true;
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user