mirror of
https://github.com/getpaseo/paseo.git
synced 2026-07-29 12:01:31 +00:00
Update files
This commit is contained in:
36
packages/server/scripts/daemon-runner.ts
Normal file
36
packages/server/scripts/daemon-runner.ts
Normal file
@@ -0,0 +1,36 @@
|
||||
import { fileURLToPath } from "url";
|
||||
import { existsSync } from "node:fs";
|
||||
import { runSupervisor } from "./supervisor.js";
|
||||
|
||||
function resolveWorkerEntry(): string {
|
||||
const candidates = [
|
||||
fileURLToPath(new URL("../server/server/index.js", import.meta.url)),
|
||||
fileURLToPath(new URL("../dist/server/server/index.js", import.meta.url)),
|
||||
fileURLToPath(new URL("../src/server/index.ts", import.meta.url)),
|
||||
fileURLToPath(new URL("../../src/server/index.ts", import.meta.url)),
|
||||
];
|
||||
|
||||
for (const candidate of candidates) {
|
||||
if (existsSync(candidate)) {
|
||||
return candidate;
|
||||
}
|
||||
}
|
||||
|
||||
return candidates[0];
|
||||
}
|
||||
|
||||
function resolveWorkerExecArgv(): string[] {
|
||||
const workerEntry = resolveWorkerEntry();
|
||||
return workerEntry.endsWith(".ts") ? ["--import", "tsx"] : [];
|
||||
}
|
||||
|
||||
runSupervisor({
|
||||
name: "DaemonRunner",
|
||||
startupMessage: "Starting daemon worker (IPC restart enabled)",
|
||||
resolveWorkerEntry,
|
||||
workerArgs: process.argv.slice(2),
|
||||
workerEnv: process.env,
|
||||
workerExecArgv: resolveWorkerExecArgv(),
|
||||
restartOnCrash: false,
|
||||
shutdownReasons: ["cli_shutdown"],
|
||||
});
|
||||
@@ -1,63 +1,36 @@
|
||||
import { fork, type ChildProcess } from "child_process";
|
||||
import { fileURLToPath } from "url";
|
||||
import path from "path";
|
||||
import { existsSync } from "node:fs";
|
||||
import { runSupervisor } from "./supervisor.js";
|
||||
|
||||
const serverEntry = fileURLToPath(
|
||||
new URL("../src/server/index.ts", import.meta.url)
|
||||
);
|
||||
function resolveWorkerEntry(): string {
|
||||
const candidates = [
|
||||
fileURLToPath(new URL("../server/server/index.js", import.meta.url)),
|
||||
fileURLToPath(new URL("../dist/server/server/index.js", import.meta.url)),
|
||||
fileURLToPath(new URL("../src/server/index.ts", import.meta.url)),
|
||||
fileURLToPath(new URL("../../src/server/index.ts", import.meta.url)),
|
||||
];
|
||||
|
||||
let child: ChildProcess | null = null;
|
||||
let restarting = false;
|
||||
|
||||
function spawnServer() {
|
||||
child = fork(serverEntry, process.argv.slice(2), {
|
||||
stdio: "inherit",
|
||||
env: process.env,
|
||||
execArgv: ["--import", "tsx"],
|
||||
});
|
||||
|
||||
child.on("message", (msg: any) => {
|
||||
if (msg?.type === "paseo:restart") {
|
||||
restartServer();
|
||||
for (const candidate of candidates) {
|
||||
if (existsSync(candidate)) {
|
||||
return candidate;
|
||||
}
|
||||
});
|
||||
|
||||
child.on("exit", (code, signal) => {
|
||||
const exitDescriptor =
|
||||
signal ?? (typeof code === "number" ? `code ${code}` : "unknown");
|
||||
|
||||
// Restart on: explicit restart request, or any non-zero exit (crash)
|
||||
if (restarting || (code !== 0 && code !== null)) {
|
||||
restarting = false;
|
||||
process.stderr.write(`[DevRunner] Server exited (${exitDescriptor}). Restarting...\n`);
|
||||
spawnServer();
|
||||
return;
|
||||
}
|
||||
|
||||
process.stderr.write(`[DevRunner] Server exited (${exitDescriptor}). Shutting down.\n`);
|
||||
process.exit(0);
|
||||
});
|
||||
}
|
||||
|
||||
function restartServer() {
|
||||
if (!child || restarting) {
|
||||
return;
|
||||
}
|
||||
|
||||
restarting = true;
|
||||
process.stderr.write("[DevRunner] Restart requested. Stopping current server...\n");
|
||||
child.kill("SIGTERM");
|
||||
return candidates[0];
|
||||
}
|
||||
|
||||
function forwardSignal(signal: NodeJS.Signals) {
|
||||
if (!child) {
|
||||
process.exit(0);
|
||||
}
|
||||
child.kill(signal);
|
||||
function resolveWorkerExecArgv(): string[] {
|
||||
const workerEntry = resolveWorkerEntry();
|
||||
return workerEntry.endsWith(".ts") ? ["--import", "tsx"] : [];
|
||||
}
|
||||
|
||||
process.on("SIGINT", () => forwardSignal("SIGINT"));
|
||||
process.on("SIGTERM", () => forwardSignal("SIGTERM"));
|
||||
|
||||
process.stdout.write("[DevRunner] Starting server with tsx (explicit restarts only)\n");
|
||||
spawnServer();
|
||||
runSupervisor({
|
||||
name: "DevRunner",
|
||||
startupMessage: "Starting server worker (crash restarts enabled)",
|
||||
resolveWorkerEntry,
|
||||
workerArgs: process.argv.slice(2),
|
||||
workerEnv: process.env,
|
||||
workerExecArgv: resolveWorkerExecArgv(),
|
||||
restartOnCrash: true,
|
||||
shutdownReasons: ["cli_shutdown"],
|
||||
});
|
||||
|
||||
130
packages/server/scripts/supervisor.ts
Normal file
130
packages/server/scripts/supervisor.ts
Normal file
@@ -0,0 +1,130 @@
|
||||
import { fork, type ChildProcess } from "child_process";
|
||||
|
||||
type RestartMessage = {
|
||||
type: "paseo:restart";
|
||||
reason?: string;
|
||||
};
|
||||
|
||||
type SupervisorOptions = {
|
||||
name: string;
|
||||
startupMessage: string;
|
||||
resolveWorkerEntry: () => string;
|
||||
workerArgs?: string[];
|
||||
workerEnv?: NodeJS.ProcessEnv;
|
||||
workerExecArgv?: string[];
|
||||
restartOnCrash?: boolean;
|
||||
shutdownReasons?: string[];
|
||||
};
|
||||
|
||||
function describeExit(code: number | null, signal: NodeJS.Signals | null): string {
|
||||
return signal ?? (typeof code === "number" ? `code ${code}` : "unknown");
|
||||
}
|
||||
|
||||
function isRestartMessage(msg: unknown): msg is RestartMessage {
|
||||
return (
|
||||
typeof msg === "object" &&
|
||||
msg !== null &&
|
||||
"type" in msg &&
|
||||
(msg as { type?: unknown }).type === "paseo:restart"
|
||||
);
|
||||
}
|
||||
|
||||
export function runSupervisor(options: SupervisorOptions): void {
|
||||
const shutdownReasons = new Set(options.shutdownReasons ?? ["cli_shutdown"]);
|
||||
const restartOnCrash = options.restartOnCrash ?? false;
|
||||
const workerArgs = options.workerArgs ?? process.argv.slice(2);
|
||||
const workerEnv = options.workerEnv ?? process.env;
|
||||
const workerExecArgv = options.workerExecArgv ?? ["--import", "tsx"];
|
||||
|
||||
let child: ChildProcess | null = null;
|
||||
let restarting = false;
|
||||
let shuttingDown = false;
|
||||
|
||||
const log = (message: string): void => {
|
||||
process.stderr.write(`[${options.name}] ${message}\n`);
|
||||
};
|
||||
|
||||
const spawnWorker = () => {
|
||||
let workerEntry: string;
|
||||
try {
|
||||
// Resolve at spawn time so restarts pick up current filesystem state.
|
||||
workerEntry = options.resolveWorkerEntry();
|
||||
} catch (error) {
|
||||
const message = error instanceof Error ? error.message : String(error);
|
||||
log(`Failed to resolve worker entry: ${message}`);
|
||||
process.exit(1);
|
||||
return;
|
||||
}
|
||||
|
||||
child = fork(workerEntry, workerArgs, {
|
||||
stdio: "inherit",
|
||||
env: workerEnv,
|
||||
execArgv: workerExecArgv,
|
||||
});
|
||||
|
||||
child.on("message", (msg: unknown) => {
|
||||
if (!isRestartMessage(msg)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (msg.reason && shutdownReasons.has(msg.reason)) {
|
||||
requestShutdown(`Shutdown requested by worker (${msg.reason})`);
|
||||
return;
|
||||
}
|
||||
|
||||
requestRestart("Restart requested by worker");
|
||||
});
|
||||
|
||||
child.on("exit", (code, signal) => {
|
||||
const exitDescriptor = describeExit(code, signal);
|
||||
|
||||
if (shuttingDown) {
|
||||
log(`Worker exited (${exitDescriptor}). Supervisor shutting down.`);
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
if (restarting || (restartOnCrash && code !== 0 && code !== null)) {
|
||||
restarting = false;
|
||||
log(`Worker exited (${exitDescriptor}). Restarting worker...`);
|
||||
spawnWorker();
|
||||
return;
|
||||
}
|
||||
|
||||
log(`Worker exited (${exitDescriptor}). Supervisor exiting.`);
|
||||
process.exit(typeof code === "number" ? code : 0);
|
||||
});
|
||||
};
|
||||
|
||||
const requestRestart = (reason: string) => {
|
||||
if (!child || restarting || shuttingDown) {
|
||||
return;
|
||||
}
|
||||
restarting = true;
|
||||
log(`${reason}. Stopping worker for restart...`);
|
||||
child.kill("SIGTERM");
|
||||
};
|
||||
|
||||
const requestShutdown = (reason: string) => {
|
||||
if (shuttingDown) {
|
||||
return;
|
||||
}
|
||||
shuttingDown = true;
|
||||
restarting = false;
|
||||
log(`${reason}. Stopping worker...`);
|
||||
if (!child) {
|
||||
process.exit(0);
|
||||
return;
|
||||
}
|
||||
child.kill("SIGTERM");
|
||||
};
|
||||
|
||||
const forwardSignal = (signal: NodeJS.Signals) => {
|
||||
requestShutdown(`Received ${signal}`);
|
||||
};
|
||||
|
||||
process.on("SIGINT", () => forwardSignal("SIGINT"));
|
||||
process.on("SIGTERM", () => forwardSignal("SIGTERM"));
|
||||
|
||||
process.stdout.write(`[${options.name}] ${options.startupMessage}\n`);
|
||||
spawnWorker();
|
||||
}
|
||||
Reference in New Issue
Block a user