import { readFile, stat } from "node:fs/promises"; import { createServer } from "node:http"; import { extname, join, normalize, resolve } from "node:path"; import { fileURLToPath } from "node:url"; const _rootDir = fileURLToPath(new URL("..", import.meta.url)); const distDir = resolve( process.env.ADMINFRONT_BUILD_OUT_DIR ?? "/tmp/baron-sso-adminfront-dist", ); const host = process.env.HOST ?? "0.0.0.0"; const port = Number(process.env.PORT ?? process.env.ADMINFRONT_PORT ?? 5173); const backendTarget = new URL( process.env.API_PROXY_TARGET || "http://localhost:3000", ); const contentTypes = { ".css": "text/css; charset=utf-8", ".html": "text/html; charset=utf-8", ".js": "application/javascript; charset=utf-8", ".json": "application/json; charset=utf-8", ".map": "application/json; charset=utf-8", ".mjs": "application/javascript; charset=utf-8", ".svg": "image/svg+xml", }; function getContentType(filePath) { return ( contentTypes[extname(filePath).toLowerCase()] ?? "application/octet-stream" ); } function sendJson(res, statusCode, body) { res.writeHead(statusCode, { "Content-Type": "application/json; charset=utf-8", "Cache-Control": "no-store", }); res.end(JSON.stringify(body)); } function toSafePath(pathname) { const decoded = decodeURIComponent(pathname); const relative = decoded.replace(/^\/+/, ""); const safe = normalize(relative).replace(/^(\.\.(?:[\\/]|$))+/, ""); return join(distDir, safe); } async function tryReadFile(filePath) { try { return await readFile(filePath); } catch { return null; } } async function proxyToBackend(req, res, pathname, search) { const target = new URL(pathname + search, backendTarget); const headers = new Headers(); for (const [key, value] of Object.entries(req.headers)) { if (!value) continue; if (key === "host" || key === "content-length" || key === "connection") { continue; } if (Array.isArray(value)) { headers.set(key, value.join(", ")); continue; } headers.set(key, value); } const hasBody = !["GET", "HEAD"].includes(req.method ?? "GET"); const response = await fetch(target, { method: req.method, headers, body: hasBody ? req : undefined, duplex: hasBody ? "half" : undefined, }); const responseHeaders = new Headers(response.headers); responseHeaders.delete("content-length"); responseHeaders.delete("transfer-encoding"); responseHeaders.delete("connection"); res.writeHead(response.status, Object.fromEntries(responseHeaders.entries())); if (req.method === "HEAD") { res.end(); return; } const arrayBuffer = await response.arrayBuffer(); res.end(Buffer.from(arrayBuffer)); } async function serveStatic(req, res, pathname) { const indexPath = join(distDir, "index.html"); const filePath = toSafePath(pathname); let resolvedPath = filePath; try { const fileStat = await stat(resolvedPath); if (fileStat.isDirectory()) { resolvedPath = join(resolvedPath, "index.html"); } } catch { resolvedPath = indexPath; } let body = await tryReadFile(resolvedPath); if (!body) { body = await tryReadFile(indexPath); resolvedPath = indexPath; } if (!body) { sendJson(res, 500, { error: "dist_not_found" }); return; } res.writeHead(200, { "Content-Type": getContentType(resolvedPath), "Cache-Control": resolvedPath.endsWith("index.html") ? "no-cache" : "public, max-age=31536000, immutable", }); if (req.method === "HEAD") { res.end(); return; } res.end(body); } createServer(async (req, res) => { try { const url = new URL( req.url ?? "/", `http://${req.headers.host ?? "localhost"}`, ); const { pathname, search } = url; if (pathname === "/api" || pathname.startsWith("/api/")) { await proxyToBackend(req, res, pathname, search); return; } const normalizedPath = pathname === "/" ? "/index.html" : pathname; await serveStatic(req, res, normalizedPath); } catch (error) { sendJson(res, 500, { error: "internal_server_error", message: error instanceof Error ? error.message : String(error), }); } }).listen(port, host, () => { console.log( `Adminfront production server listening on http://${host}:${port}`, ); });