treesap
Version:
AI Agent Framework
201 lines ⢠6.87 kB
JavaScript
import process from "node:process";
import { spawn } from "node:child_process";
export class DevServerManager {
command;
port;
childProcess = null;
status;
logBuffer = [];
errorBuffer = [];
maxBufferSize = 1000;
constructor(command, port) {
this.command = command;
this.port = port;
this.status = {
running: false,
command: command,
port: port,
logs: [],
errors: []
};
}
async start() {
if (this.childProcess) {
console.log("Dev server is already running");
return;
}
try {
console.log(`š Starting dev server: ${this.command}`);
// Split command into parts for spawn
const commandParts = this.command.split(' ');
const cmd = commandParts[0];
const args = commandParts.slice(1);
this.childProcess = spawn(cmd, args, {
stdio: ['pipe', 'pipe', 'pipe'],
cwd: process.cwd(),
shell: process.platform === 'win32'
});
this.status.running = true;
this.status.pid = this.childProcess.pid;
this.status.startTime = new Date();
// Handle stdout
if (this.childProcess.stdout) {
this.childProcess.stdout.on('data', (data) => {
const output = data.toString();
this.addLog(output);
});
}
// Handle stderr
if (this.childProcess.stderr) {
this.childProcess.stderr.on('data', (data) => {
const output = data.toString();
this.addError(output);
});
}
// Handle process exit
this.childProcess.on('exit', (code) => {
console.log(`Dev server exited with code ${code}`);
this.status.running = false;
this.status.pid = undefined;
this.childProcess = null;
});
this.childProcess.on('error', (error) => {
console.error("Dev server error:", error);
this.status.running = false;
this.status.pid = undefined;
this.childProcess = null;
});
}
catch (error) {
console.error("Failed to start dev server:", error);
this.status.running = false;
this.addError(`Failed to start: ${error instanceof Error ? error.message : String(error)}`);
throw error;
}
}
async stop() {
if (!this.childProcess) {
console.log("Dev server is not running");
return;
}
try {
this.childProcess.kill("SIGTERM");
// Wait for process to exit with timeout
await new Promise((resolve, reject) => {
const timeout = setTimeout(() => {
if (this.childProcess) {
this.childProcess.kill("SIGKILL");
}
reject(new Error("Process did not exit gracefully"));
}, 5000);
this.childProcess.on('exit', () => {
clearTimeout(timeout);
resolve();
});
});
this.childProcess = null;
this.status.running = false;
this.status.pid = undefined;
}
catch (error) {
console.error("Failed to stop dev server:", error);
this.addError(`Failed to stop: ${error instanceof Error ? error.message : String(error)}`);
}
}
async restart() {
console.log("š Restarting dev server...");
await this.stop();
// Small delay to ensure cleanup
await new Promise(resolve => setTimeout(resolve, 1000));
await this.start();
}
getStatus() {
return {
...this.status,
logs: [...this.logBuffer],
errors: [...this.errorBuffer]
};
}
getLogs(_since) {
// For now, return all logs. In future, could filter by timestamp
return [...this.logBuffer];
}
sendCommand(command) {
if (!this.childProcess || !this.childProcess.stdin) {
return false;
}
try {
this.childProcess.stdin.write(command + '\n');
return true;
}
catch (error) {
console.error('Failed to send command to subprocess:', error);
return false;
}
}
addLog(message) {
const timestamp = new Date().toISOString();
const logEntry = `[${timestamp}] ${message}`;
this.logBuffer.push(logEntry);
if (this.logBuffer.length > this.maxBufferSize) {
this.logBuffer.shift();
}
}
addError(message) {
const timestamp = new Date().toISOString();
const errorEntry = `[${timestamp}] ${message}`;
this.errorBuffer.push(errorEntry);
if (this.errorBuffer.length > this.maxBufferSize) {
this.errorBuffer.shift();
}
}
// Cleanup on process exit
setupGracefulShutdown() {
const cleanup = async () => {
if (this.childProcess) {
await this.stop();
}
process.exit(0);
};
// Handle Ctrl+C (SIGINT)
process.on('SIGINT', () => {
console.log('\nš Shutting down...');
// Forward signal to child process if it exists
if (this.childProcess && this.childProcess.pid) {
try {
this.childProcess.kill('SIGINT');
}
catch {
// Silently handle signal forwarding errors
}
}
cleanup().catch(() => { });
});
// Handle termination signal
process.on('SIGTERM', () => {
console.log('\nš Terminating...');
// Forward signal to child process if it exists
if (this.childProcess && this.childProcess.pid) {
try {
this.childProcess.kill('SIGTERM');
}
catch {
// Silently handle signal forwarding errors
}
}
cleanup().catch(() => { });
});
// Handle process exit (synchronous cleanup only)
process.on('exit', () => {
if (this.childProcess) {
try {
this.childProcess.kill('SIGTERM');
}
catch {
// Silently handle cleanup errors during exit
}
}
});
}
}
//# sourceMappingURL=dev-server.js.map