// Tiny static file server over client/dist, for the two e2e scripts that need
// their OWN port (the app's allowed-origin list only covers 4173/5173, and one
// of them deliberately runs off-allowlist against a stub mailbox).
//
// Replaces the `npx vite --port …` the TypeScript era used: there is no vite in
// this repo any more. Run a build first — this serves the built dist, not source.
//
// node e2e/serve.mjs <port>
import { createServer } from "node:http";
import { existsSync, readFileSync, statSync } from "node:fs";
import { extname, join, normalize } from "node:path";
import { fileURLToPath } from "node:url";
const DIST = join(fileURLToPath(new URL("..", import.meta.url)), "dist");
const TYPES = {
".html": "text/html; charset=utf-8",
".js": "text/javascript; charset=utf-8",
".css": "text/css; charset=utf-8",
".json": "application/json; charset=utf-8",
".webmanifest": "application/manifest+json",
".png": "image/png",
".svg": "image/svg+xml",
};
export function serve(port) {
const server = createServer((req, res) => {
const url = new URL(req.url, "http://localhost");
// strip the leading slash and normalise away any ".." before joining
let rel = normalize(decodeURIComponent(url.pathname)).replace(/^(\.\.[/\\])+/, "").replace(/^[/\\]+/, "");
let file = join(DIST, rel);
if (!file.startsWith(DIST)) {
res.writeHead(403).end();
return;
}
if (rel === "" || (existsSync(file) && statSync(file).isDirectory())) file = join(file, "index.html");
if (!existsSync(file)) file = join(DIST, "index.html"); // SPA fallback
res.writeHead(200, {
"content-type": TYPES[extname(file)] ?? "application/octet-stream",
// never let the browser cache between e2e runs
"cache-control": "no-store",
});
res.end(readFileSync(file));
});
return new Promise((resolve) => server.listen(port, () => resolve(server)));
}
if (import.meta.url === `file://${process.argv[1]}`) {
const port = Number(process.argv[2] ?? 5174);
await serve(port);
console.log(`serving ${DIST} on http://localhost:${port}`);
}