versacompiler 1.0.4 → 2.0.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 +357 -145
- package/dist/compiler/compile.js +1120 -0
- package/dist/compiler/error-reporter.js +467 -0
- package/dist/compiler/linter.js +72 -0
- package/dist/{services → compiler}/minify.js +40 -31
- package/dist/compiler/parser.js +30 -0
- package/dist/compiler/tailwindcss.js +39 -0
- package/dist/compiler/transformTStoJS.js +16 -0
- package/dist/compiler/transforms.js +544 -0
- package/dist/compiler/typescript-error-parser.js +282 -0
- package/dist/compiler/typescript-sync-validator.js +230 -0
- package/dist/compiler/typescript-worker-thread.cjs +457 -0
- package/dist/compiler/typescript-worker.js +309 -0
- package/dist/compiler/typescript.js +382 -0
- package/dist/compiler/vuejs.js +296 -0
- package/dist/hrm/VueHRM.js +353 -0
- package/dist/hrm/errorScreen.js +23 -1
- package/dist/hrm/getInstanciaVue.js +313 -0
- package/dist/hrm/initHRM.js +140 -0
- package/dist/main.js +287 -0
- package/dist/servicios/browserSync.js +177 -0
- package/dist/servicios/chokidar.js +178 -0
- package/dist/servicios/logger.js +33 -0
- package/dist/servicios/readConfig.js +429 -0
- package/dist/utils/module-resolver.js +506 -0
- package/dist/utils/promptUser.js +48 -0
- package/dist/utils/resolve-bin.js +29 -0
- package/dist/utils/utils.js +21 -48
- package/dist/wrappers/eslint-node.js +145 -0
- package/dist/wrappers/oxlint-node.js +120 -0
- package/dist/wrappers/tailwind-node.js +92 -0
- package/package.json +62 -15
- package/dist/hrm/devMode.js +0 -249
- package/dist/hrm/instanciaVue.js +0 -35
- package/dist/hrm/setupHMR.js +0 -57
- package/dist/index.js +0 -873
- package/dist/services/acorn.js +0 -29
- package/dist/services/linter.js +0 -55
- package/dist/services/typescript.js +0 -89
- package/dist/services/vueLoader.js +0 -324
- package/dist/services/vuejs.js +0 -259
|
@@ -0,0 +1,1120 @@
|
|
|
1
|
+
import { glob, mkdir, readFile, stat, unlink, writeFile, } from 'node:fs/promises';
|
|
2
|
+
import os from 'node:os';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
import { env } from 'node:process';
|
|
5
|
+
// Lazy loading optimizations - Only import lightweight modules synchronously
|
|
6
|
+
import { logger } from '../servicios/logger.js';
|
|
7
|
+
import { promptUser } from '../utils/promptUser.js';
|
|
8
|
+
import { showTimingForHumans } from '../utils/utils.js';
|
|
9
|
+
// Heavy dependencies will be loaded dynamically when needed
|
|
10
|
+
let chalk;
|
|
11
|
+
let ESLint;
|
|
12
|
+
let OxLint;
|
|
13
|
+
let minifyJS;
|
|
14
|
+
let getCodeFile;
|
|
15
|
+
let generateTailwindCSS;
|
|
16
|
+
let estandarizaCode;
|
|
17
|
+
let preCompileTS;
|
|
18
|
+
let preCompileVue;
|
|
19
|
+
// 🚀 Sistema de Carga Inteligente de Módulos - VERSIÓN OPTIMIZADA
|
|
20
|
+
class ModuleManager {
|
|
21
|
+
static instance;
|
|
22
|
+
isInitialized = false;
|
|
23
|
+
initializationPromise = null;
|
|
24
|
+
loadedModules = new Set();
|
|
25
|
+
// NUEVOS: Gestión de modo y precarga
|
|
26
|
+
currentMode = null;
|
|
27
|
+
preloadPromises = new Map();
|
|
28
|
+
moduleCache = new Map();
|
|
29
|
+
constructor() { }
|
|
30
|
+
static getInstance() {
|
|
31
|
+
if (!ModuleManager.instance) {
|
|
32
|
+
ModuleManager.instance = new ModuleManager();
|
|
33
|
+
}
|
|
34
|
+
return ModuleManager.instance;
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* NUEVO: Precarga estratégica para modo watch
|
|
38
|
+
*/
|
|
39
|
+
async preloadForWatchMode() {
|
|
40
|
+
const essentialModules = ['chalk', 'parser', 'transforms'];
|
|
41
|
+
const preloadPromises = essentialModules.map(async (moduleName) => {
|
|
42
|
+
if (!this.loadedModules.has(moduleName)) {
|
|
43
|
+
switch (moduleName) {
|
|
44
|
+
case 'chalk':
|
|
45
|
+
return this._loadModule('chalk', loadChalk);
|
|
46
|
+
case 'parser':
|
|
47
|
+
return this._loadModule('parser', loadParser);
|
|
48
|
+
case 'transforms':
|
|
49
|
+
return this._loadModule('transforms', loadTransforms);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
});
|
|
53
|
+
await Promise.all(preloadPromises);
|
|
54
|
+
// console.log('[ModuleManager] Precarga completada para modo watch');
|
|
55
|
+
} /**
|
|
56
|
+
* MEJORADO: Inicializa los módulos necesarios según el contexto de compilación
|
|
57
|
+
* @param context Contexto de compilación: 'individual', 'batch', 'watch'
|
|
58
|
+
* @param fileExtensions Extensiones de archivos a compilar para optimizar la carga
|
|
59
|
+
*/
|
|
60
|
+
async initializeModules(context = 'individual', fileExtensions = new Set()) {
|
|
61
|
+
// Si cambia el contexto, reinicializar
|
|
62
|
+
if (this.currentMode !== context) {
|
|
63
|
+
this.currentMode = context;
|
|
64
|
+
// En modo watch, precargar módulos esenciales
|
|
65
|
+
if (context === 'watch') {
|
|
66
|
+
await this.preloadForWatchMode();
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
if (this.initializationPromise) {
|
|
70
|
+
return this.initializationPromise;
|
|
71
|
+
}
|
|
72
|
+
if (this.isInitialized && context !== 'individual') {
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
75
|
+
this.initializationPromise = this._performInitialization(context, fileExtensions);
|
|
76
|
+
await this.initializationPromise;
|
|
77
|
+
this.initializationPromise = null;
|
|
78
|
+
}
|
|
79
|
+
async _performInitialization(context, fileExtensions) {
|
|
80
|
+
const modulesToLoad = [];
|
|
81
|
+
// Módulos siempre necesarios
|
|
82
|
+
if (!this.loadedModules.has('chalk')) {
|
|
83
|
+
modulesToLoad.push(this._loadModule('chalk', loadChalk));
|
|
84
|
+
}
|
|
85
|
+
if (!this.loadedModules.has('parser')) {
|
|
86
|
+
modulesToLoad.push(this._loadModule('parser', loadParser));
|
|
87
|
+
}
|
|
88
|
+
if (!this.loadedModules.has('transforms')) {
|
|
89
|
+
modulesToLoad.push(this._loadModule('transforms', loadTransforms));
|
|
90
|
+
}
|
|
91
|
+
// Carga contextual según el tipo de compilación
|
|
92
|
+
if (context === 'batch' || context === 'watch') {
|
|
93
|
+
// En modo batch/watch, precargar todos los módulos necesarios
|
|
94
|
+
if (!this.loadedModules.has('vue')) {
|
|
95
|
+
modulesToLoad.push(this._loadModule('vue', loadVue));
|
|
96
|
+
}
|
|
97
|
+
if (!this.loadedModules.has('typescript')) {
|
|
98
|
+
modulesToLoad.push(this._loadModule('typescript', loadTypeScript));
|
|
99
|
+
}
|
|
100
|
+
if (!this.loadedModules.has('minify')) {
|
|
101
|
+
modulesToLoad.push(this._loadModule('minify', loadMinify));
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
else {
|
|
105
|
+
// En modo individual, cargar solo según las extensiones necesarias
|
|
106
|
+
if (fileExtensions.has('.vue') && !this.loadedModules.has('vue')) {
|
|
107
|
+
modulesToLoad.push(this._loadModule('vue', loadVue));
|
|
108
|
+
}
|
|
109
|
+
if ((fileExtensions.has('.ts') || fileExtensions.has('.vue')) &&
|
|
110
|
+
!this.loadedModules.has('typescript')) {
|
|
111
|
+
modulesToLoad.push(this._loadModule('typescript', loadTypeScript));
|
|
112
|
+
}
|
|
113
|
+
if (env.isPROD === 'true' && !this.loadedModules.has('minify')) {
|
|
114
|
+
modulesToLoad.push(this._loadModule('minify', loadMinify));
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
// Cargar módulos en paralelo
|
|
118
|
+
await Promise.all(modulesToLoad);
|
|
119
|
+
this.isInitialized = true;
|
|
120
|
+
}
|
|
121
|
+
async _loadModule(name, loadFunction) {
|
|
122
|
+
if (!this.loadedModules.has(name)) {
|
|
123
|
+
await loadFunction();
|
|
124
|
+
this.loadedModules.add(name);
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
/**
|
|
128
|
+
* Carga un módulo específico bajo demanda (lazy loading)
|
|
129
|
+
*/
|
|
130
|
+
async ensureModuleLoaded(moduleName) {
|
|
131
|
+
if (this.loadedModules.has(moduleName)) {
|
|
132
|
+
return;
|
|
133
|
+
}
|
|
134
|
+
switch (moduleName) {
|
|
135
|
+
case 'vue':
|
|
136
|
+
await this._loadModule('vue', loadVue);
|
|
137
|
+
break;
|
|
138
|
+
case 'typescript':
|
|
139
|
+
await this._loadModule('typescript', loadTypeScript);
|
|
140
|
+
break;
|
|
141
|
+
case 'minify':
|
|
142
|
+
await this._loadModule('minify', loadMinify);
|
|
143
|
+
break;
|
|
144
|
+
case 'tailwind':
|
|
145
|
+
await this._loadModule('tailwind', loadTailwind);
|
|
146
|
+
break;
|
|
147
|
+
case 'linter':
|
|
148
|
+
await this._loadModule('linter', loadLinter);
|
|
149
|
+
break;
|
|
150
|
+
default:
|
|
151
|
+
throw new Error(`Módulo desconocido: ${moduleName}`);
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
/**
|
|
155
|
+
* Resetea el estado del manager (útil para tests)
|
|
156
|
+
*/
|
|
157
|
+
reset() {
|
|
158
|
+
this.isInitialized = false;
|
|
159
|
+
this.initializationPromise = null;
|
|
160
|
+
this.loadedModules.clear();
|
|
161
|
+
}
|
|
162
|
+
/**
|
|
163
|
+
* Obtiene estadísticas de módulos cargados
|
|
164
|
+
*/
|
|
165
|
+
getStats() {
|
|
166
|
+
return {
|
|
167
|
+
loaded: Array.from(this.loadedModules),
|
|
168
|
+
initialized: this.isInitialized,
|
|
169
|
+
};
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
// Lazy loading helper functions
|
|
173
|
+
async function loadChalk() {
|
|
174
|
+
if (!chalk) {
|
|
175
|
+
chalk = (await import('chalk')).default;
|
|
176
|
+
}
|
|
177
|
+
return chalk;
|
|
178
|
+
}
|
|
179
|
+
async function loadLinter() {
|
|
180
|
+
if (!ESLint || !OxLint) {
|
|
181
|
+
const linterModule = await import('./linter.js');
|
|
182
|
+
ESLint = linterModule.ESLint;
|
|
183
|
+
OxLint = linterModule.OxLint;
|
|
184
|
+
}
|
|
185
|
+
return { ESLint, OxLint };
|
|
186
|
+
}
|
|
187
|
+
async function loadMinify() {
|
|
188
|
+
if (!minifyJS) {
|
|
189
|
+
const minifyModule = await import('./minify.js');
|
|
190
|
+
minifyJS = minifyModule.minifyJS;
|
|
191
|
+
}
|
|
192
|
+
return minifyJS;
|
|
193
|
+
}
|
|
194
|
+
async function loadParser() {
|
|
195
|
+
if (!getCodeFile) {
|
|
196
|
+
const parserModule = await import('./parser.js');
|
|
197
|
+
getCodeFile = parserModule.getCodeFile;
|
|
198
|
+
}
|
|
199
|
+
return getCodeFile;
|
|
200
|
+
}
|
|
201
|
+
async function loadTailwind() {
|
|
202
|
+
if (!generateTailwindCSS) {
|
|
203
|
+
const tailwindModule = await import('./tailwindcss.js');
|
|
204
|
+
generateTailwindCSS = tailwindModule.generateTailwindCSS;
|
|
205
|
+
}
|
|
206
|
+
return generateTailwindCSS;
|
|
207
|
+
}
|
|
208
|
+
async function loadTransforms() {
|
|
209
|
+
if (!estandarizaCode) {
|
|
210
|
+
const transformsModule = await import('./transforms.js');
|
|
211
|
+
estandarizaCode = transformsModule.estandarizaCode;
|
|
212
|
+
}
|
|
213
|
+
return estandarizaCode;
|
|
214
|
+
}
|
|
215
|
+
async function loadTypeScript() {
|
|
216
|
+
if (!preCompileTS) {
|
|
217
|
+
const typescriptModule = await import('./typescript.js');
|
|
218
|
+
preCompileTS = typescriptModule.preCompileTS;
|
|
219
|
+
}
|
|
220
|
+
return preCompileTS;
|
|
221
|
+
}
|
|
222
|
+
async function loadVue() {
|
|
223
|
+
if (!preCompileVue) {
|
|
224
|
+
const vueModule = await import('./vuejs.js');
|
|
225
|
+
preCompileVue = vueModule.preCompileVue;
|
|
226
|
+
}
|
|
227
|
+
return preCompileVue;
|
|
228
|
+
}
|
|
229
|
+
// Almacenamiento global de errores y resultados
|
|
230
|
+
const compilationErrors = [];
|
|
231
|
+
const compilationResults = [];
|
|
232
|
+
const compilationCache = new Map();
|
|
233
|
+
const CACHE_DIR = path.join(path.resolve(env.PATH_PROY || process.cwd(), 'compiler'), '.cache');
|
|
234
|
+
const CACHE_FILE = path.join(CACHE_DIR, 'versacompile-cache.json');
|
|
235
|
+
async function loadCache() {
|
|
236
|
+
try {
|
|
237
|
+
if (env.cleanCache === 'true') {
|
|
238
|
+
compilationCache.clear();
|
|
239
|
+
try {
|
|
240
|
+
await unlink(CACHE_FILE);
|
|
241
|
+
}
|
|
242
|
+
catch {
|
|
243
|
+
// Ignorar errores al eliminar el archivo
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
const cacheData = await readFile(CACHE_FILE, 'utf-8');
|
|
247
|
+
const parsed = JSON.parse(cacheData);
|
|
248
|
+
for (const [key, value] of Object.entries(parsed)) {
|
|
249
|
+
compilationCache.set(key, value);
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
catch {
|
|
253
|
+
// Cache file doesn't exist or is invalid, start fresh
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
async function saveCache() {
|
|
257
|
+
try {
|
|
258
|
+
await mkdir(CACHE_DIR, { recursive: true });
|
|
259
|
+
const cacheData = Object.fromEntries(compilationCache);
|
|
260
|
+
await writeFile(CACHE_FILE, JSON.stringify(cacheData, null, 2));
|
|
261
|
+
}
|
|
262
|
+
catch {
|
|
263
|
+
// Ignore save errors
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
// 🎯 Funciones del Sistema Unificado de Manejo de Errores
|
|
267
|
+
/**
|
|
268
|
+
* Registra un error de compilación en el sistema unificado
|
|
269
|
+
*/
|
|
270
|
+
function registerCompilationError(file, stage, message, severity = 'error', details, help) {
|
|
271
|
+
compilationErrors.push({
|
|
272
|
+
file,
|
|
273
|
+
stage,
|
|
274
|
+
message,
|
|
275
|
+
severity,
|
|
276
|
+
details,
|
|
277
|
+
help,
|
|
278
|
+
timestamp: Date.now(),
|
|
279
|
+
});
|
|
280
|
+
}
|
|
281
|
+
/**
|
|
282
|
+
* Registra un resultado de compilación (éxitos/errores por etapa)
|
|
283
|
+
*/
|
|
284
|
+
function registerCompilationResult(stage, errors, success, files = []) {
|
|
285
|
+
const existingResult = compilationResults.find(r => r.stage === stage);
|
|
286
|
+
if (existingResult) {
|
|
287
|
+
existingResult.errors += errors;
|
|
288
|
+
existingResult.success += success;
|
|
289
|
+
existingResult.files.push(...files);
|
|
290
|
+
}
|
|
291
|
+
else {
|
|
292
|
+
compilationResults.push({
|
|
293
|
+
stage,
|
|
294
|
+
errors,
|
|
295
|
+
success,
|
|
296
|
+
files: [...files],
|
|
297
|
+
});
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
/**
|
|
301
|
+
* Maneja errores según el modo de compilación
|
|
302
|
+
*/
|
|
303
|
+
async function handleCompilationError(error, fileName, stage, mode, isVerbose = false) {
|
|
304
|
+
const errorMessage = error instanceof Error ? error.message : error;
|
|
305
|
+
const errorDetails = error instanceof Error ? error.stack : undefined;
|
|
306
|
+
// Registrar el error en el sistema unificado
|
|
307
|
+
registerCompilationError(fileName, stage, errorMessage, 'error', errorDetails);
|
|
308
|
+
registerCompilationResult(stage, 1, 0, [fileName]); // Mostrar error inmediatamente solo en modo individual y watch
|
|
309
|
+
if (mode === 'individual' || mode === 'watch') {
|
|
310
|
+
const chalk = await loadChalk();
|
|
311
|
+
const baseName = path.basename(fileName);
|
|
312
|
+
const stageColor = await getStageColor(stage);
|
|
313
|
+
if (isVerbose) {
|
|
314
|
+
// Modo verbose: Mostrar error completo con contexto
|
|
315
|
+
logger.error(chalk.red(`❌ Error en etapa ${stageColor(stage)} - ${baseName}:`));
|
|
316
|
+
logger.error(chalk.red(errorMessage));
|
|
317
|
+
if (errorDetails && (stage === 'typescript' || stage === 'vue')) {
|
|
318
|
+
// Mostrar stack trace limitado para TypeScript y Vue
|
|
319
|
+
const stackLines = errorDetails.split('\n').slice(0, 5);
|
|
320
|
+
stackLines.forEach(line => {
|
|
321
|
+
if (line.trim()) {
|
|
322
|
+
logger.error(chalk.gray(` ${line.trim()}`));
|
|
323
|
+
}
|
|
324
|
+
});
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
else {
|
|
328
|
+
// Modo normal: Mostrar error simplificado
|
|
329
|
+
const firstLine = errorMessage.split('\n')[0];
|
|
330
|
+
logger.error(chalk.red(`❌ Error en ${stageColor(stage)}: ${baseName}`));
|
|
331
|
+
logger.error(chalk.red(` ${firstLine}`));
|
|
332
|
+
logger.info(chalk.yellow(`💡 Usa --verbose para ver detalles completos`));
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
// En modo 'all', los errores se acumulan silenciosamente para el resumen final
|
|
336
|
+
}
|
|
337
|
+
/**
|
|
338
|
+
* Registra un éxito de compilación
|
|
339
|
+
*/
|
|
340
|
+
function registerCompilationSuccess(fileName, stage) {
|
|
341
|
+
registerCompilationResult(stage, 0, 1, [fileName]);
|
|
342
|
+
}
|
|
343
|
+
/**
|
|
344
|
+
* Limpia todos los errores y resultados acumulados
|
|
345
|
+
*/
|
|
346
|
+
function clearCompilationState() {
|
|
347
|
+
compilationErrors.length = 0;
|
|
348
|
+
compilationResults.length = 0;
|
|
349
|
+
}
|
|
350
|
+
/**
|
|
351
|
+
* Muestra un resumen detallado de todos los errores de compilación
|
|
352
|
+
*/
|
|
353
|
+
async function displayCompilationSummary(isVerbose = false) {
|
|
354
|
+
const chalk = await loadChalk();
|
|
355
|
+
if (compilationErrors.length === 0 && compilationResults.length === 0) {
|
|
356
|
+
logger.info(chalk.green('✅ No hay errores de compilación para mostrar.'));
|
|
357
|
+
return;
|
|
358
|
+
}
|
|
359
|
+
logger.info(chalk.bold('\n--- 📊 RESUMEN DE COMPILACIÓN ---'));
|
|
360
|
+
// Mostrar estadísticas por etapa
|
|
361
|
+
if (compilationResults.length > 0) {
|
|
362
|
+
logger.info(chalk.blue('\n🔍 Estadísticas por etapa:'));
|
|
363
|
+
for (const result of compilationResults) {
|
|
364
|
+
const totalFiles = result.success + result.errors;
|
|
365
|
+
const successRate = totalFiles > 0
|
|
366
|
+
? Math.round((result.success / totalFiles) * 100)
|
|
367
|
+
: 0;
|
|
368
|
+
const statusIcon = result.errors === 0 ? '✅' : '❌';
|
|
369
|
+
const stageColor = await getStageColor(result.stage);
|
|
370
|
+
const statusText = `${result.success} éxitos, ${result.errors} errores`;
|
|
371
|
+
const coloredStatusText = result.errors === 0
|
|
372
|
+
? chalk.green(statusText)
|
|
373
|
+
: chalk.red(statusText);
|
|
374
|
+
logger.info(`${statusIcon} ${stageColor(result.stage)}: ${coloredStatusText} (${successRate}% éxito)`);
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
// Mostrar errores detallados
|
|
378
|
+
if (compilationErrors.length > 0) {
|
|
379
|
+
logger.info(chalk.red(`\n❌ Se encontraron ${compilationErrors.length} errores:`));
|
|
380
|
+
// Agrupar errores por archivo para mejor organización
|
|
381
|
+
const errorsByFile = new Map();
|
|
382
|
+
compilationErrors.forEach(error => {
|
|
383
|
+
if (!errorsByFile.has(error.file)) {
|
|
384
|
+
errorsByFile.set(error.file, []);
|
|
385
|
+
}
|
|
386
|
+
errorsByFile.get(error.file).push(error);
|
|
387
|
+
});
|
|
388
|
+
// Mostrar errores por archivo
|
|
389
|
+
let fileIndex = 1;
|
|
390
|
+
for (const [filePath, fileErrors] of errorsByFile) {
|
|
391
|
+
const baseName = path.basename(filePath);
|
|
392
|
+
const errorCount = fileErrors.filter(e => e.severity === 'error').length;
|
|
393
|
+
const warningCount = fileErrors.filter(e => e.severity === 'warning').length;
|
|
394
|
+
logger.info(chalk.cyan(`\n📄 ${fileIndex}. ${baseName}`));
|
|
395
|
+
logger.info(chalk.gray(` Ruta: ${filePath}`));
|
|
396
|
+
logger.info(chalk.yellow(` ${errorCount} errores, ${warningCount} advertencias`));
|
|
397
|
+
for (const error of fileErrors) {
|
|
398
|
+
const icon = error.severity === 'error' ? '❌' : '⚠️';
|
|
399
|
+
const stageColor = await getStageColor(error.stage);
|
|
400
|
+
logger.info(` ${icon} [${stageColor(error.stage)}] ${error.message}`);
|
|
401
|
+
if (isVerbose && error.details) {
|
|
402
|
+
// En modo verbose, mostrar detalles adicionales
|
|
403
|
+
const detailLines = error.details.split('\n').slice(0, 5);
|
|
404
|
+
detailLines.forEach(line => {
|
|
405
|
+
if (line.trim()) {
|
|
406
|
+
logger.info(chalk.gray(` ${line.trim()}`));
|
|
407
|
+
}
|
|
408
|
+
});
|
|
409
|
+
}
|
|
410
|
+
if (error.help) {
|
|
411
|
+
logger.info(chalk.blue(` 💡 ${error.help}`));
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
fileIndex++;
|
|
415
|
+
}
|
|
416
|
+
// Mostrar totales finales
|
|
417
|
+
const totalErrors = compilationErrors.filter(e => e.severity === 'error').length;
|
|
418
|
+
const totalWarnings = compilationErrors.filter(e => e.severity === 'warning').length;
|
|
419
|
+
const totalFiles = errorsByFile.size;
|
|
420
|
+
logger.info(chalk.bold('\n--- 📈 ESTADÍSTICAS FINALES ---'));
|
|
421
|
+
logger.info(`📁 Archivos con errores: ${totalFiles}`);
|
|
422
|
+
logger.info(`❌ Total de errores: ${totalErrors}`);
|
|
423
|
+
logger.info(`⚠️ Total de advertencias: ${totalWarnings}`);
|
|
424
|
+
if (totalErrors > 0) {
|
|
425
|
+
logger.info(chalk.red('🚨 Compilación completada con errores que requieren atención.'));
|
|
426
|
+
}
|
|
427
|
+
else {
|
|
428
|
+
logger.info(chalk.yellow('✅ Compilación completada con solo advertencias.'));
|
|
429
|
+
}
|
|
430
|
+
}
|
|
431
|
+
else {
|
|
432
|
+
logger.info(chalk.green('✅ ¡Compilación exitosa sin errores!'));
|
|
433
|
+
}
|
|
434
|
+
logger.info(chalk.bold('--- FIN DEL RESUMEN ---\n'));
|
|
435
|
+
}
|
|
436
|
+
/**
|
|
437
|
+
* Muestra errores del linter de forma detallada
|
|
438
|
+
*/
|
|
439
|
+
async function displayLinterErrors(errors) {
|
|
440
|
+
const chalk = await loadChalk();
|
|
441
|
+
logger.info(chalk.bold('--- Errores y Advertencias de Linting ---'));
|
|
442
|
+
const errorsByFile = new Map();
|
|
443
|
+
errors.forEach(error => {
|
|
444
|
+
if (!errorsByFile.has(error.file)) {
|
|
445
|
+
errorsByFile.set(error.file, []);
|
|
446
|
+
}
|
|
447
|
+
errorsByFile.get(error.file).push(error);
|
|
448
|
+
});
|
|
449
|
+
const totalErrors = errors.filter(e => e.severity === 'error').length;
|
|
450
|
+
const totalWarnings = errors.filter(e => e.severity === 'warning').length;
|
|
451
|
+
const totalFiles = errorsByFile.size;
|
|
452
|
+
logger.info(chalk.yellow(`📊 Resumen: ${totalErrors} errores, ${totalWarnings} advertencias en ${totalFiles} archivos\n`));
|
|
453
|
+
errorsByFile.forEach((fileErrors, filePath) => {
|
|
454
|
+
const baseName = path.basename(filePath);
|
|
455
|
+
logger.info(chalk.cyan(`\n📄 ${baseName}`));
|
|
456
|
+
fileErrors.forEach(error => {
|
|
457
|
+
const icon = error.severity === 'error' ? '❌' : '⚠️';
|
|
458
|
+
logger.info(`${icon} ${error.message}`);
|
|
459
|
+
if (error.help) {
|
|
460
|
+
logger.info(` └─ ${error.help}`);
|
|
461
|
+
}
|
|
462
|
+
});
|
|
463
|
+
});
|
|
464
|
+
logger.info(chalk.bold('--- Fin de Errores y Advertencias ---\n'));
|
|
465
|
+
}
|
|
466
|
+
/**
|
|
467
|
+
* Obtiene el color apropiado para cada etapa de compilación
|
|
468
|
+
*/
|
|
469
|
+
async function getStageColor(stage) {
|
|
470
|
+
const chalk = await loadChalk();
|
|
471
|
+
switch (stage) {
|
|
472
|
+
case 'vue':
|
|
473
|
+
return chalk.green;
|
|
474
|
+
case 'typescript':
|
|
475
|
+
return chalk.blue;
|
|
476
|
+
case 'standardization':
|
|
477
|
+
return chalk.yellow;
|
|
478
|
+
case 'minification':
|
|
479
|
+
return chalk.red;
|
|
480
|
+
case 'tailwind':
|
|
481
|
+
return chalk.magenta;
|
|
482
|
+
case 'file-read':
|
|
483
|
+
return chalk.gray;
|
|
484
|
+
default:
|
|
485
|
+
return chalk.white;
|
|
486
|
+
}
|
|
487
|
+
}
|
|
488
|
+
export function normalizeRuta(ruta) {
|
|
489
|
+
if (path.isAbsolute(ruta)) {
|
|
490
|
+
return path.normalize(ruta).replace(/\\/g, '/');
|
|
491
|
+
}
|
|
492
|
+
const file = path
|
|
493
|
+
.normalize(!ruta.startsWith('.') ? './' + ruta : ruta)
|
|
494
|
+
.replace(/\\/g, '/');
|
|
495
|
+
const sourceForDist = file.startsWith('./') ? file : `./${file}`;
|
|
496
|
+
return sourceForDist;
|
|
497
|
+
}
|
|
498
|
+
export function getOutputPath(ruta) {
|
|
499
|
+
const pathSource = env.PATH_SOURCE ?? '';
|
|
500
|
+
const pathDist = env.PATH_DIST ?? '';
|
|
501
|
+
if (!pathSource || !pathDist) {
|
|
502
|
+
return ruta.replace(/\.(vue|ts)$/, '.js');
|
|
503
|
+
}
|
|
504
|
+
const normalizedRuta = path.normalize(ruta).replace(/\\/g, '/');
|
|
505
|
+
const normalizedSource = path.normalize(pathSource).replace(/\\/g, '/');
|
|
506
|
+
const normalizedDist = path.normalize(pathDist).replace(/\\/g, '/');
|
|
507
|
+
let outputPath;
|
|
508
|
+
if (normalizedRuta.includes(normalizedSource)) {
|
|
509
|
+
const relativePath = normalizedRuta
|
|
510
|
+
.substring(normalizedRuta.indexOf(normalizedSource) +
|
|
511
|
+
normalizedSource.length)
|
|
512
|
+
.replace(/^[/\\]/, '');
|
|
513
|
+
outputPath = path
|
|
514
|
+
.join(normalizedDist, relativePath)
|
|
515
|
+
.replace(/\\/g, '/');
|
|
516
|
+
}
|
|
517
|
+
else {
|
|
518
|
+
const fileName = path.basename(normalizedRuta);
|
|
519
|
+
outputPath = path.join(normalizedDist, fileName).replace(/\\/g, '/');
|
|
520
|
+
}
|
|
521
|
+
if (outputPath.includes('vue') || outputPath.includes('ts')) {
|
|
522
|
+
return outputPath.replace(/\.(vue|ts)$/, '.js');
|
|
523
|
+
}
|
|
524
|
+
else {
|
|
525
|
+
return outputPath;
|
|
526
|
+
}
|
|
527
|
+
}
|
|
528
|
+
// Optimización para modo watch: debouncing y cache de archivos
|
|
529
|
+
class WatchModeOptimizer {
|
|
530
|
+
static instance;
|
|
531
|
+
fileSystemCache = new Map();
|
|
532
|
+
debounceTimers = new Map();
|
|
533
|
+
DEBOUNCE_DELAY = 100; // 100ms debounce
|
|
534
|
+
static getInstance() {
|
|
535
|
+
if (!WatchModeOptimizer.instance) {
|
|
536
|
+
WatchModeOptimizer.instance = new WatchModeOptimizer();
|
|
537
|
+
}
|
|
538
|
+
return WatchModeOptimizer.instance;
|
|
539
|
+
}
|
|
540
|
+
async compileForWatch(filePath, compileFn) {
|
|
541
|
+
return new Promise(resolve => {
|
|
542
|
+
const existingTimer = this.debounceTimers.get(filePath);
|
|
543
|
+
if (existingTimer) {
|
|
544
|
+
clearTimeout(existingTimer);
|
|
545
|
+
}
|
|
546
|
+
const timer = setTimeout(async () => {
|
|
547
|
+
this.debounceTimers.delete(filePath);
|
|
548
|
+
try {
|
|
549
|
+
const stats = await stat(filePath);
|
|
550
|
+
const cached = this.fileSystemCache.get(filePath);
|
|
551
|
+
if (cached && cached.mtime >= stats.mtimeMs) {
|
|
552
|
+
resolve({ success: true, cached: true });
|
|
553
|
+
return;
|
|
554
|
+
} // Configurar worker para modo watch
|
|
555
|
+
const { TypeScriptWorkerManager } = await import('./typescript-worker.js');
|
|
556
|
+
const workerManager = TypeScriptWorkerManager.getInstance();
|
|
557
|
+
workerManager.setMode('watch');
|
|
558
|
+
const result = await compileFn(filePath);
|
|
559
|
+
this.fileSystemCache.set(filePath, {
|
|
560
|
+
mtime: stats.mtimeMs,
|
|
561
|
+
});
|
|
562
|
+
resolve(result);
|
|
563
|
+
}
|
|
564
|
+
catch (error) {
|
|
565
|
+
resolve({ success: false, error });
|
|
566
|
+
}
|
|
567
|
+
}, this.DEBOUNCE_DELAY);
|
|
568
|
+
this.debounceTimers.set(filePath, timer);
|
|
569
|
+
});
|
|
570
|
+
}
|
|
571
|
+
cleanup() {
|
|
572
|
+
this.debounceTimers.forEach(timer => clearTimeout(timer));
|
|
573
|
+
this.debounceTimers.clear();
|
|
574
|
+
this.fileSystemCache.clear();
|
|
575
|
+
}
|
|
576
|
+
}
|
|
577
|
+
async function compileJS(inPath, outPath, mode = 'individual') {
|
|
578
|
+
const timings = {};
|
|
579
|
+
// Si la ruta ya es absoluta, no la resolvamos de nuevo
|
|
580
|
+
inPath = path.isAbsolute(inPath)
|
|
581
|
+
? normalizeRuta(inPath)
|
|
582
|
+
: normalizeRuta(path.resolve(inPath));
|
|
583
|
+
// 🚀 Usar ModuleManager para carga optimizada
|
|
584
|
+
const moduleManager = ModuleManager.getInstance();
|
|
585
|
+
// Timing de lectura
|
|
586
|
+
let start = Date.now();
|
|
587
|
+
const extension = path.extname(inPath);
|
|
588
|
+
// Asegurar que el parser esté cargado
|
|
589
|
+
await moduleManager.ensureModuleLoaded('parser');
|
|
590
|
+
const getCodeFile = await loadParser();
|
|
591
|
+
let { code, error } = await getCodeFile(inPath);
|
|
592
|
+
timings.fileRead = Date.now() - start;
|
|
593
|
+
if (error) {
|
|
594
|
+
await handleCompilationError(error instanceof Error ? error : new Error(String(error)), inPath, 'file-read', mode, env.VERBOSE === 'true');
|
|
595
|
+
throw new Error(error instanceof Error ? error.message : String(error));
|
|
596
|
+
}
|
|
597
|
+
if (!code ||
|
|
598
|
+
code.trim().length === 0 ||
|
|
599
|
+
code === 'undefined' ||
|
|
600
|
+
code === 'null') {
|
|
601
|
+
await handleCompilationError(new Error('El archivo está vacío o no se pudo leer.'), inPath, 'file-read', mode, env.VERBOSE === 'true');
|
|
602
|
+
throw new Error('El archivo está vacío o no se pudo leer.');
|
|
603
|
+
}
|
|
604
|
+
// Logs detallados solo en modo verbose + all
|
|
605
|
+
const shouldShowDetailedLogs = env.VERBOSE === 'true' && mode === 'all';
|
|
606
|
+
// Compilación de Vue
|
|
607
|
+
let vueResult;
|
|
608
|
+
if (extension === '.vue') {
|
|
609
|
+
start = Date.now();
|
|
610
|
+
if (shouldShowDetailedLogs) {
|
|
611
|
+
logger.info(chalk.green(`💚 Precompilando VUE: ${inPath}`));
|
|
612
|
+
}
|
|
613
|
+
// Asegurar que el módulo Vue esté cargado
|
|
614
|
+
await moduleManager.ensureModuleLoaded('vue');
|
|
615
|
+
const preCompileVue = await loadVue();
|
|
616
|
+
if (typeof preCompileVue !== 'function') {
|
|
617
|
+
throw new Error(`loadVue devolvió ${typeof preCompileVue} en lugar de una función para archivo: ${inPath}`);
|
|
618
|
+
}
|
|
619
|
+
vueResult = await preCompileVue(code, inPath, env.isPROD === 'true');
|
|
620
|
+
timings.vueCompile = Date.now() - start;
|
|
621
|
+
if (vueResult === undefined || vueResult === null) {
|
|
622
|
+
throw new Error(`preCompileVue devolvió ${vueResult} para archivo: ${inPath}`);
|
|
623
|
+
}
|
|
624
|
+
if (vueResult.error) {
|
|
625
|
+
await handleCompilationError(vueResult.error instanceof Error
|
|
626
|
+
? vueResult.error
|
|
627
|
+
: new Error(String(vueResult.error)), inPath, 'vue', mode, env.VERBOSE === 'true');
|
|
628
|
+
throw new Error(vueResult.error instanceof Error
|
|
629
|
+
? vueResult.error.message
|
|
630
|
+
: String(vueResult.error));
|
|
631
|
+
}
|
|
632
|
+
registerCompilationSuccess(inPath, 'vue');
|
|
633
|
+
code = vueResult.data;
|
|
634
|
+
}
|
|
635
|
+
if (!code || code.trim().length === 0) {
|
|
636
|
+
await handleCompilationError(new Error('El código Vue compilado está vacío.'), inPath, 'vue', mode, env.VERBOSE === 'true');
|
|
637
|
+
throw new Error('El código Vue compilado está vacío.');
|
|
638
|
+
}
|
|
639
|
+
// Compilación de TypeScript
|
|
640
|
+
let tsResult;
|
|
641
|
+
if (extension === '.ts' || vueResult?.lang === 'ts') {
|
|
642
|
+
start = Date.now();
|
|
643
|
+
if (shouldShowDetailedLogs) {
|
|
644
|
+
logger.info(chalk.blue(`🔄️ Precompilando TS: ${inPath}`));
|
|
645
|
+
}
|
|
646
|
+
// Asegurar que el módulo TypeScript esté cargado
|
|
647
|
+
await moduleManager.ensureModuleLoaded('typescript');
|
|
648
|
+
const preCompileTS = await loadTypeScript();
|
|
649
|
+
if (typeof preCompileTS !== 'function') {
|
|
650
|
+
throw new Error(`loadTypeScript devolvió ${typeof preCompileTS} en lugar de una función para archivo: ${inPath}`);
|
|
651
|
+
}
|
|
652
|
+
tsResult = await preCompileTS(code, inPath);
|
|
653
|
+
timings.tsCompile = Date.now() - start;
|
|
654
|
+
if (tsResult === undefined || tsResult === null) {
|
|
655
|
+
throw new Error(`preCompileTS devolvió ${tsResult} para archivo: ${inPath}`);
|
|
656
|
+
}
|
|
657
|
+
if (tsResult.error) {
|
|
658
|
+
if (mode === 'all') {
|
|
659
|
+
// En modo --all, registrar el error pero continuar la compilación
|
|
660
|
+
registerCompilationError(inPath, 'typescript', tsResult.error instanceof Error
|
|
661
|
+
? tsResult.error.message
|
|
662
|
+
: String(tsResult.error), 'error');
|
|
663
|
+
}
|
|
664
|
+
else {
|
|
665
|
+
await handleCompilationError(tsResult.error, inPath, 'typescript', mode, env.VERBOSE === 'true');
|
|
666
|
+
throw new Error(tsResult.error instanceof Error
|
|
667
|
+
? tsResult.error.message
|
|
668
|
+
: String(tsResult.error));
|
|
669
|
+
}
|
|
670
|
+
}
|
|
671
|
+
else {
|
|
672
|
+
registerCompilationSuccess(inPath, 'typescript');
|
|
673
|
+
code = tsResult.data;
|
|
674
|
+
}
|
|
675
|
+
}
|
|
676
|
+
if (!code || code.trim().length === 0) {
|
|
677
|
+
await handleCompilationError(new Error('El código TypeScript compilado está vacío.'), inPath, 'typescript', mode, env.VERBOSE === 'true');
|
|
678
|
+
throw new Error('El código TypeScript compilado está vacío.');
|
|
679
|
+
}
|
|
680
|
+
// Estandarización
|
|
681
|
+
if (shouldShowDetailedLogs) {
|
|
682
|
+
logger.info(chalk.yellow(`💛 Estandarizando: ${inPath}`));
|
|
683
|
+
}
|
|
684
|
+
start = Date.now();
|
|
685
|
+
// Asegurar que el módulo de transformaciones esté cargado
|
|
686
|
+
await moduleManager.ensureModuleLoaded('transforms');
|
|
687
|
+
const estandarizaCode = await loadTransforms();
|
|
688
|
+
const resultSTD = await estandarizaCode(code, inPath);
|
|
689
|
+
timings.standardization = Date.now() - start;
|
|
690
|
+
if (resultSTD === undefined || resultSTD === null) {
|
|
691
|
+
throw new Error(`estandarizaCode devolvió ${resultSTD} para archivo: ${inPath}`);
|
|
692
|
+
}
|
|
693
|
+
if (resultSTD.error) {
|
|
694
|
+
await handleCompilationError(new Error(resultSTD.error), inPath, 'standardization', mode, env.VERBOSE === 'true');
|
|
695
|
+
throw new Error(resultSTD.error);
|
|
696
|
+
}
|
|
697
|
+
registerCompilationSuccess(inPath, 'standardization');
|
|
698
|
+
code = resultSTD.code;
|
|
699
|
+
if (!code || code.trim().length === 0) {
|
|
700
|
+
await handleCompilationError(new Error('El código estandarizado está vacío.'), inPath, 'standardization', mode, env.VERBOSE === 'true');
|
|
701
|
+
throw new Error('El código estandarizado está vacío.');
|
|
702
|
+
}
|
|
703
|
+
// Minificación (solo en producción)
|
|
704
|
+
if (env.isPROD === 'true') {
|
|
705
|
+
start = Date.now();
|
|
706
|
+
if (shouldShowDetailedLogs) {
|
|
707
|
+
logger.info(chalk.red(`🤖 Minificando: ${inPath}`));
|
|
708
|
+
}
|
|
709
|
+
// Asegurar que el módulo de minificación esté cargado
|
|
710
|
+
await moduleManager.ensureModuleLoaded('minify');
|
|
711
|
+
const minifyJS = await loadMinify();
|
|
712
|
+
const resultMinify = await minifyJS(code, inPath, true);
|
|
713
|
+
timings.minification = Date.now() - start;
|
|
714
|
+
if (resultMinify === undefined || resultMinify === null) {
|
|
715
|
+
throw new Error(`minifyJS devolvió ${resultMinify} para archivo: ${inPath}`);
|
|
716
|
+
}
|
|
717
|
+
if (resultMinify.error) {
|
|
718
|
+
await handleCompilationError(resultMinify.error instanceof Error
|
|
719
|
+
? resultMinify.error
|
|
720
|
+
: new Error(String(resultMinify.error)), inPath, 'minification', mode, env.VERBOSE === 'true');
|
|
721
|
+
throw new Error(resultMinify.error instanceof Error
|
|
722
|
+
? resultMinify.error.message
|
|
723
|
+
: String(resultMinify.error));
|
|
724
|
+
}
|
|
725
|
+
registerCompilationSuccess(inPath, 'minification');
|
|
726
|
+
code = resultMinify.code;
|
|
727
|
+
}
|
|
728
|
+
// Escribir archivo final
|
|
729
|
+
const destinationDir = path.dirname(outPath);
|
|
730
|
+
await mkdir(destinationDir, { recursive: true });
|
|
731
|
+
await writeFile(outPath, code, 'utf-8');
|
|
732
|
+
if (env.VERBOSE === 'true') {
|
|
733
|
+
logger.info(`\n📊 Timings para ${path.basename(inPath)}:`, JSON.stringify(timings));
|
|
734
|
+
}
|
|
735
|
+
return {
|
|
736
|
+
error: null,
|
|
737
|
+
action: 'extension',
|
|
738
|
+
};
|
|
739
|
+
}
|
|
740
|
+
export async function initCompile(ruta, compileTailwind = true, mode = 'individual') {
|
|
741
|
+
try {
|
|
742
|
+
// 🚀 Sistema de Carga Inteligente de Módulos
|
|
743
|
+
const moduleManager = ModuleManager.getInstance();
|
|
744
|
+
const fileExtension = path.extname(ruta);
|
|
745
|
+
const fileExtensions = new Set([fileExtension]);
|
|
746
|
+
// Inicializar módulos según el contexto
|
|
747
|
+
await moduleManager.initializeModules(mode === 'all' ? 'batch' : mode, fileExtensions);
|
|
748
|
+
// Generar TailwindCSS si está habilitado
|
|
749
|
+
if (compileTailwind && Boolean(env.TAILWIND)) {
|
|
750
|
+
await moduleManager.ensureModuleLoaded('tailwind');
|
|
751
|
+
const generateTailwindCSS = await loadTailwind();
|
|
752
|
+
const resultTW = await generateTailwindCSS();
|
|
753
|
+
if (typeof resultTW !== 'boolean') {
|
|
754
|
+
if (resultTW?.success) {
|
|
755
|
+
logger.info(`🎨 ${resultTW.message}`);
|
|
756
|
+
}
|
|
757
|
+
else {
|
|
758
|
+
const errorMsg = `${resultTW.message}${resultTW.details ? '\n' + resultTW.details : ''}`;
|
|
759
|
+
await handleCompilationError(new Error(errorMsg), 'tailwind.config.js', 'tailwind', mode, env.VERBOSE === 'true');
|
|
760
|
+
// No hacer throw aquí, permitir que la compilación continúe
|
|
761
|
+
}
|
|
762
|
+
}
|
|
763
|
+
}
|
|
764
|
+
const startTime = Date.now();
|
|
765
|
+
const file = normalizeRuta(ruta);
|
|
766
|
+
const outFile = getOutputPath(file);
|
|
767
|
+
if (mode === 'individual' && env.VERBOSE === 'true') {
|
|
768
|
+
logger.info(`🔜 Fuente: ${file}`);
|
|
769
|
+
}
|
|
770
|
+
const result = await compileJS(file, outFile, mode);
|
|
771
|
+
if (result.error) {
|
|
772
|
+
throw new Error(result.error);
|
|
773
|
+
}
|
|
774
|
+
const endTime = Date.now();
|
|
775
|
+
const elapsedTime = showTimingForHumans(endTime - startTime);
|
|
776
|
+
if (mode === 'individual') {
|
|
777
|
+
if (env.VERBOSE === 'true') {
|
|
778
|
+
logger.info(`🔚 Destino: ${outFile}`);
|
|
779
|
+
logger.info(`⏱️ Tiempo: ${elapsedTime}`);
|
|
780
|
+
}
|
|
781
|
+
const chalk = await loadChalk();
|
|
782
|
+
logger.info(chalk.green(`✅ Compilación exitosa: ${path.basename(file)}`));
|
|
783
|
+
}
|
|
784
|
+
return {
|
|
785
|
+
success: true,
|
|
786
|
+
output: outFile,
|
|
787
|
+
action: result.action,
|
|
788
|
+
};
|
|
789
|
+
}
|
|
790
|
+
catch (error) {
|
|
791
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
792
|
+
if (env.VERBOSE === 'true') {
|
|
793
|
+
logger.error(`❌ Error en compilación de ${path.basename(ruta)}: ${errorMessage}`);
|
|
794
|
+
}
|
|
795
|
+
return {
|
|
796
|
+
success: false,
|
|
797
|
+
output: '',
|
|
798
|
+
error: errorMessage,
|
|
799
|
+
};
|
|
800
|
+
}
|
|
801
|
+
}
|
|
802
|
+
// Variable para el último progreso mostrado (evitar spam)
|
|
803
|
+
let lastProgressUpdate = 0;
|
|
804
|
+
// Función para ejecutar el linter antes de la compilación
|
|
805
|
+
export async function runLinter(showResult = false) {
|
|
806
|
+
const linterENV = env.linter;
|
|
807
|
+
const linterPromises = [];
|
|
808
|
+
const linterErrors = [];
|
|
809
|
+
let proceedWithCompilation = true;
|
|
810
|
+
if (env.ENABLE_LINTER !== 'true') {
|
|
811
|
+
return true;
|
|
812
|
+
}
|
|
813
|
+
if (typeof linterENV === 'string' && linterENV.trim() !== '') {
|
|
814
|
+
logger.info('🔍 Ejecutando linting...');
|
|
815
|
+
try {
|
|
816
|
+
const parsedLinterEnv = JSON.parse(linterENV);
|
|
817
|
+
if (Array.isArray(parsedLinterEnv)) {
|
|
818
|
+
// Cargar dependencias de linting de forma lazy
|
|
819
|
+
const { ESLint, OxLint } = await loadLinter();
|
|
820
|
+
for (const item of parsedLinterEnv) {
|
|
821
|
+
if (item.name.toLowerCase() === 'eslint') {
|
|
822
|
+
logger.info(`🔧 Ejecutando ESLint con config: ${item.configFile || 'por defecto'}`);
|
|
823
|
+
const eslintPromise = ESLint(item)
|
|
824
|
+
.then((eslintResult) => {
|
|
825
|
+
if (eslintResult && eslintResult.json) {
|
|
826
|
+
// Procesar resultados de ESLint
|
|
827
|
+
if (Array.isArray(eslintResult.json)) {
|
|
828
|
+
eslintResult.json.forEach((result) => {
|
|
829
|
+
linterErrors.push({
|
|
830
|
+
file: result.filePath ||
|
|
831
|
+
'archivo no especificado',
|
|
832
|
+
message: result.message,
|
|
833
|
+
severity: result.severity === 2
|
|
834
|
+
? 'error'
|
|
835
|
+
: 'warning',
|
|
836
|
+
help: result.ruleId
|
|
837
|
+
? `Regla ESLint: ${result.ruleId}`
|
|
838
|
+
: undefined,
|
|
839
|
+
});
|
|
840
|
+
});
|
|
841
|
+
}
|
|
842
|
+
else if (eslintResult.json.results &&
|
|
843
|
+
Array.isArray(eslintResult.json.results)) {
|
|
844
|
+
eslintResult.json.results.forEach((fileResult) => {
|
|
845
|
+
if (fileResult.messages &&
|
|
846
|
+
Array.isArray(fileResult.messages)) {
|
|
847
|
+
fileResult.messages.forEach((msg) => {
|
|
848
|
+
linterErrors.push({
|
|
849
|
+
file: fileResult.filePath ||
|
|
850
|
+
'archivo no especificado',
|
|
851
|
+
message: msg.message,
|
|
852
|
+
severity: msg.severity ===
|
|
853
|
+
2
|
|
854
|
+
? 'error'
|
|
855
|
+
: 'warning',
|
|
856
|
+
help: msg.ruleId
|
|
857
|
+
? `Regla ESLint: ${msg.ruleId}`
|
|
858
|
+
: undefined,
|
|
859
|
+
});
|
|
860
|
+
});
|
|
861
|
+
}
|
|
862
|
+
});
|
|
863
|
+
}
|
|
864
|
+
}
|
|
865
|
+
})
|
|
866
|
+
.catch((err) => {
|
|
867
|
+
logger.error(`❌ Error durante la ejecución de ESLint: ${err.message}`);
|
|
868
|
+
linterErrors.push({
|
|
869
|
+
file: item.configFile || 'ESLint Config',
|
|
870
|
+
message: `Fallo al ejecutar ESLint: ${err.message}`,
|
|
871
|
+
severity: 'error',
|
|
872
|
+
});
|
|
873
|
+
});
|
|
874
|
+
linterPromises.push(eslintPromise);
|
|
875
|
+
}
|
|
876
|
+
else if (item.name.toLowerCase() === 'oxlint') {
|
|
877
|
+
logger.info(`🔧 Ejecutando OxLint con config: ${item.configFile || 'por defecto'}`);
|
|
878
|
+
const oxlintPromise = OxLint(item)
|
|
879
|
+
.then((oxlintResult) => {
|
|
880
|
+
if (oxlintResult &&
|
|
881
|
+
oxlintResult['json'] &&
|
|
882
|
+
Array.isArray(oxlintResult['json'])) {
|
|
883
|
+
oxlintResult['json'].forEach((result) => {
|
|
884
|
+
linterErrors.push({
|
|
885
|
+
file: result.filename ||
|
|
886
|
+
result.file ||
|
|
887
|
+
'archivo no especificado',
|
|
888
|
+
message: result.message,
|
|
889
|
+
severity: typeof result.severity ===
|
|
890
|
+
'string'
|
|
891
|
+
? result.severity.toLowerCase()
|
|
892
|
+
: 'error',
|
|
893
|
+
help: result.help ||
|
|
894
|
+
(result.rule_id
|
|
895
|
+
? `Regla Oxlint: ${result.rule_id}`
|
|
896
|
+
: undefined),
|
|
897
|
+
});
|
|
898
|
+
});
|
|
899
|
+
}
|
|
900
|
+
})
|
|
901
|
+
.catch((err) => {
|
|
902
|
+
logger.error(`❌ Error durante la ejecución de OxLint: ${err.message}`);
|
|
903
|
+
linterErrors.push({
|
|
904
|
+
file: item.configFile || 'Oxlint Config',
|
|
905
|
+
message: `Fallo al ejecutar Oxlint: ${err.message}`,
|
|
906
|
+
severity: 'error',
|
|
907
|
+
});
|
|
908
|
+
});
|
|
909
|
+
linterPromises.push(oxlintPromise);
|
|
910
|
+
}
|
|
911
|
+
}
|
|
912
|
+
}
|
|
913
|
+
else {
|
|
914
|
+
logger.warn('⚠️ La configuración de linter no es un array válido.');
|
|
915
|
+
}
|
|
916
|
+
await Promise.all(linterPromises);
|
|
917
|
+
if (showResult) {
|
|
918
|
+
if (linterErrors.length > 0) {
|
|
919
|
+
await displayLinterErrors(linterErrors);
|
|
920
|
+
}
|
|
921
|
+
else {
|
|
922
|
+
const chalk = await loadChalk();
|
|
923
|
+
logger.info(chalk.green('✅ No se encontraron errores ni advertencias de linting.'));
|
|
924
|
+
}
|
|
925
|
+
}
|
|
926
|
+
}
|
|
927
|
+
catch (parseError) {
|
|
928
|
+
logger.warn(`Error parseando configuración de linter: ${parseError instanceof Error ? parseError.message : 'Error desconocido'}, omitiendo...`);
|
|
929
|
+
}
|
|
930
|
+
if (!showResult && linterErrors.length > 0) {
|
|
931
|
+
await displayLinterErrors(linterErrors);
|
|
932
|
+
logger.warn('🚨 Se encontraron errores o advertencias durante el linting.');
|
|
933
|
+
if (env.yes === 'false') {
|
|
934
|
+
const result = await promptUser('¿Deseas continuar con la compilación a pesar de los errores de linting? (s/N): ');
|
|
935
|
+
if (result.toLowerCase() !== 's') {
|
|
936
|
+
logger.info('🛑 Compilación cancelada por el usuario.');
|
|
937
|
+
proceedWithCompilation = false;
|
|
938
|
+
}
|
|
939
|
+
}
|
|
940
|
+
}
|
|
941
|
+
}
|
|
942
|
+
return proceedWithCompilation;
|
|
943
|
+
}
|
|
944
|
+
// Función para crear una barra de progreso visual
|
|
945
|
+
function createProgressBar(current, total, width = 30) {
|
|
946
|
+
const percentage = Math.round((current / total) * 100);
|
|
947
|
+
const filled = Math.round((percentage / 100) * width);
|
|
948
|
+
const empty = width - filled;
|
|
949
|
+
return `[${'█'.repeat(filled)}${' '.repeat(empty)}] ${percentage}% (${current}/${total})`;
|
|
950
|
+
}
|
|
951
|
+
// Función helper para verificar si un archivo debe ser omitido por cache
|
|
952
|
+
async function shouldSkipFile(filePath) {
|
|
953
|
+
try {
|
|
954
|
+
const stats = await stat(filePath);
|
|
955
|
+
const cacheEntry = compilationCache.get(filePath);
|
|
956
|
+
if (!cacheEntry) {
|
|
957
|
+
return false; // No hay entrada en cache, debe compilarse
|
|
958
|
+
}
|
|
959
|
+
// Verificar si el archivo no ha cambiado desde la última compilación
|
|
960
|
+
if (stats.mtimeMs <= cacheEntry.mtime) {
|
|
961
|
+
// Verificar si el archivo de salida existe
|
|
962
|
+
try {
|
|
963
|
+
await stat(cacheEntry.outputPath);
|
|
964
|
+
return true; // Archivo existe y no ha cambiado, se puede omitir
|
|
965
|
+
}
|
|
966
|
+
catch {
|
|
967
|
+
return false; // Archivo de salida no existe, debe recompilarse
|
|
968
|
+
}
|
|
969
|
+
}
|
|
970
|
+
return false; // Archivo ha cambiado, debe recompilarse
|
|
971
|
+
}
|
|
972
|
+
catch {
|
|
973
|
+
return false; // Error al verificar, compilar por seguridad
|
|
974
|
+
}
|
|
975
|
+
}
|
|
976
|
+
// Función para compilar archivos con límite de concurrencia
|
|
977
|
+
async function compileWithConcurrencyLimit(files, maxConcurrency = 8) {
|
|
978
|
+
const results = [];
|
|
979
|
+
const executing = [];
|
|
980
|
+
const total = files.length;
|
|
981
|
+
let completed = 0;
|
|
982
|
+
let skipped = 0;
|
|
983
|
+
let failed = 0; // Función para mostrar progreso
|
|
984
|
+
function showProgress() {
|
|
985
|
+
const currentTotal = completed + skipped + failed;
|
|
986
|
+
const progressBar = createProgressBar(currentTotal, total);
|
|
987
|
+
const progressPercent = Math.round((currentTotal / total) * 100);
|
|
988
|
+
if (progressPercent > lastProgressUpdate + 1 ||
|
|
989
|
+
currentTotal === total) {
|
|
990
|
+
process.stdout.write(`\r🚀 ${progressBar} [✅ ${completed} | ⏭️ ${skipped} | ❌ ${failed}]`);
|
|
991
|
+
lastProgressUpdate = progressPercent;
|
|
992
|
+
}
|
|
993
|
+
}
|
|
994
|
+
for (const file of files) {
|
|
995
|
+
const promise = (async () => {
|
|
996
|
+
try {
|
|
997
|
+
// Verificar cache antes de compilar
|
|
998
|
+
if (await shouldSkipFile(file)) {
|
|
999
|
+
skipped++;
|
|
1000
|
+
showProgress();
|
|
1001
|
+
return {
|
|
1002
|
+
success: true,
|
|
1003
|
+
cached: true,
|
|
1004
|
+
output: compilationCache.get(file)?.outputPath || '',
|
|
1005
|
+
};
|
|
1006
|
+
}
|
|
1007
|
+
const result = await initCompile(file, false, 'batch');
|
|
1008
|
+
// Actualizar cache si la compilación fue exitosa
|
|
1009
|
+
if (result.success && result.output) {
|
|
1010
|
+
const stats = await stat(file);
|
|
1011
|
+
compilationCache.set(file, {
|
|
1012
|
+
hash: '', // Se podría implementar hash del contenido si es necesario
|
|
1013
|
+
mtime: stats.mtimeMs,
|
|
1014
|
+
outputPath: result.output,
|
|
1015
|
+
});
|
|
1016
|
+
}
|
|
1017
|
+
completed++;
|
|
1018
|
+
showProgress();
|
|
1019
|
+
return result;
|
|
1020
|
+
}
|
|
1021
|
+
catch (error) {
|
|
1022
|
+
failed++;
|
|
1023
|
+
showProgress();
|
|
1024
|
+
return {
|
|
1025
|
+
success: false,
|
|
1026
|
+
error: error instanceof Error ? error.message : String(error),
|
|
1027
|
+
};
|
|
1028
|
+
}
|
|
1029
|
+
})();
|
|
1030
|
+
results.push(promise);
|
|
1031
|
+
executing.push(promise);
|
|
1032
|
+
if (executing.length >= maxConcurrency) {
|
|
1033
|
+
await Promise.race(executing);
|
|
1034
|
+
executing.splice(executing.findIndex(p => p === promise), 1);
|
|
1035
|
+
}
|
|
1036
|
+
}
|
|
1037
|
+
await Promise.all(results);
|
|
1038
|
+
console.log('\n'); // Nueva línea después de la barra de progreso
|
|
1039
|
+
}
|
|
1040
|
+
export async function initCompileAll() {
|
|
1041
|
+
try {
|
|
1042
|
+
// Limpiar estado de compilación anterior
|
|
1043
|
+
clearCompilationState();
|
|
1044
|
+
// Cargar cache al inicio
|
|
1045
|
+
await loadCache();
|
|
1046
|
+
lastProgressUpdate = 0;
|
|
1047
|
+
const shouldContinue = await runLinter();
|
|
1048
|
+
if (!shouldContinue) {
|
|
1049
|
+
// await displayCompilationSummary(env.VERBOSE === 'true');
|
|
1050
|
+
return;
|
|
1051
|
+
}
|
|
1052
|
+
const startTime = Date.now();
|
|
1053
|
+
const rawPathSource = env.PATH_SOURCE ?? '';
|
|
1054
|
+
const pathDist = env.PATH_DIST ?? '';
|
|
1055
|
+
const normalizedGlobPathSource = rawPathSource.replace(/\\/g, '/');
|
|
1056
|
+
const patterns = [
|
|
1057
|
+
`${normalizedGlobPathSource}/**/*.js`,
|
|
1058
|
+
`${normalizedGlobPathSource}/**/*.vue`,
|
|
1059
|
+
`${normalizedGlobPathSource}/**/*.ts`,
|
|
1060
|
+
`${normalizedGlobPathSource}/**/*.mjs`,
|
|
1061
|
+
`${normalizedGlobPathSource}/**/*.cjs`,
|
|
1062
|
+
];
|
|
1063
|
+
logger.info(`📝 Compilando todos los archivos...`);
|
|
1064
|
+
logger.info(`🔜 Fuente: ${rawPathSource}`);
|
|
1065
|
+
logger.info(`🔚 Destino: ${pathDist}\n`); // Generar TailwindCSS
|
|
1066
|
+
const generateTailwindCSS = await loadTailwind();
|
|
1067
|
+
const resultTW = await generateTailwindCSS();
|
|
1068
|
+
if (typeof resultTW !== 'boolean') {
|
|
1069
|
+
if (resultTW?.success) {
|
|
1070
|
+
logger.info(`🎨 ${resultTW.message}\n`);
|
|
1071
|
+
}
|
|
1072
|
+
else {
|
|
1073
|
+
await handleCompilationError(new Error(`${resultTW.message}${resultTW.details ? '\n' + resultTW.details : ''}`), 'tailwind.config.js', 'tailwind', 'all', env.VERBOSE === 'true');
|
|
1074
|
+
}
|
|
1075
|
+
} // Recopilar todos los archivos
|
|
1076
|
+
const filesToCompile = [];
|
|
1077
|
+
for await (const file of glob(patterns)) {
|
|
1078
|
+
if (file.endsWith('.d.ts')) {
|
|
1079
|
+
continue;
|
|
1080
|
+
}
|
|
1081
|
+
// Usar la ruta tal como viene de glob, sin modificar
|
|
1082
|
+
filesToCompile.push(file);
|
|
1083
|
+
}
|
|
1084
|
+
// Determinar concurrencia óptima
|
|
1085
|
+
const cpuCount = os.cpus().length;
|
|
1086
|
+
const fileCount = filesToCompile.length;
|
|
1087
|
+
let maxConcurrency;
|
|
1088
|
+
if (fileCount < 10) {
|
|
1089
|
+
maxConcurrency = Math.min(fileCount, cpuCount);
|
|
1090
|
+
}
|
|
1091
|
+
else if (fileCount < 50) {
|
|
1092
|
+
maxConcurrency = Math.min(cpuCount * 2, 12);
|
|
1093
|
+
}
|
|
1094
|
+
else {
|
|
1095
|
+
maxConcurrency = Math.min(cpuCount * 2, 16);
|
|
1096
|
+
}
|
|
1097
|
+
logger.info(`🚀 Compilando ${fileCount} archivos con concurrencia optimizada (${maxConcurrency} hilos)...`);
|
|
1098
|
+
await compileWithConcurrencyLimit(filesToCompile, maxConcurrency);
|
|
1099
|
+
// Guardar cache al final
|
|
1100
|
+
await saveCache();
|
|
1101
|
+
const endTime = Date.now();
|
|
1102
|
+
const elapsedTime = showTimingForHumans(endTime - startTime);
|
|
1103
|
+
logger.info(`⏱️ Tiempo total de compilación: ${elapsedTime}\n`); // Mostrar resumen de compilación
|
|
1104
|
+
await displayCompilationSummary(env.VERBOSE === 'true');
|
|
1105
|
+
}
|
|
1106
|
+
catch (error) {
|
|
1107
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
1108
|
+
logger.error(`🚩 Error al compilar todos los archivos: ${errorMessage}`);
|
|
1109
|
+
// Registrar el error en el sistema unificado
|
|
1110
|
+
await handleCompilationError(error instanceof Error ? error : new Error(String(error)), 'compilación general', 'all', 'all', env.VERBOSE === 'true');
|
|
1111
|
+
// Mostrar resumen incluso si hay errores generales
|
|
1112
|
+
await displayCompilationSummary(env.VERBOSE === 'true');
|
|
1113
|
+
}
|
|
1114
|
+
}
|
|
1115
|
+
// Función wrapper para compatibilidad con tests
|
|
1116
|
+
export async function compileFile(filePath) {
|
|
1117
|
+
return await initCompile(filePath, true, 'individual');
|
|
1118
|
+
}
|
|
1119
|
+
export { WatchModeOptimizer };
|
|
1120
|
+
//# sourceMappingURL=compile.js.map
|