Files
blog-manager/lib/files.ts
2026-11-01 13:23:44 -05:00

46 lines
1.5 KiB
TypeScript

import { readFile } from "node:fs/promises";
import path from "node:path";
import { PUBLIC_DIR, REPO_DIR, safeJoin } from "./paths";
/* Read-only file serving from the portfolio checkout, so the preview iframe
* can load the site's real stylesheets and the post's images. */
const TYPES: Record<string, string> = {
".css": "text/css; charset=utf-8",
".png": "image/png",
".jpg": "image/jpeg",
".jpeg": "image/jpeg",
".gif": "image/gif",
".webp": "image/webp",
".svg": "image/svg+xml",
".ico": "image/x-icon",
".mp4": "video/mp4",
};
/** Stylesheets the preview may load, keyed by their /site/... path. */
const SITE_CSS = new Set(["app/globals.css", "styles/markdown-container.css", "styles/blog.css"]);
async function send(file: string): Promise<Response> {
const type = TYPES[path.extname(file).toLowerCase()];
if (!type) return new Response("not found", { status: 404 });
try {
const body = await readFile(file);
return new Response(new Uint8Array(body), {
headers: { "content-type": type, "cache-control": "no-cache" },
});
} catch {
return new Response("not found", { status: 404 });
}
}
/** /site/<css allowlist> -> repo file; /site/<anything else> -> repo public/. */
export function serveSitePath(segments: string[]): Promise<Response> {
const rel = segments.join("/");
if (SITE_CSS.has(rel)) return send(path.join(REPO_DIR, rel));
return send(safeJoin(PUBLIC_DIR, ...segments));
}
export function servePublic(segments: string[]): Promise<Response> {
return send(safeJoin(PUBLIC_DIR, ...segments));
}