import { defineConfig, type Plugin } from "vite"; import wasm from "vite-plugin-wasm"; import topLevelAwait from "vite-plugin-top-level-await"; // Normalise a Forge browse/raw URL to its contents API equivalent. // Returns { apiUrl, accept } or null if no pattern matched. function forgeApiUrl(input: string): { apiUrl: string; accept: string } | null { try { const u = new URL(input); // GitHub: github.com/{owner}/{repo}/blob/{branch}/{filepath} if (u.hostname === "github.com") { const m = u.pathname.match(/^\/([^/]+)\/([^/]+)\/blob\/([^/]+)\/(.+)$/); if (!m) return null; const [, owner, repo, branch, filepath] = m; return { apiUrl: `https://api.github.com/repos/${owner}/${repo}/contents/${filepath}?ref=${encodeURIComponent(branch)}`, accept: "application/vnd.github.v3+json", }; } // Forgejo / Gitea: {forge}/{owner}/{repo}/src/branch/{branch}/{filepath} // {forge}/{owner}/{repo}/raw/branch/{branch}/{filepath} const m = u.pathname.match( /^\/([^/]+)\/([^/]+)\/(?:src|raw)\/branch\/([^/]+)\/(.+)$/, ); if (!m) return null; const [, owner, repo, branch, filepath] = m; return { apiUrl: `${u.origin}/api/v1/repos/${owner}/${repo}/contents/${filepath}?ref=${encodeURIComponent(branch)}`, accept: "application/json", }; } catch { return null; } } function forgeProxy(): Plugin { async function handler(req: any, res: any) { const inputUrl = new URL(req.url, "http://localhost").searchParams.get("url"); if (!inputUrl) { res.statusCode = 400; res.end("missing url parameter"); return; } const parsed = forgeApiUrl(inputUrl); const apiUrl = parsed?.apiUrl ?? inputUrl; const accept = parsed?.accept ?? "application/json"; try { const upstream = await fetch(apiUrl, { headers: { accept }, }); res.setHeader("content-type", "text/plain; charset=utf-8"); if (!upstream.ok) { res.statusCode = upstream.status; res.end(`HTTP ${upstream.status}`); return; } const ct = upstream.headers.get("content-type") || ""; // Forgejo contents API — decode base64 content field if (ct.includes("application/json")) { const json = await upstream.json() as any; if (json.encoding === "base64" && typeof json.content === "string") { // content may have newlines inserted every 60 chars const b64 = json.content.replace(/\s/g, ""); res.statusCode = 200; res.end(Buffer.from(b64, "base64").toString("utf-8")); return; } res.statusCode = 422; res.end("Unexpected API response shape"); return; } // Plain text fallthrough (e.g. already a direct raw URL) if (ct.includes("text/html")) { res.statusCode = 422; res.end("Forge returned HTML — check the URL or authentication"); return; } res.statusCode = 200; res.end(await upstream.text()); } catch (e) { res.statusCode = 502; res.end(String(e)); } } return { name: "forge-proxy", configureServer(server) { server.middlewares.use("/forge-proxy", handler); }, configurePreviewServer(server) { server.middlewares.use("/forge-proxy", handler); }, }; } export default defineConfig({ plugins: [wasm(), topLevelAwait(), forgeProxy()], build: { target: "esnext", rollupOptions: { external: (id) => { // MicroPython is served from public/mp/ — never bundled. if (id.includes("micropython.mjs")) return true; return false; }, }, }, });