mirror of
https://github.com/Pandipipas/scoreko-electron-dev.git
synced 2026-06-06 05:32:06 +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 nodecgProcess: ChildProcess | null = null;
|
||||||
let stopNodecgPromise: Promise<void> | 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> => {
|
const startNodecgProcess = async (): Promise<ChildProcess> => {
|
||||||
validateNodecgInstall(
|
validateNodecgInstall(
|
||||||
@@ -85,16 +87,21 @@ export function createNodecgProcessManager({
|
|||||||
});
|
});
|
||||||
|
|
||||||
child.stderr?.on("data", (chunk) => {
|
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}`);
|
log(`NodeCG started with pid=${child.pid} using ${NODE_RUNTIME_NAME}`);
|
||||||
|
|
||||||
child.on("exit", (code, signal) => {
|
child.on("exit", (code, signal) => {
|
||||||
log(`NodeCG exited code=${code} signal=${signal ?? "none"}`);
|
log(`NodeCG exited code=${code} signal=${signal ?? "none"}`);
|
||||||
|
lastExit = { code, signal };
|
||||||
nodecgProcess = null;
|
nodecgProcess = null;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
lastExit = null;
|
||||||
|
lastStderrLine = null;
|
||||||
nodecgProcess = child;
|
nodecgProcess = child;
|
||||||
return child;
|
return child;
|
||||||
};
|
};
|
||||||
@@ -102,7 +109,19 @@ export function createNodecgProcessManager({
|
|||||||
const waitForNodecgReady = async (startTime: number): Promise<void> => {
|
const waitForNodecgReady = async (startTime: number): Promise<void> => {
|
||||||
while (Date.now() - startTime < appConfig.startupTimeoutMs) {
|
while (Date.now() - startTime < appConfig.startupTimeoutMs) {
|
||||||
if (!nodecgProcess) {
|
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 {
|
try {
|
||||||
|
|||||||
@@ -236,3 +236,46 @@ test("startNodeCG falla si el puerto ya está ocupado", async () => {
|
|||||||
await manager.startNodecgProcess();
|
await manager.startNodecgProcess();
|
||||||
}, /ya está en uso/);
|
}, /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