/** * Build dist/diagram-render.html — the single-file offline render page. * * One command updates everything: `bun run build` (in this directory) or * `bun run build:diagram-render` (repo root). To bump a dependency: edit the * exact pin in package.json, `bun install`, rebuild, commit src + dist + * BUILD_INFO.json together. The drift test (test/diagram-render-drift.test.ts) * fails CI when dist and BUILD_INFO disagree. * * Page assembly notes (learned in the spike, do not "simplify" away): * - The script MUST be `type="module"` — mermaid's bundle contains * import.meta, which throws in a classic script. * - ` terminates early ("Unexpected end of input"). * - A with an absolute URL is required: the page lives at * about:blank (page.setContent), where relative URL construction throws. */ import { createHash } from "node:crypto"; import path from "node:path"; const ROOT = path.resolve(import.meta.dir, ".."); const ENTRY = path.join(ROOT, "src", "entry.ts"); const DIST_DIR = path.join(ROOT, "dist"); const DIST_HTML = path.join(DIST_DIR, "diagram-render.html"); const BUILD_INFO = path.join(DIST_DIR, "BUILD_INFO.json"); const pkg = await Bun.file(path.join(ROOT, "package.json")).json(); const deps: Record = pkg.dependencies; const result = await Bun.build({ entrypoints: [ENTRY], target: "browser", minify: true, define: { __BUNDLE_INFO_DEPS__: JSON.stringify(deps), "process.env.NODE_ENV": '"production"', }, }); if (!result.success) { for (const log of result.logs) console.error(log); process.exit(1); } const js = await result.outputs[0].text(); // @excalidraw/excalidraw's npm bundle carries excalidraw.com's public Firebase // client config (an `AIza…` key) in an env literal nothing in the package // reads. The offline page never opens a collab session, and a key-shaped // literal in a committed file fails the pre-gate secret scan on every rebuild, // so the literal is emptied. Exactly one match is expected: a bump that changes // the shape must fail here rather than put the key back silently. const FIREBASE_CONFIG = /VITE_APP_FIREBASE_CONFIG:'\{[^']*\}'/g; const firebaseHits = js.match(FIREBASE_CONFIG)?.length ?? 0; if (firebaseHits !== 1) { console.error(`expected exactly one VITE_APP_FIREBASE_CONFIG literal in the excalidraw bundle, found ${firebaseHits}; update the strip rule in scripts/build.ts`); process.exit(1); } const strippedJs = js.replace(FIREBASE_CONFIG, "VITE_APP_FIREBASE_CONFIG:'{}'"); if (/AIza[0-9A-Za-z_-]{35}/.test(strippedJs)) { console.error("a Google API key literal survived the strip; update the strip rule in scripts/build.ts"); process.exit(1); } // Escape inline-script terminators (see header note). const inlineJs = strippedJs.replaceAll(" gstack diagram-render
loading
`; const html = head + inlineJs + tail; await Bun.write(DIST_HTML, html); const sha256 = createHash("sha256").update(html).digest("hex"); // Source fingerprint: lets the drift test catch "edited src, forgot to // rebuild dist" WITHOUT needing node_modules for a full rebuild (the deep // rebuild check only runs where deps are installed). const srcSha256 = createHash("sha256") .update(await Bun.file(ENTRY).text()) .update(await Bun.file(import.meta.path).text()) .digest("hex"); const info = { name: "gstack-diagram-render", sha256, srcSha256, bytes: Buffer.byteLength(html), bunVersion: Bun.version, deps, }; await Bun.write(BUILD_INFO, JSON.stringify(info, null, 2) + "\n"); console.log(`built ${path.relative(process.cwd(), DIST_HTML)} (${(info.bytes / 1024 / 1024).toFixed(2)} MB)`); console.log(`sha256 ${sha256}`);