Files
at-container-registry/scripts/generate-icons.js

165 lines
6.7 KiB
JavaScript

#!/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 <symbol> 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: '<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"/>'
}
};
// 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(` <symbol id="${iconName}" viewBox="${icon.viewBox}">${icon.content}</symbol>`);
}
}
// Process custom icons
for (const [name, icon] of Object.entries(CUSTOM_ICONS)) {
symbols.push(` <symbol id="${name}" viewBox="${icon.viewBox}">${icon.content}</symbol>`);
}
const sprite = `<svg xmlns="http://www.w3.org/2000/svg" style="display:none">
${symbols.join('\n')}
</svg>`;
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);
}