A container registry that uses the AT Protocol for manifest storage and S3 for blob storage. atcr.io
docker container atproto go
at main 164 lines 6.9 kB view raw
1#!/usr/bin/env node 2 3/** 4 * Generate SVG sprite sheet from Lucide icons 5 * 6 * Usage: npm run icons:build 7 * 8 * This script auto-discovers icons used in the codebase and generates 9 * an SVG sprite file with <symbol> elements for each icon. 10 */ 11 12const fs = require('fs'); 13const path = require('path'); 14const { globSync } = require('glob'); 15 16/** 17 * Auto-discover icons from the codebase 18 */ 19function discoverIcons() { 20 const icons = new Set(); 21 const basePath = path.join(__dirname, '..'); 22 23 // 1. Scan templates for {{ icon "name" ... }} 24 const templatePattern = /\{\{\s*icon\s+"([^"]+)"/g; 25 const templates = [ 26 ...globSync('pkg/appview/templates/**/*.html', { cwd: basePath }), 27 ...globSync('pkg/hold/admin/templates/**/*.html', { cwd: basePath }), 28 ]; 29 templates.forEach(file => { 30 const content = fs.readFileSync(path.join(basePath, file), 'utf8'); 31 let match; 32 while ((match = templatePattern.exec(content)) !== null) { 33 icons.add(match[1]); 34 } 35 }); 36 37 // 2. Scan templates and JS for icons.svg#name (direct SVG use references) 38 const svgUsePattern = /icons\.svg#([a-z0-9-]+)/g; 39 const allFiles = [ 40 ...globSync('pkg/appview/templates/**/*.html', { cwd: basePath }), 41 ...globSync('pkg/hold/admin/templates/**/*.html', { cwd: basePath }), 42 ...globSync('pkg/appview/src/js/**/*.js', { cwd: basePath }), 43 ...globSync('pkg/hold/admin/src/js/**/*.js', { cwd: basePath }), 44 ]; 45 allFiles.forEach(file => { 46 const content = fs.readFileSync(path.join(basePath, file), 'utf8'); 47 let match; 48 while ((match = svgUsePattern.exec(content)) !== null) { 49 icons.add(match[1]); 50 } 51 }); 52 53 // 3. Scan JS for iconMap object values (theme toggle, etc.) 54 const jsFiles = globSync('pkg/appview/src/js/**/*.js', { cwd: basePath }); 55 const iconMapPattern = /iconMap\s*=\s*\{([^}]+)\}/g; 56 const iconValuePattern = /['"]([a-z][a-z0-9-]*)['"](?:\s*[:,])/g; 57 jsFiles.forEach(file => { 58 const content = fs.readFileSync(path.join(basePath, file), 'utf8'); 59 let mapMatch; 60 while ((mapMatch = iconMapPattern.exec(content)) !== null) { 61 const mapContent = mapMatch[1]; 62 let valueMatch; 63 while ((valueMatch = iconValuePattern.exec(mapContent)) !== null) { 64 // Only add values (after colon), not keys 65 icons.add(valueMatch[1]); 66 } 67 } 68 }); 69 70 return Array.from(icons).sort(); 71} 72 73const ICONS = discoverIcons(); 74 75// Custom Helm icon (from Simple Icons - official Helm logo) 76const CUSTOM_ICONS = { 77 'helm': { 78 viewBox: '0 0 24 24', 79 content: '<path d="M12.337 0c-.475 0-.861 1.016-.861 2.269 0 .527.069 1.011.183 1.396a8.514 8.514 0 0 0-3.961 1.22 5.229 5.229 0 0 0-.595-1.093c-.606-.866-1.34-1.436-1.79-1.43a.381.381 0 0 0-.217.066c-.39.273-.123 1.326.596 2.353.267.381.559.705.84.948a8.683 8.683 0 0 0-1.528 1.716h1.734a7.179 7.179 0 0 1 5.381-2.421 7.18 7.18 0 0 1 5.382 2.42h1.733a8.687 8.687 0 0 0-1.32-1.53c.35-.249.735-.643 1.078-1.133.719-1.027.986-2.08.596-2.353a.382.382 0 0 0-.217-.065c-.45-.007-1.184.563-1.79 1.43a4.897 4.897 0 0 0-.676 1.325 8.52 8.52 0 0 0-3.899-1.42c.12-.39.193-.887.193-1.429 0-1.253-.386-2.269-.862-2.269zM1.624 9.443v5.162h1.358v-1.968h1.64v1.968h1.357V9.443H4.62v1.838H2.98V9.443zm5.912 0v5.162h3.21v-1.108H8.893v-.95h1.64v-1.142h-1.64v-.84h1.853V9.443zm4.698 0v5.162h3.218v-1.362h-1.86v-3.8zm4.706 0v5.162h1.364v-2.643l1.357 1.225 1.35-1.232v2.65h1.365V9.443h-.614l-2.1 1.914-2.109-1.914zm-11.82 7.28a8.688 8.688 0 0 0 1.412 1.548 5.206 5.206 0 0 0-.841.948c-.719 1.027-.985 2.08-.596 2.353.39.273 1.289-.338 2.007-1.364a5.23 5.23 0 0 0 .595-1.092 8.514 8.514 0 0 0 3.961 1.219 5.01 5.01 0 0 0-.183 1.396c0 1.253.386 2.269.861 2.269.476 0 .862-1.016.862-2.269 0-.542-.072-1.04-.193-1.43a8.52 8.52 0 0 0 3.9-1.42c.121.4.352.865.675 1.327.719 1.026 1.617 1.637 2.007 1.364.39-.273.123-1.326-.596-2.353-.343-.49-.727-.885-1.077-1.135a8.69 8.69 0 0 0 1.202-1.36h-1.771a7.174 7.174 0 0 1-5.227 2.252 7.174 7.174 0 0 1-5.226-2.252z" fill="currentColor" stroke="none"/>' 80 } 81}; 82 83// Lucide icon name to Pascal case mapping (for require path) 84function toPascalCase(str) { 85 return str.split('-').map(word => 86 word.charAt(0).toUpperCase() + word.slice(1) 87 ).join(''); 88} 89 90// Get icon content from lucide package 91function getLucideIcon(iconName) { 92 try { 93 const lucide = require('lucide'); 94 const pascalName = toPascalCase(iconName); 95 const iconData = lucide[pascalName]; 96 97 if (!iconData) { 98 console.warn(`Warning: Icon "${iconName}" (${pascalName}) not found in lucide package`); 99 return null; 100 } 101 102 // Lucide exports icons as arrays of [tagName, attrs] tuples 103 // e.g., [ ['path', { d: '...' }], ['circle', { cx: '...', cy: '...', r: '...' }] ] 104 const content = iconData.map(([tag, attrs]) => { 105 const attrStr = Object.entries(attrs) 106 .map(([k, v]) => `${k}="${v}"`) 107 .join(' '); 108 return `<${tag} ${attrStr}/>`; 109 }).join(''); 110 111 return { 112 viewBox: '0 0 24 24', 113 content 114 }; 115 } catch (err) { 116 console.error(`Error loading icon "${iconName}":`, err.message); 117 return null; 118 } 119} 120 121// Generate SVG sprite 122function generateSprite() { 123 const symbols = []; 124 125 // Process Lucide icons (skip custom icons handled below) 126 for (const iconName of ICONS) { 127 if (CUSTOM_ICONS[iconName]) continue; 128 const icon = getLucideIcon(iconName); 129 if (icon) { 130 symbols.push(` <symbol id="${iconName}" viewBox="${icon.viewBox}">${icon.content}</symbol>`); 131 } 132 } 133 134 // Process custom icons 135 for (const [name, icon] of Object.entries(CUSTOM_ICONS)) { 136 symbols.push(` <symbol id="${name}" viewBox="${icon.viewBox}">${icon.content}</symbol>`); 137 } 138 139 const sprite = `<svg xmlns="http://www.w3.org/2000/svg" style="display:none"> 140${symbols.join('\n')} 141</svg>`; 142 143 return sprite; 144} 145 146// Main 147const outputPaths = [ 148 path.join(__dirname, '..', 'pkg', 'appview', 'public', 'icons.svg'), 149 path.join(__dirname, '..', 'pkg', 'hold', 'admin', 'public', 'icons.svg'), 150]; 151 152try { 153 const sprite = generateSprite(); 154 for (const outputPath of outputPaths) { 155 fs.writeFileSync(outputPath, sprite); 156 console.log(`Generated ${outputPath}`); 157 } 158 console.log(`Discovered icons (${ICONS.length}): ${ICONS.join(', ')}`); 159 console.log(`Custom icons (${Object.keys(CUSTOM_ICONS).length}): ${Object.keys(CUSTOM_ICONS).join(', ')}`); 160 console.log(`Total: ${ICONS.length + Object.keys(CUSTOM_ICONS).length} icons`); 161} catch (err) { 162 console.error('Error generating sprite:', err); 163 process.exit(1); 164}