import {ChildProcess} from 'node:child_process'; import type {IProcessManager, ProcessManagerEvents} from '../interfaces/IProcessManager.js'; import type {IProcessSpawner} from '../interfaces/IProcessSpawner.js'; import type {ILogger} from '../interfaces/ILogger.js'; import {EventEmitter} from 'node:events'; /** * Manages process lifecycle * Single Responsibility: Process management only * Open/Closed: Can be extended without modification */ export class ProcessManager extends EventEmitter implements IProcessManager { private activeProcess: ChildProcess | null = null; constructor( private readonly processSpawner: IProcessSpawner, private readonly logger: ILogger ) { super(); } start(command: string, args: string[]): void { if (this.isRunning()) { throw new Error('Process is already running'); } if (!command) { throw new Error('Invalid command: command cannot be empty'); } try { this.activeProcess = this.processSpawner.spawn(command, args); if (!this.activeProcess) { throw new Error('Failed to spawn process'); } this.setupEventHandlers(); } catch (error) { this.logger.error('Failed to start process:', error); this.activeProcess = null; throw error; } } stop(): void { if (this.activeProcess) { this.activeProcess.kill(); this.activeProcess = null; } } isRunning(): boolean { return this.activeProcess !== null && !this.activeProcess.killed; } private setupEventHandlers(): void { if (!this.activeProcess) { return; } this.activeProcess.stdout?.on('data', (data: Buffer) => { this.logger.log(`stdout: ${data}`); this.emit('data', data); }); this.activeProcess.stderr?.on('data', (data: Buffer) => { this.logger.log(`stderr: ${data}`); this.emit('data', data); }); this.activeProcess.on('close', (code: number | null) => { this.logger.log(`child process exited with code ${code}`); this.activeProcess = null; this.emit('close', code); }); this.activeProcess.on('error', (error: Error) => { this.logger.error('Process error:', error); this.activeProcess = null; this.emit('error', error); }); } }