70 lines
1.8 KiB
TypeScript
70 lines
1.8 KiB
TypeScript
import { spawn, type ChildProcess } from "node:child_process";
|
|
import { request } from "node:http";
|
|
|
|
interface ServerHandle {
|
|
baseUrl: string;
|
|
stop: () => void;
|
|
}
|
|
|
|
function probePort(port: number): Promise<boolean> {
|
|
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<void> {
|
|
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<ServerHandle> {
|
|
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");
|
|
},
|
|
};
|
|
}
|