import { spawn } from "node:child_process"; import fs from "node:fs/promises"; import path from "node:path"; const root = path.resolve(import.meta.dirname, ".."); const appPort = Number(process.env.E2E_PORT ?? 3100); const fixturePort = Number(process.env.FIXTURE_PORT ?? 3101); const baseUrl = `http://127.0.0.1:${appPort}`; const children = []; function run(command, args, options = {}) { return new Promise((resolve, reject) => { const child = spawn(command, args, { cwd: root, env: process.env, stdio: "inherit", ...options }); child.once("error", reject); child.once("exit", (code) => code === 0 ? resolve() : reject(new Error(`${command} ${args.join(" ")} exited with code ${code ?? "null"}`))); }); } function start(command, args, env = {}) { const child = spawn(command, args, { cwd: root, env: { ...process.env, ...env }, stdio: "inherit", }); children.push(child); return child; } async function waitFor(url) { for (let attempt = 0; attempt < 60; attempt += 1) { try { const response = await fetch(url); if (response.ok) return; } catch { // The process is still starting. } await new Promise((resolve) => setTimeout(resolve, 250)); } throw new Error(`Timed out waiting for ${url}`); } async function stopChildren() { await Promise.all(children.map((child) => new Promise((resolve) => { if (child.exitCode !== null) return resolve(); child.once("exit", resolve); child.kill("SIGTERM"); setTimeout(() => { if (child.exitCode === null) child.kill("SIGKILL"); }, 3_000).unref(); }))); } async function prepareStandaloneServer() { const standaloneRoot = path.join(root, ".next", "standalone"); await fs.rm(path.join(standaloneRoot, "public"), { recursive: true, force: true }); await fs.rm(path.join(standaloneRoot, ".next", "static"), { recursive: true, force: true }); await fs.cp(path.join(root, "public"), path.join(standaloneRoot, "public"), { recursive: true }); await fs.cp( path.join(root, ".next", "static"), path.join(standaloneRoot, ".next", "static"), { recursive: true }, ); } const npm = process.platform === "win32" ? "npm.cmd" : "npm"; try { await run(npm, ["run", "build"]); await prepareStandaloneServer(); start(process.execPath, ["e2e/fixture-server.mjs"], { FIXTURE_PORT: String(fixturePort) }); start(process.execPath, [".next/standalone/server.js"], { CALENDAR_URL: `http://127.0.0.1:${fixturePort}/calendar.ics`, APP_URL: baseUrl, CORS_ORIGIN: baseUrl, PORT: String(appPort), HOSTNAME: "127.0.0.1", }); await waitFor(`${baseUrl}/api/events`); await run(process.execPath, ["--test", "e2e/smoke.e2e.mjs"], { env: { ...process.env, E2E_BASE_URL: baseUrl }, }); } finally { await stopChildren(); }