const { existsSync } = require('fs'); const { spawn } = require('child_process'); const { performance } = require('perf_hooks'); function parseArgs(argv) { const options = { entry: 'dist/main.js', port: 4100, timeout: 30000, path: '/api/v1/health', }; for (let index = 0; index < argv.length; index += 1) { const arg = argv[index]; const next = argv[index + 1]; if (arg === '--entry' && next) { options.entry = next; index += 1; continue; } if (arg === '--port' && next) { options.port = Number(next); index += 1; continue; } if (arg === '--timeout' && next) { options.timeout = Number(next); index += 1; continue; } if (arg === '--path' && next) { options.path = next.startsWith('/') ? next : `/${next}`; index += 1; } } if (!existsSync(options.entry)) { throw new Error(`Entry file not found: ${options.entry}. Run "npm run build" first.`); } if (!Number.isInteger(options.port) || options.port <= 0) { throw new Error('port must be a positive integer'); } if (!Number.isFinite(options.timeout) || options.timeout <= 0) { throw new Error('timeout must be a positive number of milliseconds'); } return options; } async function waitForHealth(url, timeoutMs) { const deadline = Date.now() + timeoutMs; let lastError = null; while (Date.now() < deadline) { try { const response = await fetch(url, { method: 'GET' }); if (response.ok) { const body = await response.text(); return { status: response.status, body }; } } catch (error) { lastError = error; } await new Promise((resolve) => setTimeout(resolve, 250)); } if (lastError instanceof Error) { throw new Error(`Startup timeout. Last error: ${lastError.message}`); } throw new Error('Startup timeout. Health endpoint did not become ready.'); } async function terminate(child) { if (child.exitCode !== null) { return; } child.kill(); await new Promise((resolve) => { child.once('exit', resolve); setTimeout(resolve, 5000); }); } async function main() { const options = parseArgs(process.argv.slice(2)); const url = `http://127.0.0.1:${options.port}${options.path}`; const stdoutLines = []; const stderrLines = []; const startedAt = performance.now(); const child = spawn(process.execPath, [options.entry], { cwd: process.cwd(), env: { ...process.env, PORT: String(options.port), PUBLIC_BASE_URL: `http://127.0.0.1:${options.port}`, }, stdio: ['ignore', 'pipe', 'pipe'], }); child.stdout.on('data', (chunk) => { stdoutLines.push(...String(chunk).split(/\r?\n/).filter(Boolean)); if (stdoutLines.length > 20) { stdoutLines.splice(0, stdoutLines.length - 20); } }); child.stderr.on('data', (chunk) => { stderrLines.push(...String(chunk).split(/\r?\n/).filter(Boolean)); if (stderrLines.length > 20) { stderrLines.splice(0, stderrLines.length - 20); } }); try { const healthResult = await waitForHealth(url, options.timeout); const readyMs = performance.now() - startedAt; console.log( JSON.stringify( { entry: options.entry, url, startupMs: Number(readyMs.toFixed(2)), healthStatus: healthResult.status, recentStdout: stdoutLines, recentStderr: stderrLines, }, null, 2, ), ); } finally { await terminate(child); } } main().catch((error) => { console.error(error instanceof Error ? error.message : String(error)); process.exitCode = 1; });