versacompiler 2.3.5 → 2.4.0

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.
@@ -15,6 +15,7 @@ export class IntegrityValidator {
15
15
  static instance;
16
16
  cache = new Map();
17
17
  MAX_CACHE_SIZE = 100;
18
+ CACHE_TTL = 30 * 60 * 1000; // 30 minutos
18
19
  // Estadísticas
19
20
  stats = {
20
21
  totalValidations: 0,
@@ -47,12 +48,12 @@ export class IntegrityValidator {
47
48
  // Revisar cache
48
49
  const cacheKey = this.getCacheKey(context, processed);
49
50
  const cached = this.cache.get(cacheKey);
50
- if (cached) {
51
+ if (cached && (Date.now() - cached.timestamp) < this.CACHE_TTL) {
51
52
  this.stats.cacheHits++;
52
53
  if (options.verbose) {
53
54
  logger.info(`[IntegrityValidator] Cache hit for ${context}`);
54
55
  }
55
- return cached;
56
+ return cached.result;
56
57
  }
57
58
  this.stats.cacheMisses++;
58
59
  const errors = [];
@@ -70,14 +71,11 @@ export class IntegrityValidator {
70
71
  this.handleValidationResult(result, context, options);
71
72
  return result;
72
73
  }
73
- // Check 2: Structure (~1ms) - TEMPORALMENTE DESHABILITADO
74
- // TODO: Mejorar detección de character classes en regex literals
75
- const structureOk = true; // this.checkStructure(processed);
76
- // if (!structureOk) {
77
- // errors.push(
78
- // 'Estructura de código inválida (paréntesis/llaves/corchetes desbalanceados)',
79
- // );
80
- // }
74
+ // Check 2: Structure (~1ms)
75
+ const structureOk = this.checkStructure(processed);
76
+ if (!structureOk) {
77
+ errors.push('Estructura de código inválida (paréntesis/llaves/corchetes desbalanceados)');
78
+ }
81
79
  // Check 3: Exports (~1ms)
82
80
  const exportsOk = this.checkExports(original, processed);
83
81
  if (!exportsOk) {
@@ -493,7 +491,7 @@ export class IntegrityValidator {
493
491
  this.cache.delete(firstKey);
494
492
  }
495
493
  }
496
- this.cache.set(key, result);
494
+ this.cache.set(key, { result, timestamp: Date.now() });
497
495
  }
498
496
  /**
499
497
  * Obtener estadísticas de validación
@@ -57,13 +57,12 @@ class MinificationCache {
57
57
  const validation = integrityValidator.validate(data, result.code, `minify:${filename}`, {
58
58
  skipSyntaxCheck: false,
59
59
  verbose: process.env.VERBOSE === 'true',
60
- throwOnError: true, // Detener build si falla
60
+ throwOnError: false, // Usar fallback en lugar de lanzar excepción
61
61
  });
62
62
  if (!validation.valid) {
63
- // El validator ya lanzó el error si throwOnError=true
64
- // Pero por si acaso, retornamos el código original
65
- logger.error(`❌ Validación de integridad fallida para ${filename}`, validation.errors.join(', '));
66
- throw new Error(`Integrity check failed for ${filename}: ${validation.errors.join(', ')}`);
63
+ logger.warn(`⚠️ Validación de integridad fallida para ${filename}: ${validation.errors.join(', ')}`);
64
+ logger.warn(` Usando código original sin minificar`);
65
+ return { code: data, error: null, cached: false };
67
66
  }
68
67
  if (process.env.VERBOSE === 'true') {
69
68
  logger.info(`✅ Validación de integridad OK para ${filename} (${validation.metrics.duration.toFixed(2)}ms)`);
@@ -3,8 +3,8 @@ import { env } from 'node:process';
3
3
  import { logger } from '../servicios/logger.js';
4
4
  import { EXCLUDED_MODULES } from '../utils/excluded-modules.js';
5
5
  import { getModuleSubPath } from '../utils/module-resolver.js';
6
- import { integrityValidator } from './integrity-validator.js';
7
6
  import { analyzeAndFormatMultipleErrors } from './error-reporter.js';
7
+ import { integrityValidator } from './integrity-validator.js';
8
8
  import { getOptimizedAliasPath, getOptimizedModulePath, } from './module-resolution-optimizer.js';
9
9
  import { parser } from './parser.js';
10
10
  // ✨ OPTIMIZACIÓN CRÍTICA: Cache de PATH_ALIAS parseado
@@ -135,139 +135,6 @@ function cleanErrorMessage(message) {
135
135
  .replace(/\s+/g, ' ')
136
136
  .trim());
137
137
  }
138
- /**
139
- * Mejora significativamente el mensaje de error TypeScript con contexto visual
140
- * ⚠️ DEPRECATED: Ya no se usa en el flujo normal para evitar overhead de performance
141
- * Se mantiene para compatibilidad futura o modo verbose avanzado
142
- * @param scriptInfo - Información de extracción de script para archivos Vue (opcional)
143
- */
144
- function enhanceErrorMessage(diagnostic, fileName, sourceCode, scriptInfo) {
145
- // Extraer el mensaje del error
146
- const message = typeof diagnostic.messageText === 'string'
147
- ? diagnostic.messageText
148
- : typescript.flattenDiagnosticMessageText(diagnostic.messageText, '\n');
149
- const enhancedMessage = cleanErrorMessage(message); // Información de ubicación
150
- let location = `Código TS${diagnostic.code}`;
151
- let codeContext = '';
152
- if (diagnostic.file && diagnostic.start !== undefined) {
153
- const sourceFile = diagnostic.file;
154
- // Verificar que el método getLineAndCharacterOfPosition existe (para compatibilidad con mocks)
155
- if (typeof sourceFile.getLineAndCharacterOfPosition === 'function') {
156
- try {
157
- const lineAndChar = sourceFile.getLineAndCharacterOfPosition(diagnostic.start);
158
- // Ajustar línea si es un archivo Vue con script extraído
159
- const line = scriptInfo
160
- ? lineAndChar.line + scriptInfo.startLine
161
- : lineAndChar.line + 1;
162
- const column = lineAndChar.character + 1;
163
- location = `Línea ${line}, Columna ${column} | Código TS${diagnostic.code}`;
164
- }
165
- catch {
166
- // Si falla, solo mostrar el código de error
167
- location = `Código TS${diagnostic.code}`;
168
- }
169
- }
170
- else {
171
- // Fallback: solo mostrar el código de error
172
- location = `Código TS${diagnostic.code}`;
173
- } // Agregar contexto del código si está disponible
174
- if ((sourceCode || scriptInfo?.originalData || sourceFile.text) &&
175
- typeof sourceFile.getLineAndCharacterOfPosition === 'function') {
176
- try {
177
- const lineAndChar = sourceFile.getLineAndCharacterOfPosition(diagnostic.start);
178
- // Obtener código fuente apropiado
179
- const text = scriptInfo?.originalData || sourceCode || sourceFile.text;
180
- const lines = text.split('\n');
181
- // Calcular la línea real en el archivo original
182
- const actualLineIndex = scriptInfo
183
- ? lineAndChar.line + scriptInfo.startLine - 1
184
- : lineAndChar.line;
185
- const errorLine = lines[actualLineIndex];
186
- if (errorLine) {
187
- // Mostrar hasta 2 líneas antes y después para contexto
188
- const startLine = Math.max(0, actualLineIndex - 2);
189
- const endLine = Math.min(lines.length - 1, actualLineIndex + 2);
190
- codeContext = '\n\n📝 Contexto del código:\n';
191
- for (let i = startLine; i <= endLine; i++) {
192
- const currentLine = i + 1;
193
- const lineContent = lines[i] || '';
194
- const isErrorLine = i === actualLineIndex;
195
- if (isErrorLine) {
196
- codeContext += ` ${currentLine.toString().padStart(3, ' ')} ❌ ${lineContent}\n`;
197
- // Agregar flecha apuntando al error
198
- const arrow = ' '.repeat(6 + lineAndChar.character + 1) +
199
- '^^^';
200
- codeContext += ` ${arrow}\n`;
201
- }
202
- else {
203
- codeContext += ` ${currentLine.toString().padStart(3, ' ')} ${lineContent}\n`;
204
- }
205
- }
206
- }
207
- }
208
- catch {
209
- // Si falla obtener el contexto, continuar sin él
210
- }
211
- }
212
- }
213
- // Agregar sugerencias basadas en el tipo de error
214
- const suggestions = getErrorSuggestions(diagnostic.code, enhancedMessage);
215
- const suggestionsText = suggestions.length > 0
216
- ? `\n\n💡 Sugerencias:\n${suggestions.map(s => ` • ${s}`).join('\n')}`
217
- : '';
218
- return `${enhancedMessage}\n 📍 ${location}${codeContext}${suggestionsText}`;
219
- }
220
- /**
221
- * Proporciona sugerencias específicas basadas en el código de error TypeScript
222
- */
223
- function getErrorSuggestions(errorCode, message) {
224
- const suggestions = [];
225
- switch (errorCode) {
226
- case 2304: // Cannot find name
227
- suggestions.push('Verifica que la variable esté declarada');
228
- suggestions.push('Asegúrate de importar el módulo correspondiente');
229
- suggestions.push('Revisa la ortografía del nombre');
230
- break;
231
- case 2322: // Type assignment error
232
- suggestions.push('Verifica que los tipos sean compatibles');
233
- suggestions.push('Considera usar type assertion: valor as TipoEsperado');
234
- break;
235
- case 2307: // Cannot find module
236
- suggestions.push('Verifica que el archivo exista en la ruta especificada');
237
- suggestions.push('Revisa las rutas en tsconfig.json');
238
- suggestions.push('Asegúrate de que el paquete esté instalado');
239
- break;
240
- case 2451: // Cannot redeclare block-scoped variable
241
- suggestions.push('Cambia el nombre de la variable');
242
- suggestions.push('Usa un scope diferente (function, block)');
243
- break;
244
- case 7006: // Parameter implicitly has 'any' type
245
- suggestions.push('Agrega tipos explícitos a los parámetros');
246
- suggestions.push('Considera habilitar "noImplicitAny": false en tsconfig.json');
247
- break;
248
- case 1155: // 'const' declarations must be initialized
249
- suggestions.push('Agrega un valor inicial: const variable = valor;');
250
- suggestions.push('O cambia a "let" si quieres asignar después');
251
- break;
252
- case 2339: // Property does not exist
253
- suggestions.push('Verifica que la propiedad exista en el tipo');
254
- suggestions.push('Considera usar optional chaining: objeto?.propiedad');
255
- break;
256
- default:
257
- // Sugerencias genéricas basadas en el mensaje
258
- if (message.includes('Cannot find')) {
259
- suggestions.push('Verifica que el elemento exista y esté importado');
260
- }
261
- if (message.includes('Type')) {
262
- suggestions.push('Revisa la compatibilidad de tipos');
263
- }
264
- if (message.includes('missing')) {
265
- suggestions.push('Agrega el elemento faltante');
266
- }
267
- break;
268
- }
269
- return suggestions;
270
- }
271
138
  /**
272
139
  * Crea un mensaje de error unificado para errores múltiples
273
140
  */
@@ -94,17 +94,17 @@ class TypeScriptLanguageServiceHost {
94
94
  getDefaultLibFileName(options) {
95
95
  return typescript.getDefaultLibFilePath(options);
96
96
  }
97
- fileExists(path) {
98
- return this.files.has(path) || fs.existsSync(path);
97
+ fileExists(filePath) {
98
+ return this.files.has(filePath) || fs.existsSync(filePath);
99
99
  }
100
- readFile(path) {
101
- const file = this.files.get(path);
100
+ readFile(filePath) {
101
+ const file = this.files.get(filePath);
102
102
  if (file) {
103
103
  return file.content;
104
104
  }
105
- if (fs.existsSync(path)) {
105
+ if (fs.existsSync(filePath)) {
106
106
  try {
107
- return fs.readFileSync(path, 'utf-8');
107
+ return fs.readFileSync(filePath, 'utf-8');
108
108
  }
109
109
  catch {
110
110
  return undefined;
@@ -167,7 +167,6 @@ export const validateTypesWithLanguageService = (fileName, content, compilerOpti
167
167
  try {
168
168
  // Verificar que el archivo existe en el host antes de solicitar diagnósticos
169
169
  if (!host.fileExists(actualFileName)) {
170
- console.log('File does not exist in host, returning empty result');
171
170
  return { diagnostics: [], hasErrors: false };
172
171
  } // Obtener diagnósticos de tipos con manejo de errores
173
172
  let syntacticDiagnostics = [];
@@ -266,6 +265,12 @@ export const validateTypesWithLanguageService = (fileName, content, compilerOpti
266
265
  catch {
267
266
  return { diagnostics: [], hasErrors: false };
268
267
  }
268
+ finally {
269
+ try {
270
+ languageService.dispose();
271
+ }
272
+ catch { /* ignore dispose errors */ }
273
+ }
269
274
  }
270
275
  catch (error) {
271
276
  // En caso de error, devolver diagnóstico de error
@@ -26,15 +26,20 @@ export class TypeScriptWorkerPool {
26
26
  // ✨ FIX #1: Referencias a timers para limpieza adecuada
27
27
  memoryCheckInterval = null;
28
28
  cleanupInterval = null;
29
+ // Cola de tareas para cuando todos los workers están ocupados
30
+ taskQueue = [];
31
+ MAX_QUEUE_SIZE = 50;
32
+ // Flag para evitar concurrencia en ensureWorkerCapacity
33
+ isScalingUp = false;
29
34
  // Métricas de rendimiento
30
35
  totalTasks = 0;
31
36
  completedTasks = 0;
32
37
  failedTasks = 0;
33
38
  constructor() {
34
- // Determinar tamaño óptimo del pool - MÁS AGRESIVO para mejor rendimiento
39
+ // Determinar tamaño conservador del pool para evitar sobrecarga de RAM/CPU
35
40
  const cpuCount = os.cpus().length;
36
- // Usar más workers para aprovechar mejor el CPU
37
- this.poolSize = Math.min(Math.max(cpuCount, 4), 16); // Entre 4 y 16 workers
41
+ const configuredMax = parseInt(process.env.TS_MAX_WORKERS || '2', 10);
42
+ this.poolSize = Math.min(configuredMax, Math.max(1, cpuCount - 1));
38
43
  this.workerPath = path.join(process.env.PATH_PROY || path.join(process.cwd(), 'src'), 'compiler', 'typescript-worker-thread.cjs');
39
44
  // ✨ ISSUE #4: Configurar monitoreo de memoria automático this.startMemoryMonitoring();
40
45
  }
@@ -190,19 +195,19 @@ export class TypeScriptWorkerPool {
190
195
  * Configura el modo de operación del pool - OPTIMIZADO para máxima velocidad
191
196
  */
192
197
  setMode(mode) {
193
- // Ajustar configuración según el modo - MÁS AGRESIVO
198
+ const cpuCount = os.cpus().length;
194
199
  switch (mode) {
195
200
  case 'batch':
196
- // Para modo batch, máxima concurrencia para throughput
197
- this.poolSize = Math.min(os.cpus().length, 20);
201
+ // Para modo batch, hasta 3 workers conservando al menos 1 CPU libre
202
+ this.poolSize = Math.min(3, Math.max(1, cpuCount - 1));
198
203
  break;
199
204
  case 'watch':
200
- // Para modo watch, más workers para mejor responsividad
201
- this.poolSize = Math.min(os.cpus().length, 12);
205
+ // Para modo watch, hasta 2 workers para menor impacto en sistema
206
+ this.poolSize = Math.min(2, Math.max(1, cpuCount - 1));
202
207
  break;
203
208
  case 'individual':
204
- // Para individual, pool moderado
205
- this.poolSize = Math.min(8, os.cpus().length);
209
+ // Para individual, 1 worker suficiente
210
+ this.poolSize = 1;
206
211
  break;
207
212
  }
208
213
  }
@@ -227,9 +232,9 @@ export class TypeScriptWorkerPool {
227
232
  if (!exists) {
228
233
  throw new Error(`Worker thread file not found: ${this.workerPath}`);
229
234
  }
230
- // Crear workers en paralelo
231
- const workerPromises = Array.from({ length: this.poolSize }, (_, index) => this.createWorker(index));
232
- this.workers = await Promise.all(workerPromises);
235
+ // Crear solo el primer worker al iniciar; el resto se crean bajo demanda
236
+ const firstWorker = await this.createWorker(0);
237
+ this.workers = [firstWorker];
233
238
  this.isInitialized = true;
234
239
  }
235
240
  catch (error) {
@@ -237,6 +242,30 @@ export class TypeScriptWorkerPool {
237
242
  throw error;
238
243
  }
239
244
  }
245
+ /**
246
+ * Crea un worker adicional si todos están ocupados y la cola tiene tareas pendientes.
247
+ * Usa un flag para evitar que múltiples llamadas concurrentes creen workers de más.
248
+ */
249
+ async ensureWorkerCapacity() {
250
+ if (this.isScalingUp)
251
+ return;
252
+ if (this.workers.length >= this.poolSize)
253
+ return;
254
+ const allBusy = this.workers.every(w => w.busy || w.pendingTasks.size > 0);
255
+ if (!allBusy || this.taskQueue.length === 0)
256
+ return;
257
+ this.isScalingUp = true;
258
+ try {
259
+ const newWorker = await this.createWorker(this.workers.length);
260
+ this.workers.push(newWorker);
261
+ }
262
+ catch {
263
+ // Silencioso: se usará la cola cuando un worker quede libre
264
+ }
265
+ finally {
266
+ this.isScalingUp = false;
267
+ }
268
+ }
240
269
  /**
241
270
  * Crea un worker individual
242
271
  */
@@ -338,8 +367,9 @@ export class TypeScriptWorkerPool {
338
367
  };
339
368
  // ✨ FIX MEMORIA: No mantener response completo en memoria
340
369
  resolve(result);
341
- // FIX MEMORIA: Limpiar diagnostics después de resolver
370
+ // Procesar siguiente tarea de la cola y limpiar diagnostics
342
371
  setImmediate(() => {
372
+ this.drainQueue(poolWorker);
343
373
  if (response.diagnostics) {
344
374
  response.diagnostics.length = 0;
345
375
  }
@@ -543,6 +573,28 @@ export class TypeScriptWorkerPool {
543
573
  }
544
574
  return null; // Todos los workers están muy ocupados
545
575
  }
576
+ /**
577
+ * Procesa la siguiente tarea de la cola en el worker liberado
578
+ */
579
+ drainQueue(poolWorker) {
580
+ if (this.taskQueue.length === 0)
581
+ return;
582
+ if (poolWorker.busy || poolWorker.pendingTasks.size > 0)
583
+ return;
584
+ if (poolWorker.taskCounter >= this.MAX_TASKS_PER_WORKER)
585
+ return;
586
+ const queued = this.taskQueue.shift();
587
+ if (!queued)
588
+ return;
589
+ const waitMs = Date.now() - queued.queuedAt;
590
+ if (waitMs > 30000) {
591
+ queued.reject(new Error(`Queue task expired after ${waitMs}ms`));
592
+ return;
593
+ }
594
+ this.typeCheckWithWorker(poolWorker, queued.fileName, queued.content, queued.compilerOptions)
595
+ .then(queued.resolve)
596
+ .catch(queued.reject);
597
+ }
546
598
  /**
547
599
  * Realiza type checking usando el pool de workers
548
600
  */
@@ -550,30 +602,45 @@ export class TypeScriptWorkerPool {
550
602
  // Asegurar que el pool esté inicializado
551
603
  await this.initializePool();
552
604
  if (!this.isInitialized) {
605
+ // Solo usar fallback síncrono si el pool no pudo inicializarse del todo
553
606
  return this.typeCheckWithSyncFallback(fileName, content, compilerOptions);
554
607
  }
608
+ this.totalTasks++;
555
609
  // Buscar worker disponible
556
610
  const availableWorker = this.findAvailableWorker();
557
- // FIX: Incrementar totalTasks ANTES del try/catch para conteo correcto
558
- this.totalTasks++;
559
- if (!availableWorker) {
560
- return this.typeCheckWithSyncFallback(fileName, content, compilerOptions);
561
- }
562
- try {
563
- return await this.typeCheckWithWorker(availableWorker, fileName, content, compilerOptions);
611
+ if (availableWorker) {
612
+ try {
613
+ return await this.typeCheckWithWorker(availableWorker, fileName, content, compilerOptions);
614
+ }
615
+ catch {
616
+ // Worker falló; intentar encolar
617
+ }
564
618
  }
565
- catch {
619
+ // Ningún worker disponible: encolar la tarea
620
+ if (this.taskQueue.length >= this.MAX_QUEUE_SIZE) {
621
+ // Cola llena → fallback síncrono para no perder el type check
566
622
  return this.typeCheckWithSyncFallback(fileName, content, compilerOptions);
567
623
  }
624
+ // Escalar el pool si es posible
625
+ this.ensureWorkerCapacity().catch(() => { });
626
+ return new Promise((resolve, reject) => {
627
+ this.taskQueue.push({
628
+ fileName,
629
+ content,
630
+ compilerOptions,
631
+ resolve,
632
+ reject,
633
+ queuedAt: Date.now(),
634
+ });
635
+ });
568
636
  }
569
637
  /**
570
638
  * Realiza type checking usando un worker específico con reciclaje automático
571
639
  * ✨ FIX MEMORIA: Optimizado para prevenir fugas de memoria
572
640
  */
573
641
  async typeCheckWithWorker(poolWorker, fileName, content, compilerOptions) {
574
- // Verificar si el worker necesita reciclaje por número de tareas
642
+ // Si el worker alcanzó su límite de tareas, usar fallback síncrono
575
643
  if (poolWorker.taskCounter >= this.MAX_TASKS_PER_WORKER) {
576
- // ✨ FIX: No esperar el reciclaje, usar fallback
577
644
  return this.typeCheckWithSyncFallback(fileName, content, compilerOptions);
578
645
  }
579
646
  return new Promise((resolve, reject) => {
@@ -725,6 +792,11 @@ export class TypeScriptWorkerPool {
725
792
  console.log('[WorkerPool] Cerrando pool de workers...');
726
793
  // 0. ✨ FIX #1: Detener monitoreo de memoria primero
727
794
  this.stopMemoryMonitoring();
795
+ // 0b. Rechazar todas las tareas en cola
796
+ for (const queued of this.taskQueue) {
797
+ queued.reject(new Error('Worker pool cerrado'));
798
+ }
799
+ this.taskQueue = [];
728
800
  // 1. Rechazar todas las tareas pendientes con cleanup
729
801
  let totalPendingTasks = 0;
730
802
  for (const poolWorker of this.workers) {
@@ -128,19 +128,19 @@ class WorkerTypeScriptLanguageServiceHost {
128
128
  return ts.getDefaultLibFilePath(options);
129
129
  }
130
130
 
131
- fileExists(path) {
132
- return this.files.has(path) || fs.existsSync(path);
131
+ fileExists(filePath) {
132
+ return this.files.has(filePath) || fs.existsSync(filePath);
133
133
  }
134
134
 
135
- readFile(path) {
136
- const file = this.files.get(path);
135
+ readFile(filePath) {
136
+ const file = this.files.get(filePath);
137
137
  if (file) {
138
138
  return file.content;
139
139
  }
140
140
 
141
- if (fs.existsSync(path)) {
141
+ if (fs.existsSync(filePath)) {
142
142
  try {
143
- return fs.readFileSync(path, 'utf-8');
143
+ return fs.readFileSync(filePath, 'utf-8');
144
144
  } catch {
145
145
  return undefined;
146
146
  }
@@ -225,6 +225,7 @@ function validateTypesInWorker(fileName, content, compilerOptions) {
225
225
  return { diagnostics: [], hasErrors: false };
226
226
  }
227
227
 
228
+
228
229
  // Obtener diagnósticos de tipos con manejo de errores
229
230
  let syntacticDiagnostics = [];
230
231
  let semanticDiagnostics = [];
@@ -344,6 +345,8 @@ function validateTypesInWorker(fileName, content, compilerOptions) {
344
345
  };
345
346
  } catch {
346
347
  return { diagnostics: [], hasErrors: false };
348
+ } finally {
349
+ try { languageService.dispose(); } catch { /* ignore dispose errors */ }
347
350
  }
348
351
  } catch (error) {
349
352
  // En caso de error, devolver diagnóstico de error
@@ -91,16 +91,22 @@ class VueHMRInjectionCache {
91
91
  };
92
92
  }
93
93
  /**
94
- * Limpia entradas de cache cuando se excede el límite
94
+ * Limpia entradas de cache cuando se excede el límite (O(n) en lugar de O(n log n))
95
95
  */
96
96
  evictIfNeeded() {
97
97
  if (this.cache.size <= this.MAX_CACHE_SIZE)
98
98
  return;
99
- const entries = Array.from(this.cache.entries());
100
- entries.sort((a, b) => a[1].timestamp - b[1].timestamp);
101
- // Eliminar las entradas más antiguas
102
- const toDelete = entries.slice(0, entries.length - this.MAX_CACHE_SIZE);
103
- toDelete.forEach(([key]) => this.cache.delete(key));
99
+ // Eliminar la entrada más antigua con un solo barrido O(n)
100
+ let oldestKey = '';
101
+ let oldestTime = Infinity;
102
+ for (const [key, entry] of this.cache) {
103
+ if (entry.timestamp < oldestTime) {
104
+ oldestTime = entry.timestamp;
105
+ oldestKey = key;
106
+ }
107
+ }
108
+ if (oldestKey)
109
+ this.cache.delete(oldestKey);
104
110
  }
105
111
  /**
106
112
  * Limpia entradas expiradas
@@ -213,7 +213,7 @@ export async function handleLibraryHotReload(
213
213
  ) {
214
214
  try {
215
215
  newLibraryVersion.clearCache();
216
- } catch (_e) {
216
+ } catch {
217
217
  // Ignorar errores de clearCache, no es crítico
218
218
  }
219
219
  }
@@ -238,7 +238,7 @@ export async function handleLibraryHotReload(
238
238
  ) {
239
239
  window.performance.clearResourceTimings();
240
240
  }
241
- } catch (_e) {
241
+ } catch {
242
242
  // Ignorar errores de limpieza de cache
243
243
  }
244
244
 
package/dist/main.js CHANGED
@@ -63,10 +63,10 @@ function stopCompile() {
63
63
  async function main() {
64
64
  // Load yargs dynamically
65
65
  const { yargs: yargsInstance, hideBin: hideBinFn } = await loadYargs();
66
- const chalk = await loadChalk();
66
+ const chalkInstance = await loadChalk();
67
67
  let yargInstance = yargsInstance(hideBinFn(globalProcess.argv))
68
68
  .scriptName('versa')
69
- .usage(chalk.blue('VersaCompiler') + ' - Compilador de archivos Vue/TS/JS')
69
+ .usage(chalkInstance.blue('VersaCompiler') + ' - Compilador de archivos Vue/TS/JS')
70
70
  .option('init', {
71
71
  type: 'boolean',
72
72
  description: 'Inicializar la configuración',
@@ -147,8 +147,8 @@ async function main() {
147
147
  const argv = (await yargInstance
148
148
  .help()
149
149
  .alias('h', 'help')
150
- .command('* [files...]', 'Compilar archivos específicos', (yargs) => {
151
- return yargs.positional('files', {
150
+ .command('* [files...]', 'Compilar archivos específicos', (yargsCmd) => {
151
+ return yargsCmd.positional('files', {
152
152
  describe: 'Archivos para compilar',
153
153
  type: 'string',
154
154
  array: true,
@@ -4,7 +4,7 @@ import * as process from 'node:process';
4
4
  const { env } = process;
5
5
  import * as chokidar from 'chokidar';
6
6
  import { minimatch } from 'minimatch';
7
- import { getOutputPath, initCompile, normalizeRuta } from '../compiler/compile.js';
7
+ import { clearCompilationState, getOutputPath, initCompile, normalizeRuta } from '../compiler/compile.js';
8
8
  import { promptUser } from '../utils/promptUser.js';
9
9
  import { emitirCambios } from './browserSync.js';
10
10
  import { logger } from './logger.js';
@@ -76,6 +76,8 @@ class WatchDebouncer {
76
76
  if (this.isProcessing || this.pendingChanges.size === 0) {
77
77
  return;
78
78
  }
79
+ // Limpiar errores del ciclo anterior para evitar memory leak en watch mode
80
+ clearCompilationState();
79
81
  this.isProcessing = true;
80
82
  const changes = Array.from(this.pendingChanges.values());
81
83
  this.pendingChanges.clear();
@@ -24,6 +24,11 @@ export function validatePath(pathStr) {
24
24
  logger.warn(`Ruta demasiado larga: ${pathStr.length} caracteres`);
25
25
  return false;
26
26
  }
27
+ // Rechazar rutas absolutas de Windows (válido en cualquier plataforma)
28
+ if (/^[A-Za-z]:[\\\/]/.test(pathStr)) {
29
+ logger.error(`Ruta absoluta de Windows no permitida: ${pathStr}`);
30
+ return false;
31
+ }
27
32
  // Normalizar la ruta para detectar path traversal
28
33
  const normalizedPath = normalize(pathStr);
29
34
  const resolvedPath = resolve(process.cwd(), normalizedPath);
@@ -243,7 +248,7 @@ export async function dynamicImport(url) {
243
248
  * Timeout wrapper para promises
244
249
  */
245
250
  export function withTimeout(promise, timeoutMs, errorMessage) {
246
- const timeoutPromise = new Promise((resolve, reject) => setTimeout(() => reject(new Error(errorMessage)), timeoutMs));
251
+ const timeoutPromise = new Promise((_, reject) => setTimeout(() => reject(new Error(errorMessage)), timeoutMs));
247
252
  return Promise.race([promise, timeoutPromise]);
248
253
  }
249
254
  /**
@@ -309,6 +314,11 @@ export async function readConfig() {
309
314
  env.PATH_DIST = outDir;
310
315
  env.aditionalWatch = safeJsonStringify(tsConfig?.aditionalWatch, '[]');
311
316
  env.bundlers = safeJsonStringify(tsConfig?.bundlers, 'false');
317
+ // Configurar número máximo de workers para type checking
318
+ if (tsConfig?.typeCheckOptions?.maxWorkers) {
319
+ const maxWorkers = Math.max(1, Math.min(8, Number(tsConfig.typeCheckOptions.maxWorkers)));
320
+ env.TS_MAX_WORKERS = String(maxWorkers);
321
+ }
312
322
  // Configuración adicional para compatibilidad
313
323
  if (!tsConfig.compilerOptions.sourceRoot) {
314
324
  env.tsConfig = safeJsonStringify(tsConfig, '{}');