/** * Rasterizes src/icon.svg into the PNG sizes Chrome's install prompt wants. * * bun scripts/make-icons.ts * * The output is COMMITTED rather than produced by build.ts, because this needs * a Chrome binary on PATH and the build must not. Re-run it whenever * src/icon.svg changes; src/icons.test.ts checks the committed files are real * images of the right dimensions, but it cannot tell you they are STALE. * * Chrome is the rasterizer because it is the only one present in this * environment — no ImageMagick, no rsvg-convert, no cairosvg. It renders the * SVG in a page sized exactly to the target, on a transparent backdrop, so the * icon's own rounded corners stay transparent. */ import { rm, mkdtemp } from "node:fs/promises"; import { tmpdir } from "node:os"; import path from "node:path"; const SIZES = [192, 512]; const CHROME = process.env.CHROME ?? "google-chrome"; /** Chrome has been seen not to exit on its own here, so never wait forever. */ const TIMEOUT_MS = 60_000; const work = await mkdtemp(path.join(tmpdir(), "budget-icons-")); try { await Bun.write(path.join(work, "icon.svg"), Bun.file("src/icon.svg")); await Bun.write( path.join(work, "wrap.html"), ` `, ); for (const size of SIZES) { const out = path.join(work, `icon-${size}.png`); const chrome = Bun.spawn( [ CHROME, "--headless", "--disable-gpu", "--no-sandbox", "--no-first-run", "--hide-scrollbars", "--force-device-scale-factor=1", "--default-background-color=00000000", `--screenshot=${out}`, `--window-size=${size},${size}`, `file://${path.join(work, "wrap.html")}`, ], { stdout: "ignore", stderr: "ignore" }, ); const timer = setTimeout(() => chrome.kill(), TIMEOUT_MS); await chrome.exited; clearTimeout(timer); if (!(await Bun.file(out).exists())) { throw new Error(`${CHROME} wrote no screenshot for ${size}px`); } const bytes = new Uint8Array(await Bun.file(out).arrayBuffer()); const view = new DataView(bytes.buffer); const width = view.getUint32(16); const height = view.getUint32(20); if (width !== size || height !== size) { throw new Error(`Expected ${size}x${size}, got ${width}x${height}`); } const dest = `src/icon-${size}.png`; await Bun.write(dest, bytes); console.log(`${dest} ${width}x${height} ${bytes.length} bytes`); } } finally { await rm(work, { recursive: true, force: true }); }