import { readFileSync, writeFileSync, readdirSync, existsSync } from 'fs'; import { join } from 'path'; const ICONS_SOURCE = join(process.cwd(), 'static/lucide/icons'); const ICONS_TARGET = join(process.cwd(), 'src/uix/air/icons/lib'); const INDEX_FILE = join(process.cwd(), 'src/uix/air/icons/sound.svelte.ts'); function getExistingIcons() { const files = readdirSync(ICONS_TARGET); return new Set(files.map((f) => f.replace('.svelte', ''))); } function parseSvgContent(svgContent) { // Extract all child elements (path, circle, rect, polyline, line, polygon, ellipse) const elements = []; const patterns = [ /]*)>/g, /]*)>/g, /]*)>/g, /]*)>/g, /]*)>/g, /]*)>/g, /]*)>/g ]; for (const pattern of patterns) { let match; while ((match = pattern.exec(svgContent)) !== null) { const tagName = pattern.source.replace('\\s+([^>]*)>', '').replace('<', '').replace('\\', ''); const attrs = match[1]; elements.push(`<${tagName} ${attrs}>`); } } return elements.join('\n\t'); } function toPascalCase(str) { return str .split(/[-_]/) .map((word) => word.charAt(0).toUpperCase() + word.slice(1)) .join(''); } function convertIcon(iconName) { const svgPath = join(ICONS_SOURCE, `${iconName}.svg`); if (!existsSync(svgPath)) { return null; } const svgContent = readFileSync(svgPath, 'utf-8'); const innerContent = parseSvgContent(svgContent); // Generate Svelte component const component = ` ${innerContent} `; return component; } function updateIndex(icons) { const existingIndex = readFileSync(INDEX_FILE, 'utf-8'); const lines = existingIndex.split('\n'); // Find the last export line (before export type) const lastExportLine = lines.findIndex((l) => l.startsWith('export type')); // Collect existing exports const existingExports = new Set(); lines.forEach((line) => { const match = line.match(/export \{ default as (\w+) \}/); if (match) existingExports.add(match[1]); }); // Add new exports const newExports = []; for (const icon of icons) { const pascalName = toPascalCase(icon); if (!existingExports.has(pascalName)) { newExports.push(`export { default as ${pascalName} } from './lib/${icon}.svelte';`); existingExports.add(pascalName); } } if (newExports.length === 0) return; // Insert new exports before the export type line lines.splice(lastExportLine, 0, ...newExports); // Sort exports (keep TooltipContext first, then alphabetically) const rootLine = lines[0]; const otherExports = lines.slice(1, lastExportLine + newExports.length); const typeLine = lines[lastExportLine + newExports.length]; otherExports.sort(); const newContent = [ rootLine, ...otherExports, typeLine, ...lines.slice(lastExportLine + newExports.length + 1) ].join('\n'); writeFileSync(INDEX_FILE, newContent); } function main() { const existingIcons = getExistingIcons(); const allSvgFiles = readdirSync(ICONS_SOURCE).filter((f) => f.endsWith('.svg')); const missingIcons = allSvgFiles .map((f) => f.replace('.svg', '')) .filter((name) => !existingIcons.has(name)); console.log(`Found ${missingIcons.length} icons to convert...`); if (missingIcons.length === 0) { console.log('No new icons to convert!'); return; } // Limit to first 100 for testing const toConvert = process.argv[2] === 'all' ? missingIcons : missingIcons.slice(0, 100); console.log(`Converting ${toConvert.length} icons...`); const converted = []; for (const iconName of toConvert) { const component = convertIcon(iconName); if (component) { const targetPath = join(ICONS_TARGET, `${iconName}.svelte`); writeFileSync(targetPath, component); converted.push(iconName); console.log(`✓ ${iconName}`); } } updateIndex(converted); console.log(`\nDone! Converted ${converted.length} icons.`); } main();