// 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 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 = ` Lineage Explorer
`; 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)})`);