lineage-explorer / legacy /bundle.mjs
yurko
Claude Opus 5.5
Bundle legacy explorer into one self-contained legacy/index.html
e9b01c7
Raw History Blame Contribute Delete
3.95 kB
// Bundles the prebuilt legacy explorer (EQTYLab/lineage-explorer `static/`) into one
// self-contained legacy/index.html: JS, CSS, fonts and images all inline.
//
// Why: in a private Space, only the first request carries the `__sign` token; later
// requests from the embedded frame get no cookie (SameSite=Lax, cross-site) and 401.
// One file = one request, which index.html makes with the token.
//
// Usage: node legacy/bundle.mjs [path/to/lineage-explorer/static]
import { build } from "esbuild";
import { readFileSync, writeFileSync, readdirSync, mkdtempSync, cpSync } from "node:fs";
import { join, extname, dirname } from "node:path";
import { tmpdir } from "node:os";
import { fileURLToPath } from "node:url";
const here = dirname(fileURLToPath(import.meta.url));
const src = process.argv[2] || join(here, "../../lineage-explorer/static");
const MIME = { ".svg": "image/svg+xml", ".png": "image/png", ".woff2": "font/woff2", ".woff": "font/woff", ".ttf": "font/ttf" };
const dataUri = (file) => `data:${MIME[extname(file)]};base64,${readFileSync(file).toString("base64")}`;
const work = mkdtempSync(join(tmpdir(), "legacy-"));
cpSync(src, work, { recursive: true });
const assets = join(work, "assets");
const html = readFileSync(join(work, "index.html"), "utf8");
const entry = html.match(/src="\/assets\/([^"]+\.js)"/)[1];
const entryCss = html.match(/href="\/assets\/([^"]+\.css)"/)[1];
// Vite's preload helper injects <link> tags for each lazy chunk's deps; everything is
// inline now, so make it just run the import.
const entryPath = join(assets, entry);
const entrySrc = readFileSync(entryPath, "utf8");
const preload = "if(!n||n.length===0)return t();";
if (!entrySrc.includes(preload)) throw new Error("vite preload helper not found");
writeFileSync(entryPath, entrySrc.replace(preload, "return t();"));
// Absolute asset paths (/assets/x, /eci.svg, ...) -> data: URIs
const inlinePaths = (text, quote) =>
text.replace(new RegExp(`${quote}/((?:assets/)?[\\w.-]+\\.(?:svg|png|woff2?|ttf))${quote}`, "g"),
(m, p) => `${quote}${dataUri(join(work, p))}${quote}`);
const out = await build({
entryPoints: [entryPath], bundle: true, format: "esm", write: false, minify: true,
logLevel: "warning", legalComments: "none",
plugins: [{
name: "inline-assets",
setup(b) {
b.onLoad({ filter: /\.js$/ }, (a) => ({ contents: inlinePaths(readFileSync(a.path, "utf8"), '"'), loader: "js" }));
},
}],
});
const js = out.outputFiles[0].text;
// Entry CSS first, then the lazy chunks' CSS (the order Vite would have loaded them in)
const css = [entryCss, ...readdirSync(assets).filter((f) => f.endsWith(".css") && f !== entryCss).sort()]
.map((f) => readFileSync(join(assets, f), "utf8").replace(/url\(\/assets\/([^)]+)\)/g, (m, p) => `url(${dataUri(join(assets, p))})`))
.join("\n");
const page = `<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Lineage Explorer</title>
<!--
LEGACY explorer: prebuilt EQTYLab/lineage-explorer app, for repos with an .integrity/ folder.
Opened in an iframe by the 2026 explorer (/index.html). Generated by legacy/bundle.mjs; don't edit.
-->
<script>
// Its router expects /explorer/lineage/... paths, which a static host can't serve, so
// rewrite the URL in place before the app boots: ?model=owner/name
(function () {
var model = new URLSearchParams(location.search).get("model");
history.replaceState(null, "", model ? "/explorer/lineage/" + model : "/");
})();
</script>
<style>${css}</style>
</head>
<body>
<div id="root"></div>
<script type="module">${js.replace(/<\/script/gi, "<\\/script")}</script>
</body>
</html>
`;
writeFileSync(join(here, "index.html"), page);
console.log(`legacy/index.html: ${(page.length / 1e6).toFixed(2)} MB (js ${(js.length / 1e6).toFixed(2)}, css ${(css.length / 1e6).toFixed(2)})`);