101 lines
2.4 KiB
TypeScript
101 lines
2.4 KiB
TypeScript
import { spawn, ChildProcess } from 'child_process';
|
|
import { join } from 'path';
|
|
|
|
export interface ApiServerHarnessOptions {
|
|
port?: number;
|
|
env?: Record<string, string>;
|
|
}
|
|
|
|
export class ApiServerHarness {
|
|
private process: ChildProcess | null = null;
|
|
private logs: string[] = [];
|
|
private port: number;
|
|
|
|
constructor(options: ApiServerHarnessOptions = {}) {
|
|
this.port = options.port || 3001;
|
|
}
|
|
|
|
async start(): Promise<void> {
|
|
return new Promise((resolve, reject) => {
|
|
const cwd = join(process.cwd(), 'apps/api');
|
|
|
|
this.process = spawn('npm', ['run', 'start:dev'], {
|
|
cwd,
|
|
env: {
|
|
...process.env,
|
|
PORT: this.port.toString(),
|
|
GRIDPILOT_API_PERSISTENCE: 'inmemory',
|
|
ENABLE_BOOTSTRAP: 'true',
|
|
},
|
|
shell: true,
|
|
detached: true,
|
|
});
|
|
|
|
let resolved = false;
|
|
|
|
const checkReadiness = async () => {
|
|
if (resolved) return;
|
|
try {
|
|
const res = await fetch(`http://localhost:${this.port}/health`);
|
|
if (res.ok) {
|
|
resolved = true;
|
|
resolve();
|
|
}
|
|
} catch (e) {
|
|
// Not ready yet
|
|
}
|
|
};
|
|
|
|
this.process.stdout?.on('data', (data) => {
|
|
const str = data.toString();
|
|
this.logs.push(str);
|
|
if (str.includes('Nest application successfully started') || str.includes('started')) {
|
|
checkReadiness();
|
|
}
|
|
});
|
|
|
|
this.process.stderr?.on('data', (data) => {
|
|
const str = data.toString();
|
|
this.logs.push(str);
|
|
});
|
|
|
|
this.process.on('error', (err) => {
|
|
if (!resolved) {
|
|
resolved = true;
|
|
reject(err);
|
|
}
|
|
});
|
|
|
|
this.process.on('exit', (code) => {
|
|
if (!resolved && code !== 0 && code !== null) {
|
|
resolved = true;
|
|
reject(new Error(`API server exited with code ${code}`));
|
|
}
|
|
});
|
|
|
|
// Timeout after 60 seconds
|
|
setTimeout(() => {
|
|
if (!resolved) {
|
|
resolved = true;
|
|
reject(new Error(`API server failed to start within 60s. Logs:\n${this.getLogTail(20)}`));
|
|
}
|
|
}, 60000);
|
|
});
|
|
}
|
|
|
|
async stop(): Promise<void> {
|
|
if (this.process && this.process.pid) {
|
|
try {
|
|
process.kill(-this.process.pid);
|
|
} catch (e) {
|
|
this.process.kill();
|
|
}
|
|
this.process = null;
|
|
}
|
|
}
|
|
|
|
getLogTail(lines: number = 60): string {
|
|
return this.logs.slice(-lines).join('');
|
|
}
|
|
}
|