mirror of
https://github.com/garrytan/gstack.git
synced 2026-11-01 06:24:41 +01:00
* fix(deps): bump dompurify, @modelcontextprotocol/sdk, zod, body-parser, fast-uri and ip-address to their OSV-fixed releases (#3462) Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com> * build(diagram-render): empty excalidraw's public Firebase config literal in the bundle; declare BUILD_INFO.json's test lane Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com> * test(diagram-render): name the bundle, BUILD_INFO and build script by repo path so the pre-gate sees their lane Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com> * chore(pregate): drop the BUILD_INFO.json lane declaration; the drift test names the file now Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com> * fix: pre-landing review fixes Testing specialist: the drift test asserts the bundled DOMPurify is the override and the bundle carries no Google API key; deps-smoke refuses ip-address below 10.7.4 and checks every bumped package against its OSV-fixed floor in both lockfiles. Adversarial: build.ts fails when any AIza key survives the strip. Outside voice and red team: the CHANGELOG hash and scope lines match the shipped tree. Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com> * docs(diagram-render): document the Firebase-literal strip and the new drift checks Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com> --------- Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
118 lines
4.3 KiB
TypeScript
118 lines
4.3 KiB
TypeScript
/**
|
|
* 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.
|
|
* - `</scri` sequences inside the minified JS MUST be escaped to `<\/scri`,
|
|
* or the inline <script> terminates early ("Unexpected end of input").
|
|
* - A <base href> 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<string, string> = 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("</scri", "<\\/scri");
|
|
|
|
const head = `<!doctype html>
|
|
<html>
|
|
<head>
|
|
<meta charset="utf-8">
|
|
<base href="https://gstack-render.localhost/">
|
|
<title>gstack diagram-render</title>
|
|
<style>
|
|
body { font-family: Helvetica, "Liberation Sans", Arial, sans-serif; margin: 0; }
|
|
</style>
|
|
<script>
|
|
window.__errors = [];
|
|
window.onerror = function (msg, src, line, col, err) {
|
|
window.__errors.push(String(msg) + " @" + line + ":" + col);
|
|
};
|
|
window.addEventListener("unhandledrejection", function (e) {
|
|
window.__errors.push("unhandledrejection: " + String(e.reason).slice(0, 500));
|
|
});
|
|
</script>
|
|
</head>
|
|
<body>
|
|
<div id="status">loading</div>
|
|
<script type="module">
|
|
`;
|
|
const tail = `
|
|
</script>
|
|
</body>
|
|
</html>
|
|
`;
|
|
|
|
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}`);
|