// Minimal local simulator for testing SAN01 replacement only. // - Serves the exported "out" folder. // - For text files (.html, .js, .json, .css, .txt) replaces <%=SAN01%> -> "ismail" on the fly. // - Supports mapping /CPL?/path to /path in the export. import http from "http"; import fs from "fs"; import path from "path"; const PORT = process.env.PORT ? Number(process.env.PORT) : 3030; const ROOT = path.join(process.cwd(), "out"); function exists(p) { try { fs.accessSync(p, fs.constants.F_OK); return true; } catch { return false; } } function contentTypeByExt(ext) { switch (ext) { case ".html": return "text/html; charset=utf-8"; case ".js": return "application/javascript; charset=utf-8"; case ".json": return "application/json; charset=utf-8"; case ".css": return "text/css; charset=utf-8"; case ".txt": return "text/plain; charset=utf-8"; case ".svg": return "image/svg+xml"; case ".png": return "image/png"; case ".jpg": case ".jpeg": return "image/jpeg"; case ".ico": return "image/x-icon"; default: return "application/octet-stream"; } } function isTextExt(ext) { return [".html", ".js", ".json", ".css", ".txt", ".svg"].includes(ext); } function sendFileWithSAN01Replace(res, filePath) { const ext = path.extname(filePath).toLowerCase(); const type = contentTypeByExt(ext); if (isTextExt(ext)) { try { let content = fs.readFileSync(filePath, "utf8"); content = content.replace(/<%=SAN01%>/g, "ismail"); res.writeHead(200, { "Content-Type": type }); res.end(content); return; } catch { // fall through to stream as binary if read fails } } res.writeHead(200, { "Content-Type": type }); fs.createReadStream(filePath).pipe(res); } function notFound(res) { res.writeHead(404, { "Content-Type": "text/plain; charset=utf-8" }); res.end("Not Found"); } const server = http.createServer((req, res) => { try { const url = new URL(req.url, `http://localhost:${PORT}`); const pathname = decodeURIComponent(url.pathname); const rawQuery = req.url.includes("?") ? req.url.split("?")[1] : ""; // Minimal CPL? mapping: /CPL?/CPL/... -> /CPL/... if (pathname === "/CPL" && rawQuery) { const q = decodeURIComponent(rawQuery); if (q.startsWith("/")) { const rel = q.replace(/^\/+/, ""); const target = path.join(ROOT, rel); if (exists(target) && fs.statSync(target).isFile()) { return sendFileWithSAN01Replace(res, target); } if (exists(target) && fs.statSync(target).isDirectory()) { const indexFile = path.join(target, "index.html"); if (exists(indexFile)) return sendFileWithSAN01Replace(res, indexFile); } return notFound(res); } // Non-file commands: just 200 OK res.writeHead(200, { "Content-Type": "text/plain; charset=utf-8" }); res.end("OK"); return; } // Static serving from export let filePath = path.join(ROOT, pathname); if (pathname.endsWith("/")) { filePath = path.join(filePath, "index.html"); if (exists(filePath)) return sendFileWithSAN01Replace(res, filePath); } if (exists(filePath) && fs.statSync(filePath).isFile()) { return sendFileWithSAN01Replace(res, filePath); } const htmlVariant = filePath + ".html"; if (exists(htmlVariant)) { return sendFileWithSAN01Replace(res, htmlVariant); } // Fallback: out/index.html const fallback = path.join(ROOT, "index.html"); if (exists(fallback)) return sendFileWithSAN01Replace(res, fallback); return notFound(res); } catch (err) { res.writeHead(500, { "Content-Type": "text/plain; charset=utf-8" }); res.end( "Internal Server Error\n" + (err && err.stack ? err.stack : String(err)) ); } }); server.listen(PORT, () => { console.log(`Local CPL simulator running on http://localhost:${PORT}`); console.log(`Serving from: ${ROOT}`); console.log("Replacing <%=SAN01%> -> ismail in text responses"); });