188 lines
4.6 KiB
JavaScript
188 lines
4.6 KiB
JavaScript
import { spawn } from "node:child_process";
|
|
import path from "node:path";
|
|
import process from "node:process";
|
|
import { fileURLToPath } from "node:url";
|
|
|
|
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
const repoRoot = path.resolve(__dirname, "..");
|
|
const npmCommand = process.platform === "win32" ? "npm.cmd" : "npm";
|
|
const shouldReset = process.argv.includes("--reset");
|
|
|
|
const colors = {
|
|
api: "\x1b[38;5;180m",
|
|
worker: "\x1b[38;5;110m",
|
|
admin: "\x1b[38;5;117m",
|
|
submission: "\x1b[38;5;216m",
|
|
system: "\x1b[38;5;246m",
|
|
reset: "\x1b[0m"
|
|
};
|
|
|
|
const services = [
|
|
{
|
|
id: "api",
|
|
title: "API",
|
|
command: npmCommand,
|
|
args: ["run", "dev:api"],
|
|
url: "http://localhost:4300/health",
|
|
waitForReady: true
|
|
},
|
|
{
|
|
id: "worker",
|
|
title: "Worker",
|
|
command: npmCommand,
|
|
args: ["run", "dev:worker"],
|
|
url: "http://localhost:4301/health",
|
|
waitForReady: true
|
|
},
|
|
{
|
|
id: "admin",
|
|
title: "Admin",
|
|
command: npmCommand,
|
|
args: ["run", "dev:admin"],
|
|
url: "http://localhost:4200"
|
|
},
|
|
{
|
|
id: "submission",
|
|
title: "Submission",
|
|
command: npmCommand,
|
|
args: ["run", "dev:submission"],
|
|
url: "http://localhost:4100"
|
|
}
|
|
];
|
|
|
|
const prefixLine = (serviceId, title, line) => {
|
|
const color = colors[serviceId] ?? colors.system;
|
|
return `${color}[${title}]${colors.reset} ${line}`;
|
|
};
|
|
|
|
const attachStream = (child, streamName, serviceId, title) => {
|
|
const stream = child[streamName];
|
|
if (!stream) {
|
|
return;
|
|
}
|
|
|
|
let buffer = "";
|
|
stream.setEncoding("utf8");
|
|
stream.on("data", (chunk) => {
|
|
buffer += chunk;
|
|
const lines = buffer.split(/\r?\n/);
|
|
buffer = lines.pop() ?? "";
|
|
for (const line of lines) {
|
|
if (line.length > 0) {
|
|
console.log(prefixLine(serviceId, title, line));
|
|
}
|
|
}
|
|
});
|
|
stream.on("end", () => {
|
|
if (buffer.length > 0) {
|
|
console.log(prefixLine(serviceId, title, buffer));
|
|
}
|
|
});
|
|
};
|
|
|
|
const childProcesses = [];
|
|
let shuttingDown = false;
|
|
|
|
const delay = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
|
|
const waitForService = async (service, timeoutMs = 20_000) => {
|
|
const startedAt = Date.now();
|
|
while (Date.now() - startedAt < timeoutMs) {
|
|
try {
|
|
const response = await fetch(service.url);
|
|
if (response.ok) {
|
|
return true;
|
|
}
|
|
} catch {
|
|
// keep waiting
|
|
}
|
|
|
|
await delay(400);
|
|
}
|
|
|
|
return false;
|
|
};
|
|
|
|
const shutdown = (reason = "shutdown") => {
|
|
if (shuttingDown) {
|
|
return;
|
|
}
|
|
shuttingDown = true;
|
|
|
|
console.log(prefixLine("system", "SYSTEM", `Stopping local stack (${reason})...`));
|
|
for (const child of childProcesses) {
|
|
if (!child.killed) {
|
|
child.kill("SIGTERM");
|
|
}
|
|
}
|
|
|
|
setTimeout(() => {
|
|
for (const child of childProcesses) {
|
|
if (!child.killed) {
|
|
child.kill("SIGKILL");
|
|
}
|
|
}
|
|
}, 1200).unref();
|
|
};
|
|
|
|
process.on("SIGINT", () => shutdown("SIGINT"));
|
|
process.on("SIGTERM", () => shutdown("SIGTERM"));
|
|
|
|
if (shouldReset) {
|
|
console.log(prefixLine("system", "SYSTEM", "Resetting runtime state before startup..."));
|
|
const reset = spawn(npmCommand, ["run", "reset:runtime"], {
|
|
cwd: repoRoot,
|
|
stdio: ["ignore", "pipe", "pipe"]
|
|
});
|
|
|
|
attachStream(reset, "stdout", "system", "RESET");
|
|
attachStream(reset, "stderr", "system", "RESET");
|
|
|
|
const resetExitCode = await new Promise((resolve) => {
|
|
reset.on("exit", (code) => resolve(code ?? 1));
|
|
});
|
|
|
|
if (resetExitCode !== 0) {
|
|
process.exit(resetExitCode);
|
|
}
|
|
}
|
|
|
|
console.log(prefixLine("system", "SYSTEM", "Starting local Good Grief stack..."));
|
|
for (const service of services) {
|
|
console.log(prefixLine("system", "SYSTEM", `${service.title}: ${service.url}`));
|
|
}
|
|
|
|
for (const service of services) {
|
|
const child = spawn(service.command, service.args, {
|
|
cwd: repoRoot,
|
|
env: {
|
|
...process.env
|
|
},
|
|
stdio: ["ignore", "pipe", "pipe"]
|
|
});
|
|
|
|
childProcesses.push(child);
|
|
attachStream(child, "stdout", service.id, service.title);
|
|
attachStream(child, "stderr", service.id, service.title);
|
|
|
|
child.on("exit", (code, signal) => {
|
|
if (!shuttingDown) {
|
|
const reason = signal ? `${service.title} exited from ${signal}` : `${service.title} exited with code ${code ?? 1}`;
|
|
console.error(prefixLine(service.id, service.title, reason));
|
|
shutdown(reason);
|
|
process.exitCode = code ?? 1;
|
|
}
|
|
});
|
|
|
|
if (service.waitForReady) {
|
|
const ready = await waitForService(service);
|
|
if (ready) {
|
|
console.log(prefixLine("system", "SYSTEM", `${service.title} ready.`));
|
|
} else {
|
|
console.log(prefixLine("system", "SYSTEM", `${service.title} did not report ready before timeout.`));
|
|
}
|
|
}
|
|
}
|
|
|
|
await new Promise(() => {});
|