import { mkdirSync, writeFileSync } from 'node:fs'; import { resolve } from 'node:path'; import type { Plugin } from 'vite'; import { defineConfig } from 'vite'; /** * Dev-only: lets the running game POST a rendered frame to disk. * * The game cannot always be looked at — a backgrounded or non-compositing tab * never fires requestAnimationFrame, so nothing draws and nothing can be * screenshotted from outside. With this, plus the `?debug=1` handle in * src/debug.ts, a script can step the simulation, render a frame, and drop the * image somewhere it can actually be opened. * * Never registered in a production build. */ function screenshotEndpoint(): Plugin { const directory = resolve(process.cwd(), 'shots'); return { name: 'dbtl-screenshot', apply: 'serve', configureServer(server) { server.middlewares.use('/__shot', (req, res) => { if (req.method !== 'POST') { res.statusCode = 405; res.end('POST only'); return; } const name = (new URL(req.url ?? '', 'http://x').searchParams.get('name') ?? 'frame') // Keep this from writing outside the shots directory. .replace(/[^a-z0-9_-]/gi, ''); const chunks: Buffer[] = []; req.on('data', (chunk: Buffer) => chunks.push(chunk)); req.on('end', () => { try { mkdirSync(directory, { recursive: true }); const file = resolve(directory, `${name || 'frame'}.jpg`); writeFileSync(file, Buffer.concat(chunks)); res.setHeader('content-type', 'application/json'); res.end(JSON.stringify({ ok: true, file, bytes: Buffer.concat(chunks).length })); } catch (err) { res.statusCode = 500; res.end(String(err)); } }); }); }, }; } export default defineConfig({ plugins: [screenshotEndpoint()], });