mirror of
https://github.com/vercel/eve.git
synced 2026-09-20 05:35:39 +08:00
bfc7191254
Signed-off-by: Andrew Barba <barba@hey.com>
108 lines
2.5 KiB
JavaScript
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");
|
|
}
|
|
});
|