init project

This commit is contained in:
root
2026-07-31 13:12:54 -04:00
parent 0da92d5e02
commit f3863f760c
7215 changed files with 1860260 additions and 1 deletions
+148
View File
@@ -0,0 +1,148 @@
import { randomUUID } from 'node:crypto';
import { EventEmitter } from 'node:events';
import { execa } from 'execa';
import { config } from '../config.js';
import { logger } from './logger.js';
class ProcessManager {
processes = new Map();
start(options) {
if (this.runningCount() >= config.maxConcurrentProcesses) {
throw new Error(`Refusing to start another process: already at the max of ${config.maxConcurrentProcesses} concurrent managed processes. Stop one first with stop_process.`);
}
const id = randomUUID();
const emitter = new EventEmitter();
const subprocess = execa(options.command, options.args, {
cwd: options.cwd,
env: options.env,
reject: false,
stdin: 'ignore',
forceKillAfterDelay: 5000,
});
const managed = {
id,
label: options.label,
command: options.command,
args: options.args,
cwd: options.cwd,
pid: subprocess.pid,
status: 'running',
exitCode: null,
startedAt: new Date().toISOString(),
emitter,
logs: [],
subprocess,
};
const appendLog = (chunk) => {
const text = chunk.toString();
for (const line of text.split(/\r?\n/)) {
if (line.length === 0)
continue;
managed.logs.push(line);
if (managed.logs.length > config.maxBufferedLogLines) {
managed.logs.splice(0, managed.logs.length - config.maxBufferedLogLines);
}
emitter.emit('log', line);
}
};
subprocess.stdout?.on('data', appendLog);
subprocess.stderr?.on('data', appendLog);
subprocess
.then((result) => {
managed.status = managed.status === 'stopped' ? 'stopped' : 'exited';
managed.exitCode = result.exitCode ?? null;
managed.exitedAt = new Date().toISOString();
emitter.emit('exit');
})
.catch((error) => {
managed.status = 'error';
managed.exitedAt = new Date().toISOString();
appendLog(`[process error] ${error instanceof Error ? error.message : String(error)}`);
emitter.emit('exit');
logger.error(`managed process ${id} failed`, { error: error instanceof Error ? error.message : error });
});
this.processes.set(id, managed);
return this.toInfo(managed);
}
/**
* Resolves once a log line matches `pattern`, the process exits, or `timeoutMs` elapses.
* Used by start_dev_server so it can report the actual "ready" URL instead of guessing.
*/
waitForLog(id, pattern, timeoutMs) {
const managed = this.require(id);
const existing = managed.logs.find((line) => pattern.test(line));
if (existing)
return Promise.resolve({ matchedLine: existing, timedOut: false });
return new Promise((resolve) => {
const cleanup = () => {
clearTimeout(timer);
managed.emitter.off('log', onLog);
managed.emitter.off('exit', onExit);
};
const onLog = (line) => {
if (pattern.test(line)) {
cleanup();
resolve({ matchedLine: line, timedOut: false });
}
};
const onExit = () => {
cleanup();
resolve({ matchedLine: null, timedOut: false });
};
const timer = setTimeout(() => {
cleanup();
resolve({ matchedLine: null, timedOut: true });
}, timeoutMs);
managed.emitter.on('log', onLog);
managed.emitter.on('exit', onExit);
});
}
stop(id) {
const managed = this.require(id);
if (managed.status === 'running') {
managed.status = 'stopped';
managed.subprocess.kill('SIGTERM');
}
return this.toInfo(managed);
}
getLogs(id, tail) {
const managed = this.require(id);
const logs = tail && tail > 0 ? managed.logs.slice(-tail) : managed.logs.slice();
return { info: this.toInfo(managed), logs };
}
list() {
return Array.from(this.processes.values()).map((p) => this.toInfo(p));
}
stopAll() {
for (const managed of this.processes.values()) {
if (managed.status === 'running') {
managed.subprocess.kill('SIGTERM');
}
}
}
runningCount() {
return Array.from(this.processes.values()).filter((p) => p.status === 'running').length;
}
require(id) {
const managed = this.processes.get(id);
if (!managed) {
throw new Error(`Unknown processId "${id}". It may have already exited and been forgotten, or never existed.`);
}
return managed;
}
toInfo(p) {
return {
id: p.id,
label: p.label,
command: p.command,
args: p.args,
cwd: p.cwd,
pid: p.pid,
status: p.status,
exitCode: p.exitCode,
startedAt: p.startedAt,
exitedAt: p.exitedAt,
};
}
}
export const processManager = new ProcessManager();
//# sourceMappingURL=processManager.js.map