Monorepo for Aesthetic.Computer aesthetic.computer
4
fork

Configure Feed

Select the types of activity you want to include in your feed.

lith: proxy /api/pack-html + /api/bundle-html + /api/os to oven

netlify.toml had status=200 rewrites from /api/pack-html →
oven.aesthetic.computer, but production runs on lith now, not
Netlify. Caddy sends /api/* to lith; lith's function resolver had no
pack-html function and returned 404. Broke ableton.mjs offline .amxd
builder, the pack command, and the m4d prompt command.

Mirrors the existing /api/os-image proxy pattern — fetch through,
forward Content-{Type,Disposition,Length}, Cache-Control, ETag, and
oven's x-ac-os-* / x-build / x-patch headers, stream the body.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

+38
+38
lith/server.mjs
··· 782 782 } 783 783 }); 784 784 785 + // --- /api/pack-html, /api/bundle-html, /api/os (proxy to oven) --- 786 + // netlify.toml used status=200 rewrites for these; on lith we must proxy 787 + // explicitly since no `pack-html` / `os` netlify function exists. The 788 + // ableton.mjs offline-amxd builder + `pack` / `m4d` prompt commands all 789 + // depend on pack-html reaching oven's /pack-html?format=m4d endpoint. 790 + async function proxyToOven(ovenPath, req, res) { 791 + try { 792 + const search = new URLSearchParams(req.query || {}).toString(); 793 + const ovenUrl = `https://oven.aesthetic.computer${ovenPath}` + (search ? `?${search}` : ""); 794 + const fwdHeaders = {}; 795 + if (req.headers.authorization) fwdHeaders.Authorization = req.headers.authorization; 796 + if (req.headers.accept) fwdHeaders.Accept = req.headers.accept; 797 + const ovenRes = await fetch(ovenUrl, { method: "GET", headers: fwdHeaders }); 798 + res.status(ovenRes.status); 799 + const forward = [ 800 + "content-type", "content-disposition", "content-length", "cache-control", 801 + "etag", "last-modified", 802 + "x-ac-os-requested-layout", "x-ac-os-layout", 803 + "x-ac-os-fallback", "x-ac-os-fallback-reason", 804 + "x-build", "x-patch", 805 + ]; 806 + for (const h of forward) { 807 + const v = ovenRes.headers.get(h); 808 + if (v) res.set(h, v); 809 + } 810 + res.set("Access-Control-Allow-Origin", "*"); 811 + if (!ovenRes.body) return res.end(); 812 + const { Readable } = await import("stream"); 813 + Readable.fromWeb(ovenRes.body).pipe(res); 814 + } catch (err) { 815 + console.error(`proxyToOven ${ovenPath} error:`, err.message); 816 + if (!res.headersSent) res.status(502).json({ error: `Oven unavailable: ${err.message}` }); 817 + } 818 + } 819 + app.get("/api/pack-html", (req, res) => proxyToOven("/pack-html", req, res)); 820 + app.get("/api/bundle-html", (req, res) => proxyToOven("/bundle-html", req, res)); 821 + app.get("/api/os", (req, res) => proxyToOven("/os", req, res)); 822 + 785 823 // --- /media/* handler (ports Netlify edge function media.js) --- 786 824 app.all("/media/*rest", async (req, res) => { 787 825 const parts = req.path.split("/").filter(Boolean); // ["media", ...]