bimr-web/vite.config.ts
2026-09-01 10:37:51 +02:00

118 lines
3.6 KiB
TypeScript

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;
},
},
},
});