versacompiler 2.3.3 → 2.3.5
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 +21 -2
- package/dist/compiler/integrity-validator.js +527 -0
- package/dist/compiler/minify.js +19 -1
- package/dist/compiler/minifyTemplate.js +16 -0
- package/dist/compiler/transforms.js +34 -3
- package/dist/compiler/typescript-error-parser.js +113 -26
- package/dist/compiler/typescript-manager.js +10 -4
- package/dist/compiler/vuejs.js +16 -1
- package/dist/main.js +14 -2
- package/dist/servicios/readConfig.js +6 -3
- package/dist/utils/module-resolver.js +164 -154
- package/package.json +5 -4
|
@@ -0,0 +1,527 @@
|
|
|
1
|
+
import { parseSync } from 'oxc-parser';
|
|
2
|
+
import { logger } from '../servicios/logger.js';
|
|
3
|
+
/**
|
|
4
|
+
* Sistema de validación de integridad para código compilado/transformado
|
|
5
|
+
*
|
|
6
|
+
* Detecta automáticamente:
|
|
7
|
+
* - Código vacío después de minificación
|
|
8
|
+
* - Exports eliminados por error
|
|
9
|
+
* - Sintaxis inválida introducida por transformaciones
|
|
10
|
+
* - Estructura de código corrupta
|
|
11
|
+
*
|
|
12
|
+
* Performance: <5ms por archivo (típicamente 1-3ms)
|
|
13
|
+
*/
|
|
14
|
+
export class IntegrityValidator {
|
|
15
|
+
static instance;
|
|
16
|
+
cache = new Map();
|
|
17
|
+
MAX_CACHE_SIZE = 100;
|
|
18
|
+
// Estadísticas
|
|
19
|
+
stats = {
|
|
20
|
+
totalValidations: 0,
|
|
21
|
+
successfulValidations: 0,
|
|
22
|
+
failedValidations: 0,
|
|
23
|
+
cacheHits: 0,
|
|
24
|
+
cacheMisses: 0,
|
|
25
|
+
totalDuration: 0,
|
|
26
|
+
averageDuration: 0,
|
|
27
|
+
};
|
|
28
|
+
constructor() { }
|
|
29
|
+
static getInstance() {
|
|
30
|
+
if (!IntegrityValidator.instance) {
|
|
31
|
+
IntegrityValidator.instance = new IntegrityValidator();
|
|
32
|
+
}
|
|
33
|
+
return IntegrityValidator.instance;
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Valida la integridad del código procesado
|
|
37
|
+
*
|
|
38
|
+
* @param original - Código original antes del procesamiento
|
|
39
|
+
* @param processed - Código después del procesamiento
|
|
40
|
+
* @param context - Contexto de la validación (ej: "minify:file.js")
|
|
41
|
+
* @param options - Opciones de validación
|
|
42
|
+
* @returns Resultado detallado de la validación
|
|
43
|
+
*/
|
|
44
|
+
validate(original, processed, context, options = {}) {
|
|
45
|
+
const startTime = performance.now();
|
|
46
|
+
this.stats.totalValidations++;
|
|
47
|
+
// Revisar cache
|
|
48
|
+
const cacheKey = this.getCacheKey(context, processed);
|
|
49
|
+
const cached = this.cache.get(cacheKey);
|
|
50
|
+
if (cached) {
|
|
51
|
+
this.stats.cacheHits++;
|
|
52
|
+
if (options.verbose) {
|
|
53
|
+
logger.info(`[IntegrityValidator] Cache hit for ${context}`);
|
|
54
|
+
}
|
|
55
|
+
return cached;
|
|
56
|
+
}
|
|
57
|
+
this.stats.cacheMisses++;
|
|
58
|
+
const errors = [];
|
|
59
|
+
// Check 1: Size (más rápido, ~0.1ms)
|
|
60
|
+
const sizeOk = this.checkSize(processed);
|
|
61
|
+
if (!sizeOk) {
|
|
62
|
+
errors.push('Código procesado está vacío o demasiado pequeño');
|
|
63
|
+
// Early return - no tiene sentido continuar
|
|
64
|
+
const result = this.createResult(false, {
|
|
65
|
+
size: false,
|
|
66
|
+
structure: false,
|
|
67
|
+
exports: false,
|
|
68
|
+
syntax: options?.skipSyntaxCheck === true, // Respetar skipSyntaxCheck incluso en early return
|
|
69
|
+
}, errors, original, processed, startTime);
|
|
70
|
+
this.handleValidationResult(result, context, options);
|
|
71
|
+
return result;
|
|
72
|
+
}
|
|
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
|
+
// }
|
|
81
|
+
// Check 3: Exports (~1ms)
|
|
82
|
+
const exportsOk = this.checkExports(original, processed);
|
|
83
|
+
if (!exportsOk) {
|
|
84
|
+
errors.push('Exports fueron eliminados o modificados incorrectamente');
|
|
85
|
+
}
|
|
86
|
+
// Check 4: Syntax (~3ms) - solo si otros checks pasaron y no está skippeado
|
|
87
|
+
let syntaxOk = true;
|
|
88
|
+
if (options?.skipSyntaxCheck) {
|
|
89
|
+
// Si se salta el check de sintaxis, asumir que es válido
|
|
90
|
+
syntaxOk = true;
|
|
91
|
+
}
|
|
92
|
+
else if (structureOk && exportsOk) {
|
|
93
|
+
// Solo validar sintaxis si estructura y exports pasaron
|
|
94
|
+
syntaxOk = this.checkSyntax(processed);
|
|
95
|
+
if (!syntaxOk) {
|
|
96
|
+
errors.push('Código procesado contiene errores de sintaxis');
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
else {
|
|
100
|
+
// Si otros checks fallaron, no ejecutar syntax check (optimización)
|
|
101
|
+
// pero mantener syntaxOk = true para no agregar más errores
|
|
102
|
+
syntaxOk = true;
|
|
103
|
+
}
|
|
104
|
+
const valid = errors.length === 0;
|
|
105
|
+
const result = this.createResult(valid, {
|
|
106
|
+
size: sizeOk,
|
|
107
|
+
structure: structureOk,
|
|
108
|
+
exports: exportsOk,
|
|
109
|
+
syntax: syntaxOk,
|
|
110
|
+
}, errors, original, processed, startTime);
|
|
111
|
+
// Guardar en caché
|
|
112
|
+
this.saveToCache(cacheKey, result);
|
|
113
|
+
// Actualizar estadísticas
|
|
114
|
+
this.handleValidationResult(result, context, options);
|
|
115
|
+
return result;
|
|
116
|
+
}
|
|
117
|
+
/**
|
|
118
|
+
* Check 1: Verificar que el código no esté vacío
|
|
119
|
+
*/
|
|
120
|
+
checkSize(code) {
|
|
121
|
+
// Código debe tener al menos 10 caracteres y no ser solo whitespace
|
|
122
|
+
const trimmed = code.trim();
|
|
123
|
+
return trimmed.length >= 10;
|
|
124
|
+
}
|
|
125
|
+
/**
|
|
126
|
+
* Check 2: Verificar estructura básica del código
|
|
127
|
+
*/
|
|
128
|
+
checkStructure(code) {
|
|
129
|
+
// Verificar paréntesis, llaves y corchetes balanceados
|
|
130
|
+
const counters = {
|
|
131
|
+
'(': 0,
|
|
132
|
+
'[': 0,
|
|
133
|
+
'{': 0,
|
|
134
|
+
};
|
|
135
|
+
let inString = false;
|
|
136
|
+
let inTemplate = false;
|
|
137
|
+
let inTemplateInterpolation = false; // Dentro de ${ } en template literal
|
|
138
|
+
let templateBraceDepth = 0; // Para trackear nested braces en interpolación
|
|
139
|
+
let inComment = false;
|
|
140
|
+
let inMultilineComment = false;
|
|
141
|
+
let inRegex = false; // Dentro de regex literal /pattern/flags
|
|
142
|
+
let stringChar = '';
|
|
143
|
+
let escapeNext = false;
|
|
144
|
+
let prevNonWhitespaceChar = ''; // Para detectar contexto de regex
|
|
145
|
+
for (let i = 0; i < code.length;) {
|
|
146
|
+
const char = code[i];
|
|
147
|
+
const nextChar = i < code.length - 1 ? code[i + 1] : '';
|
|
148
|
+
// Manejar escape (en strings, templates y regex)
|
|
149
|
+
if (escapeNext) {
|
|
150
|
+
escapeNext = false;
|
|
151
|
+
i++;
|
|
152
|
+
continue;
|
|
153
|
+
}
|
|
154
|
+
if (char === '\\' && (inString || inTemplate || inRegex)) {
|
|
155
|
+
escapeNext = true;
|
|
156
|
+
i++;
|
|
157
|
+
continue;
|
|
158
|
+
}
|
|
159
|
+
// Detectar regex literals (antes de comentarios, porque ambos usan /)
|
|
160
|
+
if (!inString &&
|
|
161
|
+
!inTemplate &&
|
|
162
|
+
!inComment &&
|
|
163
|
+
!inMultilineComment &&
|
|
164
|
+
!inRegex &&
|
|
165
|
+
char === '/' &&
|
|
166
|
+
nextChar !== '/' &&
|
|
167
|
+
nextChar !== '*') {
|
|
168
|
+
// Contexto donde se espera regex (no división)
|
|
169
|
+
const regexContext = /[=([,;:!&|?+\-{]$/;
|
|
170
|
+
if (regexContext.test(prevNonWhitespaceChar)) {
|
|
171
|
+
inRegex = true;
|
|
172
|
+
i++;
|
|
173
|
+
continue;
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
// Detectar fin de regex literal
|
|
177
|
+
if (inRegex && char === '/') {
|
|
178
|
+
inRegex = false;
|
|
179
|
+
// Skip flags como g, i, m, s, u, y
|
|
180
|
+
let j = i + 1;
|
|
181
|
+
while (j < code.length) {
|
|
182
|
+
const flag = code[j];
|
|
183
|
+
if (flag && /[gimsuvy]/.test(flag)) {
|
|
184
|
+
j++;
|
|
185
|
+
}
|
|
186
|
+
else {
|
|
187
|
+
break;
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
i = j;
|
|
191
|
+
continue;
|
|
192
|
+
}
|
|
193
|
+
// Skip contenido dentro de regex
|
|
194
|
+
if (inRegex) {
|
|
195
|
+
i++;
|
|
196
|
+
continue;
|
|
197
|
+
}
|
|
198
|
+
// Detectar inicio de comentario de línea
|
|
199
|
+
if (char === '/' &&
|
|
200
|
+
nextChar === '/' &&
|
|
201
|
+
!inString &&
|
|
202
|
+
!inTemplate &&
|
|
203
|
+
!inMultilineComment) {
|
|
204
|
+
inComment = true;
|
|
205
|
+
i += 2; // Skip // completamente
|
|
206
|
+
continue;
|
|
207
|
+
}
|
|
208
|
+
// Detectar fin de comentario de línea
|
|
209
|
+
if (inComment && (char === '\n' || char === '\r')) {
|
|
210
|
+
inComment = false;
|
|
211
|
+
i++;
|
|
212
|
+
continue;
|
|
213
|
+
}
|
|
214
|
+
// Detectar inicio de comentario multilínea
|
|
215
|
+
if (char === '/' &&
|
|
216
|
+
nextChar === '*' &&
|
|
217
|
+
!inString &&
|
|
218
|
+
!inTemplate &&
|
|
219
|
+
!inComment) {
|
|
220
|
+
inMultilineComment = true;
|
|
221
|
+
i += 2; // Skip /* completamente
|
|
222
|
+
continue;
|
|
223
|
+
}
|
|
224
|
+
// Detectar fin de comentario multilínea
|
|
225
|
+
if (inMultilineComment && char === '*' && nextChar === '/') {
|
|
226
|
+
inMultilineComment = false;
|
|
227
|
+
i += 2; // Skip */ completamente
|
|
228
|
+
continue;
|
|
229
|
+
}
|
|
230
|
+
// Skip caracteres dentro de comentarios
|
|
231
|
+
if (inComment || inMultilineComment) {
|
|
232
|
+
i++;
|
|
233
|
+
continue;
|
|
234
|
+
}
|
|
235
|
+
// Detectar strings (incluyendo dentro de interpolaciones de template)
|
|
236
|
+
if (char === '"' || char === "'") {
|
|
237
|
+
// Las comillas funcionan normalmente FUERA de templates O DENTRO de interpolaciones
|
|
238
|
+
if (!inTemplate || inTemplateInterpolation) {
|
|
239
|
+
if (!inString) {
|
|
240
|
+
inString = true;
|
|
241
|
+
stringChar = char;
|
|
242
|
+
}
|
|
243
|
+
else if (char === stringChar) {
|
|
244
|
+
inString = false;
|
|
245
|
+
stringChar = '';
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
i++;
|
|
249
|
+
continue;
|
|
250
|
+
}
|
|
251
|
+
// Detectar template literals (solo fuera de regex)
|
|
252
|
+
if (!inString && !inRegex && char === '`') {
|
|
253
|
+
if (inTemplate && !inTemplateInterpolation) {
|
|
254
|
+
// Salir de template
|
|
255
|
+
inTemplate = false;
|
|
256
|
+
}
|
|
257
|
+
else if (!inTemplate) {
|
|
258
|
+
// Entrar a template
|
|
259
|
+
inTemplate = true;
|
|
260
|
+
inTemplateInterpolation = false;
|
|
261
|
+
templateBraceDepth = 0;
|
|
262
|
+
}
|
|
263
|
+
i++;
|
|
264
|
+
continue;
|
|
265
|
+
}
|
|
266
|
+
// Detectar inicio de interpolación en template literal: ${
|
|
267
|
+
if (inTemplate &&
|
|
268
|
+
!inTemplateInterpolation &&
|
|
269
|
+
char === '$' &&
|
|
270
|
+
nextChar === '{') {
|
|
271
|
+
inTemplateInterpolation = true;
|
|
272
|
+
templateBraceDepth = 0;
|
|
273
|
+
i += 2; // Skip ${ completamente
|
|
274
|
+
continue;
|
|
275
|
+
}
|
|
276
|
+
// Dentro de interpolación de template, contar brackets
|
|
277
|
+
if (inTemplateInterpolation) {
|
|
278
|
+
if (char === '{') {
|
|
279
|
+
templateBraceDepth++;
|
|
280
|
+
counters['{']++;
|
|
281
|
+
}
|
|
282
|
+
else if (char === '}') {
|
|
283
|
+
if (templateBraceDepth === 0) {
|
|
284
|
+
// Este } cierra la interpolación
|
|
285
|
+
inTemplateInterpolation = false;
|
|
286
|
+
}
|
|
287
|
+
else {
|
|
288
|
+
templateBraceDepth--;
|
|
289
|
+
counters['{']--;
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
else if (char === '(') {
|
|
293
|
+
counters['(']++;
|
|
294
|
+
}
|
|
295
|
+
else if (char === ')') {
|
|
296
|
+
counters['(']--;
|
|
297
|
+
}
|
|
298
|
+
else if (char === '[') {
|
|
299
|
+
counters['[']++;
|
|
300
|
+
}
|
|
301
|
+
else if (char === ']') {
|
|
302
|
+
counters['[']--;
|
|
303
|
+
}
|
|
304
|
+
// Early return si algún contador se vuelve negativo
|
|
305
|
+
if (counters['('] < 0 ||
|
|
306
|
+
counters['['] < 0 ||
|
|
307
|
+
counters['{'] < 0) {
|
|
308
|
+
return false;
|
|
309
|
+
}
|
|
310
|
+
i++;
|
|
311
|
+
continue;
|
|
312
|
+
}
|
|
313
|
+
// Skip contenido dentro de strings o templates (pero no interpolaciones)
|
|
314
|
+
if (inString || inTemplate) {
|
|
315
|
+
i++;
|
|
316
|
+
continue;
|
|
317
|
+
}
|
|
318
|
+
// Solo contar brackets fuera de strings/templates/comentarios
|
|
319
|
+
if (char === '(')
|
|
320
|
+
counters['(']++;
|
|
321
|
+
else if (char === ')')
|
|
322
|
+
counters['(']--;
|
|
323
|
+
else if (char === '[')
|
|
324
|
+
counters['[']++;
|
|
325
|
+
else if (char === ']')
|
|
326
|
+
counters['[']--;
|
|
327
|
+
else if (char === '{')
|
|
328
|
+
counters['{']++;
|
|
329
|
+
else if (char === '}')
|
|
330
|
+
counters['{']--;
|
|
331
|
+
// Early return si algún contador se vuelve negativo
|
|
332
|
+
if (counters['('] < 0 || counters['['] < 0 || counters['{'] < 0) {
|
|
333
|
+
return false;
|
|
334
|
+
}
|
|
335
|
+
// Track prev non-whitespace char para contexto de regex
|
|
336
|
+
if (char &&
|
|
337
|
+
char !== ' ' &&
|
|
338
|
+
char !== '\t' &&
|
|
339
|
+
char !== '\n' &&
|
|
340
|
+
char !== '\r') {
|
|
341
|
+
prevNonWhitespaceChar = char;
|
|
342
|
+
}
|
|
343
|
+
i++;
|
|
344
|
+
}
|
|
345
|
+
// Verificar que todos estén balanceados
|
|
346
|
+
return (counters['('] === 0 && counters['['] === 0 && counters['{'] === 0);
|
|
347
|
+
}
|
|
348
|
+
/**
|
|
349
|
+
* Check 3: Verificar que los exports se mantengan
|
|
350
|
+
*/
|
|
351
|
+
checkExports(original, processed) {
|
|
352
|
+
const originalExports = this.extractExports(original);
|
|
353
|
+
const processedExports = this.extractExports(processed);
|
|
354
|
+
// Si no hay exports en el original, no hay nada que validar
|
|
355
|
+
if (originalExports.length === 0) {
|
|
356
|
+
return true;
|
|
357
|
+
}
|
|
358
|
+
// Verificar que todos los exports originales estén presentes
|
|
359
|
+
for (const exp of originalExports) {
|
|
360
|
+
if (!processedExports.includes(exp)) {
|
|
361
|
+
return false;
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
return true;
|
|
365
|
+
}
|
|
366
|
+
/**
|
|
367
|
+
* Extraer exports de código JavaScript/TypeScript
|
|
368
|
+
*/
|
|
369
|
+
extractExports(code) {
|
|
370
|
+
const exports = [];
|
|
371
|
+
// export default
|
|
372
|
+
if (/export\s+default\s/.test(code)) {
|
|
373
|
+
exports.push('default');
|
|
374
|
+
}
|
|
375
|
+
// export { a, b, c }
|
|
376
|
+
const namedExportsMatches = code.matchAll(/export\s*\{\s*([^}]+)\s*\}/g);
|
|
377
|
+
for (const match of namedExportsMatches) {
|
|
378
|
+
if (match[1]) {
|
|
379
|
+
const names = match[1]
|
|
380
|
+
.split(',')
|
|
381
|
+
.map(n => {
|
|
382
|
+
const parts = n.trim().split(/\s+as\s+/);
|
|
383
|
+
return parts[0]?.trim() || '';
|
|
384
|
+
})
|
|
385
|
+
.filter(n => n);
|
|
386
|
+
exports.push(...names);
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
// export const/let/var/function/class
|
|
390
|
+
const directExportsMatches = code.matchAll(/export\s+(?:const|let|var|function|class|async\s+function)\s+(\w+)/g);
|
|
391
|
+
for (const match of directExportsMatches) {
|
|
392
|
+
if (match[1]) {
|
|
393
|
+
exports.push(match[1]);
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
// export * from
|
|
397
|
+
if (/export\s+\*\s+from/.test(code)) {
|
|
398
|
+
exports.push('*');
|
|
399
|
+
}
|
|
400
|
+
return [...new Set(exports)]; // Deduplicar
|
|
401
|
+
}
|
|
402
|
+
/**
|
|
403
|
+
* Check 4: Validación de sintaxis con oxc-parser
|
|
404
|
+
*/
|
|
405
|
+
checkSyntax(code) {
|
|
406
|
+
try {
|
|
407
|
+
const parseResult = parseSync('integrity-check.js', code, {
|
|
408
|
+
sourceType: 'module',
|
|
409
|
+
});
|
|
410
|
+
return parseResult.errors.length === 0;
|
|
411
|
+
}
|
|
412
|
+
catch {
|
|
413
|
+
// Si parseSync lanza error, la sintaxis es inválida
|
|
414
|
+
return false;
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
/**
|
|
418
|
+
* Crear objeto de resultado
|
|
419
|
+
*/
|
|
420
|
+
createResult(valid, checks, errors, original, processed, startTime) {
|
|
421
|
+
const duration = performance.now() - startTime;
|
|
422
|
+
return {
|
|
423
|
+
valid,
|
|
424
|
+
checks,
|
|
425
|
+
errors,
|
|
426
|
+
metrics: {
|
|
427
|
+
duration,
|
|
428
|
+
originalSize: original.length,
|
|
429
|
+
processedSize: processed.length,
|
|
430
|
+
exportCount: this.extractExports(processed).length,
|
|
431
|
+
},
|
|
432
|
+
};
|
|
433
|
+
}
|
|
434
|
+
/**
|
|
435
|
+
* Manejar resultado de validación (estadísticas, logging, errores)
|
|
436
|
+
*/
|
|
437
|
+
handleValidationResult(result, context, options) {
|
|
438
|
+
// Actualizar estadísticas
|
|
439
|
+
if (result.valid) {
|
|
440
|
+
this.stats.successfulValidations++;
|
|
441
|
+
}
|
|
442
|
+
else {
|
|
443
|
+
this.stats.failedValidations++;
|
|
444
|
+
}
|
|
445
|
+
this.stats.totalDuration += result.metrics.duration;
|
|
446
|
+
this.stats.averageDuration =
|
|
447
|
+
this.stats.totalDuration / this.stats.totalValidations;
|
|
448
|
+
// Logging
|
|
449
|
+
if (options.verbose) {
|
|
450
|
+
if (result.valid) {
|
|
451
|
+
logger.info(`[IntegrityValidator] ✓ ${context} - ` +
|
|
452
|
+
`${result.metrics.duration.toFixed(2)}ms - ` +
|
|
453
|
+
`${result.metrics.originalSize} → ${result.metrics.processedSize} bytes`);
|
|
454
|
+
}
|
|
455
|
+
else {
|
|
456
|
+
logger.error(`[IntegrityValidator] ✗ ${context} - ` +
|
|
457
|
+
`Failed: ${result.errors.join(', ')}`);
|
|
458
|
+
}
|
|
459
|
+
}
|
|
460
|
+
// Lanzar error si está configurado
|
|
461
|
+
if (!result.valid && options.throwOnError) {
|
|
462
|
+
throw new Error(`Integrity validation failed for ${context}: ${result.errors.join(', ')}`);
|
|
463
|
+
}
|
|
464
|
+
}
|
|
465
|
+
/**
|
|
466
|
+
* Generar clave de caché
|
|
467
|
+
*/
|
|
468
|
+
getCacheKey(context, code) {
|
|
469
|
+
// Hash simple pero rápido
|
|
470
|
+
const hash = this.hashCode(code);
|
|
471
|
+
return `${context}:${hash}`;
|
|
472
|
+
}
|
|
473
|
+
/**
|
|
474
|
+
* Hash simple para cache
|
|
475
|
+
*/
|
|
476
|
+
hashCode(str) {
|
|
477
|
+
let hash = 0;
|
|
478
|
+
for (let i = 0; i < str.length; i++) {
|
|
479
|
+
const char = str.charCodeAt(i);
|
|
480
|
+
hash = (hash << 5) - hash + char;
|
|
481
|
+
hash = hash & hash; // Convert to 32-bit integer
|
|
482
|
+
}
|
|
483
|
+
return hash.toString(36);
|
|
484
|
+
}
|
|
485
|
+
/**
|
|
486
|
+
* Guardar en caché con LRU eviction
|
|
487
|
+
*/
|
|
488
|
+
saveToCache(key, result) {
|
|
489
|
+
// LRU eviction
|
|
490
|
+
if (this.cache.size >= this.MAX_CACHE_SIZE) {
|
|
491
|
+
const firstKey = this.cache.keys().next().value;
|
|
492
|
+
if (firstKey) {
|
|
493
|
+
this.cache.delete(firstKey);
|
|
494
|
+
}
|
|
495
|
+
}
|
|
496
|
+
this.cache.set(key, result);
|
|
497
|
+
}
|
|
498
|
+
/**
|
|
499
|
+
* Obtener estadísticas de validación
|
|
500
|
+
*/
|
|
501
|
+
getStats() {
|
|
502
|
+
return { ...this.stats };
|
|
503
|
+
}
|
|
504
|
+
/**
|
|
505
|
+
* Limpiar caché
|
|
506
|
+
*/
|
|
507
|
+
clearCache() {
|
|
508
|
+
this.cache.clear();
|
|
509
|
+
}
|
|
510
|
+
/**
|
|
511
|
+
* Resetear estadísticas
|
|
512
|
+
*/
|
|
513
|
+
resetStats() {
|
|
514
|
+
this.stats = {
|
|
515
|
+
totalValidations: 0,
|
|
516
|
+
successfulValidations: 0,
|
|
517
|
+
failedValidations: 0,
|
|
518
|
+
cacheHits: 0,
|
|
519
|
+
cacheMisses: 0,
|
|
520
|
+
totalDuration: 0,
|
|
521
|
+
averageDuration: 0,
|
|
522
|
+
};
|
|
523
|
+
}
|
|
524
|
+
}
|
|
525
|
+
// Export singleton instance
|
|
526
|
+
export const integrityValidator = IntegrityValidator.getInstance();
|
|
527
|
+
//# sourceMappingURL=integrity-validator.js.map
|
package/dist/compiler/minify.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { createHash } from 'node:crypto';
|
|
2
2
|
import { minifySync } from 'oxc-minify';
|
|
3
3
|
import { logger } from '../servicios/logger.js';
|
|
4
|
+
import { integrityValidator } from './integrity-validator.js';
|
|
4
5
|
import { minifyTemplate } from './minifyTemplate.js';
|
|
5
6
|
class MinificationCache {
|
|
6
7
|
static instance;
|
|
@@ -51,6 +52,23 @@ class MinificationCache {
|
|
|
51
52
|
const originalSize = data.length;
|
|
52
53
|
try {
|
|
53
54
|
const result = minifySync(filename, data, options);
|
|
55
|
+
// VALIDACIÓN DE INTEGRIDAD - Solo si flag está activo
|
|
56
|
+
if (process.env.CHECK_INTEGRITY === 'true') {
|
|
57
|
+
const validation = integrityValidator.validate(data, result.code, `minify:${filename}`, {
|
|
58
|
+
skipSyntaxCheck: false,
|
|
59
|
+
verbose: process.env.VERBOSE === 'true',
|
|
60
|
+
throwOnError: true, // Detener build si falla
|
|
61
|
+
});
|
|
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(', ')}`);
|
|
67
|
+
}
|
|
68
|
+
if (process.env.VERBOSE === 'true') {
|
|
69
|
+
logger.info(`✅ Validación de integridad OK para ${filename} (${validation.metrics.duration.toFixed(2)}ms)`);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
54
72
|
// Si el código de entrada no estaba vacío pero el resultado sí,
|
|
55
73
|
// retornar código original sin minificar con advertencia
|
|
56
74
|
if (data.trim() && !result.code.trim()) {
|
|
@@ -233,7 +251,7 @@ export const minifyJS = async (data, filename, isProd = true) => {
|
|
|
233
251
|
normal: true,
|
|
234
252
|
jsdoc: true,
|
|
235
253
|
annotation: true,
|
|
236
|
-
legal: true
|
|
254
|
+
legal: true,
|
|
237
255
|
},
|
|
238
256
|
sourcemap: !isProd,
|
|
239
257
|
};
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { minifyHTMLLiterals } from 'minify-html-literals';
|
|
2
2
|
import { logger } from '../servicios/logger.js';
|
|
3
|
+
import { integrityValidator } from './integrity-validator.js';
|
|
3
4
|
const defaultMinifyOptions = {
|
|
4
5
|
// Opciones esenciales para componentes Vue
|
|
5
6
|
caseSensitive: true, // Preserva mayúsculas/minúsculas en nombres de componentes
|
|
@@ -219,6 +220,21 @@ const minifyTemplate = (data, fileName) => {
|
|
|
219
220
|
// Esto convierte __VERSA_TEMP__` de vuelta a ` para que el código
|
|
220
221
|
// final no contenga los marcadores temporales
|
|
221
222
|
const finalCode = removeTemporaryTags(minifiedCode);
|
|
223
|
+
// VALIDACIÓN DE INTEGRIDAD - Solo si flag está activo
|
|
224
|
+
if (process.env.CHECK_INTEGRITY === 'true') {
|
|
225
|
+
const validation = integrityValidator.validate(data, finalCode, `minifyTemplate:${fileName}`, {
|
|
226
|
+
skipSyntaxCheck: true, // No validar sintaxis (puede no ser JS puro)
|
|
227
|
+
verbose: process.env.VERBOSE === 'true',
|
|
228
|
+
throwOnError: true, // Detener build si falla
|
|
229
|
+
});
|
|
230
|
+
if (!validation.valid) {
|
|
231
|
+
logger.error(`❌ Validación de integridad fallida para template ${fileName}`, validation.errors.join(', '));
|
|
232
|
+
throw new Error(`Template integrity check failed for ${fileName}: ${validation.errors.join(', ')}`);
|
|
233
|
+
}
|
|
234
|
+
if (process.env.VERBOSE === 'true') {
|
|
235
|
+
logger.info(`✅ Validación de template OK para ${fileName} (${validation.metrics.duration.toFixed(2)}ms)`);
|
|
236
|
+
}
|
|
237
|
+
}
|
|
222
238
|
return { code: finalCode, error: null };
|
|
223
239
|
}
|
|
224
240
|
catch (error) {
|
|
@@ -3,9 +3,24 @@ 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';
|
|
6
7
|
import { analyzeAndFormatMultipleErrors } from './error-reporter.js';
|
|
7
8
|
import { getOptimizedAliasPath, getOptimizedModulePath, } from './module-resolution-optimizer.js';
|
|
8
9
|
import { parser } from './parser.js';
|
|
10
|
+
// ✨ OPTIMIZACIÓN CRÍTICA: Cache de PATH_ALIAS parseado
|
|
11
|
+
let cachedPathAlias = null;
|
|
12
|
+
let lastPathAliasString = null;
|
|
13
|
+
function getParsedPathAlias() {
|
|
14
|
+
if (!env.PATH_ALIAS)
|
|
15
|
+
return null;
|
|
16
|
+
// Solo parsear si el string cambió
|
|
17
|
+
if (cachedPathAlias && lastPathAliasString === env.PATH_ALIAS) {
|
|
18
|
+
return cachedPathAlias;
|
|
19
|
+
}
|
|
20
|
+
cachedPathAlias = JSON.parse(env.PATH_ALIAS);
|
|
21
|
+
lastPathAliasString = env.PATH_ALIAS;
|
|
22
|
+
return cachedPathAlias;
|
|
23
|
+
}
|
|
9
24
|
// Módulos built-in de Node.js que no deben ser resueltos
|
|
10
25
|
const NODE_BUILTIN_MODULES = new Set([
|
|
11
26
|
'fs',
|
|
@@ -94,7 +109,7 @@ export async function replaceAliasImportStatic(file, code) {
|
|
|
94
109
|
if (!env.PATH_ALIAS || !env.PATH_DIST) {
|
|
95
110
|
return code;
|
|
96
111
|
}
|
|
97
|
-
const pathAlias =
|
|
112
|
+
const pathAlias = getParsedPathAlias();
|
|
98
113
|
let resultCode = code;
|
|
99
114
|
// Usar regex para transformar imports estáticos
|
|
100
115
|
const importRegex = /import\s+(?:(?:\{[^}]*\}|\*\s+as\s+\w+|\w+)(?:\s*,\s*(?:\{[^}]*\}|\*\s+as\s+\w+|\w+))*\s+from\s+)?['"`]([^'"`]+)['"`]/g;
|
|
@@ -206,7 +221,7 @@ export async function replaceAliasImportDynamic(code, _imports, file) {
|
|
|
206
221
|
if (!env.PATH_ALIAS || !env.PATH_DIST) {
|
|
207
222
|
return code;
|
|
208
223
|
}
|
|
209
|
-
const pathAlias =
|
|
224
|
+
const pathAlias = getParsedPathAlias();
|
|
210
225
|
const pathDist = env.PATH_DIST;
|
|
211
226
|
let resultCode = code;
|
|
212
227
|
// Regex para imports dinámicos normales con string (solo comillas simples y dobles)
|
|
@@ -391,7 +406,7 @@ async function replaceAliasInStrings(code) {
|
|
|
391
406
|
if (!env.PATH_ALIAS || !env.PATH_DIST) {
|
|
392
407
|
return code;
|
|
393
408
|
}
|
|
394
|
-
const pathAlias =
|
|
409
|
+
const pathAlias = getParsedPathAlias();
|
|
395
410
|
const pathDist = env.PATH_DIST;
|
|
396
411
|
let resultCode = code; // Regex para encontrar strings que contengan posibles alias
|
|
397
412
|
// Busca strings entre comillas simples, dobles o backticks que contengan alias
|
|
@@ -518,6 +533,7 @@ const removeCodeTagImport = async (data) => {
|
|
|
518
533
|
return data;
|
|
519
534
|
};
|
|
520
535
|
export async function estandarizaCode(code, file) {
|
|
536
|
+
const originalCode = code; // Guardar código original para validación
|
|
521
537
|
try {
|
|
522
538
|
const ast = await parser(file, code);
|
|
523
539
|
if (ast && ast.errors && ast.errors.length > 0) {
|
|
@@ -542,6 +558,21 @@ export async function estandarizaCode(code, file) {
|
|
|
542
558
|
if (env.isPROD === 'true') {
|
|
543
559
|
code = await removePreserverComent(code);
|
|
544
560
|
}
|
|
561
|
+
// VALIDACIÓN DE INTEGRIDAD - Solo si flag está activo
|
|
562
|
+
if (env.CHECK_INTEGRITY === 'true') {
|
|
563
|
+
const validation = integrityValidator.validate(originalCode, code, `transforms:${path.basename(file)}`, {
|
|
564
|
+
skipSyntaxCheck: false, // SÍ validar sintaxis en transformaciones
|
|
565
|
+
verbose: env.VERBOSE === 'true',
|
|
566
|
+
throwOnError: true,
|
|
567
|
+
});
|
|
568
|
+
if (!validation.valid) {
|
|
569
|
+
logger.error(`❌ Validación de integridad fallida en transformaciones para ${path.basename(file)}`, validation.errors.join(', '));
|
|
570
|
+
throw new Error(`Transform integrity check failed for ${path.basename(file)}: ${validation.errors.join(', ')}`);
|
|
571
|
+
}
|
|
572
|
+
if (env.VERBOSE === 'true') {
|
|
573
|
+
logger.info(`✅ Validación de transformaciones OK para ${path.basename(file)} (${validation.metrics.duration.toFixed(2)}ms)`);
|
|
574
|
+
}
|
|
575
|
+
}
|
|
545
576
|
return { code, error: null };
|
|
546
577
|
}
|
|
547
578
|
catch (error) {
|