From 002d74917db3396dc0963a96e407dc280adba436 Mon Sep 17 00:00:00 2001 From: "prompt.ac/@jeffrey" Date: Thu, 21 May 2026 16:46:14 -0400 Subject: [PATCH] lith: public /pop-demos route for the demo page MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit system/netlify/functions/pop-demos.mjs serves pop/demos.html and its ES-module tree (pop/lib, pop/demos, pop/dance/synths) under the public /pop-demos/ path — text assets only, path-traversal refused, serving the live pop/ source on disk (no copies). server.mjs mounts /pop-demos and /pop-demos/*rest via directFn. Live at aesthetic.computer/pop-demos/ on the next lith deploy. Co-Authored-By: Claude Opus 4.7 (1M context) --- lith/server.mjs | 2 ++ system/netlify/functions/pop-demos.mjs | 50 ++++++++++++++++++++++++++ 2 files changed, 52 insertions(+) create mode 100644 system/netlify/functions/pop-demos.mjs diff --git a/lith/server.mjs b/lith/server.mjs index ec909a2a5d..4039848ee9 100644 --- a/lith/server.mjs +++ b/lith/server.mjs @@ -1001,6 +1001,8 @@ app.all("/presigned-download-url/*rest", directFn("presigned-url")); app.all("/docs", directFn("docs")); app.all("/docs.json", directFn("docs")); app.all("/docs/*rest", directFn("docs")); +app.all("/pop-demos", directFn("pop-demos")); +app.all("/pop-demos/*rest", directFn("pop-demos")); app.all("/api-docs", directFn("api-docs")); app.all("/api-docs.json", directFn("api-docs")); app.all("/media-collection", directFn("media-collection")); diff --git a/system/netlify/functions/pop-demos.mjs b/system/netlify/functions/pop-demos.mjs new file mode 100644 index 0000000000..98563e843f --- /dev/null +++ b/system/netlify/functions/pop-demos.mjs @@ -0,0 +1,50 @@ +// pop-demos.mjs — serves the /pop demo page and its ES-module tree at +// the public `/pop-demos/` route on lith. +// +// pop/demos.html imports modules from pop/lib, pop/demos and +// pop/dance/synths. Those all live under pop/ in the repo (deployed to +// lith), so this maps /pop-demos/ → /pop/ for text +// assets only. A bare /pop-demos redirects to /pop-demos/ so the page's +// relative module imports resolve correctly. + +import { readFile } from "node:fs/promises"; +import { dirname, resolve, extname } from "node:path"; +import { fileURLToPath } from "node:url"; + +// system/netlify/functions/ → repo root → pop/ +const POP = resolve(dirname(fileURLToPath(import.meta.url)), "../../../pop"); + +const TYPES = { + ".html": "text/html; charset=utf-8", + ".mjs": "text/javascript; charset=utf-8", + ".js": "text/javascript; charset=utf-8", + ".css": "text/css; charset=utf-8", + ".json": "application/json; charset=utf-8", +}; + +export const handler = async (event) => { + let rel = (event.path || "/pop-demos").replace(/^\/pop-demos/, ""); + + // bare /pop-demos → /pop-demos/ so `./lib/...` imports resolve + if (rel === "") return { statusCode: 302, headers: { Location: "/pop-demos/" }, body: "" }; + if (rel === "/") rel = "/demos.html"; + + const type = TYPES[extname(rel)]; + if (!type) return { statusCode: 404, body: "not found" }; + + // resolve under pop/ and refuse anything that escapes it + const file = resolve(POP, "." + rel); + if (file !== POP && !file.startsWith(POP + "/")) + return { statusCode: 403, body: "forbidden" }; + + try { + const body = await readFile(file, "utf8"); + return { + statusCode: 200, + headers: { "content-type": type, "cache-control": "public, max-age=300" }, + body, + }; + } catch { + return { statusCode: 404, body: "not found: " + rel }; + } +}; -- 2.51.2