import type { ManagedProcess } from './process' import { isPortReachable, startLoggedProcess, stopManagedProcess, waitForUrl } from './process' type WebServerStartOptions = { baseURL: string command: string args?: string[] cwd: string logFilePath: string reuseExistingServer: boolean timeoutMs: number } let activeProcess: ManagedProcess | undefined const getUrlHostAndPort = (url: string) => { const parsedUrl = new URL(url) const isHttps = parsedUrl.protocol === 'https:' return { host: parsedUrl.hostname, port: parsedUrl.port ? Number(parsedUrl.port) : isHttps ? 443 : 80, } } export const startWebServer = async ({ baseURL, command, args = [], cwd, logFilePath, reuseExistingServer, timeoutMs, }: WebServerStartOptions) => { const { host, port } = getUrlHostAndPort(baseURL) if (reuseExistingServer && (await isPortReachable(host, port))) return activeProcess = await startLoggedProcess({ command, args, cwd, label: 'web server', logFilePath, }) let startupError: Error | undefined activeProcess.childProcess.once('error', (error) => { startupError = error }) activeProcess.childProcess.once('exit', (code, signal) => { if (startupError) return startupError = new Error( `Web server exited before readiness (code: ${code ?? 'unknown'}, signal: ${signal ?? 'none'}).`, ) }) const deadline = Date.now() + timeoutMs while (Date.now() < deadline) { if (startupError) { await stopManagedProcess(activeProcess) activeProcess = undefined throw startupError } try { await waitForUrl(baseURL, 1_000, 250, 1_000) return } catch { // Continue polling until timeout or child exit. } } await stopManagedProcess(activeProcess) activeProcess = undefined throw new Error(`Timed out waiting for web server readiness at ${baseURL} after ${timeoutMs}ms.`) } export const stopWebServer = async () => { await stopManagedProcess(activeProcess) activeProcess = undefined }