// Zero-dep static server for the voyage2026 itinerary. // Usage: node server.mjs [port] — serves this directory, / -> index, /healthz -> ok import { createServer } from "node:http"; import { readFile } from "node:fs/promises"; import { join, extname, normalize } from "node:path"; import { fileURLToPath } from "node:url"; const ROOT = fileURLToPath(new URL(".", import.meta.url)); const PORT = Number(process.argv[2] || process.env.PORT || 8060); const INDEX = "itineraire-toronto-2026.html"; const MIME = { ".html": "text/html; charset=utf-8", ".css": "text/css; charset=utf-8", ".js": "text/javascript; charset=utf-8", ".json": "application/json", ".png": "image/png", ".jpg": "image/jpeg", ".jpeg": "image/jpeg", ".webp": "image/webp", ".svg": "image/svg+xml", ".ico": "image/x-icon", ".pdf": "application/pdf", }; createServer(async (req, res) => { const url = new URL(req.url, "http://x"); if (url.pathname === "/healthz") { res.writeHead(200, { "content-type": "text/plain" }); return res.end("ok"); } let path = decodeURIComponent(url.pathname); if (path === "/" || path === "/index.html") path = "/" + INDEX; const file = normalize(join(ROOT, path)); if (!file.startsWith(ROOT)) { res.writeHead(403); return res.end("forbidden"); } try { const body = await readFile(file); res.writeHead(200, { "content-type": MIME[extname(file).toLowerCase()] || "application/octet-stream", "cache-control": "public, max-age=300", }); res.end(body); } catch { res.writeHead(404, { "content-type": "text/plain; charset=utf-8" }); res.end("404 — page introuvable"); } }).listen(PORT, "0.0.0.0", () => { console.log(`voyage2026 static server on http://0.0.0.0:${PORT} (root: ${ROOT})`); });