import { spawn, type ChildProcess } from "node:child_process"; import { request } from "node:http"; interface ServerHandle { baseUrl: string; stop: () => void; } function probePort(port: number): Promise { return new Promise((resolve) => { const req = request( { hostname: "127.0.0.1", port, path: "/", method: "HEAD", timeout: 2000 }, (res) => { res.resume(); resolve(true); }, ); req.on("error", () => resolve(false)); req.on("timeout", () => { req.destroy(); resolve(false); }); req.end(); }); } async function waitForServer(port: number, timeoutMs: number): Promise { const start = Date.now(); while (Date.now() - start < timeoutMs) { if (await probePort(port)) return; await new Promise((r) => setTimeout(r, 500)); } throw new Error(`Server did not start on port ${port} within ${timeoutMs}ms`); } export async function ensureServer(port: number): Promise { const baseUrl = `http://127.0.0.1:${port}`; if (await probePort(port)) { console.log(`Reusing existing server on port ${port}`); return { baseUrl, stop: () => {} }; } console.log(`Starting dev server on port ${port}...`); const child: ChildProcess = spawn("npx", ["next", "dev", "--port", String(port)], { cwd: new URL("../../", import.meta.url).pathname, stdio: "pipe", env: { ...process.env, NODE_ENV: "development" }, }); child.stdout?.resume(); child.stderr?.on("data", (chunk: Buffer) => { const line = chunk.toString().trim(); if (line) console.log(`[next] ${line}`); }); try { await waitForServer(port, 30_000); } catch (err) { child.kill("SIGTERM"); throw err; } console.log(`Dev server ready on ${baseUrl}`); return { baseUrl, stop: () => { child.kill("SIGTERM"); }, }; }