import fs from 'fs' import path from 'path' function toPascalCase(str: string): string { return str .split(/[-_.]/) .filter((part) => part.length > 0) .map((word) => { if (/^\d/.test(word)) { return word.charAt(0).toUpperCase() + word.slice(1) } return word.charAt(0).toUpperCase() + word.slice(1).toLowerCase() }) .join('') } function generateIconExports(): { imports: string; exports: string } { const packageRoot = path.resolve(__dirname, '..') const iconsDir = path.join(packageRoot, 'icons') if (!fs.existsSync(iconsDir)) { throw new Error(`Icons directory not found: ${iconsDir}`) } const files = fs .readdirSync(iconsDir) .filter((file) => file.endsWith('.svg')) .sort() let imports = '' let exports = '' files.forEach((file) => { const baseName = path.basename(file, '.svg') let pascalName = toPascalCase(baseName) if (pascalName === '') { pascalName = 'Unknown' } if (!pascalName.endsWith('Icon')) { pascalName += 'Icon' } const privateName = `_${pascalName}` imports += `import ${privateName} from './icons/${file}?component'\n` exports += `export const ${pascalName} = ${privateName}\n` }) return { imports, exports } } function runTests(): void { console.log('๐Ÿงช Running conversion tests...\n') const testCases: Array<{ input: string; expected: string }> = [ { input: 'align-left', expected: 'AlignLeftIcon' }, { input: 'arrow-big-up-dash', expected: 'ArrowBigUpDashIcon' }, { input: 'check-check', expected: 'CheckCheckIcon' }, { input: 'chevron-left', expected: 'ChevronLeftIcon' }, { input: 'file-archive', expected: 'FileArchiveIcon' }, { input: 'heart-handshake', expected: 'HeartHandshakeIcon' }, { input: 'monitor-smartphone', expected: 'MonitorSmartphoneIcon' }, { input: 'x-circle', expected: 'XCircleIcon' }, { input: 'rotate-ccw', expected: 'RotateCcwIcon' }, { input: 'bell-ring', expected: 'BellRingIcon' }, { input: 'more-horizontal', expected: 'MoreHorizontalIcon' }, { input: 'list_bulleted', expected: 'ListBulletedIcon' }, { input: 'test.name', expected: 'TestNameIcon' }, { input: 'test-name_final.icon', expected: 'TestNameFinalIcon' }, ] let passed = 0 let failed = 0 testCases.forEach(({ input, expected }) => { const result = toPascalCase(input) + (toPascalCase(input).endsWith('Icon') ? '' : 'Icon') const success = result === expected if (success) { console.log(`โœ… ${input} โ†’ ${result}`) passed++ } else { console.log(`โŒ ${input} โ†’ ${result} (expected: ${expected})`) failed++ } }) console.log(`\n๐Ÿ“Š Test Results: ${passed} passed, ${failed} failed`) if (failed > 0) { process.exit(1) } } function generateFiles(): void { try { console.log('๐Ÿ”„ Generating icon exports...') const { imports, exports } = generateIconExports() const output = `// Auto-generated icon imports and exports // Do not edit this file manually - run 'pnpm run fix' to regenerate ${imports} ${exports}` const packageRoot = path.resolve(__dirname, '..') const outputPath = path.join(packageRoot, 'generated-icons.ts') fs.writeFileSync(outputPath, output) console.log(`โœ… Generated icon exports to: ${outputPath}`) console.log( `๐Ÿ“ฆ Generated ${imports.split('\n').filter((line) => line.trim()).length} icon imports/exports`, ) } catch (error) { console.error('โŒ Error generating icons:', error) process.exit(1) } } function main(): void { const args = process.argv.slice(2) if (args.includes('--test')) { runTests() } else if (args.includes('--validate')) { validateIconConsistency() } else { generateFiles() } } main() function getExpectedIconExports(iconsDir: string): string[] { if (!fs.existsSync(iconsDir)) { return [] } return fs .readdirSync(iconsDir) .filter((file) => file.endsWith('.svg')) .map((file) => { const baseName = path.basename(file, '.svg') let pascalName = toPascalCase(baseName) if (pascalName === '') { pascalName = 'Unknown' } if (!pascalName.endsWith('Icon')) { pascalName += 'Icon' } return pascalName }) .sort() } function getActualIconExports(indexFile: string): string[] { if (!fs.existsSync(indexFile)) { return [] } const content = fs.readFileSync(indexFile, 'utf8') const exportMatches = content.match(/export const (\w+Icon) = _\w+Icon/g) || [] return exportMatches .map((match) => { const result = match.match(/export const (\w+Icon)/) return result ? result[1] : '' }) .filter((name) => name.endsWith('Icon')) .sort() } function validateIconConsistency(): void { try { console.log('๐Ÿ” Validating icon consistency...') const packageRoot = path.resolve(__dirname, '..') const iconsDir = path.join(packageRoot, 'icons') const declarationFile = path.join(packageRoot, 'generated-icons.ts') const expectedExports = getExpectedIconExports(iconsDir) const actualExports = getActualIconExports(declarationFile) const missingExports = expectedExports.filter((name) => !actualExports.includes(name)) const extraExports = actualExports.filter((name) => !expectedExports.includes(name)) if (missingExports.length > 0) { console.error(`โŒ Missing icon exports: ${missingExports.join(', ')}`) console.error("Run 'pnpm run fix' to generate them.") process.exit(1) } if (extraExports.length > 0) { console.error( `โŒ Extra icon exports (no corresponding SVG files): ${extraExports.join(', ')}`, ) console.error("Run 'pnpm run fix' to clean them up.") process.exit(1) } console.log('โœ… Icon exports are consistent with SVG files') } catch (error) { console.error('โŒ Error validating icons:', error) process.exit(1) } }