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.
- package/README.md +106 -17
- package/dist/compiler/compile.js +221 -205
- package/dist/compiler/integrity-validator.js +525 -0
- package/dist/compiler/minify.js +18 -1
- package/dist/compiler/minifyTemplate.js +16 -0
- package/dist/compiler/transforms.js +17 -0
- package/dist/compiler/typescript-error-parser.js +0 -133
- package/dist/compiler/typescript-sync-validator.js +12 -7
- package/dist/compiler/typescript-worker-pool.js +96 -24
- package/dist/compiler/typescript-worker-thread.cjs +9 -6
- package/dist/compiler/vuejs.js +12 -6
- package/dist/hrm/initHRM.js +2 -2
- package/dist/main.js +18 -6
- package/dist/servicios/file-watcher.js +3 -1
- package/dist/servicios/readConfig.js +17 -4
- package/package.json +32 -32
|
@@ -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(
|
|
98
|
-
return this.files.has(
|
|
97
|
+
fileExists(filePath) {
|
|
98
|
+
return this.files.has(filePath) || fs.existsSync(filePath);
|
|
99
99
|
}
|
|
100
|
-
readFile(
|
|
101
|
-
const file = this.files.get(
|
|
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(
|
|
105
|
+
if (fs.existsSync(filePath)) {
|
|
106
106
|
try {
|
|
107
|
-
return fs.readFileSync(
|
|
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
|
|
39
|
+
// Determinar tamaño conservador del pool para evitar sobrecarga de RAM/CPU
|
|
35
40
|
const cpuCount = os.cpus().length;
|
|
36
|
-
|
|
37
|
-
this.poolSize = Math.min(Math.max(
|
|
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
|
-
|
|
198
|
+
const cpuCount = os.cpus().length;
|
|
194
199
|
switch (mode) {
|
|
195
200
|
case 'batch':
|
|
196
|
-
// Para modo batch,
|
|
197
|
-
this.poolSize = Math.min(
|
|
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,
|
|
201
|
-
this.poolSize = Math.min(
|
|
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,
|
|
205
|
-
this.poolSize =
|
|
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
|
|
231
|
-
const
|
|
232
|
-
this.workers =
|
|
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
|
-
//
|
|
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
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
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
|
-
|
|
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
|
-
//
|
|
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(
|
|
132
|
-
return this.files.has(
|
|
131
|
+
fileExists(filePath) {
|
|
132
|
+
return this.files.has(filePath) || fs.existsSync(filePath);
|
|
133
133
|
}
|
|
134
134
|
|
|
135
|
-
readFile(
|
|
136
|
-
const file = this.files.get(
|
|
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(
|
|
141
|
+
if (fs.existsSync(filePath)) {
|
|
142
142
|
try {
|
|
143
|
-
return fs.readFileSync(
|
|
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
|
package/dist/compiler/vuejs.js
CHANGED
|
@@ -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
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
const
|
|
103
|
-
|
|
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
|
package/dist/hrm/initHRM.js
CHANGED
|
@@ -213,7 +213,7 @@ export async function handleLibraryHotReload(
|
|
|
213
213
|
) {
|
|
214
214
|
try {
|
|
215
215
|
newLibraryVersion.clearCache();
|
|
216
|
-
} catch
|
|
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
|
|
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
|
|
66
|
+
const chalkInstance = await loadChalk();
|
|
67
67
|
let yargInstance = yargsInstance(hideBinFn(globalProcess.argv))
|
|
68
68
|
.scriptName('versa')
|
|
69
|
-
.usage(
|
|
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', (
|
|
145
|
-
return
|
|
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';
|
|
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 ||
|
|
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 &&
|
|
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 &&
|
|
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((
|
|
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, '{}');
|