Files
2026-06-19 10:33:56 -04:00

108 lines
2.5 KiB
JavaScript

import { spawn } from "node:child_process";
const commands = [
{
args: ["--filter", "eve", "run", "dev"],
label: "eve",
},
{
// Port 0 asks the OS to allocate an available port atomically.
args: ["--filter", "weather-agent", "run", "dev", "--no-ui", "--port", "0"],
label: "weather-agent",
},
];
const childProcesses = [];
let isShuttingDown = false;
let pendingChildren = commands.length;
let processExitCode = 0;
// Each command fans out into a tree (pnpm → shell → tsc/eve), so a signal to
// the direct child alone orphans the watchers underneath it. Killing the
// child's process group reaches the whole tree.
function killProcessGroup(childProcess, signal) {
if (
childProcess.pid === undefined ||
childProcess.exitCode !== null ||
childProcess.signalCode !== null
) {
return;
}
try {
process.kill(-childProcess.pid, signal);
} catch {
// The group is already gone; fall back to the direct child just in case.
try {
childProcess.kill(signal);
} catch {
// Already dead.
}
}
}
function stopAll(signal = "SIGTERM") {
if (isShuttingDown) {
return;
}
isShuttingDown = true;
for (const childProcess of childProcesses) {
killProcessGroup(childProcess, signal);
}
// A wedged watcher that ignores the polite signal still dies; unref so the
// escalation timer never keeps this orchestrator alive on its own.
setTimeout(() => {
for (const childProcess of childProcesses) {
killProcessGroup(childProcess, "SIGKILL");
}
}, 5000).unref();
}
for (const command of commands) {
const childProcess = spawn("pnpm", command.args, {
env: process.env,
stdio: "inherit",
// Own process group per command, so shutdown can signal the entire tree.
detached: true,
});
childProcess.on("exit", (code) => {
pendingChildren -= 1;
if (!isShuttingDown && code !== 0) {
processExitCode = code ?? 1;
stopAll();
}
if (pendingChildren === 0) {
process.exit(processExitCode);
}
});
childProcess.on("error", (error) => {
console.error(`[dev:${command.label}] ${error.message}`);
processExitCode = 1;
stopAll();
});
childProcesses.push(childProcess);
}
process.on("SIGINT", () => {
stopAll("SIGINT");
});
process.on("SIGTERM", () => {
stopAll("SIGTERM");
});
// Last resort for crash exits: process groups die with the orchestrator.
process.on("exit", () => {
for (const childProcess of childProcesses) {
killProcessGroup(childProcess, "SIGKILL");
}
});