/** * Renders the app icons from one SVG so the set never drifts. * * node apps/web/scripts/generate-icons.mjs * * Chromium comes with Playwright, which the repository already has for the e2e suite, so * this needs no image library. The output is committed: a build must not depend on a * browser being installed. */ import { chromium } from '@playwright/test'; import { mkdir, writeFile } from 'node:fs/promises'; import { dirname, join } from 'node:path'; import { fileURLToPath } from 'node:url'; const PUBLIC_DIR = join(dirname(fileURLToPath(import.meta.url)), '..', 'public'); /** Deep teal, the accent from globals.css, with the check the product is built around. */ const svg = (size, padded) => ` `; const TARGETS = [ { file: 'icon-192.png', size: 192, padded: false }, { file: 'icon-512.png', size: 512, padded: false }, // Maskable icons are cropped to a circle on Android, so the mark sits inside a safe area // and the background reaches every corner. { file: 'icon-maskable-512.png', size: 512, padded: true }, { file: 'apple-touch-icon.png', size: 180, padded: true }, ]; const browser = await chromium.launch(); try { await mkdir(PUBLIC_DIR, { recursive: true }); for (const target of TARGETS) { const page = await browser.newPage({ viewport: { width: target.size, height: target.size }, deviceScaleFactor: 1, }); await page.setContent( `${svg(target.size, target.padded)}`, ); const png = await page.screenshot({ omitBackground: true }); await writeFile(join(PUBLIC_DIR, target.file), png); await page.close(); console.log(`wrote ${target.file}`); } } finally { await browser.close(); }