import { spawn, ChildProcessWithoutNullStreams } from 'child_process'; import path from 'path'; export default class PythonSubprocessManager { // eslint-disable-next-line no-use-before-define public static instance: PythonSubprocessManager | null = null; private process: ChildProcessWithoutNullStreams | null = null; private scriptPath: string; private working_dir: string = path.join( __dirname, '..', '..', '..', 'audio-service', ); public portNumber: number | null = null; constructor(scriptPath: string) { this.scriptPath = scriptPath; PythonSubprocessManager.instance = this; } start(args: string[] = []): void { if (this.process) { throw new Error('Process already running.'); } console.log(`Using Python working directory at: ${this.working_dir}`); console.log(`Starting Python subprocess with script: ${this.scriptPath}`); this.process = spawn( 'venv/Scripts/python.exe', [this.scriptPath, ...args], { cwd: this.working_dir, detached: false, stdio: 'pipe', }, ); this.process.stdout.on('data', (data: Buffer) => { // console.log(`Python stdout: ${data.toString()}`); }); this.process.stderr.on('data', (data: Buffer) => { // console.error(`Python stderr: ${data.toString()}`); const lines = data.toString().split('\n'); // eslint-disable-next-line no-restricted-syntax for (const line of lines) { const match = line.match(/Running on .*:(\d+)/); if (match) { const port = parseInt(match[1], 10); console.log(`Detected port: ${port}`); this.portNumber = port; } } }); this.process.on('exit', () => { console.log('Python subprocess exited.'); this.process = null; }); } stop(): void { if (this.process) { this.process.kill(); this.process = null; } } restart(args: string[] = []): void { this.stop(); this.start(args); } isHealthy(): boolean { return !!this.process && !this.process.killed; } }