#!/usr/bin/env node import { readFile, writeFile } from 'fs/promises'; import { join, dirname } from 'path'; import { fileURLToPath } from 'url'; import sharp from 'sharp'; const __dirname = dirname(fileURLToPath(import.meta.url)); const publicDir = join(__dirname, '..', 'public'); const svgPath = join(publicDir, 'icon.svg'); const sizes = [ { size: 192, name: 'icon-192x192.png', maskable: false }, { size: 512, name: 'icon-512x512.png', maskable: false }, { size: 192, name: 'icon-192x192-maskable.png', maskable: true }, { size: 512, name: 'icon-512x512-maskable.png', maskable: true }, ]; async function generateIcons() { console.log('Reading SVG icon...'); const svgBuffer = await readFile(svgPath); for (const { size, name, maskable } of sizes) { console.log(`Generating ${name}...`); let buffer; if (maskable) { // Maskable icons need safe zone padding (80% of icon in center) // Create a transparent canvas with padding const paddedSize = size; const iconSize = Math.floor(size * 0.8); const offset = Math.floor((paddedSize - iconSize) / 2); // Resize SVG to icon size const iconBuffer = await sharp(svgBuffer) .resize(iconSize, iconSize) .png() .toBuffer(); // Create transparent background and composite buffer = await sharp({ create: { width: paddedSize, height: paddedSize, channels: 4, background: { r: 0, g: 0, b: 0, alpha: 0 } } }) .composite([{ input: iconBuffer, top: offset, left: offset }]) .png() .toBuffer(); } else { // Regular icon - full size buffer = await sharp(svgBuffer) .resize(size, size) .png() .toBuffer(); } await writeFile(join(publicDir, name), buffer); console.log(`āœ“ ${name}`); } console.log('\nāœ… All icons generated successfully!'); } generateIcons().catch(console.error);