// Draws the app's icons, which installing the app or adding it to a phone's home screen shows, // from src/logo.svg on a square tile, through headless Chrome. Run it with // `pnpm --filter @scn-chat/web icons` after changing the logo, and commit what it writes to // public/icons/. `CHROME` names the browser's binary where it isn't in the macOS default place. import { execFileSync } from 'node:child_process' import { mkdtempSync, readFileSync, rmSync, writeFileSync } from 'node:fs' import { tmpdir } from 'node:os' import { join } from 'node:path' import { fileURLToPath } from 'node:url' const browser = process.env.CHROME ?? '/Applications/Google Chrome.app/Contents/MacOS/Google Chrome' /** * The theme's tokens, taken from theme.css so the icons follow its colors. The tile and the logo * are drawn in the light scheme, as the sign-in page shows them there. */ const tokens = Array.from( readFileSync(new URL('../src/styles/theme.css', import.meta.url), 'utf8') .replace(/\/\*[\s\S]*?\*\//g, '') .matchAll(/(--[\w-]+)\s*:\s*([^;{}]+);/g), (match) => `${match[1]}:${match[2]};`, ).join('') type Icon = { file: string size: number /** The logo's width, as a share of the icon's. */ logo: number /** The tile's corner radius, as a share of the icon's size. A platform that rounds it gets 0. */ radius: number } const icons: Icon[] = [ { file: 'icon-192.png', size: 192, logo: 0.68, radius: 0.225 }, { file: 'icon-512.png', size: 512, logo: 0.68, radius: 0.225 }, // A launcher crops a maskable icon to its own shape, keeping only the middle 80% circle. { file: 'icon-maskable-512.png', size: 512, logo: 0.58, radius: 0 }, // iOS rounds the corners itself, and fills any transparency with black. { file: 'apple-touch-icon.png', size: 180, logo: 0.68, radius: 0 }, ] const logo = readFileSync(new URL('../src/logo.svg', import.meta.url), 'utf8') // The logo's own colors follow the system's scheme, which an icon mustn't. .replace(/