#!/usr/bin/env node /** * Generate SVG sprite sheet from Lucide icons * * Usage: npm run icons:build * * This script auto-discovers icons used in the codebase and generates * an SVG sprite file with elements for each icon. */ const fs = require('fs'); const path = require('path'); const { globSync } = require('glob'); /** * Auto-discover icons from the codebase */ function discoverIcons() { const icons = new Set(); const basePath = path.join(__dirname, '..'); // 1. Scan templates for {{ icon "name" ... }} const templatePattern = /\{\{\s*icon\s+"([^"]+)"/g; const templates = [ ...globSync('pkg/appview/templates/**/*.html', { cwd: basePath }), ...globSync('pkg/hold/admin/templates/**/*.html', { cwd: basePath }), ]; templates.forEach(file => { const content = fs.readFileSync(path.join(basePath, file), 'utf8'); let match; while ((match = templatePattern.exec(content)) !== null) { icons.add(match[1]); } }); // 2. Scan templates and JS for icons.svg#name (direct SVG use references) const svgUsePattern = /icons\.svg#([a-z0-9-]+)/g; const allFiles = [ ...globSync('pkg/appview/templates/**/*.html', { cwd: basePath }), ...globSync('pkg/hold/admin/templates/**/*.html', { cwd: basePath }), ...globSync('pkg/appview/src/js/**/*.js', { cwd: basePath }), ...globSync('pkg/hold/admin/src/js/**/*.js', { cwd: basePath }), ]; allFiles.forEach(file => { const content = fs.readFileSync(path.join(basePath, file), 'utf8'); let match; while ((match = svgUsePattern.exec(content)) !== null) { icons.add(match[1]); } }); // 3. Scan JS for iconMap object values (theme toggle, etc.) const jsFiles = globSync('pkg/appview/src/js/**/*.js', { cwd: basePath }); const iconMapPattern = /iconMap\s*=\s*\{([^}]+)\}/g; const iconValuePattern = /['"]([a-z][a-z0-9-]*)['"](?:\s*[:,])/g; jsFiles.forEach(file => { const content = fs.readFileSync(path.join(basePath, file), 'utf8'); let mapMatch; while ((mapMatch = iconMapPattern.exec(content)) !== null) { const mapContent = mapMatch[1]; let valueMatch; while ((valueMatch = iconValuePattern.exec(mapContent)) !== null) { // Only add values (after colon), not keys icons.add(valueMatch[1]); } } }); return Array.from(icons).sort(); } const ICONS = discoverIcons(); // Custom Helm icon (from Simple Icons - official Helm logo) const CUSTOM_ICONS = { 'helm': { viewBox: '0 0 24 24', content: '' } }; // Lucide icon name to Pascal case mapping (for require path) function toPascalCase(str) { return str.split('-').map(word => word.charAt(0).toUpperCase() + word.slice(1) ).join(''); } // Get icon content from lucide package function getLucideIcon(iconName) { try { const lucide = require('lucide'); const pascalName = toPascalCase(iconName); const iconData = lucide[pascalName]; if (!iconData) { console.warn(`Warning: Icon "${iconName}" (${pascalName}) not found in lucide package`); return null; } // Lucide exports icons as arrays of [tagName, attrs] tuples // e.g., [ ['path', { d: '...' }], ['circle', { cx: '...', cy: '...', r: '...' }] ] const content = iconData.map(([tag, attrs]) => { const attrStr = Object.entries(attrs) .map(([k, v]) => `${k}="${v}"`) .join(' '); return `<${tag} ${attrStr}/>`; }).join(''); return { viewBox: '0 0 24 24', content }; } catch (err) { console.error(`Error loading icon "${iconName}":`, err.message); return null; } } // Generate SVG sprite function generateSprite() { const symbols = []; // Process Lucide icons (skip custom icons handled below) for (const iconName of ICONS) { if (CUSTOM_ICONS[iconName]) continue; const icon = getLucideIcon(iconName); if (icon) { symbols.push(` ${icon.content}`); } } // Process custom icons for (const [name, icon] of Object.entries(CUSTOM_ICONS)) { symbols.push(` ${icon.content}`); } const sprite = ` ${symbols.join('\n')} `; return sprite; } // Main const outputPaths = [ path.join(__dirname, '..', 'pkg', 'appview', 'public', 'icons.svg'), path.join(__dirname, '..', 'pkg', 'hold', 'admin', 'public', 'icons.svg'), ]; try { const sprite = generateSprite(); for (const outputPath of outputPaths) { fs.writeFileSync(outputPath, sprite); console.log(`Generated ${outputPath}`); } console.log(`Discovered icons (${ICONS.length}): ${ICONS.join(', ')}`); console.log(`Custom icons (${Object.keys(CUSTOM_ICONS).length}): ${Object.keys(CUSTOM_ICONS).join(', ')}`); console.log(`Total: ${ICONS.length + Object.keys(CUSTOM_ICONS).length} icons`); } catch (err) { console.error('Error generating sprite:', err); process.exit(1); }