versacompiler 2.3.4 → 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.
@@ -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',
@@ -121,7 +121,13 @@ async function main() {
121
121
  description: 'Habilitar/Deshabilitar la verificación de tipos. Por defecto --typeCheck=false',
122
122
  default: false,
123
123
  })
124
- .alias('t', 'typeCheck');
124
+ .alias('t', 'typeCheck')
125
+ .option('checkIntegrity', {
126
+ type: 'boolean',
127
+ description: 'Validar la integridad del código compilado (para builds de deploy). Por defecto --checkIntegrity=false',
128
+ default: false,
129
+ })
130
+ .alias('ci', 'checkIntegrity');
125
131
  // Definir la opción tailwind dinámicamente
126
132
  // Asumiendo que env.TAILWIND es una cadena que podría ser 'true', 'false', o undefined
127
133
  if (env.tailwindcss !== 'false') {
@@ -141,8 +147,8 @@ async function main() {
141
147
  const argv = (await yargInstance
142
148
  .help()
143
149
  .alias('h', 'help')
144
- .command('* [files...]', 'Compilar archivos específicos', (yargs) => {
145
- return yargs.positional('files', {
150
+ .command('* [files...]', 'Compilar archivos específicos', (yargsCmd) => {
151
+ return yargsCmd.positional('files', {
146
152
  describe: 'Archivos para compilar',
147
153
  type: 'string',
148
154
  array: true,
@@ -177,7 +183,8 @@ async function main() {
177
183
  env.TAILWIND =
178
184
  argv.tailwind === undefined ? 'true' : String(argv.tailwind);
179
185
  env.ENABLE_LINTER = String(argv.linter);
180
- env.VERBOSE = argv.verbose ? 'true' : 'false'; // 🎯 Configuración moderna y organizada
186
+ env.VERBOSE = argv.verbose ? 'true' : 'false';
187
+ env.CHECK_INTEGRITY = argv.checkIntegrity ? 'true' : 'false'; // 🎯 Configuración moderna y organizada
181
188
  logger.info(chalk.bold.blue('⚙️ Configuración'));
182
189
  logger.info(chalk.gray(' ┌─ Modo de ejecución'));
183
190
  const modes = [
@@ -199,6 +206,11 @@ async function main() {
199
206
  icon: '🔍',
200
207
  },
201
208
  { label: 'Verificar tipos', value: argv.typeCheck, icon: '📘' },
209
+ {
210
+ label: 'Validar integridad',
211
+ value: argv.checkIntegrity,
212
+ icon: '🛡️',
213
+ },
202
214
  { label: 'Detallado', value: env.VERBOSE === 'true', icon: '📝' },
203
215
  ];
204
216
  modes.forEach(mode => {
@@ -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);
@@ -108,7 +113,8 @@ export function validateConfigStructure(config) {
108
113
  logger.error('compilerOptions es requerido y debe ser un objeto');
109
114
  return false;
110
115
  }
111
- if (!config.compilerOptions.pathsAlias || typeof config.compilerOptions.pathsAlias !== 'object') {
116
+ if (!config.compilerOptions.pathsAlias ||
117
+ typeof config.compilerOptions.pathsAlias !== 'object') {
112
118
  logger.error('pathsAlias es requerido y debe ser un objeto');
113
119
  return false;
114
120
  }
@@ -130,11 +136,13 @@ export function validateConfigStructure(config) {
130
136
  }
131
137
  }
132
138
  // Validar sourceRoot si existe
133
- if (config.compilerOptions.sourceRoot && !validatePath(config.compilerOptions.sourceRoot)) {
139
+ if (config.compilerOptions.sourceRoot &&
140
+ !validatePath(config.compilerOptions.sourceRoot)) {
134
141
  return false;
135
142
  }
136
143
  // Validar outDir si existe
137
- if (config.compilerOptions.outDir && !validatePath(config.compilerOptions.outDir)) {
144
+ if (config.compilerOptions.outDir &&
145
+ !validatePath(config.compilerOptions.outDir)) {
138
146
  return false;
139
147
  }
140
148
  // Validar linter si existe
@@ -240,7 +248,7 @@ export async function dynamicImport(url) {
240
248
  * Timeout wrapper para promises
241
249
  */
242
250
  export function withTimeout(promise, timeoutMs, errorMessage) {
243
- const timeoutPromise = new Promise((resolve, reject) => setTimeout(() => reject(new Error(errorMessage)), timeoutMs));
251
+ const timeoutPromise = new Promise((_, reject) => setTimeout(() => reject(new Error(errorMessage)), timeoutMs));
244
252
  return Promise.race([promise, timeoutPromise]);
245
253
  }
246
254
  /**
@@ -306,6 +314,11 @@ export async function readConfig() {
306
314
  env.PATH_DIST = outDir;
307
315
  env.aditionalWatch = safeJsonStringify(tsConfig?.aditionalWatch, '[]');
308
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
+ }
309
322
  // Configuración adicional para compatibilidad
310
323
  if (!tsConfig.compilerOptions.sourceRoot) {
311
324
  env.tsConfig = safeJsonStringify(tsConfig, '{}');