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.
- package/dist/compiler/compile.js +1086 -116
- package/dist/compiler/module-resolution-optimizer.js +17 -40
- package/dist/compiler/transform-optimizer.js +111 -6
- package/dist/compiler/transforms.js +3 -31
- package/dist/compiler/typescript-error-parser.js +1 -1
- package/dist/compiler/typescript-sync-validator.js +1 -1
- package/dist/compiler/typescript-worker-pool.js +387 -24
- package/dist/compiler/typescript-worker-thread.cjs +1 -2
- package/dist/hrm/getInstanciaVue.js +1 -1
- package/dist/hrm/initHRM.js +1 -1
- package/dist/main.js +64 -17
- package/dist/servicios/browserSync.js +1 -1
- package/dist/servicios/file-watcher.js +85 -22
- package/dist/servicios/logger.js +36 -7
- package/dist/servicios/readConfig.js +1 -1
- package/dist/utils/excluded-modules.js +36 -0
- package/dist/utils/module-resolver.js +4 -33
- package/package.json +2 -1
package/dist/compiler/compile.js
CHANGED
|
@@ -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
|
|
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 =
|
|
346
|
-
maxMemory =
|
|
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
|
|
373
|
-
if (entry.contentHash !==
|
|
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
|
-
|
|
625
|
-
|
|
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('
|
|
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
|
-
|
|
634
|
-
const
|
|
635
|
-
const
|
|
636
|
-
const
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
logger.info(
|
|
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
|
-
|
|
686
|
-
|
|
687
|
-
logger.info(
|
|
688
|
-
logger.info(
|
|
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('🚨
|
|
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.
|
|
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
|
-
|
|
698
|
-
|
|
699
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1092
|
-
|
|
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
|
-
|
|
1111
|
-
|
|
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
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
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.
|
|
1156
|
-
? `Regla Oxlint: ${result.
|
|
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;
|
|
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
|
-
|
|
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
|
-
|
|
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');
|
|
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
|
-
|
|
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
|
-
|
|
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`);
|
|
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
|
-
}
|
|
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
|
-
|
|
1323
|
-
|
|
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
|
|
2262
|
+
maxConcurrency = Math.min(cpuCount, 6); // Reducido
|
|
1327
2263
|
}
|
|
1328
2264
|
else {
|
|
1329
|
-
maxConcurrency = Math.min(cpuCount
|
|
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
|
-
|
|
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
|
-
|
|
1346
|
-
|
|
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');
|