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