// bun preview/shot.ts [width] [height] // Headless Chrome over CDP: the preview server must be running. const [out, url, w = "340", h = "720"] = process.argv.slice(2); const port = 9334; const proc = Bun.spawn( [ "/Applications/Google Chrome.app/Contents/MacOS/Google Chrome", "--headless=new", "--disable-gpu", "--hide-scrollbars", `--remote-debugging-port=${port}`, `--window-size=${w},${h}`, "--user-data-dir=/tmp/beaver-preview-chrome", "about:blank", ], { stderr: "ignore", stdout: "ignore" } ); const wait = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms)); let targets: { type: string; webSocketDebuggerUrl: string }[] = []; for (let i = 0; i < 40; i += 1) { try { // biome-ignore lint/performance/noAwaitInLoops: polling until Chrome is up targets = await (await fetch(`http://127.0.0.1:${port}/json`)).json(); if (targets.length) { break; } } catch { /* not up yet */ } await wait(250); } const page = targets.find((t) => t.type === "page"); if (!page) { proc.kill(); throw new Error("no page target"); } const ws = new WebSocket(page.webSocketDebuggerUrl); await new Promise((resolve) => { ws.onopen = resolve; }); let id = 0; const pending = new Map void>(); ws.onmessage = (m) => { const d = JSON.parse(String(m.data)); if (d.id && pending.has(d.id)) { pending.get(d.id)?.(d); pending.delete(d.id); } }; const send = (method: string, params: Record = {}) => new Promise<{ result?: { data?: string } }>((resolve) => { id += 1; pending.set(id, resolve); ws.send(JSON.stringify({ id, method, params })); }); await send("Emulation.setDeviceMetricsOverride", { deviceScaleFactor: 2, height: Number(h), mobile: Number(w) < 500, width: Number(w), }); await send("Page.enable"); await send("Page.navigate", { url }); await wait(2500); if (process.env.EVAL) { const evaluated = await send("Runtime.evaluate", { expression: process.env.EVAL, returnByValue: true, }); console.log( JSON.stringify( (evaluated.result as { result?: { value?: unknown } }).result?.value ) ); await wait(400); } const shot = await send("Page.captureScreenshot", { format: "png" }); await Bun.write(out, Buffer.from(shot.result?.data ?? "", "base64")); console.log(`wrote ${out}`); ws.close(); proc.kill();