Initial commit
This commit is contained in:
@@ -0,0 +1,30 @@
|
||||
import fs from "node:fs/promises";
|
||||
import path from "node:path";
|
||||
import { fileURLToPath } from "node:url";
|
||||
|
||||
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
||||
const repoRoot = path.resolve(__dirname, "..");
|
||||
|
||||
const resetTargets = [
|
||||
path.join(repoRoot, "data", "runtime"),
|
||||
path.join(repoRoot, "storage", "runtime", "seed"),
|
||||
path.join(repoRoot, "storage", "runtime", "originals"),
|
||||
path.join(repoRoot, "storage", "runtime", "thumbs"),
|
||||
path.join(repoRoot, "storage", "runtime", "previews"),
|
||||
path.join(repoRoot, "storage", "runtime", "renders")
|
||||
];
|
||||
|
||||
await Promise.all(
|
||||
resetTargets.map(async (targetPath) => {
|
||||
await fs.rm(targetPath, { recursive: true, force: true });
|
||||
})
|
||||
);
|
||||
|
||||
await Promise.all(
|
||||
[
|
||||
path.join(repoRoot, "data"),
|
||||
path.join(repoRoot, "storage")
|
||||
].map((targetPath) => fs.mkdir(targetPath, { recursive: true }))
|
||||
);
|
||||
|
||||
console.log("Runtime state cleared.");
|
||||
@@ -0,0 +1,187 @@
|
||||
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(() => {});
|
||||
Reference in New Issue
Block a user