#!/usr/bin/env node /** * Generate app icons from a single SVG source. * * Usage: * npm i -D sharp * node scripts/generate-icons.mjs * * Requires: public/icons/source.svg * Outputs into: public/icons/ */ import { readFile, writeFile, mkdir } from "node:fs/promises"; import { fileURLToPath } from "node:url"; import { dirname, join } from "node:path"; import sharp from "sharp"; const here = dirname(fileURLToPath(import.meta.url)); const root = join(here, ".."); const iconsDir = join(root, "public", "icons"); const SOURCE = join(iconsDir, "source.svg"); const TARGETS = [ { out: "icon-192.png", size: 192, padding: 0 }, { out: "icon-512.png", size: 512, padding: 0 }, { out: "icon-maskable-512.png", size: 512, padding: 64 }, // safe zone { out: "apple-touch-icon.png", size: 180, padding: 0 }, ]; async function main() { await mkdir(iconsDir, { recursive: true }); const svg = await readFile(SOURCE); for (const t of TARGETS) { const inner = t.size - t.padding * 2; const buf = await sharp(svg, { density: 384 }) .resize(inner, inner, { fit: "contain", background: { r: 0, g: 0, b: 0, alpha: 0 } }) .extend({ top: t.padding, bottom: t.padding, left: t.padding, right: t.padding, background: { r: 0, g: 0, b: 0, alpha: 0 }, }) .png({ compressionLevel: 9 }) .toBuffer(); await writeFile(join(iconsDir, t.out), buf); console.log(`wrote ${t.out} (${t.size}x${t.size})`); } // simple favicon — 32x32 png renamed .ico works in modern browsers const faviconBuf = await sharp(svg, { density: 192 }) .resize(32, 32, { fit: "contain", background: { r: 0, g: 0, b: 0, alpha: 0 } }) .png() .toBuffer(); await writeFile(join(iconsDir, "favicon.ico"), faviconBuf); console.log("wrote favicon.ico (32x32 png)"); } main().catch((err) => { console.error(err); process.exit(1); });