A container registry that uses the AT Protocol for manifest storage and S3 for blob storage. atcr.io
docker container atproto go
72
fork

Configure Feed

Select the types of activity you want to include in your feed.

at label-service 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}