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.
@@ -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
@@ -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) {