Primera versión del plugin

This commit is contained in:
2025-12-14 23:23:56 -05:00
commit d215f498b9
37 changed files with 670696 additions and 0 deletions

886
scripts/extract-metadata.js Normal file
View File

@@ -0,0 +1,886 @@
#!/usr/bin/env node
const fs = require('fs');
const path = require('path');
// Configuración
const CONFIG = {
// Rutas de entrada (FontAwesome Pro)
FA_METADATA: path.join(__dirname, '..', 'assets', 'fontawesome', 'metadata', 'icons.json'),
FA_CATEGORIES: path.join(__dirname, '..', 'assets', 'fontawesome', 'metadata', 'categories.yml'),
// Rutas de salida
OUTPUT_DIR: path.join(__dirname, '..', 'assets', 'processed'),
OUTPUT_METADATA: path.join(__dirname, '..', 'assets', 'processed', 'metadata.json'),
OUTPUT_STYLES: path.join(__dirname, '..', 'assets', 'processed', 'styles.json'),
OUTPUT_CATEGORIES: path.join(__dirname, '..', 'assets', 'processed', 'categories.json'),
// Configuración de procesamiento
MIN_ICONS_TO_PROCESS: 100,
VERSION: '2.0.0'
};
// Colores para consola
const colors = {
reset: '\x1b[0m',
green: '\x1b[32m',
yellow: '\x1b[33m',
blue: '\x1b[34m',
red: '\x1b[31m',
magenta: '\x1b[35m',
cyan: '\x1b[36m'
};
// Banner
console.log(colors.cyan + '='.repeat(70));
console.log(' EXTRACTOR DE METADATA FONTAWESOME PRO');
console.log(' Versión: ' + CONFIG.VERSION);
console.log('='.repeat(70) + colors.reset + '\n');
/**
* Crear directorios necesarios
*/
function createDirectories() {
console.log(colors.blue + '📁 Creando directorios...' + colors.reset);
const dirs = [
CONFIG.OUTPUT_DIR,
path.join(CONFIG.OUTPUT_DIR, 'backup'),
path.join(CONFIG.OUTPUT_DIR, 'logs')
];
dirs.forEach(dir => {
if (!fs.existsSync(dir)) {
fs.mkdirSync(dir, { recursive: true });
console.log(` ✅ Creado: ${path.relative(process.cwd(), dir)}`);
} else {
console.log(` 📁 Existe: ${path.relative(process.cwd(), dir)}`);
}
});
console.log('');
}
/**
* Verificar archivos de entrada
*/
function checkInputFiles() {
console.log(colors.blue + '🔍 Verificando archivos de entrada...' + colors.reset);
const files = [
{ path: CONFIG.FA_METADATA, name: 'icons.json', required: true },
{ path: CONFIG.FA_CATEGORIES, name: 'categories.yml', required: false }
];
let allFound = true;
files.forEach(file => {
if (fs.existsSync(file.path)) {
const stats = fs.statSync(file.path);
console.log(`${file.name}: ${(stats.size / 1024).toFixed(2)} KB`);
if (file.name === 'icons.json') {
try {
const content = fs.readFileSync(file.path, 'utf8');
const data = JSON.parse(content);
console.log(` Íconos en metadata: ${Object.keys(data).length}`);
} catch (error) {
console.log(`${file.name}: Error de formato - ${error.message}`);
allFound = false;
}
}
} else {
if (file.required) {
console.log(`${file.name}: NO ENCONTRADO (requerido)`);
allFound = false;
} else {
console.log(` ⚠️ ${file.name}: No encontrado (opcional)`);
}
}
});
console.log('');
return allFound;
}
/**
* Hacer backup de archivos existentes
*/
function backupExistingFiles() {
console.log(colors.blue + '💾 Creando respaldo de archivos existentes...' + colors.reset);
const filesToBackup = [
CONFIG.OUTPUT_METADATA,
CONFIG.OUTPUT_STYLES,
CONFIG.OUTPUT_CATEGORIES
];
const backupDir = path.join(CONFIG.OUTPUT_DIR, 'backup', new Date().toISOString().replace(/[:.]/g, '-'));
if (!fs.existsSync(backupDir)) {
fs.mkdirSync(backupDir, { recursive: true });
}
let backedUpCount = 0;
filesToBackup.forEach(file => {
if (fs.existsSync(file)) {
const fileName = path.basename(file);
const backupPath = path.join(backupDir, fileName);
try {
fs.copyFileSync(file, backupPath);
console.log(` ✅ Respaldo: ${fileName}`);
backedUpCount++;
} catch (error) {
console.log(` ❌ Error respaldando ${fileName}: ${error.message}`);
}
}
});
if (backedUpCount > 0) {
console.log(` 📊 Total respaldados: ${backedUpCount} archivos`);
console.log(` 📍 Ubicación: ${path.relative(process.cwd(), backupDir)}`);
} else {
console.log(' ⚠️ No hay archivos existentes para respaldar');
}
console.log('');
return backupDir;
}
/**
* Extraer y procesar metadata de íconos
*/
function extractIconMetadata() {
console.log(colors.blue + '📊 Extrayendo metadata de íconos...' + colors.reset);
try {
// Leer archivo original
const content = fs.readFileSync(CONFIG.FA_METADATA, 'utf8');
const rawMetadata = JSON.parse(content);
const totalIcons = Object.keys(rawMetadata).length;
console.log(` 📈 Total íconos encontrados: ${totalIcons}`);
if (totalIcons < CONFIG.MIN_ICONS_TO_PROCESS) {
throw new Error(`Muy pocos íconos (${totalIcons}). ¿Metadata corrupto?`);
}
// Procesar cada ícono
const processedIcons = {};
let processedCount = 0;
let stylesFound = new Set();
let categoriesFound = new Set();
Object.entries(rawMetadata).forEach(([iconName, iconData]) => {
processedCount++;
// Mostrar progreso cada 1000 íconos
if (processedCount % 1000 === 0) {
console.log(` ⏳ Procesados: ${processedCount}/${totalIcons} íconos...`);
}
// Extraer información básica
const processedIcon = {
id: iconName,
name: iconName,
label: iconData.label || formatIconName(iconName),
unicode: iconData.unicode || '',
version: iconData.version || '6.0.0',
search: iconData.search || {}
};
// Estilos disponibles
if (iconData.styles && Array.isArray(iconData.styles)) {
processedIcon.styles = iconData.styles.map(style => style.toLowerCase());
iconData.styles.forEach(style => stylesFound.add(style.toLowerCase()));
} else {
processedIcon.styles = [];
}
// Categorías
if (iconData.categories && Array.isArray(iconData.categories)) {
processedIcon.categories = iconData.categories;
iconData.categories.forEach(cat => categoriesFound.add(cat));
} else {
processedIcon.categories = ['uncategorized'];
categoriesFound.add('uncategorized');
}
// Información adicional
if (iconData.svg) {
processedIcon.svg = {
width: iconData.svg.width,
height: iconData.svg.height,
path: iconData.svg.path
};
}
// Términos de búsqueda mejorados
const searchTerms = new Set();
// Términos originales
if (iconData.search && iconData.search.terms) {
iconData.search.terms.forEach(term => {
if (term && typeof term === 'string') {
searchTerms.add(term.toLowerCase());
}
});
}
// Agregar nombre y label
searchTerms.add(iconName.toLowerCase());
if (iconData.label) {
searchTerms.add(iconData.label.toLowerCase());
// Variantes sin espacios
searchTerms.add(iconData.label.toLowerCase().replace(/\s+/g, '-'));
}
// Agregar sinónimos comunes
addCommonSynonyms(iconName, searchTerms);
processedIcon.searchTerms = Array.from(searchTerms);
processedIcon.searchCount = searchTerms.size;
// Agregar a resultados
processedIcons[iconName] = processedIcon;
});
console.log(`\n ✅ Procesados: ${processedCount} íconos`);
console.log(` 🎨 Estilos encontrados: ${stylesFound.size}`);
console.log(` 📁 Categorías encontradas: ${categoriesFound.size}`);
// Guardar metadata procesado
const metadataOutput = {
meta: {
generated: new Date().toISOString(),
source: 'FontAwesome Pro',
version: CONFIG.VERSION,
totalIcons: processedCount,
totalStyles: stylesFound.size,
totalCategories: categoriesFound.size
},
icons: processedIcons,
statistics: {
styles: Array.from(stylesFound).sort(),
categories: Array.from(categoriesFound).sort(),
iconsPerStyle: countIconsPerStyle(processedIcons),
iconsPerCategory: countIconsPerCategory(processedIcons)
}
};
fs.writeFileSync(CONFIG.OUTPUT_METADATA, JSON.stringify(metadataOutput, null, 2), 'utf8');
console.log(`\n 💾 Metadata guardado: ${CONFIG.OUTPUT_METADATA}`);
console.log(` 📊 Tamaño: ${(fs.statSync(CONFIG.OUTPUT_METADATA).size / 1024 / 1024).toFixed(2)} MB`);
return metadataOutput;
} catch (error) {
console.error(colors.red + ` ❌ Error procesando metadata: ${error.message}` + colors.reset);
throw error;
}
}
/**
* Formatear nombre de ícono
*/
function formatIconName(iconName) {
return iconName
.split('-')
.map(word => word.charAt(0).toUpperCase() + word.slice(1))
.join(' ');
}
/**
* Agregar sinónimos comunes
*/
function addCommonSynonyms(iconName, searchTerms) {
const synonyms = {
'home': ['house', 'casa', 'hogar', 'dwelling', 'residence'],
'user': ['person', 'profile', 'persona', 'perfil', 'account', 'human'],
'heart': ['love', 'like', 'favorite', 'amor', 'favorito', 'affection'],
'star': ['favorite', 'rating', 'estrella', 'calificación', 'rate', 'review'],
'settings': ['cog', 'gear', 'preferences', 'configuración', 'ajustes', 'options'],
'bell': ['notification', 'alert', 'notificación', 'alerta', 'ring', 'chime'],
'envelope': ['email', 'mail', 'correo', 'message', 'letter'],
'calendar': ['date', 'schedule', 'fecha', 'calendario', 'agenda', 'planner'],
'clock': ['time', 'hour', 'tiempo', 'hora', 'watch', 'timer'],
'search': ['find', 'lookup', 'buscar', 'encontrar', 'seek', 'locate'],
'plus': ['add', 'new', 'create', 'agregar', 'añadir', 'insert'],
'minus': ['remove', 'delete', 'subtract', 'quitar', 'eliminar', 'restar'],
'check': ['verify', 'confirm', 'mark', 'verificar', 'confirmar', 'tick'],
'times': ['close', 'exit', 'cancel', 'cerrar', 'salir', 'cancelar'],
'trash': ['delete', 'remove', 'garbage', 'eliminar', 'basura', 'waste'],
'edit': ['modify', 'change', 'update', 'modificar', 'cambiar', 'actualizar'],
'download': ['save', 'get', 'fetch', 'descargar', 'guardar', 'obtener'],
'upload': ['send', 'share', 'post', 'subir', 'enviar', 'compartir'],
'print': ['printer', 'hardcopy', 'paper', 'imprimir', 'copiar', 'document']
};
Object.entries(synonyms).forEach(([key, words]) => {
if (iconName.includes(key)) {
words.forEach(word => searchTerms.add(word));
}
});
}
/**
* Contar íconos por estilo
*/
function countIconsPerStyle(icons) {
const counts = {};
Object.values(icons).forEach(icon => {
if (icon.styles) {
icon.styles.forEach(style => {
counts[style] = (counts[style] || 0) + 1;
});
}
});
return counts;
}
/**
* Contar íconos por categoría
*/
function countIconsPerCategory(icons) {
const counts = {};
Object.values(icons).forEach(icon => {
if (icon.categories) {
icon.categories.forEach(category => {
counts[category] = (counts[category] || 0) + 1;
});
}
});
return counts;
}
/**
* Extraer información de estilos
*/
function extractStyleInfo(metadata) {
console.log(colors.blue + '\n🎨 Extrayendo información de estilos...' + colors.reset);
// Definición de todos los estilos posibles
const allStyles = {
'solid': {
id: 'solid',
name: 'Solid',
prefix: 'fas',
class: 'fas',
weight: 900,
pro: true,
sharp: false,
family: 'Font Awesome 6 Pro',
file: 'fa-solid-900.woff2'
},
'regular': {
id: 'regular',
name: 'Regular',
prefix: 'far',
class: 'far',
weight: 400,
pro: true,
sharp: false,
family: 'Font Awesome 6 Pro',
file: 'fa-regular-400.woff2'
},
'light': {
id: 'light',
name: 'Light',
prefix: 'fal',
class: 'fal',
weight: 300,
pro: true,
sharp: false,
family: 'Font Awesome 6 Pro',
file: 'fa-light-300.woff2'
},
'thin': {
id: 'thin',
name: 'Thin',
prefix: 'fat',
class: 'fat',
weight: 100,
pro: true,
sharp: false,
family: 'Font Awesome 6 Pro',
file: 'fa-thin-100.woff2'
},
'duotone': {
id: 'duotone',
name: 'Duotone',
prefix: 'fad',
class: 'fad',
weight: 900,
pro: true,
sharp: false,
family: 'Font Awesome 6 Duotone',
file: 'fa-duotone-900.woff2'
},
'sharp-solid': {
id: 'sharp-solid',
name: 'Sharp Solid',
prefix: 'fass',
class: 'fass',
weight: 900,
pro: true,
sharp: true,
family: 'Font Awesome 6 Sharp',
file: 'fa-sharp-solid-900.woff2'
},
'sharp-regular': {
id: 'sharp-regular',
name: 'Sharp Regular',
prefix: 'fasr',
class: 'fasr',
weight: 400,
pro: true,
sharp: true,
family: 'Font Awesome 6 Sharp',
file: 'fa-sharp-regular-400.woff2'
},
'sharp-light': {
id: 'sharp-light',
name: 'Sharp Light',
prefix: 'fasl',
class: 'fasl',
weight: 300,
pro: true,
sharp: true,
family: 'Font Awesome 6 Sharp',
file: 'fa-sharp-light-300.woff2'
},
'sharp-thin': {
id: 'sharp-thin',
name: 'Sharp Thin',
prefix: 'fast',
class: 'fast',
weight: 100,
pro: true,
sharp: true,
family: 'Font Awesome 6 Sharp',
file: 'fa-sharp-thin-100.woff2'
},
'sharp-duotone': {
id: 'sharp-duotone',
name: 'Sharp Duotone',
prefix: 'fasd',
class: 'fasd',
weight: 900,
pro: true,
sharp: true,
family: 'Font Awesome 6 Sharp Duotone',
file: 'fa-sharp-duotone-900.woff2'
},
'brands': {
id: 'brands',
name: 'Brands',
prefix: 'fab',
class: 'fab',
weight: 400,
pro: false,
sharp: false,
family: 'Font Awesome 6 Brands',
file: 'fa-brands-400.woff2'
}
};
// Contar íconos por estilo (desde metadata)
const styleCounts = metadata.statistics.iconsPerStyle || {};
// Enriquecer información de estilos
const stylesInfo = {};
Object.keys(allStyles).forEach(styleId => {
const style = allStyles[styleId];
const count = styleCounts[styleId] || 0;
stylesInfo[styleId] = {
...style,
count: count,
availableInMetadata: count > 0,
percentage: metadata.meta.totalIcons > 0
? ((count / metadata.meta.totalIcons) * 100).toFixed(1)
: '0.0'
};
});
// Guardar información de estilos
const stylesOutput = {
meta: {
generated: new Date().toISOString(),
totalStyles: Object.keys(stylesInfo).length,
stylesWithIcons: Object.values(stylesInfo).filter(s => s.count > 0).length
},
styles: stylesInfo,
statistics: {
totalIconsByStyle: styleCounts,
styleCoverage: calculateStyleCoverage(stylesInfo, metadata.meta.totalIcons)
}
};
fs.writeFileSync(CONFIG.OUTPUT_STYLES, JSON.stringify(stylesOutput, null, 2), 'utf8');
console.log(` ✅ Información de estilos guardada: ${CONFIG.OUTPUT_STYLES}`);
// Mostrar resumen
console.log('\n 📊 RESUMEN DE ESTILOS:');
Object.values(stylesInfo)
.filter(style => style.count > 0)
.sort((a, b) => b.count - a.count)
.forEach(style => {
console.log(` ${style.name.padEnd(20)}: ${style.count.toString().padStart(5)} íconos (${style.percentage}%)`);
});
return stylesOutput;
}
/**
* Calcular cobertura de estilos
*/
function calculateStyleCoverage(stylesInfo, totalIcons) {
const coverage = {};
Object.values(stylesInfo).forEach(style => {
if (style.count > 0) {
coverage[style.id] = {
hasIcons: true,
count: style.count,
percentage: ((style.count / totalIcons) * 100).toFixed(1)
};
} else {
coverage[style.id] = {
hasIcons: false,
count: 0,
percentage: '0.0'
};
}
});
return coverage;
}
/**
* Extraer y procesar categorías
*/
function extractCategories(metadata) {
console.log(colors.blue + '\n📁 Extrayendo información de categorías...' + colors.reset);
// Mapeo de categorías de FontAwesome a nombres en español
const categoriesMap = {
'accessibility': 'Accesibilidad',
'alert': 'Alertas',
'animals': 'Animales',
'arrows': 'Flechas',
'automotive': 'Automotriz',
'buildings': 'Edificios',
'business': 'Negocios',
'camping': 'Camping',
'charity': 'Caridad',
'charts-diagrams': 'Gráficos',
'childhood': 'Infancia',
'clothing-fashion': 'Ropa & Moda',
'coding': 'Programación',
'communication': 'Comunicación',
'connectivity': 'Conectividad',
'construction': 'Construcción',
'design': 'Diseño',
'devices-hardware': 'Dispositivos',
'document': 'Documentos',
'editing': 'Edición',
'education': 'Educación',
'emoji': 'Emoji',
'energy': 'Energía',
'files': 'Archivos',
'film-video': 'Video',
'food-beverage': 'Comida',
'fruits-vegetables': 'Frutas',
'gaming': 'Juegos',
'gender': 'Género',
'halloween': 'Halloween',
'hands': 'Manos',
'holidays': 'Fiestas',
'household': 'Hogar',
'humanitarian': 'Humanitario',
'logistics': 'Logística',
'maps': 'Mapas',
'maritime': 'Marítimo',
'marketing': 'Marketing',
'mathematics': 'Matemáticas',
'medical-health': 'Salud',
'money': 'Dinero',
'moving': 'Mudanza',
'music-audio': 'Música',
'nature': 'Naturaleza',
'numbers': 'Números',
'photos-images': 'Fotos',
'political': 'Política',
'punctuation-symbols': 'Símbolos',
'religion': 'Religión',
'science': 'Ciencia',
'science-fiction': 'Sci-Fi',
'security': 'Seguridad',
'shapes': 'Formas',
'shopping': 'Compras',
'social': 'Social',
'spinners': 'Spinners',
'sports-fitness': 'Deportes',
'text-formatting': 'Texto',
'time': 'Tiempo',
'toggle': 'Toggle',
'transportation': 'Transporte',
'travel': 'Viajes',
'users-people': 'Personas',
'weather': 'Clima',
'writing': 'Escritura',
'uncategorized': 'Sin Categoría'
};
// Obtener conteos de categorías desde metadata
const categoryCounts = metadata.statistics.iconsPerCategory || {};
// Procesar categorías
const categories = [];
// Categorías especiales
categories.push({
id: 'all',
name: 'Todos los Íconos',
icon: 'fas fa-th',
count: metadata.meta.totalIcons,
type: 'special'
});
categories.push({
id: 'favorites',
name: 'Favoritos',
icon: 'fas fa-star',
count: 0,
type: 'special'
});
categories.push({
id: 'recent',
name: 'Recientes',
icon: 'fas fa-history',
count: 0,
type: 'special'
});
// Categorías de FontAwesome
Object.entries(categoryCounts).forEach(([categoryId, count]) => {
const categoryName = categoriesMap[categoryId] || formatCategoryName(categoryId);
categories.push({
id: categoryId,
name: categoryName,
icon: getCategoryIcon(categoryId),
count: count,
type: 'fontawesome'
});
});
// Ordenar categorías
categories.sort((a, b) => {
// Categorías especiales primero
if (a.type === 'special' && b.type !== 'special') return -1;
if (a.type !== 'special' && b.type === 'special') return 1;
// Luego por nombre
return a.name.localeCompare(b.name);
});
// Guardar categorías
const categoriesOutput = {
meta: {
generated: new Date().toISOString(),
totalCategories: categories.length,
fontawesomeCategories: Object.keys(categoryCounts).length
},
categories: categories,
mapping: categoriesMap
};
fs.writeFileSync(CONFIG.OUTPUT_CATEGORIES, JSON.stringify(categoriesOutput, null, 2), 'utf8');
console.log(` ✅ Categorías guardadas: ${CONFIG.OUTPUT_CATEGORIES}`);
console.log(` 📊 Total categorías: ${categories.length}`);
// Mostrar top 10 categorías
console.log('\n 🏆 TOP 10 CATEGORÍAS:');
categories
.filter(cat => cat.type === 'fontawesome')
.sort((a, b) => b.count - a.count)
.slice(0, 10)
.forEach((cat, index) => {
console.log(` ${index + 1}. ${cat.name.padEnd(25)}: ${cat.count} íconos`);
});
return categoriesOutput;
}
/**
* Formatear nombre de categoría
*/
function formatCategoryName(categoryId) {
return categoryId
.split('-')
.map(word => word.charAt(0).toUpperCase() + word.slice(1))
.join(' ');
}
/**
* Obtener ícono para categoría
*/
function getCategoryIcon(categoryId) {
const iconMap = {
'accessibility': 'fas fa-universal-access',
'alert': 'fas fa-exclamation-triangle',
'animals': 'fas fa-paw',
'arrows': 'fas fa-arrow-right',
'buildings': 'fas fa-building',
'business': 'fas fa-briefcase',
'coding': 'fas fa-code',
'communication': 'fas fa-comments',
'design': 'fas fa-palette',
'devices-hardware': 'fas fa-laptop',
'education': 'fas fa-graduation-cap',
'files': 'fas fa-folder',
'food-beverage': 'fas fa-utensils',
'health': 'fas fa-heartbeat',
'household': 'fas fa-home',
'maps': 'fas fa-map',
'music-audio': 'fas fa-music',
'nature': 'fas fa-leaf',
'shopping': 'fas fa-shopping-cart',
'sports-fitness': 'fas fa-football-ball',
'travel': 'fas fa-plane',
'users-people': 'fas fa-user-friends',
'weather': 'fas fa-cloud-sun',
'writing': 'fas fa-pen-fancy'
};
return iconMap[categoryId] || 'fas fa-folder';
}
/**
* Generar reporte de procesamiento
*/
function generateReport(metadata, styles, categories) {
console.log(colors.blue + '\n📋 Generando reporte de procesamiento...' + colors.reset);
const reportPath = path.join(CONFIG.OUTPUT_DIR, 'logs', `report-${new Date().toISOString().replace(/[:.]/g, '-')}.json`);
const report = {
meta: {
generated: new Date().toISOString(),
version: CONFIG.VERSION,
duration: new Date() - startTime
},
summary: {
totalIcons: metadata.meta.totalIcons,
totalStyles: styles.meta.totalStyles,
stylesWithIcons: styles.meta.stylesWithIcons,
totalCategories: categories.meta.totalCategories
},
files: {
metadata: CONFIG.OUTPUT_METADATA,
styles: CONFIG.OUTPUT_STYLES,
categories: CONFIG.OUTPUT_CATEGORIES
},
statistics: {
iconsPerStyle: metadata.statistics.iconsPerStyle,
iconsPerCategory: metadata.statistics.iconsPerCategory,
styleCoverage: styles.statistics.styleCoverage
}
};
fs.writeFileSync(reportPath, JSON.stringify(report, null, 2), 'utf8');
console.log(` ✅ Reporte guardado: ${reportPath}`);
return report;
}
/**
* Función principal
*/
async function main() {
global.startTime = new Date();
try {
console.log(colors.green + '🚀 Iniciando extracción de metadata...' + colors.reset);
// 1. Crear directorios
createDirectories();
// 2. Verificar archivos de entrada
if (!checkInputFiles()) {
console.log(colors.red + '❌ Archivos requeridos no encontrados. Abortando.' + colors.reset);
process.exit(1);
}
// 3. Hacer backup
backupExistingFiles();
// 4. Extraer metadata de íconos
const metadata = extractIconMetadata();
// 5. Extraer información de estilos
const styles = extractStyleInfo(metadata);
// 6. Extraer y procesar categorías
const categories = extractCategories(metadata);
// 7. Generar reporte
const report = generateReport(metadata, styles, categories);
// 8. Mostrar resumen final
const duration = ((new Date() - startTime) / 1000).toFixed(2);
console.log(colors.green + '\n' + '='.repeat(70));
console.log(' ✅ EXTRACCIÓN COMPLETADA EXITOSAMENTE');
console.log('='.repeat(70) + colors.reset);
console.log(`\n📊 RESUMEN FINAL:`);
console.log(` • Íconos procesados: ${metadata.meta.totalIcons}`);
console.log(` • Estilos encontrados: ${styles.meta.stylesWithIcons}/${styles.meta.totalStyles}`);
console.log(` • Categorías: ${categories.meta.totalCategories}`);
console.log(` • Tiempo total: ${duration} segundos`);
console.log(`\n📁 ARCHIVOS GENERADOS:`);
console.log(`${path.relative(process.cwd(), CONFIG.OUTPUT_METADATA)}`);
console.log(`${path.relative(process.cwd(), CONFIG.OUTPUT_STYLES)}`);
console.log(`${path.relative(process.cwd(), CONFIG.OUTPUT_CATEGORIES)}`);
console.log(`\n🚀 PRÓXIMOS PASOS:`);
console.log(` • Ejecuta: npm run build`);
console.log(` • Luego instala el plugin en Logseq`);
console.log(colors.green + '\n🎉 ¡Listo para construir la base de datos final!' + colors.reset);
} catch (error) {
console.error(colors.red + '\n❌ ERROR CRÍTICO:' + colors.reset);
console.error(` ${error.message}`);
console.error(`\n🔧 Solución:`);
console.error(` 1. Verifica que tengas FontAwesome 6 Pro instalado`);
console.error(` 2. Asegúrate de que metadata/icons.json sea válido`);
console.error(` 3. Intenta ejecutar 'npm run verify' primero`);
process.exit(1);
}
}
// Ejecutar
if (require.main === module) {
main();
}
// Exportar funciones para uso en otros scripts
module.exports = {
extractIconMetadata,
extractStyleInfo,
extractCategories,
createDirectories,
checkInputFiles,
CONFIG
};