1134344433 / src /lib /process-manager.js
Twan07's picture
Upload 22 files
5858652 verified
import { spawn } from "node:child_process";
import { EventEmitter } from "node:events";
export class ProcessManager {
constructor(maxLogLines = 2000) {
this.maxLogLines = maxLogLines;
this.runners = new Map();
this.events = new EventEmitter();
}
get(projectId) {
return this.runners.get(projectId) || null;
}
getLogs(projectId) {
const runner = this.get(projectId);
return runner ? runner.logs : [];
}
isRunning(projectId) {
const runner = this.get(projectId);
return Boolean(runner && runner.process && !runner.process.killed);
}
appendLog(projectId, chunk, stream = "stdout") {
const runner = this.get(projectId);
if (!runner) {
return;
}
const text = String(chunk);
const lines = text.split(/\r?\n/).filter((line, index, array) => line.length > 0 || index < array.length - 1);
for (const line of lines) {
runner.logs.push({
timestamp: new Date().toISOString(),
stream,
line
});
}
if (runner.logs.length > this.maxLogLines) {
runner.logs.splice(0, runner.logs.length - this.maxLogLines);
}
this.events.emit(`log:${projectId}`, {
type: "log",
data: text,
stream,
timestamp: new Date().toISOString()
});
}
run(projectId, options) {
this.stop(projectId);
const child = spawn(options.command, {
cwd: options.cwd,
shell: true,
env: {
...process.env,
FORCE_COLOR: "1"
}
});
const runner = {
process: child,
logs: [],
command: options.command,
cwd: options.cwd,
startedAt: new Date().toISOString(),
status: "running"
};
this.runners.set(projectId, runner);
this.events.emit(`status:${projectId}`, {
type: "status",
status: "running",
command: options.command,
cwd: options.cwd,
timestamp: runner.startedAt
});
child.stdout.on("data", (chunk) => {
this.appendLog(projectId, chunk, "stdout");
});
child.stderr.on("data", (chunk) => {
this.appendLog(projectId, chunk, "stderr");
});
child.on("close", (code, signal) => {
const current = this.get(projectId);
if (!current) {
return;
}
current.status = "stopped";
current.exitedAt = new Date().toISOString();
current.exitCode = code;
current.signal = signal;
this.events.emit(`status:${projectId}`, {
type: "status",
status: "stopped",
exitCode: code,
signal,
timestamp: current.exitedAt
});
});
child.on("error", (error) => {
this.appendLog(projectId, error.message, "stderr");
});
return runner;
}
stop(projectId) {
const runner = this.get(projectId);
if (!runner || !runner.process) {
return false;
}
if (!runner.process.killed) {
runner.process.kill("SIGTERM");
}
runner.status = "stopping";
this.events.emit(`status:${projectId}`, {
type: "status",
status: "stopping",
timestamp: new Date().toISOString()
});
return true;
}
}