#!/usr/bin/env node /* Generates extension and toolbar PNG icons from the base SVG with transparent background and configurable stroke colors. Requires ImageMagick (`magick`). Theme defaults (aligned with project palette): Primary 2 (deep): #223544 → add-on icon strokes (default) Primary 1 (light): #5AC3D6 → toolbar icon strokes (default) Override via environment variables (optional): ICON_COLOR_ADDON – hex color for add-on icons (48/96/128). Default: #223544 ICON_COLOR_TOOLBAR – hex color for toolbar icons (16/32). Default: #5AC3D6 Usage: node scripts/build-icons.js */ const fs = require('fs'); const path = require('path'); const { spawnSync } = require('child_process'); const REPO_ROOT = path.join(__dirname, '..'); const ICONS_DIR = path.join(REPO_ROOT, 'icons'); const SRC_SVG = path.join(ICONS_DIR, 'file-search.svg'); const ADDON_SIZES = [48, 96, 128]; const TOOLBAR_SIZES = [16, 32]; const COLOR_ADDON = process.env.ICON_COLOR_ADDON || '#223544'; const COLOR_TOOLBAR = process.env.ICON_COLOR_TOOLBAR || '#5AC3D6'; function which(cmd, args = ['--version']) { try { const res = spawnSync(cmd, args, { stdio: 'ignore' }); return res && res.status === 0; } catch (_) { return false; } } function ensureRasterizer() { // Prefer rsvg-convert (librsvg), then Inkscape, then ImageMagick. if (which('rsvg-convert', ['-v'])) return { kind: 'rsvg', bin: 'rsvg-convert' }; // Inkscape 1.x CLI: --pipe reads from stdin if (which('inkscape', ['--version'])) return { kind: 'inkscape', bin: 'inkscape' }; if (which('magick', ['-version'])) return { kind: 'magick', bin: 'magick' }; if (which('convert', ['-version'])) return { kind: 'magick', bin: 'convert' }; return null; } function readSvg() { if (!fs.existsSync(SRC_SVG)) { console.error(`Base SVG not found: ${path.relative(REPO_ROOT, SRC_SVG)}`); process.exit(1); } return fs.readFileSync(SRC_SVG, 'utf8'); } function colorize(svg, color) { // Replace currentColor with explicit hex color and inject explicit stroke attributes on each path let s = svg.replace(/currentColor/gi, color); // Ensure each has explicit stroke and fill to avoid inheritance issues in some renderers s = s.replace(/]*?)\/>/g, (m, attrs) => { let a = attrs; if (!/\bstroke\s*=/.test(a)) a = ` stroke=\"${color}\"` + a; if (!/\bstroke-width\s*=/.test(a)) a = ` stroke-width=\"1.8\"` + a; if (!/\bstroke-linecap\s*=/.test(a)) a = ` stroke-linecap=\"round\"` + a; if (!/\bstroke-linejoin\s*=/.test(a)) a = ` stroke-linejoin=\"round\"` + a; if (!/\bfill\s*=/.test(a)) a = ` fill=\"none\"` + a; return ``; }); return s; } function rasterize(raster, svgString, size, outPath) { let args; let cmd = raster.bin; if (raster.kind === 'rsvg') { // rsvg-convert reads SVG and outputs PNG; specify size and transparent background args = ['-f', 'png', '-w', String(size), '-h', String(size), '--background-color=transparent', '-o', outPath, '-']; } else if (raster.kind === 'inkscape') { // Inkscape 1.x: --export-type=png --export-filename=out --export-width/height, read from stdin via --pipe args = ['--export-type=png', `--export-filename=${outPath}`, `--export-width=${size}`, `--export-height=${size}`, '--pipe']; } else { // ImageMagick args = ['-background', 'none', '-density', '384', 'svg:-', '-resize', `${size}x${size}`, outPath]; } const proc = spawnSync(cmd, args, { input: svgString, stdio: ['pipe', 'inherit', 'inherit'] }); if (proc.status !== 0) { console.error(`Failed to generate ${outPath} using ${raster.kind} (${cmd}).`); process.exit(proc.status || 1); } } function main() { const raster = ensureRasterizer(); if (!raster) { console.error('No SVG rasterizer found. Install one of: rsvg-convert, inkscape, or ImageMagick (magick/convert).'); process.exit(1); } if (!fs.existsSync(ICONS_DIR)) fs.mkdirSync(ICONS_DIR, { recursive: true }); const base = readSvg(); // Add-on icons const addonSvg = colorize(base, COLOR_ADDON); for (const s of ADDON_SIZES) { const out = path.join(ICONS_DIR, `icon-${s}.png`); rasterize(raster, addonSvg, s, out); console.log(`Generated ${path.relative(REPO_ROOT, out)}`); } // Toolbar icons const toolbarSvg = colorize(base, COLOR_TOOLBAR); for (const s of TOOLBAR_SIZES) { const out = path.join(ICONS_DIR, `icon-${s}.png`); rasterize(raster, toolbarSvg, s, out); console.log(`Generated ${path.relative(REPO_ROOT, out)}`); } } main();