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.
- package/README.md +1 -1
- package/dist/compiler/compile.js +205 -205
- package/dist/compiler/integrity-validator.js +9 -11
- package/dist/compiler/minify.js +4 -5
- package/dist/compiler/transforms.js +1 -1
- 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 +4 -4
- package/dist/servicios/file-watcher.js +3 -1
- package/dist/servicios/readConfig.js +11 -1
- package/package.json +29 -29
|
@@ -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)
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
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
|
package/dist/compiler/minify.js
CHANGED
|
@@ -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:
|
|
60
|
+
throwOnError: false, // Usar fallback en lugar de lanzar excepción
|
|
61
61
|
});
|
|
62
62
|
if (!validation.valid) {
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
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(
|
|
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',
|
|
@@ -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', (
|
|
151
|
-
return
|
|
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((
|
|
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, '{}');
|