mockups/server.js
Rama main · réplica de lectura del repositorio original.
#!/usr/bin/env node
// Zero-dependency static server for the mockups. Run: node mockups/server.js [port]
const http = require("node:http");
const fs = require("node:fs");
const path = require("node:path");
const { execFileSync } = require("node:child_process");
const ROOT = __dirname;
const PORT = Number(process.argv[2] || process.env.PORT || 4000);
const CSS_ROUTE = "/assets/tailwind.css";
const CACHE_DIR = path.join(ROOT, ".cache");
const CSS_OUT = path.join(CACHE_DIR, "tailwind.css");
const TYPES = {
".html": "text/html; charset=utf-8",
".css": "text/css; charset=utf-8",
".js": "text/javascript; charset=utf-8",
".svg": "image/svg+xml",
".json": "application/json",
};
// bin/tailwindcss is gitignored, so a linked worktree has none of its own: fall back to the main checkout's.
function findTailwind() {
const candidates = [process.env.TAILWIND, path.join(ROOT, "..", "bin", "tailwindcss")];
try {
const common = execFileSync("git", ["-C", ROOT, "rev-parse", "--path-format=absolute", "--git-common-dir"], {
encoding: "utf8",
}).trim();
candidates.push(path.join(common, "..", "bin", "tailwindcss"));
} catch {
// not in a git checkout; the other candidates still apply
}
return candidates.find((p) => p && fs.existsSync(p));
}
function sources() {
const pages = fs.readdirSync(ROOT).filter((f) => /\.(html|js)$/.test(f) && f !== "server.js");
return [
path.join(ROOT, "input.css"),
path.join(ROOT, "..", "tailwind", "input.css"),
path.join(ROOT, "..", "cmd", "server", "frontend", "static", "code-themes.css"),
].concat(
pages.map((f) => path.join(ROOT, f)),
);
}
function buildCSS() {
const built = fs.existsSync(CSS_OUT) ? fs.statSync(CSS_OUT).mtimeMs : 0;
if (sources().every((f) => fs.statSync(f).mtimeMs <= built)) return;
const bin = findTailwind();
if (!bin) {
throw new Error("Tailwind CLI not found. Run `make setup-tailwind` or set TAILWIND=/path/to/tailwindcss.");
}
fs.mkdirSync(CACHE_DIR, { recursive: true });
execFileSync(bin, ["-i", path.join(ROOT, "input.css"), "-o", CSS_OUT], { stdio: ["ignore", "ignore", "pipe"] });
}
function resolveFile(urlPath) {
const rel = decodeURIComponent(urlPath).replace(/^\/+/, "");
const abs = path.resolve(ROOT, rel || "index.html");
if (abs !== ROOT && !abs.startsWith(ROOT + path.sep)) return null;
if (abs.startsWith(CACHE_DIR)) return null;
for (const candidate of [abs, abs + ".html", path.join(abs, "index.html")]) {
if (fs.existsSync(candidate) && fs.statSync(candidate).isFile()) return candidate;
}
return null;
}
http
.createServer((req, res) => {
const { pathname } = new URL(req.url, "http://localhost");
try {
let file;
if (pathname === CSS_ROUTE) {
buildCSS();
file = CSS_OUT;
} else {
file = resolveFile(pathname);
}
if (!file) {
res.writeHead(404, { "content-type": "text/plain" }).end("Not found");
return;
}
res.writeHead(200, {
"content-type": TYPES[path.extname(file)] || "application/octet-stream",
"cache-control": "no-store",
});
fs.createReadStream(file).pipe(res);
} catch (err) {
res.writeHead(500, { "content-type": "text/plain" }).end(String(err.message || err));
}
})
.listen(PORT, () => console.log(`Mockups: http://localhost:${PORT}`));