versacompiler 2.0.0 → 2.0.2

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.
@@ -1,7 +1,8 @@
1
+ import { createHash } from 'node:crypto';
1
2
  import { glob, mkdir, readFile, stat, unlink, writeFile, } from 'node:fs/promises';
2
3
  import os from 'node:os';
3
4
  import path from 'node:path';
4
- import { env } from 'node:process';
5
+ import { argv, cwd, env, stdout } from 'node:process';
5
6
  // Lazy loading optimizations - Only import lightweight modules synchronously
6
7
  import { logger } from '../servicios/logger.js';
7
8
  import { promptUser } from '../utils/promptUser.js';
@@ -16,157 +17,267 @@ let generateTailwindCSS;
16
17
  let estandarizaCode;
17
18
  let preCompileTS;
18
19
  let preCompileVue;
19
- // 🚀 Sistema de Carga Inteligente de Módulos - VERSIÓN OPTIMIZADA
20
- class ModuleManager {
20
+ // 🚀 Importar optimizador de transformaciones
21
+ let TransformOptimizer;
22
+ // 🚀 Importar optimizador de resolución de módulos
23
+ let ModuleResolutionOptimizer;
24
+ // 🚀 Sistema de Carga Inteligente de Módulos - VERSIÓN OPTIMIZADA V2
25
+ class OptimizedModuleManager {
21
26
  static instance;
22
27
  isInitialized = false;
23
- initializationPromise = null;
24
28
  loadedModules = new Set();
25
- // NUEVOS: Gestión de modo y precarga
26
- currentMode = null;
27
- preloadPromises = new Map();
28
- moduleCache = new Map();
29
- constructor() { }
29
+ // NUEVAS OPTIMIZACIONES
30
+ modulePool = new Map(); // Pool de instancias reutilizables
31
+ loadingPromises = new Map(); // Prevenir cargas duplicadas
32
+ usageStats = new Map(); // Estadísticas de uso
33
+ preloadQueue = new Set(); // Cola de precarga
34
+ backgroundLoader = null; // Cargador en background
35
+ // Módulos críticos que siempre se precargan
36
+ HOT_MODULES = ['chalk', 'parser'];
37
+ // Contexto actual para optimizar cargas
38
+ currentContext = null;
39
+ constructor() {
40
+ // Iniciar precarga en background inmediatamente
41
+ this.startBackgroundPreloading();
42
+ }
30
43
  static getInstance() {
31
- if (!ModuleManager.instance) {
32
- ModuleManager.instance = new ModuleManager();
44
+ if (!OptimizedModuleManager.instance) {
45
+ OptimizedModuleManager.instance = new OptimizedModuleManager();
33
46
  }
34
- return ModuleManager.instance;
47
+ return OptimizedModuleManager.instance;
35
48
  }
36
49
  /**
37
- * NUEVO: Precarga estratégica para modo watch
50
+ * NUEVO: Precarga en background para módulos críticos
38
51
  */
39
- async preloadForWatchMode() {
40
- const essentialModules = ['chalk', 'parser', 'transforms'];
41
- const preloadPromises = essentialModules.map(async (moduleName) => {
42
- if (!this.loadedModules.has(moduleName)) {
43
- switch (moduleName) {
44
- case 'chalk':
45
- return this._loadModule('chalk', loadChalk);
46
- case 'parser':
47
- return this._loadModule('parser', loadParser);
48
- case 'transforms':
49
- return this._loadModule('transforms', loadTransforms);
50
- }
51
- }
52
- });
53
- await Promise.all(preloadPromises);
54
- // console.log('[ModuleManager] Precarga completada para modo watch');
55
- } /**
56
- * MEJORADO: Inicializa los módulos necesarios según el contexto de compilación
57
- * @param context Contexto de compilación: 'individual', 'batch', 'watch'
58
- * @param fileExtensions Extensiones de archivos a compilar para optimizar la carga
52
+ startBackgroundPreloading() {
53
+ this.backgroundLoader = this.preloadCriticalModules();
54
+ }
55
+ /**
56
+ * NUEVO: Precarga módulos críticos en background
59
57
  */
60
- async initializeModules(context = 'individual', fileExtensions = new Set()) {
61
- // Si cambia el contexto, reinicializar
62
- if (this.currentMode !== context) {
63
- this.currentMode = context;
64
- // En modo watch, precargar módulos esenciales
65
- if (context === 'watch') {
66
- await this.preloadForWatchMode();
67
- }
68
- }
69
- if (this.initializationPromise) {
70
- return this.initializationPromise;
58
+ async preloadCriticalModules() {
59
+ try {
60
+ // Precargar módulos críticos de forma asíncrona
61
+ const preloadPromises = this.HOT_MODULES.map(moduleName => this.ensureModuleLoaded(moduleName).catch(() => {
62
+ // Silenciar errores de precarga, se intentará cargar después
63
+ }));
64
+ await Promise.allSettled(preloadPromises);
71
65
  }
72
- if (this.isInitialized && context !== 'individual') {
73
- return;
66
+ catch {
67
+ // Fallos en precarga no deben afectar la funcionalidad principal
74
68
  }
75
- this.initializationPromise = this._performInitialization(context, fileExtensions);
76
- await this.initializationPromise;
77
- this.initializationPromise = null;
78
69
  }
79
- async _performInitialization(context, fileExtensions) {
80
- const modulesToLoad = [];
81
- // Módulos siempre necesarios
82
- if (!this.loadedModules.has('chalk')) {
83
- modulesToLoad.push(this._loadModule('chalk', loadChalk));
84
- }
85
- if (!this.loadedModules.has('parser')) {
86
- modulesToLoad.push(this._loadModule('parser', loadParser));
87
- }
88
- if (!this.loadedModules.has('transforms')) {
89
- modulesToLoad.push(this._loadModule('transforms', loadTransforms));
70
+ /**
71
+ * MEJORADO: Precarga contextual basada en tipos de archivo
72
+ */
73
+ async preloadForContext(context, fileTypes = new Set()) {
74
+ this.currentContext = context;
75
+ // Esperar que termine la precarga crítica si está en progreso
76
+ if (this.backgroundLoader) {
77
+ await this.backgroundLoader;
90
78
  }
91
- // Carga contextual según el tipo de compilación
79
+ const toPreload = []; // Precarga basada en contexto
92
80
  if (context === 'batch' || context === 'watch') {
93
- // En modo batch/watch, precargar todos los módulos necesarios
94
- if (!this.loadedModules.has('vue')) {
95
- modulesToLoad.push(this._loadModule('vue', loadVue));
96
- }
97
- if (!this.loadedModules.has('typescript')) {
98
- modulesToLoad.push(this._loadModule('typescript', loadTypeScript));
99
- }
100
- if (!this.loadedModules.has('minify')) {
101
- modulesToLoad.push(this._loadModule('minify', loadMinify));
102
- }
81
+ // En batch/watch, precargar todos los módulos comunes
82
+ toPreload.push('transforms', 'vue', 'typescript', 'module-resolution-optimizer');
103
83
  }
104
84
  else {
105
- // En modo individual, cargar solo según las extensiones necesarias
106
- if (fileExtensions.has('.vue') && !this.loadedModules.has('vue')) {
107
- modulesToLoad.push(this._loadModule('vue', loadVue));
108
- }
109
- if ((fileExtensions.has('.ts') || fileExtensions.has('.vue')) &&
110
- !this.loadedModules.has('typescript')) {
111
- modulesToLoad.push(this._loadModule('typescript', loadTypeScript));
112
- }
113
- if (env.isPROD === 'true' && !this.loadedModules.has('minify')) {
114
- modulesToLoad.push(this._loadModule('minify', loadMinify));
115
- }
116
- }
117
- // Cargar módulos en paralelo
118
- await Promise.all(modulesToLoad);
119
- this.isInitialized = true;
120
- }
121
- async _loadModule(name, loadFunction) {
122
- if (!this.loadedModules.has(name)) {
123
- await loadFunction();
124
- this.loadedModules.add(name);
85
+ // En individual, cargar solo según tipos de archivo detectados
86
+ if (fileTypes.has('.vue'))
87
+ toPreload.push('vue');
88
+ if (fileTypes.has('.ts') || fileTypes.has('.vue'))
89
+ toPreload.push('typescript');
90
+ if (!this.loadedModules.has('transforms'))
91
+ toPreload.push('transforms');
125
92
  }
93
+ // Precargar en paralelo
94
+ const preloadPromises = toPreload.map(moduleName => this.ensureModuleLoaded(moduleName).catch(() => {
95
+ // Log warning pero no fallar
96
+ console.warn(`Warning: No se pudo precargar módulo ${moduleName}`);
97
+ }));
98
+ await Promise.allSettled(preloadPromises);
126
99
  }
127
100
  /**
128
- * Carga un módulo específico bajo demanda (lazy loading)
101
+ * MEJORADO: Carga inteligente con pooling y deduplicación
129
102
  */
130
103
  async ensureModuleLoaded(moduleName) {
131
- if (this.loadedModules.has(moduleName)) {
132
- return;
104
+ // 1. Verificar pool de módulos primero
105
+ if (this.modulePool.has(moduleName)) {
106
+ this.updateUsageStats(moduleName);
107
+ return this.modulePool.get(moduleName);
108
+ }
109
+ // 2. Verificar si ya está cargando (deduplicación)
110
+ if (this.loadingPromises.has(moduleName)) {
111
+ return this.loadingPromises.get(moduleName);
112
+ }
113
+ // 3. Iniciar carga
114
+ const loadPromise = this.loadModuleInternal(moduleName);
115
+ this.loadingPromises.set(moduleName, loadPromise);
116
+ try {
117
+ const moduleInstance = await loadPromise;
118
+ // 4. Almacenar en pool y estadísticas
119
+ this.modulePool.set(moduleName, moduleInstance);
120
+ this.loadedModules.add(moduleName);
121
+ this.updateUsageStats(moduleName);
122
+ return moduleInstance;
133
123
  }
124
+ finally {
125
+ // 5. Limpiar promesa de carga
126
+ this.loadingPromises.delete(moduleName);
127
+ }
128
+ }
129
+ /**
130
+ * ✨ NUEVO: Actualiza estadísticas de uso para optimizaciones futuras
131
+ */
132
+ updateUsageStats(moduleName) {
133
+ const currentCount = this.usageStats.get(moduleName) || 0;
134
+ this.usageStats.set(moduleName, currentCount + 1);
135
+ }
136
+ /**
137
+ * ✨ MEJORADO: Carga interna de módulos con mejor manejo de errores
138
+ */ async loadModuleInternal(moduleName) {
134
139
  switch (moduleName) {
140
+ case 'chalk':
141
+ return this.loadChalk();
142
+ case 'parser':
143
+ return this.loadParser();
144
+ case 'transforms':
145
+ return this.loadTransforms();
135
146
  case 'vue':
136
- await this._loadModule('vue', loadVue);
137
- break;
147
+ return this.loadVue();
138
148
  case 'typescript':
139
- await this._loadModule('typescript', loadTypeScript);
140
- break;
149
+ return this.loadTypeScript();
141
150
  case 'minify':
142
- await this._loadModule('minify', loadMinify);
143
- break;
151
+ return this.loadMinify();
144
152
  case 'tailwind':
145
- await this._loadModule('tailwind', loadTailwind);
146
- break;
153
+ return this.loadTailwind();
147
154
  case 'linter':
148
- await this._loadModule('linter', loadLinter);
149
- break;
155
+ return this.loadLinter();
156
+ case 'transform-optimizer':
157
+ return this.loadTransformOptimizer();
158
+ case 'module-resolution-optimizer':
159
+ return this.loadModuleResolutionOptimizer();
150
160
  default:
151
161
  throw new Error(`Módulo desconocido: ${moduleName}`);
152
162
  }
153
163
  }
164
+ // ✨ Métodos de carga específicos optimizados
165
+ async loadChalk() {
166
+ if (!chalk) {
167
+ chalk = (await import('chalk')).default;
168
+ }
169
+ return chalk;
170
+ }
171
+ async loadParser() {
172
+ if (!getCodeFile) {
173
+ const parserModule = await import('./parser.js');
174
+ getCodeFile = parserModule.getCodeFile;
175
+ }
176
+ return getCodeFile;
177
+ }
178
+ async loadTransforms() {
179
+ if (!estandarizaCode) {
180
+ const transformsModule = await import('./transforms.js');
181
+ estandarizaCode = transformsModule.estandarizaCode;
182
+ }
183
+ return estandarizaCode;
184
+ }
185
+ async loadVue() {
186
+ if (!preCompileVue) {
187
+ const vueModule = await import('./vuejs.js');
188
+ preCompileVue = vueModule.preCompileVue;
189
+ }
190
+ return preCompileVue;
191
+ }
192
+ async loadTypeScript() {
193
+ if (!preCompileTS) {
194
+ const typescriptModule = await import('./typescript-manager.js');
195
+ preCompileTS = typescriptModule.preCompileTS;
196
+ }
197
+ return preCompileTS;
198
+ }
199
+ async loadMinify() {
200
+ if (!minifyJS) {
201
+ const minifyModule = await import('./minify.js');
202
+ minifyJS = minifyModule.minifyJS;
203
+ }
204
+ return minifyJS;
205
+ }
206
+ async loadTailwind() {
207
+ if (!generateTailwindCSS) {
208
+ const tailwindModule = await import('./tailwindcss.js');
209
+ generateTailwindCSS = tailwindModule.generateTailwindCSS;
210
+ }
211
+ return generateTailwindCSS;
212
+ }
213
+ async loadLinter() {
214
+ if (!ESLint || !OxLint) {
215
+ const linterModule = await import('./linter.js');
216
+ ESLint = linterModule.ESLint;
217
+ OxLint = linterModule.OxLint;
218
+ }
219
+ return { ESLint, OxLint };
220
+ }
221
+ async loadTransformOptimizer() {
222
+ if (!TransformOptimizer) {
223
+ const transformModule = await import('./transform-optimizer.js');
224
+ TransformOptimizer =
225
+ transformModule.TransformOptimizer.getInstance();
226
+ }
227
+ return TransformOptimizer;
228
+ }
229
+ async loadModuleResolutionOptimizer() {
230
+ if (!ModuleResolutionOptimizer) {
231
+ const resolutionModule = await import('./module-resolution-optimizer.js');
232
+ ModuleResolutionOptimizer =
233
+ resolutionModule.ModuleResolutionOptimizer.getInstance();
234
+ }
235
+ return ModuleResolutionOptimizer;
236
+ }
237
+ /**
238
+ * ✨ NUEVO: Obtiene estadísticas de performance del manager
239
+ */
240
+ getPerformanceStats() {
241
+ const sortedByUsage = Array.from(this.usageStats.entries())
242
+ .sort((a, b) => b[1] - a[1])
243
+ .slice(0, 5)
244
+ .map(([name]) => name);
245
+ return {
246
+ loadedModules: Array.from(this.loadedModules),
247
+ usageStats: Object.fromEntries(this.usageStats),
248
+ poolSize: this.modulePool.size,
249
+ loadingInProgress: Array.from(this.loadingPromises.keys()),
250
+ mostUsedModules: sortedByUsage,
251
+ };
252
+ }
253
+ /**
254
+ * ✨ NUEVO: Limpia módulos no utilizados para liberar memoria
255
+ */
256
+ cleanupUnusedModules() {
257
+ const threshold = 1; // Mínimo de usos para mantener en pool
258
+ for (const [moduleName, usageCount] of this.usageStats) {
259
+ if (usageCount < threshold &&
260
+ !this.HOT_MODULES.includes(moduleName)) {
261
+ this.modulePool.delete(moduleName);
262
+ this.loadedModules.delete(moduleName);
263
+ this.usageStats.delete(moduleName);
264
+ }
265
+ }
266
+ }
154
267
  /**
155
268
  * Resetea el estado del manager (útil para tests)
156
269
  */
157
270
  reset() {
158
271
  this.isInitialized = false;
159
- this.initializationPromise = null;
160
272
  this.loadedModules.clear();
161
- }
162
- /**
163
- * Obtiene estadísticas de módulos cargados
164
- */
165
- getStats() {
166
- return {
167
- loaded: Array.from(this.loadedModules),
168
- initialized: this.isInitialized,
169
- };
273
+ this.modulePool.clear();
274
+ this.loadingPromises.clear();
275
+ this.usageStats.clear();
276
+ this.preloadQueue.clear();
277
+ this.currentContext = null;
278
+ this.backgroundLoader = null;
279
+ // Reiniciar precarga crítica
280
+ this.startBackgroundPreloading();
170
281
  }
171
282
  }
172
283
  // Lazy loading helper functions
@@ -214,7 +325,7 @@ async function loadTransforms() {
214
325
  }
215
326
  async function loadTypeScript() {
216
327
  if (!preCompileTS) {
217
- const typescriptModule = await import('./typescript.js');
328
+ const typescriptModule = await import('./typescript-manager.js');
218
329
  preCompileTS = typescriptModule.preCompileTS;
219
330
  }
220
331
  return preCompileTS;
@@ -229,40 +340,531 @@ async function loadVue() {
229
340
  // Almacenamiento global de errores y resultados
230
341
  const compilationErrors = [];
231
342
  const compilationResults = [];
232
- const compilationCache = new Map();
233
- const CACHE_DIR = path.join(path.resolve(env.PATH_PROY || process.cwd(), 'compiler'), '.cache');
234
- const CACHE_FILE = path.join(CACHE_DIR, 'versacompile-cache.json');
235
- async function loadCache() {
236
- try {
237
- if (env.cleanCache === 'true') {
238
- compilationCache.clear();
343
+ // Variables de entorno relevantes para compilación
344
+ const COMPILATION_ENV_VARS = [
345
+ 'NODE_ENV',
346
+ 'isPROD',
347
+ 'TAILWIND',
348
+ 'ENABLE_LINTER',
349
+ 'VERBOSE',
350
+ 'typeCheck',
351
+ 'PATH_ALIAS',
352
+ 'tailwindcss',
353
+ 'linter',
354
+ 'tsconfigFile',
355
+ ];
356
+ class SmartCompilationCache {
357
+ cache = new Map();
358
+ maxEntries = 500; // Máximo archivos en cache
359
+ maxMemory = 100 * 1024 * 1024; // 100MB límite
360
+ currentMemoryUsage = 0;
361
+ // ✨ ISSUE #3: Sistema de vigilancia de dependencias
362
+ fileWatchers = new Map(); // chokidar watchers
363
+ dependencyGraph = new Map(); // archivo -> dependencias
364
+ reverseDependencyGraph = new Map(); // dependencia -> archivos que la usan
365
+ packageJsonPath = path.join(cwd(), 'package.json');
366
+ nodeModulesPath = path.join(cwd(), 'node_modules');
367
+ isWatchingDependencies = false;
368
+ /**
369
+ * Genera hash SHA-256 del contenido del archivo
370
+ */ async generateContentHash(filePath) {
371
+ try {
372
+ const content = await readFile(filePath, 'utf8');
373
+ return createHash('sha256').update(content).digest('hex');
374
+ }
375
+ catch {
376
+ // Si no se puede leer el archivo, generar hash único basado en la ruta y timestamp
377
+ const fallback = `${filePath}-${Date.now()}`;
378
+ return createHash('sha256').update(fallback).digest('hex');
379
+ }
380
+ }
381
+ /**
382
+ * Genera hash de la configuración del compilador
383
+ */
384
+ generateConfigHash() {
385
+ try {
386
+ // Recopilar configuración relevante de variables de entorno
387
+ const config = {
388
+ isPROD: env.isPROD || 'false',
389
+ TAILWIND: env.TAILWIND || 'false',
390
+ ENABLE_LINTER: env.ENABLE_LINTER || 'false',
391
+ PATH_ALIAS: env.PATH_ALIAS || '{}',
392
+ tailwindcss: env.tailwindcss || 'false',
393
+ linter: env.linter || 'false',
394
+ tsconfigFile: env.tsconfigFile || './tsconfig.json',
395
+ };
396
+ const configStr = JSON.stringify(config, Object.keys(config).sort());
397
+ return createHash('sha256')
398
+ .update(configStr)
399
+ .digest('hex')
400
+ .substring(0, 12);
401
+ }
402
+ catch {
403
+ return 'no-config';
404
+ }
405
+ }
406
+ /**
407
+ * Genera hash de variables de entorno relevantes
408
+ */
409
+ generateEnvHash() {
410
+ try {
411
+ const envVars = COMPILATION_ENV_VARS.map(key => `${key}=${env[key] || ''}`).join('|');
412
+ return createHash('sha256')
413
+ .update(envVars)
414
+ .digest('hex')
415
+ .substring(0, 12);
416
+ }
417
+ catch {
418
+ return 'no-env';
419
+ }
420
+ } /**
421
+ * ✨ ISSUE #3: Genera hash avanzado de dependencias del proyecto
422
+ * Incluye vigilancia de package.json, node_modules y versiones instaladas
423
+ */
424
+ async generateDependencyHash() {
425
+ try {
426
+ const hash = createHash('sha256');
427
+ // 1. Hash del package.json con versiones
428
+ const packagePath = path.join(cwd(), 'package.json');
429
+ const packageContent = await readFile(packagePath, 'utf8');
430
+ const pkg = JSON.parse(packageContent);
431
+ const deps = {
432
+ ...pkg.dependencies,
433
+ ...pkg.devDependencies,
434
+ };
435
+ const depsStr = JSON.stringify(deps, Object.keys(deps).sort());
436
+ hash.update(`package:${depsStr}`);
437
+ // 2. Hash del package-lock.json si existe (versiones exactas instaladas)
239
438
  try {
240
- await unlink(CACHE_FILE);
439
+ const lockPath = path.join(cwd(), 'package-lock.json');
440
+ const lockContent = await readFile(lockPath, 'utf8');
441
+ const lockData = JSON.parse(lockContent);
442
+ // Solo incluir las versiones instaladas, no todo el lockfile
443
+ const installedVersions = {};
444
+ if (lockData.packages) {
445
+ for (const [pkgPath, pkgInfo] of Object.entries(lockData.packages)) {
446
+ if (pkgPath &&
447
+ pkgPath !== '' &&
448
+ typeof pkgInfo === 'object' &&
449
+ pkgInfo !== null) {
450
+ const pkgName = pkgPath.replace('node_modules/', '');
451
+ if (pkgInfo.version) {
452
+ installedVersions[pkgName] = pkgInfo.version;
453
+ }
454
+ }
455
+ }
456
+ }
457
+ hash.update(`lock:${JSON.stringify(installedVersions, Object.keys(installedVersions).sort())}`);
241
458
  }
242
459
  catch {
243
- // Ignorar errores al eliminar el archivo
460
+ // Ignorar si no existe package-lock.json
244
461
  }
462
+ // 3. ✨ NUEVO: Hash de timestamps críticos de node_modules
463
+ try {
464
+ const nodeModulesPath = path.join(cwd(), 'node_modules');
465
+ const nodeModulesStat = await stat(nodeModulesPath);
466
+ hash.update(`nmtime:${nodeModulesStat.mtimeMs}`);
467
+ // Verificar timestamps de dependencias críticas instaladas
468
+ const criticalDeps = Object.keys(deps).slice(0, 10); // Top 10 para performance
469
+ for (const dep of criticalDeps) {
470
+ try {
471
+ const depPath = path.join(nodeModulesPath, dep);
472
+ const depStat = await stat(depPath);
473
+ hash.update(`${dep}:${depStat.mtimeMs}`);
474
+ }
475
+ catch {
476
+ // Dependencia no instalada o error
477
+ hash.update(`${dep}:missing`);
478
+ }
479
+ }
480
+ }
481
+ catch {
482
+ // node_modules no existe
483
+ hash.update('nmtime:none');
484
+ }
485
+ return hash.digest('hex').substring(0, 16);
245
486
  }
246
- const cacheData = await readFile(CACHE_FILE, 'utf-8');
247
- const parsed = JSON.parse(cacheData);
248
- for (const [key, value] of Object.entries(parsed)) {
249
- compilationCache.set(key, value);
487
+ catch (error) {
488
+ // Incluir información del error en el hash para debugging
489
+ return createHash('sha256')
490
+ .update(`error:${error instanceof Error ? error.message : 'unknown'}`)
491
+ .digest('hex')
492
+ .substring(0, 16);
250
493
  }
251
494
  }
252
- catch {
253
- // Cache file doesn't exist or is invalid, start fresh
495
+ /**
496
+ * Genera clave de cache granular que incluye todos los factores
497
+ */
498
+ async generateCacheKey(filePath) {
499
+ const contentHash = await this.generateContentHash(filePath);
500
+ const configHash = this.generateConfigHash();
501
+ const envHash = this.generateEnvHash();
502
+ const dependencyHash = await this.generateDependencyHash();
503
+ // Usar | como separador para evitar problemas con rutas de Windows
504
+ return `${filePath}|${contentHash.substring(0, 12)}|${configHash}|${envHash}|${dependencyHash}`;
505
+ } /**
506
+ * Verifica si una entrada de cache es válida
507
+ */
508
+ async isValid(filePath) {
509
+ const entry = this.cache.get(filePath);
510
+ if (!entry)
511
+ return false;
512
+ try {
513
+ // Verificar si el archivo de salida existe
514
+ await stat(entry.outputPath);
515
+ // Verificar si el contenido ha cambiado
516
+ const currentContentHash = await this.generateContentHash(filePath);
517
+ if (entry.contentHash !== currentContentHash) {
518
+ this.cache.delete(filePath);
519
+ return false;
520
+ }
521
+ // Verificar si la configuración ha cambiado
522
+ const currentConfigHash = this.generateConfigHash();
523
+ if (entry.configHash !== currentConfigHash) {
524
+ this.cache.delete(filePath);
525
+ return false;
526
+ }
527
+ // Verificar si las variables de entorno han cambiado
528
+ const currentEnvHash = this.generateEnvHash();
529
+ if (entry.envHash !== currentEnvHash) {
530
+ this.cache.delete(filePath);
531
+ return false;
532
+ }
533
+ // Verificar si las dependencias han cambiado
534
+ const currentDependencyHash = await this.generateDependencyHash();
535
+ if (entry.dependencyHash !== currentDependencyHash) {
536
+ this.cache.delete(filePath);
537
+ return false;
538
+ }
539
+ // Verificar tiempo de modificación como backup
540
+ const stats = await stat(filePath);
541
+ if (stats.mtimeMs > entry.mtime) {
542
+ this.cache.delete(filePath);
543
+ return false;
544
+ }
545
+ // Actualizar tiempo de uso para LRU
546
+ entry.lastUsed = Date.now();
547
+ return true;
548
+ }
549
+ catch {
550
+ // Si hay error verificando, eliminar del cache
551
+ this.cache.delete(filePath);
552
+ return false;
553
+ }
554
+ } /**
555
+ * Añade una entrada al cache
556
+ */
557
+ async set(filePath, outputPath) {
558
+ try {
559
+ const stats = await stat(filePath);
560
+ const contentHash = await this.generateContentHash(filePath);
561
+ const configHash = this.generateConfigHash();
562
+ const envHash = this.generateEnvHash();
563
+ const dependencyHash = await this.generateDependencyHash();
564
+ const entry = {
565
+ contentHash,
566
+ configHash,
567
+ envHash,
568
+ dependencyHash,
569
+ mtime: stats.mtimeMs,
570
+ outputPath,
571
+ lastUsed: Date.now(),
572
+ size: stats.size,
573
+ };
574
+ // Aplicar límites de memoria y entradas antes de agregar
575
+ this.evictIfNeeded(entry.size);
576
+ this.cache.set(filePath, entry);
577
+ this.currentMemoryUsage += entry.size;
578
+ }
579
+ catch (error) {
580
+ // Si hay error, no cachear
581
+ console.warn(`Warning: No se pudo cachear ${filePath}:`, error);
582
+ }
254
583
  }
255
- }
256
- async function saveCache() {
257
- try {
258
- await mkdir(CACHE_DIR, { recursive: true });
259
- const cacheData = Object.fromEntries(compilationCache);
260
- await writeFile(CACHE_FILE, JSON.stringify(cacheData, null, 2));
584
+ /**
585
+ * Aplica política LRU para liberar espacio
586
+ */
587
+ evictIfNeeded(newEntrySize) {
588
+ // Verificar límite de entradas
589
+ while (this.cache.size >= this.maxEntries) {
590
+ this.evictLRU();
591
+ }
592
+ // Verificar límite de memoria
593
+ while (this.currentMemoryUsage + newEntrySize > this.maxMemory &&
594
+ this.cache.size > 0) {
595
+ this.evictLRU();
596
+ }
597
+ }
598
+ /**
599
+ * Elimina la entrada menos recientemente usada
600
+ */
601
+ evictLRU() {
602
+ let oldestKey = '';
603
+ let oldestTime = Infinity;
604
+ for (const [key, entry] of this.cache) {
605
+ if (entry.lastUsed < oldestTime) {
606
+ oldestTime = entry.lastUsed;
607
+ oldestKey = key;
608
+ }
609
+ }
610
+ if (oldestKey) {
611
+ const entry = this.cache.get(oldestKey);
612
+ if (entry) {
613
+ this.currentMemoryUsage -= entry.size;
614
+ this.cache.delete(oldestKey);
615
+ }
616
+ }
617
+ }
618
+ /**
619
+ * Carga el cache desde disco
620
+ */
621
+ async load(cacheFile) {
622
+ try {
623
+ if (env.cleanCache === 'true') {
624
+ this.cache.clear();
625
+ this.currentMemoryUsage = 0;
626
+ try {
627
+ await unlink(cacheFile);
628
+ }
629
+ catch {
630
+ // Ignorar errores al eliminar el archivo
631
+ }
632
+ return;
633
+ }
634
+ const cacheData = await readFile(cacheFile, 'utf-8');
635
+ const parsed = JSON.parse(cacheData);
636
+ // Validar y cargar entradas del cache
637
+ for (const [key, value] of Object.entries(parsed)) {
638
+ const entry = value;
639
+ if (entry.contentHash && entry.outputPath && entry.mtime) {
640
+ this.cache.set(key, entry);
641
+ this.currentMemoryUsage += entry.size || 0;
642
+ }
643
+ }
644
+ }
645
+ catch {
646
+ // Cache file doesn't exist or is invalid, start fresh
647
+ this.cache.clear();
648
+ this.currentMemoryUsage = 0;
649
+ }
650
+ }
651
+ /**
652
+ * Guarda el cache a disco
653
+ */
654
+ async save(cacheFile, cacheDir) {
655
+ try {
656
+ await mkdir(cacheDir, { recursive: true });
657
+ const cacheData = Object.fromEntries(this.cache);
658
+ await writeFile(cacheFile, JSON.stringify(cacheData, null, 2));
659
+ }
660
+ catch (error) {
661
+ console.warn('Warning: No se pudo guardar el cache:', error);
662
+ }
261
663
  }
262
- catch {
263
- // Ignore save errors
664
+ /**
665
+ * Limpia completamente el cache
666
+ */
667
+ clear() {
668
+ this.cache.clear();
669
+ this.currentMemoryUsage = 0;
670
+ } /**
671
+ * Obtiene la ruta de salida para un archivo cacheado
672
+ */
673
+ getOutputPath(filePath) {
674
+ const entry = this.cache.get(filePath);
675
+ return entry?.outputPath || '';
676
+ } /**
677
+ * Obtiene estadísticas del cache
678
+ */
679
+ getStats() {
680
+ return {
681
+ entries: this.cache.size,
682
+ memoryUsage: this.currentMemoryUsage,
683
+ hitRate: 0, // Se calculará externamente
684
+ };
685
+ }
686
+ // ✨ ISSUE #3: Métodos de vigilancia y invalidación cascada
687
+ /**
688
+ * Inicializa vigilancia de package.json y node_modules
689
+ */
690
+ async startDependencyWatching() {
691
+ if (this.isWatchingDependencies)
692
+ return;
693
+ try {
694
+ // Lazy load chokidar para evitar problemas de importación
695
+ const chokidar = await import('chokidar');
696
+ // Vigilar package.json
697
+ if (await this.fileExists(this.packageJsonPath)) {
698
+ const packageWatcher = chokidar.watch(this.packageJsonPath, {
699
+ persistent: false, // No mantener el proceso vivo
700
+ ignoreInitial: true,
701
+ });
702
+ packageWatcher.on('change', () => {
703
+ logger.info('📦 package.json modificado - invalidando cache de dependencias');
704
+ this.invalidateByDependencyChange();
705
+ });
706
+ this.fileWatchers.set('package.json', packageWatcher);
707
+ }
708
+ // Vigilar node_modules (solo cambios en el directorio raíz para performance)
709
+ if (await this.fileExists(this.nodeModulesPath)) {
710
+ const nodeModulesWatcher = chokidar.watch(this.nodeModulesPath, {
711
+ persistent: false,
712
+ ignoreInitial: true,
713
+ depth: 1, // Solo primer nivel para performance
714
+ ignored: /(^|[\/\\])\../, // Ignorar archivos ocultos
715
+ });
716
+ nodeModulesWatcher.on('addDir', (path) => {
717
+ logger.info(`📦 Nueva dependencia instalada: ${path.split(/[/\\]/).pop()}`);
718
+ this.invalidateByDependencyChange();
719
+ });
720
+ nodeModulesWatcher.on('unlinkDir', (path) => {
721
+ logger.info(`📦 Dependencia eliminada: ${path.split(/[/\\]/).pop()}`);
722
+ this.invalidateByDependencyChange();
723
+ });
724
+ this.fileWatchers.set('node_modules', nodeModulesWatcher);
725
+ }
726
+ this.isWatchingDependencies = true;
727
+ logger.info('🔍 Vigilancia de dependencias iniciada');
728
+ }
729
+ catch (error) {
730
+ logger.warn('⚠️ No se pudo iniciar vigilancia de dependencias:', error);
731
+ }
732
+ }
733
+ /**
734
+ * Detiene la vigilancia de dependencias
735
+ */
736
+ async stopDependencyWatching() {
737
+ for (const [name, watcher] of this.fileWatchers) {
738
+ try {
739
+ await watcher.close();
740
+ logger.info(`🛑 Vigilancia detenida: ${name}`);
741
+ }
742
+ catch (error) {
743
+ logger.warn(`⚠️ Error cerrando watcher ${name}:`, error);
744
+ }
745
+ }
746
+ this.fileWatchers.clear();
747
+ this.isWatchingDependencies = false;
748
+ }
749
+ /**
750
+ * Registra dependencias de un archivo para invalidación cascada
751
+ */
752
+ registerDependencies(filePath, dependencies) {
753
+ // Limpiar dependencias anteriores
754
+ const oldDeps = this.dependencyGraph.get(filePath);
755
+ if (oldDeps) {
756
+ for (const dep of oldDeps) {
757
+ const reverseDeps = this.reverseDependencyGraph.get(dep);
758
+ if (reverseDeps) {
759
+ reverseDeps.delete(filePath);
760
+ if (reverseDeps.size === 0) {
761
+ this.reverseDependencyGraph.delete(dep);
762
+ }
763
+ }
764
+ }
765
+ }
766
+ // Registrar nuevas dependencias
767
+ const newDeps = new Set(dependencies);
768
+ this.dependencyGraph.set(filePath, newDeps);
769
+ for (const dep of newDeps) {
770
+ if (!this.reverseDependencyGraph.has(dep)) {
771
+ this.reverseDependencyGraph.set(dep, new Set());
772
+ }
773
+ this.reverseDependencyGraph.get(dep).add(filePath);
774
+ }
775
+ }
776
+ /**
777
+ * Invalida cache por cambios en dependencias
778
+ */
779
+ invalidateByDependencyChange() {
780
+ let invalidatedCount = 0;
781
+ // Invalidar todos los archivos que dependen de dependencias externas
782
+ for (const [filePath] of this.cache) {
783
+ this.cache.delete(filePath);
784
+ invalidatedCount++;
785
+ }
786
+ // Limpiar grafos de dependencias
787
+ this.dependencyGraph.clear();
788
+ this.reverseDependencyGraph.clear();
789
+ this.currentMemoryUsage = 0;
790
+ logger.info(`🗑️ Cache invalidado: ${invalidatedCount} archivos (cambio en dependencias)`);
791
+ }
792
+ /**
793
+ * Invalida cascada cuando un archivo específico cambia
794
+ */
795
+ invalidateCascade(changedFile) {
796
+ const invalidated = [];
797
+ const toInvalidate = new Set([changedFile]);
798
+ // BFS para encontrar todos los archivos afectados
799
+ const queue = [changedFile];
800
+ while (queue.length > 0) {
801
+ const current = queue.shift();
802
+ const dependents = this.reverseDependencyGraph.get(current);
803
+ if (dependents) {
804
+ for (const dependent of dependents) {
805
+ if (!toInvalidate.has(dependent)) {
806
+ toInvalidate.add(dependent);
807
+ queue.push(dependent);
808
+ }
809
+ }
810
+ }
811
+ }
812
+ // Invalidar archivos
813
+ for (const filePath of toInvalidate) {
814
+ if (this.cache.has(filePath)) {
815
+ const entry = this.cache.get(filePath);
816
+ this.currentMemoryUsage -= entry.size;
817
+ this.cache.delete(filePath);
818
+ invalidated.push(filePath);
819
+ }
820
+ }
821
+ if (invalidated.length > 0) {
822
+ logger.info(`🔄 Invalidación cascada: ${invalidated.length} archivos afectados por ${changedFile}`);
823
+ }
824
+ return invalidated;
825
+ }
826
+ /**
827
+ * Verifica si un archivo existe
828
+ */
829
+ async fileExists(filePath) {
830
+ try {
831
+ await stat(filePath);
832
+ return true;
833
+ }
834
+ catch {
835
+ return false;
836
+ }
837
+ }
838
+ /**
839
+ * Obtiene estadísticas avanzadas del cache
840
+ */
841
+ getAdvancedStats() {
842
+ return {
843
+ entries: this.cache.size,
844
+ memoryUsage: this.currentMemoryUsage,
845
+ hitRate: 0,
846
+ dependencyNodes: this.dependencyGraph.size,
847
+ watchingDependencies: this.isWatchingDependencies,
848
+ activeWatchers: this.fileWatchers.size,
849
+ };
264
850
  }
265
851
  }
852
+ // Instancia global del cache inteligente
853
+ const smartCache = new SmartCompilationCache();
854
+ const CACHE_DIR = path.join(path.resolve(env.PATH_PROY || cwd(), 'compiler'), '.cache');
855
+ const CACHE_FILE = path.join(CACHE_DIR, 'versacompile-cache.json');
856
+ async function loadCache() {
857
+ await smartCache.load(CACHE_FILE);
858
+ // ✨ ISSUE #3: Iniciar vigilancia de dependencias en modo watch
859
+ if (env.WATCH_MODE === 'true' ||
860
+ argv.includes('--watch') ||
861
+ argv.includes('-w')) {
862
+ await smartCache.startDependencyWatching();
863
+ }
864
+ }
865
+ async function saveCache() {
866
+ await smartCache.save(CACHE_FILE, CACHE_DIR);
867
+ }
266
868
  // 🎯 Funciones del Sistema Unificado de Manejo de Errores
267
869
  /**
268
870
  * Registra un error de compilación en el sistema unificado
@@ -551,10 +1153,10 @@ class WatchModeOptimizer {
551
1153
  if (cached && cached.mtime >= stats.mtimeMs) {
552
1154
  resolve({ success: true, cached: true });
553
1155
  return;
554
- } // Configurar worker para modo watch
555
- const { TypeScriptWorkerManager } = await import('./typescript-worker.js');
556
- const workerManager = TypeScriptWorkerManager.getInstance();
557
- workerManager.setMode('watch');
1156
+ } // Configurar worker pool para modo watch
1157
+ const { TypeScriptWorkerPool } = await import('./typescript-worker-pool.js');
1158
+ const workerPool = TypeScriptWorkerPool.getInstance();
1159
+ workerPool.setMode('watch');
558
1160
  const result = await compileFn(filePath);
559
1161
  this.fileSystemCache.set(filePath, {
560
1162
  mtime: stats.mtimeMs,
@@ -579,9 +1181,8 @@ async function compileJS(inPath, outPath, mode = 'individual') {
579
1181
  // Si la ruta ya es absoluta, no la resolvamos de nuevo
580
1182
  inPath = path.isAbsolute(inPath)
581
1183
  ? normalizeRuta(inPath)
582
- : normalizeRuta(path.resolve(inPath));
583
- // 🚀 Usar ModuleManager para carga optimizada
584
- const moduleManager = ModuleManager.getInstance();
1184
+ : normalizeRuta(path.resolve(inPath)); // 🚀 Usar OptimizedModuleManager para carga optimizada
1185
+ const moduleManager = OptimizedModuleManager.getInstance();
585
1186
  // Timing de lectura
586
1187
  let start = Date.now();
587
1188
  const extension = path.extname(inPath);
@@ -729,9 +1330,6 @@ async function compileJS(inPath, outPath, mode = 'individual') {
729
1330
  const destinationDir = path.dirname(outPath);
730
1331
  await mkdir(destinationDir, { recursive: true });
731
1332
  await writeFile(outPath, code, 'utf-8');
732
- if (env.VERBOSE === 'true') {
733
- logger.info(`\n📊 Timings para ${path.basename(inPath)}:`, JSON.stringify(timings));
734
- }
735
1333
  return {
736
1334
  error: null,
737
1335
  action: 'extension',
@@ -740,11 +1338,11 @@ async function compileJS(inPath, outPath, mode = 'individual') {
740
1338
  export async function initCompile(ruta, compileTailwind = true, mode = 'individual') {
741
1339
  try {
742
1340
  // 🚀 Sistema de Carga Inteligente de Módulos
743
- const moduleManager = ModuleManager.getInstance();
1341
+ const moduleManager = OptimizedModuleManager.getInstance();
744
1342
  const fileExtension = path.extname(ruta);
745
1343
  const fileExtensions = new Set([fileExtension]);
746
1344
  // Inicializar módulos según el contexto
747
- await moduleManager.initializeModules(mode === 'all' ? 'batch' : mode, fileExtensions);
1345
+ await moduleManager.preloadForContext(mode === 'all' ? 'batch' : mode, fileExtensions);
748
1346
  // Generar TailwindCSS si está habilitado
749
1347
  if (compileTailwind && Boolean(env.TAILWIND)) {
750
1348
  await moduleManager.ensureModuleLoaded('tailwind');
@@ -950,28 +1548,7 @@ function createProgressBar(current, total, width = 30) {
950
1548
  }
951
1549
  // Función helper para verificar si un archivo debe ser omitido por cache
952
1550
  async function shouldSkipFile(filePath) {
953
- try {
954
- const stats = await stat(filePath);
955
- const cacheEntry = compilationCache.get(filePath);
956
- if (!cacheEntry) {
957
- return false; // No hay entrada en cache, debe compilarse
958
- }
959
- // Verificar si el archivo no ha cambiado desde la última compilación
960
- if (stats.mtimeMs <= cacheEntry.mtime) {
961
- // Verificar si el archivo de salida existe
962
- try {
963
- await stat(cacheEntry.outputPath);
964
- return true; // Archivo existe y no ha cambiado, se puede omitir
965
- }
966
- catch {
967
- return false; // Archivo de salida no existe, debe recompilarse
968
- }
969
- }
970
- return false; // Archivo ha cambiado, debe recompilarse
971
- }
972
- catch {
973
- return false; // Error al verificar, compilar por seguridad
974
- }
1551
+ return await smartCache.isValid(filePath);
975
1552
  }
976
1553
  // Función para compilar archivos con límite de concurrencia
977
1554
  async function compileWithConcurrencyLimit(files, maxConcurrency = 8) {
@@ -987,7 +1564,7 @@ async function compileWithConcurrencyLimit(files, maxConcurrency = 8) {
987
1564
  const progressPercent = Math.round((currentTotal / total) * 100);
988
1565
  if (progressPercent > lastProgressUpdate + 1 ||
989
1566
  currentTotal === total) {
990
- process.stdout.write(`\r🚀 ${progressBar} [✅ ${completed} | ⏭️ ${skipped} | ❌ ${failed}]`);
1567
+ stdout.write(`\r🚀 ${progressBar} [✅ ${completed} | ⏭️ ${skipped} | ❌ ${failed}]`);
991
1568
  lastProgressUpdate = progressPercent;
992
1569
  }
993
1570
  }
@@ -1001,18 +1578,12 @@ async function compileWithConcurrencyLimit(files, maxConcurrency = 8) {
1001
1578
  return {
1002
1579
  success: true,
1003
1580
  cached: true,
1004
- output: compilationCache.get(file)?.outputPath || '',
1581
+ output: smartCache.getOutputPath(file),
1005
1582
  };
1006
1583
  }
1007
- const result = await initCompile(file, false, 'batch');
1008
- // Actualizar cache si la compilación fue exitosa
1584
+ const result = await initCompile(file, false, 'batch'); // Actualizar cache si la compilación fue exitosa
1009
1585
  if (result.success && result.output) {
1010
- const stats = await stat(file);
1011
- compilationCache.set(file, {
1012
- hash: '', // Se podría implementar hash del contenido si es necesario
1013
- mtime: stats.mtimeMs,
1014
- outputPath: result.output,
1015
- });
1586
+ await smartCache.set(file, result.output);
1016
1587
  }
1017
1588
  completed++;
1018
1589
  showProgress();
@@ -1094,7 +1665,15 @@ export async function initCompileAll() {
1094
1665
  else {
1095
1666
  maxConcurrency = Math.min(cpuCount * 2, 16);
1096
1667
  }
1097
- logger.info(`🚀 Compilando ${fileCount} archivos con concurrencia optimizada (${maxConcurrency} hilos)...`);
1668
+ logger.info(`🚀 Compilando ${fileCount} archivos con concurrencia optimizada (${maxConcurrency} hilos)...`); // Configurar worker pool para modo batch
1669
+ try {
1670
+ const { TypeScriptWorkerPool } = await import('./typescript-worker-pool.js');
1671
+ const workerPool = TypeScriptWorkerPool.getInstance();
1672
+ workerPool.setMode('batch');
1673
+ }
1674
+ catch {
1675
+ // Error silencioso en configuración del pool
1676
+ }
1098
1677
  await compileWithConcurrencyLimit(filesToCompile, maxConcurrency);
1099
1678
  // Guardar cache al final
1100
1679
  await saveCache();