versacompiler 2.0.1 → 2.0.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -2,11 +2,13 @@ import { createHash } from 'node:crypto';
2
2
  import { glob, mkdir, readFile, stat, unlink, writeFile, } from 'node:fs/promises';
3
3
  import os from 'node:os';
4
4
  import path from 'node:path';
5
- import { cwd, env, stdout } from 'node:process';
5
+ import process, { argv, cwd, env } from 'node:process';
6
6
  // Lazy loading optimizations - Only import lightweight modules synchronously
7
- import { logger } from '../servicios/logger.js';
7
+ import { logger, setProgressManagerGetter } from '../servicios/logger.js';
8
8
  import { promptUser } from '../utils/promptUser.js';
9
9
  import { showTimingForHumans } from '../utils/utils.js';
10
+ // Configurar el getter del ProgressManager para el logger
11
+ setProgressManagerGetter(() => ProgressManager.getInstance());
10
12
  // Heavy dependencies will be loaded dynamically when needed
11
13
  let chalk;
12
14
  let ESLint;
@@ -340,11 +342,31 @@ async function loadVue() {
340
342
  // Almacenamiento global de errores y resultados
341
343
  const compilationErrors = [];
342
344
  const compilationResults = [];
345
+ // Variables de entorno relevantes para compilación
346
+ const COMPILATION_ENV_VARS = [
347
+ 'NODE_ENV',
348
+ 'isPROD',
349
+ 'TAILWIND',
350
+ 'ENABLE_LINTER',
351
+ 'VERBOSE',
352
+ 'typeCheck',
353
+ 'PATH_ALIAS',
354
+ 'tailwindcss',
355
+ 'linter',
356
+ 'tsconfigFile',
357
+ ];
343
358
  class SmartCompilationCache {
344
359
  cache = new Map();
345
- maxEntries = 500; // Máximo archivos en cache
346
- maxMemory = 100 * 1024 * 1024; // 100MB límite
360
+ maxEntries = 200; // Reducido para tests de estrés
361
+ maxMemory = 50 * 1024 * 1024; // 50MB límite (reducido)
347
362
  currentMemoryUsage = 0;
363
+ // ✨ ISSUE #3: Sistema de vigilancia de dependencias
364
+ fileWatchers = new Map(); // chokidar watchers
365
+ dependencyGraph = new Map(); // archivo -> dependencias
366
+ reverseDependencyGraph = new Map(); // dependencia -> archivos que la usan
367
+ packageJsonPath = path.join(cwd(), 'package.json');
368
+ nodeModulesPath = path.join(cwd(), 'node_modules');
369
+ isWatchingDependencies = false;
348
370
  /**
349
371
  * Genera hash SHA-256 del contenido del archivo
350
372
  */ async generateContentHash(filePath) {
@@ -359,6 +381,130 @@ class SmartCompilationCache {
359
381
  }
360
382
  }
361
383
  /**
384
+ * Genera hash de la configuración del compilador
385
+ */
386
+ generateConfigHash() {
387
+ try {
388
+ // Recopilar configuración relevante de variables de entorno
389
+ const config = {
390
+ isPROD: env.isPROD || 'false',
391
+ TAILWIND: env.TAILWIND || 'false',
392
+ ENABLE_LINTER: env.ENABLE_LINTER || 'false',
393
+ PATH_ALIAS: env.PATH_ALIAS || '{}',
394
+ tailwindcss: env.tailwindcss || 'false',
395
+ linter: env.linter || 'false',
396
+ tsconfigFile: env.tsconfigFile || './tsconfig.json',
397
+ };
398
+ const configStr = JSON.stringify(config, Object.keys(config).sort());
399
+ return createHash('sha256')
400
+ .update(configStr)
401
+ .digest('hex')
402
+ .substring(0, 12);
403
+ }
404
+ catch {
405
+ return 'no-config';
406
+ }
407
+ }
408
+ /**
409
+ * Genera hash de variables de entorno relevantes
410
+ */
411
+ generateEnvHash() {
412
+ try {
413
+ const envVars = COMPILATION_ENV_VARS.map(key => `${key}=${env[key] || ''}`).join('|');
414
+ return createHash('sha256')
415
+ .update(envVars)
416
+ .digest('hex')
417
+ .substring(0, 12);
418
+ }
419
+ catch {
420
+ return 'no-env';
421
+ }
422
+ } /**
423
+ * ✨ ISSUE #3: Genera hash avanzado de dependencias del proyecto
424
+ * Incluye vigilancia de package.json, node_modules y versiones instaladas
425
+ */
426
+ async generateDependencyHash() {
427
+ try {
428
+ const hash = createHash('sha256');
429
+ // 1. Hash del package.json con versiones
430
+ const packagePath = path.join(cwd(), 'package.json');
431
+ const packageContent = await readFile(packagePath, 'utf8');
432
+ const pkg = JSON.parse(packageContent);
433
+ const deps = {
434
+ ...pkg.dependencies,
435
+ ...pkg.devDependencies,
436
+ };
437
+ const depsStr = JSON.stringify(deps, Object.keys(deps).sort());
438
+ hash.update(`package:${depsStr}`);
439
+ // 2. Hash del package-lock.json si existe (versiones exactas instaladas)
440
+ try {
441
+ const lockPath = path.join(cwd(), 'package-lock.json');
442
+ const lockContent = await readFile(lockPath, 'utf8');
443
+ const lockData = JSON.parse(lockContent);
444
+ // Solo incluir las versiones instaladas, no todo el lockfile
445
+ const installedVersions = {};
446
+ if (lockData.packages) {
447
+ for (const [pkgPath, pkgInfo] of Object.entries(lockData.packages)) {
448
+ if (pkgPath &&
449
+ pkgPath !== '' &&
450
+ typeof pkgInfo === 'object' &&
451
+ pkgInfo !== null) {
452
+ const pkgName = pkgPath.replace('node_modules/', '');
453
+ if (pkgInfo.version) {
454
+ installedVersions[pkgName] = pkgInfo.version;
455
+ }
456
+ }
457
+ }
458
+ }
459
+ hash.update(`lock:${JSON.stringify(installedVersions, Object.keys(installedVersions).sort())}`);
460
+ }
461
+ catch {
462
+ // Ignorar si no existe package-lock.json
463
+ }
464
+ // 3. ✨ NUEVO: Hash de timestamps críticos de node_modules
465
+ try {
466
+ const nodeModulesPath = path.join(cwd(), 'node_modules');
467
+ const nodeModulesStat = await stat(nodeModulesPath);
468
+ hash.update(`nmtime:${nodeModulesStat.mtimeMs}`);
469
+ // Verificar timestamps de dependencias críticas instaladas
470
+ const criticalDeps = Object.keys(deps).slice(0, 10); // Top 10 para performance
471
+ for (const dep of criticalDeps) {
472
+ try {
473
+ const depPath = path.join(nodeModulesPath, dep);
474
+ const depStat = await stat(depPath);
475
+ hash.update(`${dep}:${depStat.mtimeMs}`);
476
+ }
477
+ catch {
478
+ // Dependencia no instalada o error
479
+ hash.update(`${dep}:missing`);
480
+ }
481
+ }
482
+ }
483
+ catch {
484
+ // node_modules no existe
485
+ hash.update('nmtime:none');
486
+ }
487
+ return hash.digest('hex').substring(0, 16);
488
+ }
489
+ catch (error) {
490
+ // Incluir información del error en el hash para debugging
491
+ return createHash('sha256')
492
+ .update(`error:${error instanceof Error ? error.message : 'unknown'}`)
493
+ .digest('hex')
494
+ .substring(0, 16);
495
+ }
496
+ }
497
+ /**
498
+ * Genera clave de cache granular que incluye todos los factores
499
+ */
500
+ async generateCacheKey(filePath) {
501
+ const contentHash = await this.generateContentHash(filePath);
502
+ const configHash = this.generateConfigHash();
503
+ const envHash = this.generateEnvHash();
504
+ const dependencyHash = await this.generateDependencyHash();
505
+ // Usar | como separador para evitar problemas con rutas de Windows
506
+ return `${filePath}|${contentHash.substring(0, 12)}|${configHash}|${envHash}|${dependencyHash}`;
507
+ } /**
362
508
  * Verifica si una entrada de cache es válida
363
509
  */
364
510
  async isValid(filePath) {
@@ -369,8 +515,26 @@ class SmartCompilationCache {
369
515
  // Verificar si el archivo de salida existe
370
516
  await stat(entry.outputPath);
371
517
  // Verificar si el contenido ha cambiado
372
- const currentHash = await this.generateContentHash(filePath);
373
- if (entry.contentHash !== currentHash) {
518
+ const currentContentHash = await this.generateContentHash(filePath);
519
+ if (entry.contentHash !== currentContentHash) {
520
+ this.cache.delete(filePath);
521
+ return false;
522
+ }
523
+ // Verificar si la configuración ha cambiado
524
+ const currentConfigHash = this.generateConfigHash();
525
+ if (entry.configHash !== currentConfigHash) {
526
+ this.cache.delete(filePath);
527
+ return false;
528
+ }
529
+ // Verificar si las variables de entorno han cambiado
530
+ const currentEnvHash = this.generateEnvHash();
531
+ if (entry.envHash !== currentEnvHash) {
532
+ this.cache.delete(filePath);
533
+ return false;
534
+ }
535
+ // Verificar si las dependencias han cambiado
536
+ const currentDependencyHash = await this.generateDependencyHash();
537
+ if (entry.dependencyHash !== currentDependencyHash) {
374
538
  this.cache.delete(filePath);
375
539
  return false;
376
540
  }
@@ -389,16 +553,21 @@ class SmartCompilationCache {
389
553
  this.cache.delete(filePath);
390
554
  return false;
391
555
  }
392
- }
393
- /**
556
+ } /**
394
557
  * Añade una entrada al cache
395
558
  */
396
559
  async set(filePath, outputPath) {
397
560
  try {
398
561
  const stats = await stat(filePath);
399
562
  const contentHash = await this.generateContentHash(filePath);
563
+ const configHash = this.generateConfigHash();
564
+ const envHash = this.generateEnvHash();
565
+ const dependencyHash = await this.generateDependencyHash();
400
566
  const entry = {
401
567
  contentHash,
568
+ configHash,
569
+ envHash,
570
+ dependencyHash,
402
571
  mtime: stats.mtimeMs,
403
572
  outputPath,
404
573
  lastUsed: Date.now(),
@@ -413,22 +582,31 @@ class SmartCompilationCache {
413
582
  // Si hay error, no cachear
414
583
  console.warn(`Warning: No se pudo cachear ${filePath}:`, error);
415
584
  }
416
- }
417
- /**
585
+ } /**
418
586
  * Aplica política LRU para liberar espacio
419
587
  */
420
588
  evictIfNeeded(newEntrySize) {
421
- // Verificar límite de entradas
422
- while (this.cache.size >= this.maxEntries) {
589
+ // Verificar límite de entradas más agresivamente
590
+ while (this.cache.size >= this.maxEntries * 0.8) {
591
+ // Limpiar cuando llegue al 80%
423
592
  this.evictLRU();
424
593
  }
425
- // Verificar límite de memoria
426
- while (this.currentMemoryUsage + newEntrySize > this.maxMemory &&
594
+ // Verificar límite de memoria más agresivamente
595
+ while (this.currentMemoryUsage + newEntrySize > this.maxMemory * 0.8 && // Limpiar cuando llegue al 80%
427
596
  this.cache.size > 0) {
428
597
  this.evictLRU();
429
598
  }
430
- }
431
- /**
599
+ // Eviction adicional si la memoria total del proceso es alta
600
+ const memUsage = process.memoryUsage();
601
+ const heapUsedMB = memUsage.heapUsed / (1024 * 1024);
602
+ if (heapUsedMB > 200 && this.cache.size > 50) {
603
+ // Si heap > 200MB, limpiar más agresivamente
604
+ const entriesToRemove = Math.min(this.cache.size - 50, 10);
605
+ for (let i = 0; i < entriesToRemove; i++) {
606
+ this.evictLRU();
607
+ }
608
+ }
609
+ } /**
432
610
  * Elimina la entrada menos recientemente usada
433
611
  */
434
612
  evictLRU() {
@@ -448,6 +626,23 @@ class SmartCompilationCache {
448
626
  }
449
627
  }
450
628
  }
629
+ /**
630
+ * Método público para limpiar entradas del cache cuando sea necesario
631
+ */
632
+ cleanOldEntries(maxEntriesToRemove = 20) {
633
+ let removedCount = 0;
634
+ for (let i = 0; i < maxEntriesToRemove && this.cache.size > 0; i++) {
635
+ const sizeBefore = this.cache.size;
636
+ this.evictLRU();
637
+ if (this.cache.size < sizeBefore) {
638
+ removedCount++;
639
+ }
640
+ else {
641
+ break; // No se pudo remover más entradas
642
+ }
643
+ }
644
+ return removedCount;
645
+ }
451
646
  /**
452
647
  * Carga el cache desde disco
453
648
  */
@@ -506,8 +701,7 @@ class SmartCompilationCache {
506
701
  getOutputPath(filePath) {
507
702
  const entry = this.cache.get(filePath);
508
703
  return entry?.outputPath || '';
509
- }
510
- /**
704
+ } /**
511
705
  * Obtiene estadísticas del cache
512
706
  */
513
707
  getStats() {
@@ -517,6 +711,171 @@ class SmartCompilationCache {
517
711
  hitRate: 0, // Se calculará externamente
518
712
  };
519
713
  }
714
+ // ✨ ISSUE #3: Métodos de vigilancia y invalidación cascada
715
+ /**
716
+ * Inicializa vigilancia de package.json y node_modules
717
+ */
718
+ async startDependencyWatching() {
719
+ if (this.isWatchingDependencies)
720
+ return;
721
+ try {
722
+ // Lazy load chokidar para evitar problemas de importación
723
+ const chokidar = await import('chokidar');
724
+ // Vigilar package.json
725
+ if (await this.fileExists(this.packageJsonPath)) {
726
+ const packageWatcher = chokidar.watch(this.packageJsonPath, {
727
+ persistent: false, // No mantener el proceso vivo
728
+ ignoreInitial: true,
729
+ });
730
+ packageWatcher.on('change', () => {
731
+ logger.info('📦 package.json modificado - invalidando cache de dependencias');
732
+ this.invalidateByDependencyChange();
733
+ });
734
+ this.fileWatchers.set('package.json', packageWatcher);
735
+ }
736
+ // Vigilar node_modules (solo cambios en el directorio raíz para performance)
737
+ if (await this.fileExists(this.nodeModulesPath)) {
738
+ const nodeModulesWatcher = chokidar.watch(this.nodeModulesPath, {
739
+ persistent: false,
740
+ ignoreInitial: true,
741
+ depth: 1, // Solo primer nivel para performance
742
+ ignored: /(^|[/\\])\../, // Ignorar archivos ocultos
743
+ });
744
+ nodeModulesWatcher.on('addDir', (path) => {
745
+ logger.info(`📦 Nueva dependencia instalada: ${path.split(/[/\\]/).pop()}`);
746
+ this.invalidateByDependencyChange();
747
+ });
748
+ nodeModulesWatcher.on('unlinkDir', (path) => {
749
+ logger.info(`📦 Dependencia eliminada: ${path.split(/[/\\]/).pop()}`);
750
+ this.invalidateByDependencyChange();
751
+ });
752
+ this.fileWatchers.set('node_modules', nodeModulesWatcher);
753
+ }
754
+ this.isWatchingDependencies = true;
755
+ logger.info('🔍 Vigilancia de dependencias iniciada');
756
+ }
757
+ catch (error) {
758
+ logger.warn('⚠️ No se pudo iniciar vigilancia de dependencias:', error);
759
+ }
760
+ }
761
+ /**
762
+ * Detiene la vigilancia de dependencias
763
+ */
764
+ async stopDependencyWatching() {
765
+ for (const [name, watcher] of this.fileWatchers) {
766
+ try {
767
+ await watcher.close();
768
+ logger.info(`🛑 Vigilancia detenida: ${name}`);
769
+ }
770
+ catch (error) {
771
+ logger.warn(`⚠️ Error cerrando watcher ${name}:`, error);
772
+ }
773
+ }
774
+ this.fileWatchers.clear();
775
+ this.isWatchingDependencies = false;
776
+ }
777
+ /**
778
+ * Registra dependencias de un archivo para invalidación cascada
779
+ */
780
+ registerDependencies(filePath, dependencies) {
781
+ // Limpiar dependencias anteriores
782
+ const oldDeps = this.dependencyGraph.get(filePath);
783
+ if (oldDeps) {
784
+ for (const dep of oldDeps) {
785
+ const reverseDeps = this.reverseDependencyGraph.get(dep);
786
+ if (reverseDeps) {
787
+ reverseDeps.delete(filePath);
788
+ if (reverseDeps.size === 0) {
789
+ this.reverseDependencyGraph.delete(dep);
790
+ }
791
+ }
792
+ }
793
+ }
794
+ // Registrar nuevas dependencias
795
+ const newDeps = new Set(dependencies);
796
+ this.dependencyGraph.set(filePath, newDeps);
797
+ for (const dep of newDeps) {
798
+ if (!this.reverseDependencyGraph.has(dep)) {
799
+ this.reverseDependencyGraph.set(dep, new Set());
800
+ }
801
+ this.reverseDependencyGraph.get(dep).add(filePath);
802
+ }
803
+ }
804
+ /**
805
+ * Invalida cache por cambios en dependencias
806
+ */
807
+ invalidateByDependencyChange() {
808
+ let invalidatedCount = 0;
809
+ // Invalidar todos los archivos que dependen de dependencias externas
810
+ for (const [filePath] of this.cache) {
811
+ this.cache.delete(filePath);
812
+ invalidatedCount++;
813
+ }
814
+ // Limpiar grafos de dependencias
815
+ this.dependencyGraph.clear();
816
+ this.reverseDependencyGraph.clear();
817
+ this.currentMemoryUsage = 0;
818
+ logger.info(`🗑️ Cache invalidado: ${invalidatedCount} archivos (cambio en dependencias)`);
819
+ }
820
+ /**
821
+ * Invalida cascada cuando un archivo específico cambia
822
+ */
823
+ invalidateCascade(changedFile) {
824
+ const invalidated = [];
825
+ const toInvalidate = new Set([changedFile]);
826
+ // BFS para encontrar todos los archivos afectados
827
+ const queue = [changedFile];
828
+ while (queue.length > 0) {
829
+ const current = queue.shift();
830
+ const dependents = this.reverseDependencyGraph.get(current);
831
+ if (dependents) {
832
+ for (const dependent of dependents) {
833
+ if (!toInvalidate.has(dependent)) {
834
+ toInvalidate.add(dependent);
835
+ queue.push(dependent);
836
+ }
837
+ }
838
+ }
839
+ }
840
+ // Invalidar archivos
841
+ for (const filePath of toInvalidate) {
842
+ if (this.cache.has(filePath)) {
843
+ const entry = this.cache.get(filePath);
844
+ this.currentMemoryUsage -= entry.size;
845
+ this.cache.delete(filePath);
846
+ invalidated.push(filePath);
847
+ }
848
+ }
849
+ if (invalidated.length > 0) {
850
+ logger.info(`🔄 Invalidación cascada: ${invalidated.length} archivos afectados por ${changedFile}`);
851
+ }
852
+ return invalidated;
853
+ }
854
+ /**
855
+ * Verifica si un archivo existe
856
+ */
857
+ async fileExists(filePath) {
858
+ try {
859
+ await stat(filePath);
860
+ return true;
861
+ }
862
+ catch {
863
+ return false;
864
+ }
865
+ }
866
+ /**
867
+ * Obtiene estadísticas avanzadas del cache
868
+ */
869
+ getAdvancedStats() {
870
+ return {
871
+ entries: this.cache.size,
872
+ memoryUsage: this.currentMemoryUsage,
873
+ hitRate: 0,
874
+ dependencyNodes: this.dependencyGraph.size,
875
+ watchingDependencies: this.isWatchingDependencies,
876
+ activeWatchers: this.fileWatchers.size,
877
+ };
878
+ }
520
879
  }
521
880
  // Instancia global del cache inteligente
522
881
  const smartCache = new SmartCompilationCache();
@@ -524,6 +883,12 @@ const CACHE_DIR = path.join(path.resolve(env.PATH_PROY || cwd(), 'compiler'), '.
524
883
  const CACHE_FILE = path.join(CACHE_DIR, 'versacompile-cache.json');
525
884
  async function loadCache() {
526
885
  await smartCache.load(CACHE_FILE);
886
+ // ✨ ISSUE #3: Iniciar vigilancia de dependencias en modo watch
887
+ if (env.WATCH_MODE === 'true' ||
888
+ argv.includes('--watch') ||
889
+ argv.includes('-w')) {
890
+ await smartCache.startDependencyWatching();
891
+ }
527
892
  }
528
893
  async function saveCache() {
529
894
  await smartCache.save(CACHE_FILE, CACHE_DIR);
@@ -615,28 +980,42 @@ function clearCompilationState() {
615
980
  /**
616
981
  * Muestra un resumen detallado de todos los errores de compilación
617
982
  */
618
- async function displayCompilationSummary(isVerbose = false) {
983
+ async function displayCompilationSummary(isVerbose = false, totalTime) {
619
984
  const chalk = await loadChalk();
620
985
  if (compilationErrors.length === 0 && compilationResults.length === 0) {
621
986
  logger.info(chalk.green('✅ No hay errores de compilación para mostrar.'));
987
+ if (totalTime) {
988
+ logger.info(chalk.bold(`\n⏱️ TIEMPO TOTAL DE COMPILACIÓN: ${totalTime}`));
989
+ }
622
990
  return;
623
991
  }
624
- logger.info(chalk.bold('\n--- 📊 RESUMEN DE COMPILACIÓN ---'));
625
- // Mostrar estadísticas por etapa
992
+ // 🎨 Header moderno del resumen
993
+ const summaryLine = '━'.repeat(40);
994
+ logger.info('');
995
+ logger.info(chalk.bold.cyan('📊 Resumen de Compilación'));
996
+ logger.info(chalk.gray(summaryLine)); // ⏱️ Tiempo total con formato elegante
997
+ if (totalTime) {
998
+ logger.info(chalk.bold(`⏱️ Tiempo Total: ${chalk.green(totalTime)}`));
999
+ logger.info('');
1000
+ } // 🔧 Estadísticas por etapa con mejor formato
626
1001
  if (compilationResults.length > 0) {
627
- logger.info(chalk.blue('\n🔍 Estadísticas por etapa:'));
1002
+ logger.info(chalk.bold.blue('🔧 Estadísticas por Etapa:'));
628
1003
  for (const result of compilationResults) {
629
1004
  const totalFiles = result.success + result.errors;
630
1005
  const successRate = totalFiles > 0
631
1006
  ? Math.round((result.success / totalFiles) * 100)
632
1007
  : 0;
633
- const statusIcon = result.errors === 0 ? '✅' : '❌';
634
- const stageColor = await getStageColor(result.stage);
635
- const statusText = `${result.success} éxitos, ${result.errors} errores`;
636
- const coloredStatusText = result.errors === 0
637
- ? chalk.green(statusText)
638
- : chalk.red(statusText);
639
- logger.info(`${statusIcon} ${stageColor(result.stage)}: ${coloredStatusText} (${successRate}% éxito)`);
1008
+ // Iconos y colores dinámicos por etapa
1009
+ const stageIcon = getStageIcon(result.stage);
1010
+ const statusColor = result.errors === 0 ? chalk.green : chalk.red;
1011
+ const progressBar = createProgressBarWithPercentage(successRate, 20);
1012
+ logger.info(` ${stageIcon} ${chalk.bold(result.stage)}`);
1013
+ logger.info(` ${statusColor('●')} ${result.success}/${totalFiles} archivos ${statusColor(`(${successRate}%)`)}`);
1014
+ logger.info(` ${progressBar}`);
1015
+ if (result.errors > 0) {
1016
+ logger.info(` ${chalk.red('⚠')} ${result.errors} ${result.errors === 1 ? 'error' : 'errores'}`);
1017
+ }
1018
+ logger.info('');
640
1019
  }
641
1020
  }
642
1021
  // Mostrar errores detallados
@@ -677,33 +1056,60 @@ async function displayCompilationSummary(isVerbose = false) {
677
1056
  }
678
1057
  }
679
1058
  fileIndex++;
680
- }
681
- // Mostrar totales finales
1059
+ } // 📊 Mostrar totales finales con diseño moderno
682
1060
  const totalErrors = compilationErrors.filter(e => e.severity === 'error').length;
683
1061
  const totalWarnings = compilationErrors.filter(e => e.severity === 'warning').length;
684
1062
  const totalFiles = errorsByFile.size;
685
- logger.info(chalk.bold('\n--- 📈 ESTADÍSTICAS FINALES ---'));
686
- logger.info(`📁 Archivos con errores: ${totalFiles}`);
687
- logger.info(`❌ Total de errores: ${totalErrors}`);
688
- logger.info(`⚠️ Total de advertencias: ${totalWarnings}`);
1063
+ // Header elegante para estadísticas finales
1064
+ const statLine = '═'.repeat(50);
1065
+ logger.info('');
1066
+ logger.info(chalk.bold.cyan(statLine));
1067
+ logger.info(chalk.bold.cyan(' 📊 RESUMEN FINAL'));
1068
+ logger.info(chalk.bold.cyan(statLine));
1069
+ // Estadísticas con iconos y colores modernos
1070
+ logger.info('');
1071
+ logger.info(chalk.bold('🎯 Resultados:'));
1072
+ logger.info(` 📁 Archivos afectados: ${chalk.cyan.bold(totalFiles)}`);
1073
+ logger.info(` ${totalErrors > 0 ? chalk.red('●') : chalk.green('○')} Errores: ${totalErrors > 0 ? chalk.red.bold(totalErrors) : chalk.green.bold('0')}`);
1074
+ logger.info(` ${totalWarnings > 0 ? chalk.yellow('●') : chalk.green('○')} Advertencias: ${totalWarnings > 0 ? chalk.yellow.bold(totalWarnings) : chalk.green.bold('0')}`);
1075
+ logger.info('');
1076
+ // Estado final con diseño visual atractivo
689
1077
  if (totalErrors > 0) {
690
- logger.info(chalk.red('🚨 Compilación completada con errores que requieren atención.'));
1078
+ logger.info(chalk.red.bold('🚨 COMPILACIÓN COMPLETADA CON ERRORES'));
1079
+ logger.info(chalk.red(' Por favor revisa y corrige los problemas anteriores.'));
1080
+ }
1081
+ else if (totalWarnings > 0) {
1082
+ logger.info(chalk.yellow.bold('⚠️ COMPILACIÓN COMPLETADA CON ADVERTENCIAS'));
1083
+ logger.info(chalk.yellow(' Considera revisar las advertencias anteriores.'));
691
1084
  }
692
1085
  else {
693
- logger.info(chalk.yellow('✅ Compilación completada con solo advertencias.'));
1086
+ logger.info(chalk.green.bold('✅ COMPILACIÓN EXITOSA'));
1087
+ logger.info(chalk.green(' ¡Todos los archivos se compilaron sin problemas!'));
694
1088
  }
1089
+ logger.info('');
1090
+ logger.info(chalk.bold.cyan(statLine));
695
1091
  }
696
1092
  else {
697
- logger.info(chalk.green('✅ ¡Compilación exitosa sin errores!'));
698
- }
699
- logger.info(chalk.bold('--- FIN DEL RESUMEN ---\n'));
1093
+ // Caso exitoso sin errores
1094
+ const successLine = '═'.repeat(50);
1095
+ logger.info('');
1096
+ logger.info(chalk.bold.green(successLine));
1097
+ logger.info(chalk.bold.green(' ✨ ÉXITO'));
1098
+ logger.info(chalk.bold.green(successLine));
1099
+ logger.info('');
1100
+ logger.info(chalk.green.bold('🎉 COMPILACIÓN COMPLETADA EXITOSAMENTE'));
1101
+ logger.info(chalk.green(' ¡No se encontraron errores ni advertencias!'));
1102
+ logger.info('');
1103
+ logger.info(chalk.bold.green(successLine));
1104
+ }
1105
+ logger.info('');
700
1106
  }
701
1107
  /**
702
- * Muestra errores del linter de forma detallada
1108
+ * Muestra errores del linter con formato visual moderno y profesional
703
1109
  */
704
1110
  async function displayLinterErrors(errors) {
705
1111
  const chalk = await loadChalk();
706
- logger.info(chalk.bold('--- Errores y Advertencias de Linting ---'));
1112
+ // Agrupar errores por archivo
707
1113
  const errorsByFile = new Map();
708
1114
  errors.forEach(error => {
709
1115
  if (!errorsByFile.has(error.file)) {
@@ -714,19 +1120,225 @@ async function displayLinterErrors(errors) {
714
1120
  const totalErrors = errors.filter(e => e.severity === 'error').length;
715
1121
  const totalWarnings = errors.filter(e => e.severity === 'warning').length;
716
1122
  const totalFiles = errorsByFile.size;
717
- logger.info(chalk.yellow(`📊 Resumen: ${totalErrors} errores, ${totalWarnings} advertencias en ${totalFiles} archivos\n`));
718
- errorsByFile.forEach((fileErrors, filePath) => {
719
- const baseName = path.basename(filePath);
720
- logger.info(chalk.cyan(`\n📄 ${baseName}`));
721
- fileErrors.forEach(error => {
722
- const icon = error.severity === 'error' ? '❌' : '⚠️';
723
- logger.info(`${icon} ${error.message}`);
724
- if (error.help) {
725
- logger.info(` └─ ${error.help}`);
1123
+ // Header estilo moderno con gradiente visual
1124
+ logger.info(chalk.bold.rgb(255, 120, 120)('╭─────────────────────────────────────────────────────────────╮'));
1125
+ logger.info(chalk.bold.rgb(255, 120, 120)('│ ') +
1126
+ chalk.bold.white('🔍 LINTER REPORT') +
1127
+ chalk.bold.rgb(255, 120, 120)(' │'));
1128
+ logger.info(chalk.bold.rgb(255, 120, 120)('╰─────────────────────────────────────────────────────────────╯'));
1129
+ // Resumen con iconos profesionales
1130
+ const errorIcon = totalErrors > 0 ? chalk.red('●') : chalk.green('○');
1131
+ const warningIcon = totalWarnings > 0 ? chalk.yellow('●') : chalk.green('○');
1132
+ logger.info('');
1133
+ logger.info(chalk.bold('📊 Summary:'));
1134
+ logger.info(` ${errorIcon} ${chalk.bold(totalErrors)} ${chalk.red('errors')}`);
1135
+ logger.info(` ${warningIcon} ${chalk.bold(totalWarnings)} ${chalk.yellow('warnings')}`);
1136
+ logger.info(` 📁 ${chalk.bold(totalFiles)} ${chalk.cyan('files')}`);
1137
+ logger.info('');
1138
+ if (totalErrors === 0 && totalWarnings === 0) {
1139
+ logger.info(chalk.green.bold('✨ All checks passed! No issues found.'));
1140
+ return;
1141
+ }
1142
+ // Mostrar errores por archivo con formato elegante
1143
+ let fileIndex = 1;
1144
+ for (const [filePath, fileErrors] of errorsByFile) {
1145
+ await displayFileErrorsGroup(filePath, fileErrors, fileIndex, totalFiles);
1146
+ fileIndex++;
1147
+ if (fileIndex <= totalFiles) {
1148
+ logger.info(chalk.gray('─'.repeat(80))); // Separador entre archivos
1149
+ }
1150
+ }
1151
+ // Footer con estadísticas
1152
+ logger.info('');
1153
+ logger.info(chalk.bold.rgb(255, 120, 120)('╭─────────────────────────────────────────────────────────────╮'));
1154
+ logger.info(chalk.bold.rgb(255, 120, 120)('│ ') +
1155
+ chalk.bold.white(`Found ${totalErrors + totalWarnings} issues in ${totalFiles} files`) +
1156
+ ' '.repeat(Math.max(0, 52 -
1157
+ `Found ${totalErrors + totalWarnings} issues in ${totalFiles} files`
1158
+ .length)) +
1159
+ chalk.bold.rgb(255, 120, 120)(' │'));
1160
+ logger.info(chalk.bold.rgb(255, 120, 120)('╰─────────────────────────────────────────────────────────────╯'));
1161
+ }
1162
+ /**
1163
+ * Muestra un grupo de errores para un archivo específico con formato moderno
1164
+ */
1165
+ async function displayFileErrorsGroup(filePath, fileErrors, _fileIndex, _totalFiles) {
1166
+ const chalk = await loadChalk();
1167
+ // Header del archivo con iconos de estado
1168
+ const errorCount = fileErrors.filter(e => e.severity === 'error').length;
1169
+ const warningCount = fileErrors.filter(e => e.severity === 'warning').length;
1170
+ const statusIcon = errorCount > 0 ? chalk.red('✕') : chalk.yellow('⚠');
1171
+ const fileIcon = filePath.endsWith('.vue')
1172
+ ? '🎨'
1173
+ : filePath.endsWith('.ts')
1174
+ ? '📘'
1175
+ : filePath.endsWith('.js')
1176
+ ? '📜'
1177
+ : '📄';
1178
+ logger.info('');
1179
+ logger.info(chalk.bold(`${statusIcon} ${fileIcon} ${chalk.cyan(path.relative(process.cwd(), filePath))}`));
1180
+ logger.info(chalk.gray(` ${errorCount} errors, ${warningCount} warnings`));
1181
+ logger.info('');
1182
+ // Mostrar cada error con formato elegante
1183
+ for (let i = 0; i < fileErrors.length; i++) {
1184
+ const error = fileErrors[i];
1185
+ await displayModernLinterError(error, filePath, i + 1, fileErrors.length);
1186
+ }
1187
+ }
1188
+ /**
1189
+ * Muestra un error individual con formato visual moderno tipo ESLint/Prettier
1190
+ */
1191
+ async function displayModernLinterError(error, filePath, errorIndex, totalErrorsInFile) {
1192
+ const chalk = await loadChalk();
1193
+ const fs = await import('node:fs/promises');
1194
+ // Determinar tipo y color del error
1195
+ const isError = error.severity === 'error';
1196
+ const typeColor = isError ? chalk.red : chalk.yellow;
1197
+ const typeIcon = isError ? '✕' : '⚠';
1198
+ const line = error.line || 1;
1199
+ const column = error.column || 1;
1200
+ const ruleId = error.ruleId || error.from || 'unknown';
1201
+ // Línea principal del error con formato moderno
1202
+ const errorHeader = ` ${typeColor(typeIcon)} ${chalk.bold(error.message)}`;
1203
+ const ruleInfo = `${chalk.gray(ruleId)}`;
1204
+ const locationInfo = `${chalk.blue(`${line}:${column}`)}`;
1205
+ logger.info(errorHeader);
1206
+ logger.info(` ${chalk.gray('at')} ${locationInfo} ${chalk.gray('·')} ${ruleInfo}`);
1207
+ // Mostrar código con contexto
1208
+ try {
1209
+ const absolutePath = path.resolve(filePath);
1210
+ const fileContent = await fs.readFile(absolutePath, 'utf-8');
1211
+ const lines = fileContent.split('\n');
1212
+ const lineNum = parseInt(line.toString()) - 1;
1213
+ if (lineNum >= 0 && lineNum < lines.length) {
1214
+ logger.info('');
1215
+ // Mostrar líneas de contexto con numeración elegante
1216
+ const startLine = Math.max(0, lineNum - 1);
1217
+ const endLine = Math.min(lines.length - 1, lineNum + 1);
1218
+ const maxLineNumWidth = (endLine + 1).toString().length;
1219
+ for (let i = startLine; i <= endLine; i++) {
1220
+ const currentLineNum = i + 1;
1221
+ const currentLine = lines[i] || '';
1222
+ const lineNumStr = currentLineNum
1223
+ .toString()
1224
+ .padStart(maxLineNumWidth, ' ');
1225
+ const isErrorLine = i === lineNum;
1226
+ if (isErrorLine) {
1227
+ // Línea con el error - destacada
1228
+ logger.info(` ${chalk.red('>')} ${chalk.gray(lineNumStr)} ${chalk.gray('│')} ${currentLine}`);
1229
+ // Indicador de posición del error
1230
+ const pointer = ' '.repeat(Math.max(0, column - 1)) + typeColor('^');
1231
+ logger.info(` ${chalk.gray(' ')} ${chalk.gray(' '.repeat(maxLineNumWidth))} ${chalk.gray('│')} ${pointer}`);
1232
+ }
1233
+ else {
1234
+ // Líneas de contexto
1235
+ logger.info(` ${chalk.gray(' ')} ${chalk.gray(lineNumStr)} ${chalk.gray('│')} ${chalk.gray(currentLine)}`);
1236
+ }
726
1237
  }
727
- });
728
- });
729
- logger.info(chalk.bold('--- Fin de Errores y Advertencias ---\n'));
1238
+ }
1239
+ }
1240
+ catch {
1241
+ // Si no se puede leer el archivo, mostrar formato simplificado
1242
+ logger.info(` ${chalk.gray('│')} ${chalk.gray('(Unable to read file content)')}`);
1243
+ }
1244
+ // Mostrar ayuda si está disponible
1245
+ if (error.help) {
1246
+ logger.info('');
1247
+ const helpText = error.help.replace(/^Regla \w+: /, '').trim();
1248
+ logger.info(` ${chalk.blue('💡')} ${chalk.blue('Help:')} ${chalk.gray(helpText)}`);
1249
+ }
1250
+ // Separador entre errores (solo si no es el último)
1251
+ if (errorIndex < totalErrorsInFile) {
1252
+ logger.info('');
1253
+ }
1254
+ }
1255
+ /**
1256
+ * Muestra un solo error del linter con formato visual mejorado
1257
+ * @deprecated Use displayModernLinterError instead
1258
+ */
1259
+ async function _displaySingleLinterError(error, filePath) {
1260
+ const chalk = await loadChalk();
1261
+ const fs = await import('node:fs/promises');
1262
+ const icon = error.severity === 'error' ? '×' : '⚠';
1263
+ const ruleInfo = error.help || '';
1264
+ const line = error.line || 'N/A';
1265
+ const column = error.column || 10; // Columna por defecto si no está disponible
1266
+ // Línea principal del error
1267
+ const mainErrorLine = `${chalk.red(icon)} ${chalk.cyan(`${error.from}(${ruleInfo.replace(/^Regla \w+: /, '')})`)}: ${error.message}`;
1268
+ logger.info(mainErrorLine);
1269
+ // Intentar leer el contenido del archivo para mostrar contexto
1270
+ try {
1271
+ const absolutePath = path.resolve(filePath);
1272
+ const fileContent = await fs.readFile(absolutePath, 'utf-8');
1273
+ const lines = fileContent.split('\n');
1274
+ const lineNum = parseInt(line.toString()) - 1; // Convertir a índice 0-based
1275
+ if (lineNum >= 0 && lineNum < lines.length) {
1276
+ // Mostrar ubicación
1277
+ logger.info(chalk.blue(` ╭─[${filePath}:${line}:${column}]`));
1278
+ // Mostrar líneas de contexto
1279
+ const startLine = Math.max(0, lineNum - 1);
1280
+ const endLine = Math.min(lines.length - 1, lineNum + 1);
1281
+ for (let i = startLine; i <= endLine; i++) {
1282
+ const currentLineNum = i + 1;
1283
+ const currentLine = lines[i] || '';
1284
+ const prefix = currentLineNum.toString().padStart(2, ' ');
1285
+ if (i === lineNum) {
1286
+ // Línea con el error
1287
+ logger.info(chalk.blue(` ${prefix} │ `) + currentLine);
1288
+ // Mostrar el indicador de error
1289
+ const indent = ' '.repeat(prefix.length + 3); // Espacios para alinear
1290
+ const pointer = ' '.repeat(Math.max(0, (column || 1) - 1)) +
1291
+ chalk.red('───────┬──────');
1292
+ logger.info(chalk.blue(indent + '·') + pointer);
1293
+ // Mensaje de ubicación específica
1294
+ const messageIndent = ' '.repeat(Math.max(0, (column || 1) + 6));
1295
+ logger.info(chalk.blue(indent + '·') +
1296
+ messageIndent +
1297
+ chalk.red('╰── ') +
1298
+ chalk.gray(getErrorLocationMessage(error)));
1299
+ }
1300
+ else {
1301
+ // Líneas de contexto
1302
+ logger.info(chalk.blue(` ${prefix} │ `) + chalk.gray(currentLine));
1303
+ }
1304
+ }
1305
+ logger.info(chalk.blue(' ╰────'));
1306
+ }
1307
+ }
1308
+ catch {
1309
+ // Si no se puede leer el archivo, mostrar formato simplificado
1310
+ logger.info(chalk.blue(` ╭─[${filePath}:${line}:${column}]`));
1311
+ logger.info(chalk.blue(' │ ') +
1312
+ chalk.gray('(No se pudo leer el contenido del archivo)'));
1313
+ logger.info(chalk.blue(' ╰────'));
1314
+ }
1315
+ // Mostrar ayuda si está disponible
1316
+ if (error.help) {
1317
+ const helpMessage = error.help.replace(/^Regla \w+: /, '');
1318
+ logger.info(chalk.blue(' help: ') + chalk.yellow(helpMessage));
1319
+ }
1320
+ logger.info(''); // Espacio entre errores
1321
+ }
1322
+ /**
1323
+ * Genera un mensaje descriptivo para la ubicación específica del error
1324
+ */
1325
+ function getErrorLocationMessage(error) {
1326
+ if (error.message.includes('declared but never used')) {
1327
+ const match = error.message.match(/'([^']+)'/);
1328
+ if (match) {
1329
+ return `'${match[1]}' is declared here`;
1330
+ }
1331
+ }
1332
+ if (error.message.includes('Unexpected var')) {
1333
+ return 'var declaration found here';
1334
+ }
1335
+ if (error.message.includes('never reassigned')) {
1336
+ const match = error.message.match(/'([^']+)'/);
1337
+ if (match) {
1338
+ return `'${match[1]}' is assigned here`;
1339
+ }
1340
+ }
1341
+ return 'error location';
730
1342
  }
731
1343
  /**
732
1344
  * Obtiene el color apropiado para cada etapa de compilación
@@ -817,7 +1429,7 @@ class WatchModeOptimizer {
817
1429
  resolve({ success: true, cached: true });
818
1430
  return;
819
1431
  } // Configurar worker pool para modo watch
820
- const { TypeScriptWorkerPool } = await import('./typescript-worker-pool.js');
1432
+ const { TypeScriptWorkerPool } = (await import('./typescript-worker-pool.js'));
821
1433
  const workerPool = TypeScriptWorkerPool.getInstance();
822
1434
  workerPool.setMode('watch');
823
1435
  const result = await compileFn(filePath);
@@ -848,11 +1460,12 @@ async function compileJS(inPath, outPath, mode = 'individual') {
848
1460
  const moduleManager = OptimizedModuleManager.getInstance();
849
1461
  // Timing de lectura
850
1462
  let start = Date.now();
851
- const extension = path.extname(inPath);
852
- // Asegurar que el parser esté cargado
1463
+ const extension = path.extname(inPath); // Asegurar que el parser esté cargado
853
1464
  await moduleManager.ensureModuleLoaded('parser');
854
1465
  const getCodeFile = await loadParser();
855
- let { code, error } = await getCodeFile(inPath);
1466
+ const result = await getCodeFile(inPath);
1467
+ let code = result.code;
1468
+ const error = result.error;
856
1469
  timings.fileRead = Date.now() - start;
857
1470
  if (error) {
858
1471
  await handleCompilationError(error instanceof Error ? error : new Error(String(error)), inPath, 'file-read', mode, env.VERBOSE === 'true');
@@ -864,15 +1477,13 @@ async function compileJS(inPath, outPath, mode = 'individual') {
864
1477
  code === 'null') {
865
1478
  await handleCompilationError(new Error('El archivo está vacío o no se pudo leer.'), inPath, 'file-read', mode, env.VERBOSE === 'true');
866
1479
  throw new Error('El archivo está vacío o no se pudo leer.');
867
- }
868
- // Logs detallados solo en modo verbose + all
869
- const shouldShowDetailedLogs = env.VERBOSE === 'true' && mode === 'all';
870
- // Compilación de Vue
1480
+ } // Logs detallados en modo verbose
1481
+ const shouldShowDetailedLogs = env.VERBOSE === 'true'; // Compilación de Vue
871
1482
  let vueResult;
872
1483
  if (extension === '.vue') {
873
1484
  start = Date.now();
874
1485
  if (shouldShowDetailedLogs) {
875
- logger.info(chalk.green(`💚 Precompilando VUE: ${inPath}`));
1486
+ logger.info(chalk.green(`💚 Precompilando VUE: ${path.basename(inPath)}`));
876
1487
  }
877
1488
  // Asegurar que el módulo Vue esté cargado
878
1489
  await moduleManager.ensureModuleLoaded('vue');
@@ -905,7 +1516,7 @@ async function compileJS(inPath, outPath, mode = 'individual') {
905
1516
  if (extension === '.ts' || vueResult?.lang === 'ts') {
906
1517
  start = Date.now();
907
1518
  if (shouldShowDetailedLogs) {
908
- logger.info(chalk.blue(`🔄️ Precompilando TS: ${inPath}`));
1519
+ logger.info(chalk.blue(`🔄️ Precompilando TS: ${path.basename(inPath)}`));
909
1520
  }
910
1521
  // Asegurar que el módulo TypeScript esté cargado
911
1522
  await moduleManager.ensureModuleLoaded('typescript');
@@ -940,10 +1551,9 @@ async function compileJS(inPath, outPath, mode = 'individual') {
940
1551
  if (!code || code.trim().length === 0) {
941
1552
  await handleCompilationError(new Error('El código TypeScript compilado está vacío.'), inPath, 'typescript', mode, env.VERBOSE === 'true');
942
1553
  throw new Error('El código TypeScript compilado está vacío.');
943
- }
944
- // Estandarización
1554
+ } // Estandarización
945
1555
  if (shouldShowDetailedLogs) {
946
- logger.info(chalk.yellow(`💛 Estandarizando: ${inPath}`));
1556
+ logger.info(chalk.yellow(`💛 Estandarizando: ${path.basename(inPath)}`));
947
1557
  }
948
1558
  start = Date.now();
949
1559
  // Asegurar que el módulo de transformaciones esté cargado
@@ -968,7 +1578,7 @@ async function compileJS(inPath, outPath, mode = 'individual') {
968
1578
  if (env.isPROD === 'true') {
969
1579
  start = Date.now();
970
1580
  if (shouldShowDetailedLogs) {
971
- logger.info(chalk.red(`🤖 Minificando: ${inPath}`));
1581
+ logger.info(chalk.red(`🤖 Minificando: ${path.basename(inPath)}`));
972
1582
  }
973
1583
  // Asegurar que el módulo de minificación esté cargado
974
1584
  await moduleManager.ensureModuleLoaded('minify');
@@ -988,11 +1598,26 @@ async function compileJS(inPath, outPath, mode = 'individual') {
988
1598
  }
989
1599
  registerCompilationSuccess(inPath, 'minification');
990
1600
  code = resultMinify.code;
991
- }
992
- // Escribir archivo final
1601
+ } // Escribir archivo final
993
1602
  const destinationDir = path.dirname(outPath);
994
1603
  await mkdir(destinationDir, { recursive: true });
995
1604
  await writeFile(outPath, code, 'utf-8');
1605
+ // Logs de timing detallados en modo verbose
1606
+ if (shouldShowDetailedLogs) {
1607
+ const totalTime = Object.values(timings).reduce((sum, time) => sum + time, 0);
1608
+ logger.info(chalk.cyan(`⏱️ Timing para ${path.basename(inPath)}:`));
1609
+ if (timings.fileRead)
1610
+ logger.info(chalk.cyan(` 📖 Lectura: ${timings.fileRead}ms`));
1611
+ if (timings.vueCompile)
1612
+ logger.info(chalk.cyan(` 💚 Vue: ${timings.vueCompile}ms`));
1613
+ if (timings.tsCompile)
1614
+ logger.info(chalk.cyan(` 🔄️ TypeScript: ${timings.tsCompile}ms`));
1615
+ if (timings.standardization)
1616
+ logger.info(chalk.cyan(` 💛 Estandarización: ${timings.standardization}ms`));
1617
+ if (timings.minification)
1618
+ logger.info(chalk.cyan(` 🤖 Minificación: ${timings.minification}ms`));
1619
+ logger.info(chalk.cyan(` 🏁 Total: ${totalTime}ms`));
1620
+ }
996
1621
  return {
997
1622
  error: null,
998
1623
  action: 'extension',
@@ -1025,6 +1650,20 @@ export async function initCompile(ruta, compileTailwind = true, mode = 'individu
1025
1650
  const startTime = Date.now();
1026
1651
  const file = normalizeRuta(ruta);
1027
1652
  const outFile = getOutputPath(file);
1653
+ // 🚀 Verificar cache antes de compilar (especialmente importante en modo watch)
1654
+ if (mode === 'watch' || mode === 'individual') {
1655
+ if (await shouldSkipFile(file)) {
1656
+ if (env.VERBOSE === 'true') {
1657
+ logger.info(`⏭️ Archivo omitido (cache): ${path.basename(file)}`);
1658
+ }
1659
+ return {
1660
+ success: true,
1661
+ cached: true,
1662
+ output: smartCache.getOutputPath(file) || outFile,
1663
+ action: 'cached',
1664
+ };
1665
+ }
1666
+ }
1028
1667
  if (mode === 'individual' && env.VERBOSE === 'true') {
1029
1668
  logger.info(`🔜 Fuente: ${file}`);
1030
1669
  }
@@ -1032,6 +1671,10 @@ export async function initCompile(ruta, compileTailwind = true, mode = 'individu
1032
1671
  if (result.error) {
1033
1672
  throw new Error(result.error);
1034
1673
  }
1674
+ // 🚀 Actualizar cache después de compilación exitosa (especialmente en modo watch)
1675
+ if (mode === 'watch' || mode === 'individual') {
1676
+ await smartCache.set(file, outFile);
1677
+ }
1035
1678
  const endTime = Date.now();
1036
1679
  const elapsedTime = showTimingForHumans(endTime - startTime);
1037
1680
  if (mode === 'individual') {
@@ -1062,6 +1705,183 @@ export async function initCompile(ruta, compileTailwind = true, mode = 'individu
1062
1705
  }
1063
1706
  // Variable para el último progreso mostrado (evitar spam)
1064
1707
  let lastProgressUpdate = 0;
1708
+ // Sistema de gestión de progreso persistente (como Jest)
1709
+ class ProgressManager {
1710
+ static instance;
1711
+ progressActive = false;
1712
+ lastProgressLine = '';
1713
+ logBuffer = [];
1714
+ originalConsoleLog;
1715
+ originalConsoleError;
1716
+ originalConsoleWarn;
1717
+ hasProgressLine = false;
1718
+ constructor() {
1719
+ // Guardar referencias originales
1720
+ this.originalConsoleLog = console.log;
1721
+ this.originalConsoleError = console.error;
1722
+ this.originalConsoleWarn = console.warn;
1723
+ }
1724
+ static getInstance() {
1725
+ if (!ProgressManager.instance) {
1726
+ ProgressManager.instance = new ProgressManager();
1727
+ }
1728
+ return ProgressManager.instance;
1729
+ }
1730
+ interceptConsole() {
1731
+ // Interceptar console.log y similares
1732
+ console.log = (...args) => {
1733
+ this.addLog(args.map(arg => String(arg)).join(' '));
1734
+ };
1735
+ console.error = (...args) => {
1736
+ this.addLog(args.map(arg => String(arg)).join(' '));
1737
+ };
1738
+ console.warn = (...args) => {
1739
+ this.addLog(args.map(arg => String(arg)).join(' '));
1740
+ };
1741
+ }
1742
+ restoreConsole() {
1743
+ console.log = this.originalConsoleLog;
1744
+ console.error = this.originalConsoleError;
1745
+ console.warn = this.originalConsoleWarn;
1746
+ }
1747
+ startProgress() {
1748
+ this.progressActive = true;
1749
+ this.logBuffer = [];
1750
+ this.hasProgressLine = false;
1751
+ this.interceptConsole();
1752
+ // 🎨 Header moderno de inicio de compilación
1753
+ const headerLine = '━'.repeat(48);
1754
+ process.stdout.write('\n\x1b[96m' + headerLine + '\x1b[0m\n');
1755
+ process.stdout.write('\x1b[96m│ \x1b[97m\x1b[1m🚀 Iniciando Compilación\x1b[0m\x1b[96m' +
1756
+ ' '.repeat(22) +
1757
+ '│\x1b[0m\n');
1758
+ process.stdout.write('\x1b[96m' + headerLine + '\x1b[0m\n');
1759
+ }
1760
+ updateProgress(progressText) {
1761
+ if (!this.progressActive)
1762
+ return;
1763
+ // Si hay logs pendientes, mostrarlos primero
1764
+ if (this.logBuffer.length > 0) {
1765
+ // Si ya hay una línea de progreso, limpiarla primero
1766
+ if (this.hasProgressLine) {
1767
+ process.stdout.write('\r\x1b[K');
1768
+ }
1769
+ // Escribir todos los logs pendientes
1770
+ for (const log of this.logBuffer) {
1771
+ process.stdout.write((this.hasProgressLine ? '\n' : '') + log + '\n');
1772
+ this.hasProgressLine = false;
1773
+ }
1774
+ this.logBuffer = [];
1775
+ } // Escribir separador elegante antes del progreso
1776
+ if (this.hasProgressLine) {
1777
+ process.stdout.write('\r\x1b[K');
1778
+ }
1779
+ else {
1780
+ process.stdout.write('\n\x1b[96m' + '▔'.repeat(50) + '\x1b[0m\n');
1781
+ }
1782
+ // 🎨 Barra de progreso con colores dinámicos
1783
+ const stage = this.getStageFromText(progressText);
1784
+ const { bgColor, textColor, icon } = this.getProgressColors(stage);
1785
+ const progressBar = '█'.repeat(3);
1786
+ const enhancedProgress = `\x1b[${bgColor}m\x1b[${textColor}m ${progressBar} ${icon} ${progressText} ${progressBar} \x1b[0m`;
1787
+ process.stdout.write(enhancedProgress);
1788
+ this.hasProgressLine = true;
1789
+ this.lastProgressLine = progressText;
1790
+ }
1791
+ addLog(message) {
1792
+ if (this.progressActive) {
1793
+ this.logBuffer.push(message);
1794
+ }
1795
+ else {
1796
+ this.originalConsoleLog(message);
1797
+ }
1798
+ }
1799
+ addImmediateLog(message) {
1800
+ if (this.progressActive) {
1801
+ if (this.hasProgressLine) {
1802
+ process.stdout.write('\r\x1b[K');
1803
+ }
1804
+ // Añadir un punto de separación visual para logs inmediatos
1805
+ process.stdout.write('\x1b[90m│\x1b[0m ' + message + '\n');
1806
+ this.hasProgressLine = false;
1807
+ }
1808
+ else {
1809
+ this.originalConsoleLog(message);
1810
+ }
1811
+ }
1812
+ endProgress() {
1813
+ if (this.progressActive) {
1814
+ if (this.hasProgressLine) {
1815
+ process.stdout.write('\n');
1816
+ }
1817
+ // Mostrar barra de progreso final completa antes del separador
1818
+ process.stdout.write('\n\x1b[33m' + '-'.repeat(50) + '\x1b[0m\n');
1819
+ const finalProgressBar = '█'.repeat(3);
1820
+ const finalProgress = `\x1b[42m\x1b[30m ${finalProgressBar} ✅ PROCESO COMPLETADO 100% ${finalProgressBar} \x1b[0m`;
1821
+ process.stdout.write(finalProgress + '\n');
1822
+ // 🎨 Footer moderno de finalización
1823
+ const footerLine = '━'.repeat(48);
1824
+ process.stdout.write('\x1b[92m' + footerLine + '\x1b[0m\n');
1825
+ process.stdout.write('\x1b[92m│ \x1b[97m\x1b[1m✅ ¡Compilación Completada!\x1b[0m\x1b[92m' +
1826
+ ' '.repeat(23) +
1827
+ '│\x1b[0m\n');
1828
+ process.stdout.write('\x1b[92m' + footerLine + '\x1b[0m\n\n');
1829
+ // Escribir logs finales pendientes
1830
+ if (this.logBuffer.length > 0) {
1831
+ for (const log of this.logBuffer) {
1832
+ process.stdout.write(log + '\n');
1833
+ }
1834
+ }
1835
+ }
1836
+ this.restoreConsole();
1837
+ this.progressActive = false;
1838
+ this.lastProgressLine = '';
1839
+ this.logBuffer = [];
1840
+ this.hasProgressLine = false;
1841
+ }
1842
+ isActive() {
1843
+ return this.progressActive;
1844
+ }
1845
+ /**
1846
+ * 🎨 Determina la etapa del progreso basándose en el texto
1847
+ */
1848
+ getStageFromText(text) {
1849
+ if (text.includes('Iniciando') || text.includes('Starting'))
1850
+ return 'start';
1851
+ if (text.includes('Tailwind') || text.includes('CSS'))
1852
+ return 'tailwind';
1853
+ if (text.includes('Recopilando') ||
1854
+ text.includes('archivos') ||
1855
+ text.includes('files'))
1856
+ return 'files';
1857
+ if (text.includes('Compilando') || text.includes('workers'))
1858
+ return 'compile';
1859
+ if (text.includes('cache') || text.includes('Guardando'))
1860
+ return 'cache';
1861
+ if (text.includes('linter') || text.includes('Linter'))
1862
+ return 'linter';
1863
+ if (text.includes('completado') || text.includes('Complete'))
1864
+ return 'complete';
1865
+ return 'default';
1866
+ }
1867
+ /**
1868
+ * 🌈 Obtiene colores dinámicos para cada etapa
1869
+ */
1870
+ getProgressColors(stage) {
1871
+ const colorSchemes = {
1872
+ start: { bgColor: '45', textColor: '97', icon: '🚀' }, // Cyan brillante
1873
+ tailwind: { bgColor: '105', textColor: '97', icon: '🎨' }, // Magenta
1874
+ files: { bgColor: '43', textColor: '30', icon: '📁' }, // Amarillo
1875
+ compile: { bgColor: '42', textColor: '30', icon: '⚙️' }, // Verde
1876
+ cache: { bgColor: '44', textColor: '97', icon: '💾' }, // Azul
1877
+ linter: { bgColor: '101', textColor: '97', icon: '🔍' }, // Rojo claro
1878
+ complete: { bgColor: '102', textColor: '30', icon: '✅' }, // Verde claro
1879
+ default: { bgColor: '100', textColor: '30', icon: '⏳' }, // Gris claro
1880
+ };
1881
+ const defaultColors = { bgColor: '100', textColor: '30', icon: '⏳' };
1882
+ return colorSchemes[stage] || defaultColors;
1883
+ }
1884
+ }
1065
1885
  // Función para ejecutar el linter antes de la compilación
1066
1886
  export async function runLinter(showResult = false) {
1067
1887
  const linterENV = env.linter;
@@ -1087,9 +1907,14 @@ export async function runLinter(showResult = false) {
1087
1907
  // Procesar resultados de ESLint
1088
1908
  if (Array.isArray(eslintResult.json)) {
1089
1909
  eslintResult.json.forEach((result) => {
1910
+ const filePath = result.filePath ||
1911
+ result.file ||
1912
+ 'archivo no especificado';
1090
1913
  linterErrors.push({
1091
- file: result.filePath ||
1092
- 'archivo no especificado',
1914
+ from: 'eslint',
1915
+ line: result.line || 'N/A',
1916
+ column: result.column || 1,
1917
+ file: filePath,
1093
1918
  message: result.message,
1094
1919
  severity: result.severity === 2
1095
1920
  ? 'error'
@@ -1106,9 +1931,16 @@ export async function runLinter(showResult = false) {
1106
1931
  if (fileResult.messages &&
1107
1932
  Array.isArray(fileResult.messages)) {
1108
1933
  fileResult.messages.forEach((msg) => {
1934
+ const filePath = fileResult.filePath ||
1935
+ fileResult.file ||
1936
+ 'archivo no especificado';
1109
1937
  linterErrors.push({
1110
- file: fileResult.filePath ||
1111
- 'archivo no especificado',
1938
+ from: 'eslint',
1939
+ line: msg.line ||
1940
+ 'N/A',
1941
+ column: msg.column ||
1942
+ 1,
1943
+ file: filePath,
1112
1944
  message: msg.message,
1113
1945
  severity: msg.severity ===
1114
1946
  2
@@ -1140,20 +1972,40 @@ export async function runLinter(showResult = false) {
1140
1972
  .then((oxlintResult) => {
1141
1973
  if (oxlintResult &&
1142
1974
  oxlintResult['json'] &&
1143
- Array.isArray(oxlintResult['json'])) {
1144
- oxlintResult['json'].forEach((result) => {
1975
+ Array.isArray(oxlintResult['json']['diagnostics'])) {
1976
+ oxlintResult['json']['diagnostics'].forEach((result) => {
1977
+ const filePath = result.filename ||
1978
+ result.file ||
1979
+ 'archivo no especificado';
1980
+ const lineNumber = result.labels &&
1981
+ result.labels[0] &&
1982
+ result.labels[0].span
1983
+ ? result.labels[0].span
1984
+ .line ||
1985
+ result.labels[0].span
1986
+ .start?.line
1987
+ : 'N/A';
1988
+ const columnNumber = result.labels &&
1989
+ result.labels[0] &&
1990
+ result.labels[0].span
1991
+ ? result.labels[0].span
1992
+ .column ||
1993
+ result.labels[0].span
1994
+ .start?.column
1995
+ : 1;
1145
1996
  linterErrors.push({
1146
- file: result.filename ||
1147
- result.file ||
1148
- 'archivo no especificado',
1997
+ from: 'oxlint',
1998
+ line: lineNumber,
1999
+ column: columnNumber,
2000
+ file: filePath,
1149
2001
  message: result.message,
1150
2002
  severity: typeof result.severity ===
1151
2003
  'string'
1152
2004
  ? result.severity.toLowerCase()
1153
2005
  : 'error',
1154
2006
  help: result.help ||
1155
- (result.rule_id
1156
- ? `Regla Oxlint: ${result.rule_id}`
2007
+ (result.code
2008
+ ? `Regla Oxlint: ${result.code}`
1157
2009
  : undefined),
1158
2010
  });
1159
2011
  });
@@ -1176,6 +2028,7 @@ export async function runLinter(showResult = false) {
1176
2028
  }
1177
2029
  await Promise.all(linterPromises);
1178
2030
  if (showResult) {
2031
+ // Modo --linter: Solo mostrar resultados sin preguntar
1179
2032
  if (linterErrors.length > 0) {
1180
2033
  await displayLinterErrors(linterErrors);
1181
2034
  }
@@ -1184,21 +2037,24 @@ export async function runLinter(showResult = false) {
1184
2037
  logger.info(chalk.green('✅ No se encontraron errores ni advertencias de linting.'));
1185
2038
  }
1186
2039
  }
2040
+ else {
2041
+ // Modo compilación: Mostrar errores si los hay y preguntar al usuario
2042
+ if (linterErrors.length > 0) {
2043
+ await displayLinterErrors(linterErrors);
2044
+ logger.warn('🚨 Se encontraron errores o advertencias durante el linting.');
2045
+ if (env.yes === 'false') {
2046
+ const result = await promptUser('¿Deseas continuar con la compilación a pesar de los errores de linting? (s/N): ');
2047
+ if (result.toLowerCase() !== 's') {
2048
+ logger.info('🛑 Compilación cancelada por el usuario.');
2049
+ proceedWithCompilation = false;
2050
+ }
2051
+ }
2052
+ }
2053
+ }
1187
2054
  }
1188
2055
  catch (parseError) {
1189
2056
  logger.warn(`Error parseando configuración de linter: ${parseError instanceof Error ? parseError.message : 'Error desconocido'}, omitiendo...`);
1190
2057
  }
1191
- if (!showResult && linterErrors.length > 0) {
1192
- await displayLinterErrors(linterErrors);
1193
- logger.warn('🚨 Se encontraron errores o advertencias durante el linting.');
1194
- if (env.yes === 'false') {
1195
- const result = await promptUser('¿Deseas continuar con la compilación a pesar de los errores de linting? (s/N): ');
1196
- if (result.toLowerCase() !== 's') {
1197
- logger.info('🛑 Compilación cancelada por el usuario.');
1198
- proceedWithCompilation = false;
1199
- }
1200
- }
1201
- }
1202
2058
  }
1203
2059
  return proceedWithCompilation;
1204
2060
  }
@@ -1220,23 +2076,47 @@ async function compileWithConcurrencyLimit(files, maxConcurrency = 8) {
1220
2076
  const total = files.length;
1221
2077
  let completed = 0;
1222
2078
  let skipped = 0;
1223
- let failed = 0; // Función para mostrar progreso
2079
+ let failed = 0;
2080
+ // Usar el gestor de progreso existente (ya iniciado en initCompileAll)
2081
+ const progressManager = ProgressManager.getInstance();
2082
+ // Variable para controlar el progreso inicial
2083
+ let hasShownInitialProgress = false;
2084
+ // Contador para limpieza periódica de memoria
2085
+ let compilationCounter = 0;
2086
+ const CLEANUP_INTERVAL = 20; // Limpiar cada 20 compilaciones
2087
+ // Función para mostrar progreso
1224
2088
  function showProgress() {
1225
2089
  const currentTotal = completed + skipped + failed;
1226
2090
  const progressBar = createProgressBar(currentTotal, total);
1227
2091
  const progressPercent = Math.round((currentTotal / total) * 100);
1228
- if (progressPercent > lastProgressUpdate + 1 ||
2092
+ // Mostrar progreso inicial cuando se inicie O cuando haya progreso real
2093
+ if ((currentTotal === 0 && !hasShownInitialProgress) ||
2094
+ (progressPercent > lastProgressUpdate + 1 && currentTotal > 0) ||
1229
2095
  currentTotal === total) {
1230
- stdout.write(`\r🚀 ${progressBar} [✅ ${completed} | ⏭️ ${skipped} | ❌ ${failed}]`);
2096
+ const progressText = `🚀 ${progressBar} [✅ ${completed} | ⏭️ ${skipped} | ❌ ${failed}]`;
2097
+ progressManager.updateProgress(progressText);
2098
+ if (currentTotal === 0) {
2099
+ hasShownInitialProgress = true;
2100
+ }
1231
2101
  lastProgressUpdate = progressPercent;
2102
+ // NO terminar el progreso aquí - se termina en initCompileAll
1232
2103
  }
1233
2104
  }
2105
+ // Mostrar progreso inicial
2106
+ showProgress();
1234
2107
  for (const file of files) {
1235
2108
  const promise = (async () => {
1236
2109
  try {
2110
+ // Log verbose: Iniciando compilación del archivo
2111
+ if (env.VERBOSE === 'true') {
2112
+ logger.info(`🔄 Compilando: ${path.basename(file)}`);
2113
+ }
1237
2114
  // Verificar cache antes de compilar
1238
2115
  if (await shouldSkipFile(file)) {
1239
2116
  skipped++;
2117
+ if (env.VERBOSE === 'true') {
2118
+ logger.info(`⏭️ Archivo omitido (cache): ${path.basename(file)}`);
2119
+ }
1240
2120
  showProgress();
1241
2121
  return {
1242
2122
  success: true,
@@ -1244,16 +2124,54 @@ async function compileWithConcurrencyLimit(files, maxConcurrency = 8) {
1244
2124
  output: smartCache.getOutputPath(file),
1245
2125
  };
1246
2126
  }
1247
- const result = await initCompile(file, false, 'batch'); // Actualizar cache si la compilación fue exitosa
2127
+ const result = await initCompile(file, false, 'batch');
2128
+ // Actualizar cache si la compilación fue exitosa
1248
2129
  if (result.success && result.output) {
1249
2130
  await smartCache.set(file, result.output);
2131
+ if (env.VERBOSE === 'true') {
2132
+ logger.info(`✅ Completado: ${path.basename(file)} → ${path.basename(result.output)}`);
2133
+ }
2134
+ }
2135
+ else if (env.VERBOSE === 'true') {
2136
+ logger.info(`❌ Error en: ${path.basename(file)}`);
1250
2137
  }
1251
2138
  completed++;
2139
+ compilationCounter++; // Limpieza periódica de memoria
2140
+ if (compilationCounter % CLEANUP_INTERVAL === 0) {
2141
+ // Forzar garbage collection si está disponible
2142
+ try {
2143
+ if (typeof globalThis.gc === 'function') {
2144
+ globalThis.gc();
2145
+ }
2146
+ }
2147
+ catch {
2148
+ // gc no disponible, continuar normalmente
2149
+ }
2150
+ // Limpiar cache si la memoria es alta
2151
+ const memUsage = process.memoryUsage();
2152
+ const heapUsedMB = memUsage.heapUsed / (1024 * 1024);
2153
+ if (heapUsedMB > 300) {
2154
+ // Si el heap supera 300MB
2155
+ const cacheEntries = smartCache.getStats().entries;
2156
+ if (cacheEntries > 50) {
2157
+ console.log(`[Memory] Heap alto (${heapUsedMB.toFixed(1)}MB), limpiando cache...`);
2158
+ // Limpiar entradas más antiguas del cache
2159
+ const removedEntries = smartCache.cleanOldEntries(20);
2160
+ if (removedEntries > 0) {
2161
+ console.log(`[Memory] Se removieron ${removedEntries} entradas del cache`);
2162
+ }
2163
+ }
2164
+ }
2165
+ }
1252
2166
  showProgress();
1253
2167
  return result;
1254
2168
  }
1255
2169
  catch (error) {
1256
2170
  failed++;
2171
+ if (env.VERBOSE === 'true') {
2172
+ const errorMsg = error instanceof Error ? error.message : String(error);
2173
+ logger.error(`💥 Falló: ${path.basename(file)} - ${errorMsg}`);
2174
+ }
1257
2175
  showProgress();
1258
2176
  return {
1259
2177
  success: false,
@@ -1269,18 +2187,26 @@ async function compileWithConcurrencyLimit(files, maxConcurrency = 8) {
1269
2187
  }
1270
2188
  }
1271
2189
  await Promise.all(results);
1272
- console.log('\n'); // Nueva línea después de la barra de progreso
2190
+ // El progreso ya se termina automáticamente en showProgress() cuando se completa
1273
2191
  }
1274
2192
  export async function initCompileAll() {
1275
2193
  try {
2194
+ // Inicializar el gestor de progreso desde el inicio
2195
+ const progressManager = ProgressManager.getInstance();
2196
+ progressManager.startProgress();
2197
+ // Fase 1: Preparación inicial
2198
+ progressManager.updateProgress('🚀 Iniciando compilación...');
1276
2199
  // Limpiar estado de compilación anterior
1277
2200
  clearCompilationState();
1278
2201
  // Cargar cache al inicio
2202
+ progressManager.updateProgress('📦 Cargando cache...');
1279
2203
  await loadCache();
1280
- lastProgressUpdate = 0;
1281
- const shouldContinue = await runLinter();
2204
+ lastProgressUpdate = 0; // Fase 2: Linting
2205
+ progressManager.updateProgress('🔍 Ejecutando linter...');
2206
+ const shouldContinue = await runLinter(false); // false = mostrar errores y preguntar si hay errores
1282
2207
  if (!shouldContinue) {
1283
2208
  // await displayCompilationSummary(env.VERBOSE === 'true');
2209
+ progressManager.endProgress();
1284
2210
  return;
1285
2211
  }
1286
2212
  const startTime = Date.now();
@@ -1296,7 +2222,9 @@ export async function initCompileAll() {
1296
2222
  ];
1297
2223
  logger.info(`📝 Compilando todos los archivos...`);
1298
2224
  logger.info(`🔜 Fuente: ${rawPathSource}`);
1299
- logger.info(`🔚 Destino: ${pathDist}\n`); // Generar TailwindCSS
2225
+ logger.info(`🔚 Destino: ${pathDist}\n`);
2226
+ // Fase 3: TailwindCSS
2227
+ progressManager.updateProgress('🎨 Generando TailwindCSS...');
1300
2228
  const generateTailwindCSS = await loadTailwind();
1301
2229
  const resultTW = await generateTailwindCSS();
1302
2230
  if (typeof resultTW !== 'boolean') {
@@ -1306,7 +2234,9 @@ export async function initCompileAll() {
1306
2234
  else {
1307
2235
  await handleCompilationError(new Error(`${resultTW.message}${resultTW.details ? '\n' + resultTW.details : ''}`), 'tailwind.config.js', 'tailwind', 'all', env.VERBOSE === 'true');
1308
2236
  }
1309
- } // Recopilar todos los archivos
2237
+ }
2238
+ // Fase 4: Recopilando archivos
2239
+ progressManager.updateProgress('📁 Recopilando archivos...');
1310
2240
  const filesToCompile = [];
1311
2241
  for await (const file of glob(patterns)) {
1312
2242
  if (file.endsWith('.d.ts')) {
@@ -1314,46 +2244,86 @@ export async function initCompileAll() {
1314
2244
  }
1315
2245
  // Usar la ruta tal como viene de glob, sin modificar
1316
2246
  filesToCompile.push(file);
1317
- }
1318
- // Determinar concurrencia óptima
2247
+ } // Determinar concurrencia óptima considerando memoria disponible
1319
2248
  const cpuCount = os.cpus().length;
1320
2249
  const fileCount = filesToCompile.length;
2250
+ const memUsage = process.memoryUsage();
2251
+ const availableMemoryMB = (memUsage.heapTotal - memUsage.heapUsed) / (1024 * 1024);
1321
2252
  let maxConcurrency;
1322
- if (fileCount < 10) {
1323
- maxConcurrency = Math.min(fileCount, cpuCount);
2253
+ // Ajustar concurrencia basado en memoria disponible y archivos
2254
+ if (availableMemoryMB < 100) {
2255
+ // Poca memoria disponible
2256
+ maxConcurrency = Math.min(2, cpuCount);
2257
+ }
2258
+ else if (fileCount < 10) {
2259
+ maxConcurrency = Math.min(fileCount, Math.min(cpuCount, 4));
1324
2260
  }
1325
2261
  else if (fileCount < 50) {
1326
- maxConcurrency = Math.min(cpuCount * 2, 12);
2262
+ maxConcurrency = Math.min(cpuCount, 6); // Reducido
1327
2263
  }
1328
2264
  else {
1329
- maxConcurrency = Math.min(cpuCount * 2, 16);
2265
+ maxConcurrency = Math.min(cpuCount, 8); // Reducido
1330
2266
  }
2267
+ // Fase 5: Configurando workers
2268
+ progressManager.updateProgress('⚙️ Configurando workers...');
1331
2269
  logger.info(`🚀 Compilando ${fileCount} archivos con concurrencia optimizada (${maxConcurrency} hilos)...`); // Configurar worker pool para modo batch
1332
2270
  try {
1333
- const { TypeScriptWorkerPool } = await import('./typescript-worker-pool.js');
2271
+ const { TypeScriptWorkerPool } = (await import('./typescript-worker-pool.js'));
1334
2272
  const workerPool = TypeScriptWorkerPool.getInstance();
1335
2273
  workerPool.setMode('batch');
1336
2274
  }
1337
2275
  catch {
1338
2276
  // Error silencioso en configuración del pool
1339
- }
1340
- await compileWithConcurrencyLimit(filesToCompile, maxConcurrency);
1341
- // Guardar cache al final
2277
+ } // Fase 6: Compilación (el progreso continúa en compileWithConcurrencyLimit)
2278
+ progressManager.updateProgress(`🚀 Iniciando compilación de ${fileCount} archivos...`);
2279
+ await compileWithConcurrencyLimit(filesToCompile, maxConcurrency); // Guardar cache al final
2280
+ progressManager.updateProgress('💾 Guardando cache...');
1342
2281
  await saveCache();
1343
2282
  const endTime = Date.now();
1344
- const elapsedTime = showTimingForHumans(endTime - startTime);
1345
- logger.info(`⏱️ Tiempo total de compilación: ${elapsedTime}\n`); // Mostrar resumen de compilación
1346
- await displayCompilationSummary(env.VERBOSE === 'true');
2283
+ const elapsedTime = showTimingForHumans(endTime - startTime); // Finalizar progreso
2284
+ progressManager.endProgress();
2285
+ // Mostrar resumen de compilación con tiempo total
2286
+ await displayCompilationSummary(env.VERBOSE === 'true', elapsedTime);
1347
2287
  }
1348
2288
  catch (error) {
2289
+ // Asegurar que el progreso termine en caso de error
2290
+ const progressManager = ProgressManager.getInstance();
2291
+ if (progressManager.isActive()) {
2292
+ progressManager.endProgress();
2293
+ }
1349
2294
  const errorMessage = error instanceof Error ? error.message : String(error);
1350
2295
  logger.error(`🚩 Error al compilar todos los archivos: ${errorMessage}`);
1351
2296
  // Registrar el error en el sistema unificado
1352
- await handleCompilationError(error instanceof Error ? error : new Error(String(error)), 'compilación general', 'all', 'all', env.VERBOSE === 'true');
1353
- // Mostrar resumen incluso si hay errores generales
2297
+ await handleCompilationError(error instanceof Error ? error : new Error(String(error)), 'compilación general', 'all', 'all', env.VERBOSE === 'true'); // Mostrar resumen incluso si hay errores generales
1354
2298
  await displayCompilationSummary(env.VERBOSE === 'true');
1355
2299
  }
1356
2300
  }
2301
+ /**
2302
+ * 🎨 Obtiene icono apropiado para cada etapa
2303
+ */
2304
+ function getStageIcon(stage) {
2305
+ const icons = {
2306
+ vue: '🎨',
2307
+ typescript: '📘',
2308
+ standardization: '💛',
2309
+ minification: '🗜️',
2310
+ tailwind: '🎨',
2311
+ 'file-read': '📖',
2312
+ default: '⚙️',
2313
+ };
2314
+ return icons[stage] ?? '⚙️';
2315
+ }
2316
+ /**
2317
+ * Crea una barra de progreso visual con porcentaje
2318
+ */
2319
+ function createProgressBarWithPercentage(percentage, width) {
2320
+ const filled = Math.round((percentage / 100) * width);
2321
+ const empty = width - filled;
2322
+ // Usar código directo para evitar problemas de importación
2323
+ const greenBar = '\x1b[32m' + '█'.repeat(filled) + '\x1b[0m';
2324
+ const grayBar = '\x1b[90m' + '░'.repeat(empty) + '\x1b[0m';
2325
+ return `${greenBar}${grayBar} ${percentage}%`;
2326
+ }
1357
2327
  // Función wrapper para compatibilidad con tests
1358
2328
  export async function compileFile(filePath) {
1359
2329
  return await initCompile(filePath, true, 'individual');