versacompiler 2.1.0 → 2.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/dist/compiler/compile.js +2520 -25
- package/dist/compiler/error-reporter.js +467 -38
- package/dist/compiler/linter.js +72 -1
- package/dist/compiler/minify.js +272 -1
- package/dist/compiler/minifyTemplate.js +230 -1
- package/dist/compiler/module-resolution-optimizer.js +844 -1
- package/dist/compiler/parser.js +336 -1
- package/dist/compiler/performance-monitor.js +204 -56
- package/dist/compiler/tailwindcss.js +39 -1
- package/dist/compiler/transform-optimizer.js +392 -1
- package/dist/compiler/transformTStoJS.js +16 -1
- package/dist/compiler/transforms.js +554 -1
- package/dist/compiler/typescript-compiler.js +172 -2
- package/dist/compiler/typescript-error-parser.js +281 -10
- package/dist/compiler/typescript-manager.js +304 -2
- package/dist/compiler/typescript-sync-validator.js +295 -31
- package/dist/compiler/typescript-worker-pool.js +936 -1
- package/dist/compiler/typescript-worker-thread.cjs +466 -22
- package/dist/compiler/typescript-worker.js +339 -1
- package/dist/compiler/vuejs.js +396 -37
- package/dist/hrm/VueHRM.js +359 -1
- package/dist/hrm/errorScreen.js +83 -1
- package/dist/hrm/getInstanciaVue.js +313 -1
- package/dist/hrm/initHRM.js +586 -1
- package/dist/main.js +353 -7
- package/dist/servicios/browserSync.js +589 -2
- package/dist/servicios/file-watcher.js +425 -4
- package/dist/servicios/logger.js +63 -3
- package/dist/servicios/readConfig.js +399 -53
- package/dist/utils/excluded-modules.js +37 -1
- package/dist/utils/module-resolver.js +466 -1
- package/dist/utils/promptUser.js +48 -2
- package/dist/utils/proxyValidator.js +68 -1
- package/dist/utils/resolve-bin.js +58 -1
- package/dist/utils/utils.js +21 -1
- package/dist/utils/vue-types-setup.js +435 -241
- package/dist/wrappers/eslint-node.js +1 -1
- package/dist/wrappers/oxlint-node.js +122 -1
- package/dist/wrappers/tailwind-node.js +94 -1
- package/package.json +106 -103
package/dist/compiler/compile.js
CHANGED
|
@@ -1,25 +1,2520 @@
|
|
|
1
|
-
import{createHash as e}from"node:crypto";import{glob as t,mkdir as n,readFile as r,stat as i,unlink as a,writeFile as o}from"node:fs/promises";import s from"node:os";import c from"node:path";import l,{argv as u,cwd as d,env as f}from"node:process";import{logger as p,setProgressManagerGetter as m}from"../servicios/logger.js";import{promptUser as h}from"../utils/promptUser.js";import{showTimingForHumans as g}from"../utils/utils.js";m(()=>Z.getInstance());async function _(e,t,n){let r=new Promise((e,r)=>setTimeout(()=>r(Error(`Timeout en ${n} (${t}ms)`)),t));return Promise.race([e,r])}let v,y,b,x,S,C,w,T,E,D,O;class k{static instance;isInitialized=!1;loadedModules=new Set;modulePool=new Map;loadingPromises=new Map;usageStats=new Map;preloadQueue=new Set;backgroundLoader=null;preloadLock=null;MAX_POOL_MEMORY=100*1024*1024;MAX_POOL_SIZE=15;HOT_MODULES=[`chalk`,`parser`];currentContext=null;constructor(){this.startBackgroundPreloading()}static getInstance(){return k.instance||(k.instance=new k),k.instance}startBackgroundPreloading(){this.backgroundLoader=this.preloadCriticalModules()}async preloadCriticalModules(){try{let e=this.HOT_MODULES.map(e=>this.ensureModuleLoaded(e).catch(()=>{}));await Promise.allSettled(e)}catch{}}async preloadForContext(e,t=new Set){if(this.preloadLock){await this.preloadLock;return}this.preloadLock=this.doPreload(e,t);try{await this.preloadLock}finally{this.preloadLock=null}}async doPreload(e,t=new Set){this.currentContext=e,this.backgroundLoader&&await this.backgroundLoader;let n=[];e===`batch`||e===`watch`?n.push(`transforms`,`vue`,`typescript`,`module-resolution-optimizer`):(t.has(`.vue`)&&n.push(`vue`),(t.has(`.ts`)||t.has(`.vue`))&&n.push(`typescript`),this.loadedModules.has(`transforms`)||n.push(`transforms`));for(let e of[[`chalk`,`parser`],[`transforms`],[`vue`,`typescript`],[`module-resolution-optimizer`],[`minify`]]){let t=e.filter(e=>n.includes(e));t.length>0&&await Promise.allSettled(t.map(async e=>{try{await this.ensureModuleLoaded(e)}catch(t){if(f.VERBOSE===`true`){let n=t instanceof Error?t.message:String(t);console.warn(`[Verbose] Warning: No se pudo precargar módulo ${e}:`,n)}}}))}}async ensureModuleLoaded(e){if(this.modulePool.has(e))return this.updateUsageStats(e),this.modulePool.get(e);if(this.loadingPromises.has(e))return this.loadingPromises.get(e);let t=this.loadModuleInternal(e);this.loadingPromises.set(e,t);try{let n=await t;return this.modulePool.set(e,n),this.loadedModules.add(e),this.updateUsageStats(e),n}finally{this.loadingPromises.delete(e)}}updateUsageStats(e){let t=this.usageStats.get(e)||0;this.usageStats.set(e,t+1)}async loadModuleInternal(e){switch(e){case`chalk`:return this.loadChalk();case`parser`:return this.loadParser();case`transforms`:return this.loadTransforms();case`vue`:return this.loadVue();case`typescript`:return this.loadTypeScript();case`minify`:return this.loadMinify();case`tailwind`:return this.loadTailwind();case`linter`:return this.loadLinter();case`transform-optimizer`:return this.loadTransformOptimizer();case`module-resolution-optimizer`:return this.loadModuleResolutionOptimizer();default:throw Error(`Módulo desconocido: ${e}`)}}async loadChalk(){return v||(v=(await import(`chalk`)).default),v}async loadParser(){return S||(S=(await import(`./parser.js`)).getCodeFile),S}async loadTransforms(){return w||(w=(await import(`./transforms.js`)).estandarizaCode),w}async loadVue(){return E||(E=(await import(`./vuejs.js`)).preCompileVue),E}async loadTypeScript(){return T||(T=(await import(`./typescript-manager.js`)).preCompileTS),T}async loadMinify(){return x||(x=(await import(`./minify.js`)).minifyWithTemplates),x}async loadTailwind(){return C||(C=(await import(`./tailwindcss.js`)).generateTailwindCSS),C}async loadLinter(){if(!y||!b){let e=await import(`./linter.js`);y=e.ESLint,b=e.OxLint}return{ESLint:y,OxLint:b}}async loadTransformOptimizer(){return D||(D=(await import(`./transform-optimizer.js`)).TransformOptimizer.getInstance()),D}async loadModuleResolutionOptimizer(){return O||(O=(await import(`./module-resolution-optimizer.js`)).ModuleResolutionOptimizer.getInstance()),O}getPerformanceStats(){let e=Array.from(this.usageStats.entries()).sort((e,t)=>t[1]-e[1]).slice(0,5).map(([e])=>e);return{loadedModules:Array.from(this.loadedModules),usageStats:Object.fromEntries(this.usageStats),poolSize:this.modulePool.size,loadingInProgress:Array.from(this.loadingPromises.keys()),mostUsedModules:e}}estimateModuleSize(e){let t={"transform-optimizer":5*1024*1024,typescript:10*1024*1024,vue:8*1024*1024,"module-resolution-optimizer":3*1024*1024,transforms:2*1024*1024,minify:2*1024*1024,linter:1*1024*1024,parser:500*1024,chalk:100*1024,default:500*1024};return t[e]??t.default}getPoolMemoryUsage(){let e=0;for(let t of this.modulePool.keys())e+=this.estimateModuleSize(t);return e}cleanupUnusedModules(){let e=this.getPoolMemoryUsage(),t=this.modulePool.size;if(e>this.MAX_POOL_MEMORY||t>this.MAX_POOL_SIZE){let n=Array.from(this.usageStats.entries()).sort((e,t)=>e[1]-t[1]).filter(([e])=>!this.HOT_MODULES.includes(e)),r=this.MAX_POOL_MEMORY*.7,i=this.MAX_POOL_SIZE*.7;for(let[e]of n){this.modulePool.delete(e),this.loadedModules.delete(e),this.usageStats.delete(e);let t=this.getPoolMemoryUsage(),n=this.modulePool.size;if(t<=r&&n<=i)break}f.VERBOSE===`true`&&console.log(`[ModuleManager] Limpieza: ${t} → ${this.modulePool.size} módulos, ${Math.round(e/1024/1024)}MB → ${Math.round(this.getPoolMemoryUsage()/1024/1024)}MB`)}}reset(){this.isInitialized=!1,this.loadedModules.clear(),this.modulePool.clear(),this.loadingPromises.clear(),this.usageStats.clear(),this.preloadQueue.clear(),this.currentContext=null,this.backgroundLoader=null,this.startBackgroundPreloading()}}async function A(){return v||(v=(await import(`chalk`)).default),v}async function j(){if(!y||!b){let e=await import(`./linter.js`);y=e.ESLint,b=e.OxLint}return{ESLint:y,OxLint:b}}async function oe(){return x||(x=(await import(`./minify.js`)).minifyWithTemplates),x}async function M(){return S||(S=(await import(`./parser.js`)).getCodeFile),S}async function N(){return C||(C=(await import(`./tailwindcss.js`)).generateTailwindCSS),C}async function se(){return w||(w=(await import(`./transforms.js`)).estandarizaCode),w}async function ce(){return T||(T=(await import(`./typescript-manager.js`)).preCompileTS),T}async function le(){return E||(E=(await import(`./vuejs.js`)).preCompileVue),E}const P=[],F=[],I=[`NODE_ENV`,`isPROD`,`TAILWIND`,`ENABLE_LINTER`,`VERBOSE`,`typeCheck`,`PATH_ALIAS`,`tailwindcss`,`linter`,`tsconfigFile`];class L{cache=new Map;maxEntries=200;maxMemory=50*1024*1024;currentMemoryUsage=0;fileWatchers=new Map;dependencyGraph=new Map;reverseDependencyGraph=new Map;packageJsonPath=c.join(d(),`package.json`);nodeModulesPath=c.join(d(),`node_modules`);isWatchingDependencies=!1;async generateContentHash(t){try{let n=await r(t,`utf8`);return e(`sha256`).update(n).digest(`hex`)}catch{let n=`${t}-${Date.now()}`;return e(`sha256`).update(n).digest(`hex`)}}generateConfigHash(){try{let t={isPROD:f.isPROD||`false`,TAILWIND:f.TAILWIND||`false`,ENABLE_LINTER:f.ENABLE_LINTER||`false`,PATH_ALIAS:f.PATH_ALIAS||`{}`,tailwindcss:f.tailwindcss||`false`,linter:f.linter||`false`,tsconfigFile:f.tsconfigFile||`./tsconfig.json`},n=JSON.stringify(t,Object.keys(t).sort());return e(`sha256`).update(n).digest(`hex`).substring(0,12)}catch{return`no-config`}}generateEnvHash(){try{let t=I.map(e=>`${e}=${f[e]||``}`).join(`|`);return e(`sha256`).update(t).digest(`hex`).substring(0,12)}catch{return`no-env`}}async generateDependencyHash(){try{let t=e(`sha256`),n=await r(c.join(d(),`package.json`),`utf8`),a=JSON.parse(n),o={...a.dependencies,...a.devDependencies},s=JSON.stringify(o,Object.keys(o).sort());t.update(`package:${s}`);try{let e=await r(c.join(d(),`package-lock.json`),`utf8`),n=JSON.parse(e),i={};if(n.packages){for(let[e,t]of Object.entries(n.packages))if(e&&e!==``&&typeof t==`object`&&t){let n=e.replace(`node_modules/`,``);t.version&&(i[n]=t.version)}}t.update(`lock:${JSON.stringify(i,Object.keys(i).sort())}`)}catch{}try{let e=c.join(d(),`node_modules`),n=await i(e);t.update(`nmtime:${n.mtimeMs}`);let r=Object.keys(o).slice(0,10);for(let n of r)try{let r=await i(c.join(e,n));t.update(`${n}:${r.mtimeMs}`)}catch{t.update(`${n}:missing`)}}catch{t.update(`nmtime:none`)}return t.digest(`hex`).substring(0,16)}catch(t){return e(`sha256`).update(`error:${t instanceof Error?t.message:`unknown`}`).digest(`hex`).substring(0,16)}}async generateCacheKey(e){let t=await this.generateContentHash(e),n=this.generateConfigHash(),r=this.generateEnvHash(),i=await this.generateDependencyHash();return`${e}|${t.substring(0,12)}|${n}|${r}|${i}`}async isValid(e){let t=this.cache.get(e);if(!t)return!1;try{await i(t.outputPath);let n=await this.generateContentHash(e);if(t.contentHash!==n)return this.cache.delete(e),!1;let r=this.generateConfigHash();if(t.configHash!==r)return this.cache.delete(e),!1;let a=this.generateEnvHash();if(t.envHash!==a)return this.cache.delete(e),!1;let o=await this.generateDependencyHash();return t.dependencyHash!==o||(await i(e)).mtimeMs>t.mtime?(this.cache.delete(e),!1):(t.lastUsed=Date.now(),!0)}catch{return this.cache.delete(e),!1}}async set(e,t){try{let n=await i(e),r={contentHash:await this.generateContentHash(e),configHash:this.generateConfigHash(),envHash:this.generateEnvHash(),dependencyHash:await this.generateDependencyHash(),mtime:n.mtimeMs,outputPath:t,lastUsed:Date.now(),size:n.size};this.evictIfNeeded(r.size),this.cache.set(e,r),this.currentMemoryUsage+=r.size}catch(t){console.warn(`Warning: No se pudo cachear ${e}:`,t)}}evictIfNeeded(e){for(;this.cache.size>=this.maxEntries*.8;)this.evictLRU();for(;this.currentMemoryUsage+e>this.maxMemory*.8&&this.cache.size>0;)this.evictLRU();if(l.memoryUsage().heapUsed/(1024*1024)>200&&this.cache.size>50){let e=Math.min(this.cache.size-50,10);for(let t=0;t<e;t++)this.evictLRU()}}evictLRU(){let e=``,t=1/0;for(let[n,r]of this.cache)r.lastUsed<t&&(t=r.lastUsed,e=n);if(e){let t=this.cache.get(e);t&&(this.currentMemoryUsage-=t.size,this.cache.delete(e))}}cleanOldEntries(e=20){let t=0;for(let n=0;n<e&&this.cache.size>0;n++){let e=this.cache.size;if(this.evictLRU(),this.cache.size<e)t++;else break}return t}async load(e){try{if(f.cleanCache===`true`){this.cache.clear(),this.currentMemoryUsage=0;try{await a(e)}catch{}return}let t=await r(e,`utf-8`),n=JSON.parse(t);for(let[e,t]of Object.entries(n)){let n=t;n.contentHash&&n.outputPath&&n.mtime&&(this.cache.set(e,n),this.currentMemoryUsage+=n.size||0)}}catch{this.cache.clear(),this.currentMemoryUsage=0}}async save(e,t){try{await n(t,{recursive:!0});let r=Object.fromEntries(this.cache);await o(e,JSON.stringify(r,null,2))}catch(e){console.warn(`Warning: No se pudo guardar el cache:`,e)}}clear(){this.cache.clear(),this.currentMemoryUsage=0}getOutputPath(e){return this.cache.get(e)?.outputPath||``}getStats(){return{entries:this.cache.size,memoryUsage:this.currentMemoryUsage,hitRate:0}}async startDependencyWatching(){if(!this.isWatchingDependencies)try{let e=await import(`chokidar`);if(await this.fileExists(this.packageJsonPath)){let t=e.watch(this.packageJsonPath,{persistent:!1,ignoreInitial:!0});t.on(`change`,()=>{p.info(`📦 package.json modificado - invalidando cache de dependencias`),this.invalidateByDependencyChange()}),this.fileWatchers.set(`package.json`,t)}if(await this.fileExists(this.nodeModulesPath)){let t=e.watch(this.nodeModulesPath,{persistent:!1,ignoreInitial:!0,depth:1,ignored:/(^|[/\\])\../});t.on(`addDir`,e=>{p.info(`📦 Nueva dependencia instalada: ${e.split(/[/\\]/).pop()}`),this.invalidateByDependencyChange()}),t.on(`unlinkDir`,e=>{p.info(`📦 Dependencia eliminada: ${e.split(/[/\\]/).pop()}`),this.invalidateByDependencyChange()}),this.fileWatchers.set(`node_modules`,t)}this.isWatchingDependencies=!0,p.info(`🔍 Vigilancia de dependencias iniciada`)}catch(e){p.warn(`⚠️ No se pudo iniciar vigilancia de dependencias:`,e)}}async stopDependencyWatching(){for(let[e,t]of this.fileWatchers)try{await t.close(),p.info(`🛑 Vigilancia detenida: ${e}`)}catch(t){p.warn(`⚠️ Error cerrando watcher ${e}:`,t)}this.fileWatchers.clear(),this.isWatchingDependencies=!1}registerDependencies(e,t){let n=this.dependencyGraph.get(e);if(n)for(let t of n){let n=this.reverseDependencyGraph.get(t);n&&(n.delete(e),n.size===0&&this.reverseDependencyGraph.delete(t))}let r=new Set(t);this.dependencyGraph.set(e,r);for(let t of r)this.reverseDependencyGraph.has(t)||this.reverseDependencyGraph.set(t,new Set),this.reverseDependencyGraph.get(t).add(e)}invalidateByDependencyChange(){let e=0;for(let[t]of this.cache)this.cache.delete(t),e++;this.dependencyGraph.clear(),this.reverseDependencyGraph.clear(),this.currentMemoryUsage=0,p.info(`🗑️ Cache invalidado: ${e} archivos (cambio en dependencias)`)}invalidateCascade(e){let t=[],n=new Set([e]),r=[e];for(;r.length>0;){let e=r.shift(),t=this.reverseDependencyGraph.get(e);if(t)for(let e of t)n.has(e)||(n.add(e),r.push(e))}for(let e of n)if(this.cache.has(e)){let n=this.cache.get(e);this.currentMemoryUsage-=n.size,this.cache.delete(e),t.push(e)}return t.length>0&&p.info(`🔄 Invalidación cascada: ${t.length} archivos afectados por ${e}`),t}async fileExists(e){try{return await i(e),!0}catch{return!1}}getAdvancedStats(){return{entries:this.cache.size,memoryUsage:this.currentMemoryUsage,hitRate:0,dependencyNodes:this.dependencyGraph.size,watchingDependencies:this.isWatchingDependencies,activeWatchers:this.fileWatchers.size}}}const R=new L,z=c.join(c.resolve(f.PATH_PROY||d(),`compiler`),`.cache`),B=c.join(z,`versacompile-cache.json`);async function ue(){await R.load(B),(f.WATCH_MODE===`true`||u.includes(`--watch`)||u.includes(`-w`))&&await R.startDependencyWatching()}async function de(){await R.save(B,z)}function V(e,t,n,r=`error`,i,a){P.push({file:e,stage:t,message:n,severity:r,details:i,help:a,timestamp:Date.now()})}function H(e,t,n,r=[]){let i=F.find(t=>t.stage===e);i?(i.errors+=t,i.success+=n,i.files.push(...r)):F.push({stage:e,errors:t,success:n,files:[...r]})}async function U(e,t,n,r,i=!1){let a=e instanceof Error?e.message:e,o=e instanceof Error?e.stack:void 0;if(V(t,n,a,`error`,o),H(n,1,0,[t]),r===`individual`||r===`watch`){let e=await A(),r=c.basename(t),s=await J(n);if(i)p.error(e.red(`❌ Error en etapa ${s(n)} - ${r}:`)),p.error(e.red(a)),o&&(n===`typescript`||n===`vue`)&&o.split(`
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
1
|
+
import { createHash } from 'node:crypto';
|
|
2
|
+
import { glob, mkdir, readFile, stat, unlink, writeFile, } from 'node:fs/promises';
|
|
3
|
+
import * as os from 'node:os';
|
|
4
|
+
import * as path from 'node:path';
|
|
5
|
+
import * as process from 'node:process';
|
|
6
|
+
const { argv, cwd, env } = process;
|
|
7
|
+
// Lazy loading optimizations - Only import lightweight modules synchronously
|
|
8
|
+
import { logger, setProgressManagerGetter } from '../servicios/logger.js';
|
|
9
|
+
import { promptUser } from '../utils/promptUser.js';
|
|
10
|
+
import { showTimingForHumans } from '../utils/utils.js';
|
|
11
|
+
// Configurar el getter del ProgressManager para el logger
|
|
12
|
+
setProgressManagerGetter(() => ProgressManager.getInstance());
|
|
13
|
+
/**
|
|
14
|
+
* ✨ FIX #5: Wrapper con timeout para operaciones críticas
|
|
15
|
+
* Evita que promesas colgadas bloqueen la compilación indefinidamente
|
|
16
|
+
*/
|
|
17
|
+
async function withTimeout(promise, timeoutMs, operationName) {
|
|
18
|
+
const timeoutPromise = new Promise((_, reject) => setTimeout(() => reject(new Error(`Timeout en ${operationName} (${timeoutMs}ms)`)), timeoutMs));
|
|
19
|
+
return Promise.race([promise, timeoutPromise]);
|
|
20
|
+
}
|
|
21
|
+
// Heavy dependencies will be loaded dynamically when needed
|
|
22
|
+
let chalk;
|
|
23
|
+
let ESLint;
|
|
24
|
+
let OxLint;
|
|
25
|
+
let minifyJS;
|
|
26
|
+
let getCodeFile;
|
|
27
|
+
let generateTailwindCSS;
|
|
28
|
+
let estandarizaCode;
|
|
29
|
+
let preCompileTS;
|
|
30
|
+
let preCompileVue;
|
|
31
|
+
// 🚀 Importar optimizador de transformaciones
|
|
32
|
+
let TransformOptimizer;
|
|
33
|
+
// 🚀 Importar optimizador de resolución de módulos
|
|
34
|
+
let ModuleResolutionOptimizer;
|
|
35
|
+
// 🚀 Sistema de Carga Inteligente de Módulos - VERSIÓN OPTIMIZADA V2
|
|
36
|
+
class OptimizedModuleManager {
|
|
37
|
+
static instance;
|
|
38
|
+
isInitialized = false;
|
|
39
|
+
loadedModules = new Set();
|
|
40
|
+
// ✨ NUEVAS OPTIMIZACIONES
|
|
41
|
+
modulePool = new Map(); // Pool de instancias reutilizables
|
|
42
|
+
loadingPromises = new Map(); // Prevenir cargas duplicadas
|
|
43
|
+
usageStats = new Map(); // Estadísticas de uso
|
|
44
|
+
preloadQueue = new Set(); // Cola de precarga
|
|
45
|
+
backgroundLoader = null; // Cargador en background
|
|
46
|
+
preloadLock = null; // Lock para evitar precargas concurrentes
|
|
47
|
+
// ✨ FIX #4: Límites estrictos de memoria para el pool
|
|
48
|
+
MAX_POOL_MEMORY = 100 * 1024 * 1024; // 100MB límite total
|
|
49
|
+
MAX_POOL_SIZE = 15; // Máximo 15 módulos en pool
|
|
50
|
+
// Módulos críticos que siempre se precargan
|
|
51
|
+
HOT_MODULES = ['chalk', 'parser'];
|
|
52
|
+
// Contexto actual para optimizar cargas
|
|
53
|
+
currentContext = null;
|
|
54
|
+
constructor() {
|
|
55
|
+
// Iniciar precarga en background inmediatamente
|
|
56
|
+
this.startBackgroundPreloading();
|
|
57
|
+
}
|
|
58
|
+
static getInstance() {
|
|
59
|
+
if (!OptimizedModuleManager.instance) {
|
|
60
|
+
OptimizedModuleManager.instance = new OptimizedModuleManager();
|
|
61
|
+
}
|
|
62
|
+
return OptimizedModuleManager.instance;
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* ✨ NUEVO: Precarga en background para módulos críticos
|
|
66
|
+
*/
|
|
67
|
+
startBackgroundPreloading() {
|
|
68
|
+
this.backgroundLoader = this.preloadCriticalModules();
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* ✨ NUEVO: Precarga módulos críticos en background
|
|
72
|
+
*/
|
|
73
|
+
async preloadCriticalModules() {
|
|
74
|
+
try {
|
|
75
|
+
// Precargar módulos críticos de forma asíncrona
|
|
76
|
+
const preloadPromises = this.HOT_MODULES.map(moduleName => this.ensureModuleLoaded(moduleName).catch(() => {
|
|
77
|
+
// Silenciar errores de precarga, se intentará cargar después
|
|
78
|
+
}));
|
|
79
|
+
await Promise.allSettled(preloadPromises);
|
|
80
|
+
}
|
|
81
|
+
catch {
|
|
82
|
+
// Fallos en precarga no deben afectar la funcionalidad principal
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* ✨ MEJORADO: Precarga contextual basada en tipos de archivo con lock para prevenir cargas concurrentes
|
|
87
|
+
*/
|
|
88
|
+
async preloadForContext(context, fileTypes = new Set()) {
|
|
89
|
+
// Si ya hay una precarga en progreso, esperar a que termine
|
|
90
|
+
if (this.preloadLock) {
|
|
91
|
+
await this.preloadLock;
|
|
92
|
+
return;
|
|
93
|
+
}
|
|
94
|
+
// Crear el lock
|
|
95
|
+
this.preloadLock = this.doPreload(context, fileTypes);
|
|
96
|
+
try {
|
|
97
|
+
await this.preloadLock;
|
|
98
|
+
}
|
|
99
|
+
finally {
|
|
100
|
+
this.preloadLock = null;
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
/**
|
|
104
|
+
* ✨ Método interno de precarga
|
|
105
|
+
*/
|
|
106
|
+
async doPreload(context, fileTypes = new Set()) {
|
|
107
|
+
this.currentContext = context;
|
|
108
|
+
// Esperar que termine la precarga crítica si está en progreso
|
|
109
|
+
if (this.backgroundLoader) {
|
|
110
|
+
await this.backgroundLoader;
|
|
111
|
+
}
|
|
112
|
+
const toPreload = []; // Precarga basada en contexto
|
|
113
|
+
if (context === 'batch' || context === 'watch') {
|
|
114
|
+
// En batch/watch, precargar todos los módulos comunes
|
|
115
|
+
toPreload.push('transforms', 'vue', 'typescript', 'module-resolution-optimizer');
|
|
116
|
+
}
|
|
117
|
+
else {
|
|
118
|
+
// En individual, cargar solo según tipos de archivo detectados
|
|
119
|
+
if (fileTypes.has('.vue'))
|
|
120
|
+
toPreload.push('vue');
|
|
121
|
+
if (fileTypes.has('.ts') || fileTypes.has('.vue'))
|
|
122
|
+
toPreload.push('typescript');
|
|
123
|
+
if (!this.loadedModules.has('transforms'))
|
|
124
|
+
toPreload.push('transforms');
|
|
125
|
+
}
|
|
126
|
+
// ✨ OPTIMIZACIÓN #10: Agrupar módulos compatibles y cargarlos en paralelo
|
|
127
|
+
// Grupos de módulos que NO comparten dependencias nativas problemáticas
|
|
128
|
+
const moduleGroups = [
|
|
129
|
+
['chalk', 'parser'], // Grupo 1: Módulos ligeros sin node:crypto
|
|
130
|
+
['transforms'], // Grupo 2: Puede usar node:crypto pero independiente
|
|
131
|
+
['vue', 'typescript'], // Grupo 3: Comparten configuración
|
|
132
|
+
['module-resolution-optimizer'], // Grupo 4: Independiente
|
|
133
|
+
['minify'], // Grupo 5: Independiente
|
|
134
|
+
];
|
|
135
|
+
// Cargar cada grupo en paralelo, pero grupos secuencialmente
|
|
136
|
+
for (const group of moduleGroups) {
|
|
137
|
+
const modulesToLoad = group.filter(name => toPreload.includes(name));
|
|
138
|
+
if (modulesToLoad.length > 0) {
|
|
139
|
+
// Cargar módulos del grupo en paralelo
|
|
140
|
+
await Promise.allSettled(modulesToLoad.map(async (moduleName) => {
|
|
141
|
+
try {
|
|
142
|
+
await this.ensureModuleLoaded(moduleName);
|
|
143
|
+
}
|
|
144
|
+
catch (error) {
|
|
145
|
+
// Silenciar errores de precarga - los módulos se cargarán bajo demanda
|
|
146
|
+
if (env.VERBOSE === 'true') {
|
|
147
|
+
const errorMessage = error instanceof Error
|
|
148
|
+
? error.message
|
|
149
|
+
: String(error);
|
|
150
|
+
console.warn(`[Verbose] Warning: No se pudo precargar módulo ${moduleName}:`, errorMessage);
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
}));
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
/**
|
|
158
|
+
* ✨ MEJORADO: Carga inteligente con pooling y deduplicación
|
|
159
|
+
*/
|
|
160
|
+
async ensureModuleLoaded(moduleName) {
|
|
161
|
+
// 1. Verificar pool de módulos primero
|
|
162
|
+
if (this.modulePool.has(moduleName)) {
|
|
163
|
+
this.updateUsageStats(moduleName);
|
|
164
|
+
return this.modulePool.get(moduleName);
|
|
165
|
+
}
|
|
166
|
+
// 2. Verificar si ya está cargando (deduplicación)
|
|
167
|
+
if (this.loadingPromises.has(moduleName)) {
|
|
168
|
+
return this.loadingPromises.get(moduleName);
|
|
169
|
+
}
|
|
170
|
+
// 3. Iniciar carga
|
|
171
|
+
const loadPromise = this.loadModuleInternal(moduleName);
|
|
172
|
+
this.loadingPromises.set(moduleName, loadPromise);
|
|
173
|
+
try {
|
|
174
|
+
const moduleInstance = await loadPromise;
|
|
175
|
+
// 4. Almacenar en pool y estadísticas
|
|
176
|
+
this.modulePool.set(moduleName, moduleInstance);
|
|
177
|
+
this.loadedModules.add(moduleName);
|
|
178
|
+
this.updateUsageStats(moduleName);
|
|
179
|
+
return moduleInstance;
|
|
180
|
+
}
|
|
181
|
+
finally {
|
|
182
|
+
// 5. Limpiar promesa de carga
|
|
183
|
+
this.loadingPromises.delete(moduleName);
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
/**
|
|
187
|
+
* ✨ NUEVO: Actualiza estadísticas de uso para optimizaciones futuras
|
|
188
|
+
*/
|
|
189
|
+
updateUsageStats(moduleName) {
|
|
190
|
+
const currentCount = this.usageStats.get(moduleName) || 0;
|
|
191
|
+
this.usageStats.set(moduleName, currentCount + 1);
|
|
192
|
+
}
|
|
193
|
+
/**
|
|
194
|
+
* ✨ MEJORADO: Carga interna de módulos con mejor manejo de errores
|
|
195
|
+
*/ async loadModuleInternal(moduleName) {
|
|
196
|
+
switch (moduleName) {
|
|
197
|
+
case 'chalk':
|
|
198
|
+
return this.loadChalk();
|
|
199
|
+
case 'parser':
|
|
200
|
+
return this.loadParser();
|
|
201
|
+
case 'transforms':
|
|
202
|
+
return this.loadTransforms();
|
|
203
|
+
case 'vue':
|
|
204
|
+
return this.loadVue();
|
|
205
|
+
case 'typescript':
|
|
206
|
+
return this.loadTypeScript();
|
|
207
|
+
case 'minify':
|
|
208
|
+
return this.loadMinify();
|
|
209
|
+
case 'tailwind':
|
|
210
|
+
return this.loadTailwind();
|
|
211
|
+
case 'linter':
|
|
212
|
+
return this.loadLinter();
|
|
213
|
+
case 'transform-optimizer':
|
|
214
|
+
return this.loadTransformOptimizer();
|
|
215
|
+
case 'module-resolution-optimizer':
|
|
216
|
+
return this.loadModuleResolutionOptimizer();
|
|
217
|
+
default:
|
|
218
|
+
throw new Error(`Módulo desconocido: ${moduleName}`);
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
// ✨ Métodos de carga específicos optimizados
|
|
222
|
+
async loadChalk() {
|
|
223
|
+
if (!chalk) {
|
|
224
|
+
chalk = (await import('chalk')).default;
|
|
225
|
+
}
|
|
226
|
+
return chalk;
|
|
227
|
+
}
|
|
228
|
+
async loadParser() {
|
|
229
|
+
if (!getCodeFile) {
|
|
230
|
+
const parserModule = await import('./parser.js');
|
|
231
|
+
getCodeFile = parserModule.getCodeFile;
|
|
232
|
+
}
|
|
233
|
+
return getCodeFile;
|
|
234
|
+
}
|
|
235
|
+
async loadTransforms() {
|
|
236
|
+
if (!estandarizaCode) {
|
|
237
|
+
const transformsModule = await import('./transforms.js');
|
|
238
|
+
estandarizaCode = transformsModule.estandarizaCode;
|
|
239
|
+
}
|
|
240
|
+
return estandarizaCode;
|
|
241
|
+
}
|
|
242
|
+
async loadVue() {
|
|
243
|
+
if (!preCompileVue) {
|
|
244
|
+
const vueModule = await import('./vuejs.js');
|
|
245
|
+
preCompileVue = vueModule.preCompileVue;
|
|
246
|
+
}
|
|
247
|
+
return preCompileVue;
|
|
248
|
+
}
|
|
249
|
+
async loadTypeScript() {
|
|
250
|
+
if (!preCompileTS) {
|
|
251
|
+
const typescriptModule = await import('./typescript-manager.js');
|
|
252
|
+
preCompileTS = typescriptModule.preCompileTS;
|
|
253
|
+
}
|
|
254
|
+
return preCompileTS;
|
|
255
|
+
}
|
|
256
|
+
async loadMinify() {
|
|
257
|
+
if (!minifyJS) {
|
|
258
|
+
const minifyModule = await import('./minify.js');
|
|
259
|
+
// ✨ Usar minifyWithTemplates para minificar templates HTML ANTES del JS
|
|
260
|
+
minifyJS = minifyModule.minifyWithTemplates;
|
|
261
|
+
}
|
|
262
|
+
return minifyJS;
|
|
263
|
+
}
|
|
264
|
+
async loadTailwind() {
|
|
265
|
+
if (!generateTailwindCSS) {
|
|
266
|
+
const tailwindModule = await import('./tailwindcss.js');
|
|
267
|
+
generateTailwindCSS = tailwindModule.generateTailwindCSS;
|
|
268
|
+
}
|
|
269
|
+
return generateTailwindCSS;
|
|
270
|
+
}
|
|
271
|
+
async loadLinter() {
|
|
272
|
+
if (!ESLint || !OxLint) {
|
|
273
|
+
const linterModule = await import('./linter.js');
|
|
274
|
+
ESLint = linterModule.ESLint;
|
|
275
|
+
OxLint = linterModule.OxLint;
|
|
276
|
+
}
|
|
277
|
+
return { ESLint, OxLint };
|
|
278
|
+
}
|
|
279
|
+
async loadTransformOptimizer() {
|
|
280
|
+
if (!TransformOptimizer) {
|
|
281
|
+
const transformModule = await import('./transform-optimizer.js');
|
|
282
|
+
TransformOptimizer =
|
|
283
|
+
transformModule.TransformOptimizer.getInstance();
|
|
284
|
+
}
|
|
285
|
+
return TransformOptimizer;
|
|
286
|
+
}
|
|
287
|
+
async loadModuleResolutionOptimizer() {
|
|
288
|
+
if (!ModuleResolutionOptimizer) {
|
|
289
|
+
const resolutionModule = await import('./module-resolution-optimizer.js');
|
|
290
|
+
ModuleResolutionOptimizer =
|
|
291
|
+
resolutionModule.ModuleResolutionOptimizer.getInstance();
|
|
292
|
+
}
|
|
293
|
+
return ModuleResolutionOptimizer;
|
|
294
|
+
}
|
|
295
|
+
/**
|
|
296
|
+
* ✨ NUEVO: Obtiene estadísticas de performance del manager
|
|
297
|
+
*/
|
|
298
|
+
getPerformanceStats() {
|
|
299
|
+
const sortedByUsage = Array.from(this.usageStats.entries())
|
|
300
|
+
.sort((a, b) => b[1] - a[1])
|
|
301
|
+
.slice(0, 5)
|
|
302
|
+
.map(([name]) => name);
|
|
303
|
+
return {
|
|
304
|
+
loadedModules: Array.from(this.loadedModules),
|
|
305
|
+
usageStats: Object.fromEntries(this.usageStats),
|
|
306
|
+
poolSize: this.modulePool.size,
|
|
307
|
+
loadingInProgress: Array.from(this.loadingPromises.keys()),
|
|
308
|
+
mostUsedModules: sortedByUsage,
|
|
309
|
+
};
|
|
310
|
+
}
|
|
311
|
+
/**
|
|
312
|
+
* ✨ FIX #4: Estima el tamaño en memoria de un módulo
|
|
313
|
+
*/
|
|
314
|
+
estimateModuleSize(moduleName) {
|
|
315
|
+
// Estimaciones basadas en tipos de módulo
|
|
316
|
+
const sizeMap = {
|
|
317
|
+
'transform-optimizer': 5 * 1024 * 1024, // 5MB
|
|
318
|
+
typescript: 10 * 1024 * 1024, // 10MB
|
|
319
|
+
vue: 8 * 1024 * 1024, // 8MB
|
|
320
|
+
'module-resolution-optimizer': 3 * 1024 * 1024, // 3MB
|
|
321
|
+
transforms: 2 * 1024 * 1024, // 2MB
|
|
322
|
+
minify: 2 * 1024 * 1024, // 2MB
|
|
323
|
+
linter: 1 * 1024 * 1024, // 1MB
|
|
324
|
+
parser: 500 * 1024, // 500KB
|
|
325
|
+
chalk: 100 * 1024, // 100KB
|
|
326
|
+
default: 500 * 1024, // 500KB por defecto
|
|
327
|
+
};
|
|
328
|
+
return sizeMap[moduleName] ?? sizeMap.default;
|
|
329
|
+
}
|
|
330
|
+
/**
|
|
331
|
+
* ✨ FIX #4: Obtiene el uso total de memoria del pool
|
|
332
|
+
*/
|
|
333
|
+
getPoolMemoryUsage() {
|
|
334
|
+
let totalSize = 0;
|
|
335
|
+
for (const moduleName of this.modulePool.keys()) {
|
|
336
|
+
totalSize += this.estimateModuleSize(moduleName);
|
|
337
|
+
}
|
|
338
|
+
return totalSize;
|
|
339
|
+
}
|
|
340
|
+
/**
|
|
341
|
+
* ✨ FIX #4: Limpia módulos no utilizados con control de memoria LRU
|
|
342
|
+
*/
|
|
343
|
+
cleanupUnusedModules() {
|
|
344
|
+
const currentMemory = this.getPoolMemoryUsage();
|
|
345
|
+
const currentSize = this.modulePool.size;
|
|
346
|
+
// Limpiar si excedemos límites de memoria o tamaño
|
|
347
|
+
if (currentMemory > this.MAX_POOL_MEMORY ||
|
|
348
|
+
currentSize > this.MAX_POOL_SIZE) {
|
|
349
|
+
// LRU: Ordenar por menos usado
|
|
350
|
+
const sortedModules = Array.from(this.usageStats.entries())
|
|
351
|
+
.sort((a, b) => a[1] - b[1]) // Ascendente por uso
|
|
352
|
+
.filter(([name]) => !this.HOT_MODULES.includes(name)); // No eliminar HOT_MODULES
|
|
353
|
+
// Eliminar módulos hasta estar por debajo del 70% del límite
|
|
354
|
+
const targetMemory = this.MAX_POOL_MEMORY * 0.7;
|
|
355
|
+
const targetSize = this.MAX_POOL_SIZE * 0.7;
|
|
356
|
+
for (const [moduleName] of sortedModules) {
|
|
357
|
+
this.modulePool.delete(moduleName);
|
|
358
|
+
this.loadedModules.delete(moduleName);
|
|
359
|
+
this.usageStats.delete(moduleName);
|
|
360
|
+
const newMemory = this.getPoolMemoryUsage();
|
|
361
|
+
const newSize = this.modulePool.size;
|
|
362
|
+
if (newMemory <= targetMemory && newSize <= targetSize) {
|
|
363
|
+
break;
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
if (env.VERBOSE === 'true') {
|
|
367
|
+
console.log(`[ModuleManager] Limpieza: ${currentSize} → ${this.modulePool.size} módulos, ` +
|
|
368
|
+
`${Math.round(currentMemory / 1024 / 1024)}MB → ${Math.round(this.getPoolMemoryUsage() / 1024 / 1024)}MB`);
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
/**
|
|
373
|
+
* Resetea el estado del manager (útil para tests)
|
|
374
|
+
*/
|
|
375
|
+
reset() {
|
|
376
|
+
this.isInitialized = false;
|
|
377
|
+
this.loadedModules.clear();
|
|
378
|
+
this.modulePool.clear();
|
|
379
|
+
this.loadingPromises.clear();
|
|
380
|
+
this.usageStats.clear();
|
|
381
|
+
this.preloadQueue.clear();
|
|
382
|
+
this.currentContext = null;
|
|
383
|
+
this.backgroundLoader = null;
|
|
384
|
+
// Reiniciar precarga crítica
|
|
385
|
+
this.startBackgroundPreloading();
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
// Lazy loading helper functions
|
|
389
|
+
async function loadChalk() {
|
|
390
|
+
if (!chalk) {
|
|
391
|
+
chalk = (await import('chalk')).default;
|
|
392
|
+
}
|
|
393
|
+
return chalk;
|
|
394
|
+
}
|
|
395
|
+
async function loadLinter() {
|
|
396
|
+
if (!ESLint || !OxLint) {
|
|
397
|
+
const linterModule = await import('./linter.js');
|
|
398
|
+
ESLint = linterModule.ESLint;
|
|
399
|
+
OxLint = linterModule.OxLint;
|
|
400
|
+
}
|
|
401
|
+
return { ESLint, OxLint };
|
|
402
|
+
}
|
|
403
|
+
async function loadMinify() {
|
|
404
|
+
if (!minifyJS) {
|
|
405
|
+
const minifyModule = await import('./minify.js');
|
|
406
|
+
// ✨ Usar minifyWithTemplates para minificar templates HTML ANTES del JS
|
|
407
|
+
minifyJS = minifyModule.minifyWithTemplates;
|
|
408
|
+
}
|
|
409
|
+
return minifyJS;
|
|
410
|
+
}
|
|
411
|
+
async function loadParser() {
|
|
412
|
+
if (!getCodeFile) {
|
|
413
|
+
const parserModule = await import('./parser.js');
|
|
414
|
+
getCodeFile = parserModule.getCodeFile;
|
|
415
|
+
}
|
|
416
|
+
return getCodeFile;
|
|
417
|
+
}
|
|
418
|
+
async function loadTailwind() {
|
|
419
|
+
if (!generateTailwindCSS) {
|
|
420
|
+
const tailwindModule = await import('./tailwindcss.js');
|
|
421
|
+
generateTailwindCSS = tailwindModule.generateTailwindCSS;
|
|
422
|
+
}
|
|
423
|
+
return generateTailwindCSS;
|
|
424
|
+
}
|
|
425
|
+
async function loadTransforms() {
|
|
426
|
+
if (!estandarizaCode) {
|
|
427
|
+
const transformsModule = await import('./transforms.js');
|
|
428
|
+
estandarizaCode = transformsModule.estandarizaCode;
|
|
429
|
+
}
|
|
430
|
+
return estandarizaCode;
|
|
431
|
+
}
|
|
432
|
+
async function loadTypeScript() {
|
|
433
|
+
if (!preCompileTS) {
|
|
434
|
+
const typescriptModule = await import('./typescript-manager.js');
|
|
435
|
+
preCompileTS = typescriptModule.preCompileTS;
|
|
436
|
+
}
|
|
437
|
+
return preCompileTS;
|
|
438
|
+
}
|
|
439
|
+
async function loadVue() {
|
|
440
|
+
if (!preCompileVue) {
|
|
441
|
+
const vueModule = await import('./vuejs.js');
|
|
442
|
+
preCompileVue = vueModule.preCompileVue;
|
|
443
|
+
}
|
|
444
|
+
return preCompileVue;
|
|
445
|
+
}
|
|
446
|
+
// Almacenamiento global de errores y resultados
|
|
447
|
+
const compilationErrors = [];
|
|
448
|
+
const compilationResults = [];
|
|
449
|
+
// Variables de entorno relevantes para compilación
|
|
450
|
+
const COMPILATION_ENV_VARS = [
|
|
451
|
+
'NODE_ENV',
|
|
452
|
+
'isPROD',
|
|
453
|
+
'TAILWIND',
|
|
454
|
+
'ENABLE_LINTER',
|
|
455
|
+
'VERBOSE',
|
|
456
|
+
'typeCheck',
|
|
457
|
+
'PATH_ALIAS',
|
|
458
|
+
'tailwindcss',
|
|
459
|
+
'linter',
|
|
460
|
+
'tsconfigFile',
|
|
461
|
+
];
|
|
462
|
+
class SmartCompilationCache {
|
|
463
|
+
cache = new Map();
|
|
464
|
+
maxEntries = 200; // Reducido para tests de estrés
|
|
465
|
+
maxMemory = 50 * 1024 * 1024; // 50MB límite (reducido)
|
|
466
|
+
currentMemoryUsage = 0;
|
|
467
|
+
// ✨ ISSUE #3: Sistema de vigilancia de dependencias
|
|
468
|
+
fileWatchers = new Map(); // chokidar watchers
|
|
469
|
+
dependencyGraph = new Map(); // archivo -> dependencias
|
|
470
|
+
reverseDependencyGraph = new Map(); // dependencia -> archivos que la usan
|
|
471
|
+
packageJsonPath = path.join(cwd(), 'package.json');
|
|
472
|
+
nodeModulesPath = path.join(cwd(), 'node_modules');
|
|
473
|
+
isWatchingDependencies = false;
|
|
474
|
+
/**
|
|
475
|
+
* Genera hash SHA-256 del contenido del archivo
|
|
476
|
+
*/ async generateContentHash(filePath) {
|
|
477
|
+
try {
|
|
478
|
+
const content = await readFile(filePath, 'utf8');
|
|
479
|
+
return createHash('sha256').update(content).digest('hex');
|
|
480
|
+
}
|
|
481
|
+
catch {
|
|
482
|
+
// Si no se puede leer el archivo, generar hash único basado en la ruta y timestamp
|
|
483
|
+
const fallback = `${filePath}-${Date.now()}`;
|
|
484
|
+
return createHash('sha256').update(fallback).digest('hex');
|
|
485
|
+
}
|
|
486
|
+
}
|
|
487
|
+
/**
|
|
488
|
+
* Genera hash de la configuración del compilador
|
|
489
|
+
*/
|
|
490
|
+
generateConfigHash() {
|
|
491
|
+
try {
|
|
492
|
+
// Recopilar configuración relevante de variables de entorno
|
|
493
|
+
const config = {
|
|
494
|
+
isPROD: env.isPROD || 'false',
|
|
495
|
+
TAILWIND: env.TAILWIND || 'false',
|
|
496
|
+
ENABLE_LINTER: env.ENABLE_LINTER || 'false',
|
|
497
|
+
PATH_ALIAS: env.PATH_ALIAS || '{}',
|
|
498
|
+
tailwindcss: env.tailwindcss || 'false',
|
|
499
|
+
linter: env.linter || 'false',
|
|
500
|
+
tsconfigFile: env.tsconfigFile || './tsconfig.json',
|
|
501
|
+
};
|
|
502
|
+
const configStr = JSON.stringify(config, Object.keys(config).sort());
|
|
503
|
+
return createHash('sha256')
|
|
504
|
+
.update(configStr)
|
|
505
|
+
.digest('hex')
|
|
506
|
+
.substring(0, 12);
|
|
507
|
+
}
|
|
508
|
+
catch {
|
|
509
|
+
return 'no-config';
|
|
510
|
+
}
|
|
511
|
+
}
|
|
512
|
+
/**
|
|
513
|
+
* Genera hash de variables de entorno relevantes
|
|
514
|
+
*/
|
|
515
|
+
generateEnvHash() {
|
|
516
|
+
try {
|
|
517
|
+
const envVars = COMPILATION_ENV_VARS.map(key => `${key}=${env[key] || ''}`).join('|');
|
|
518
|
+
return createHash('sha256')
|
|
519
|
+
.update(envVars)
|
|
520
|
+
.digest('hex')
|
|
521
|
+
.substring(0, 12);
|
|
522
|
+
}
|
|
523
|
+
catch {
|
|
524
|
+
return 'no-env';
|
|
525
|
+
}
|
|
526
|
+
} /**
|
|
527
|
+
* ✨ ISSUE #3: Genera hash avanzado de dependencias del proyecto
|
|
528
|
+
* Incluye vigilancia de package.json, node_modules y versiones instaladas
|
|
529
|
+
*/
|
|
530
|
+
async generateDependencyHash() {
|
|
531
|
+
try {
|
|
532
|
+
const hash = createHash('sha256');
|
|
533
|
+
// 1. Hash del package.json con versiones
|
|
534
|
+
const packagePath = path.join(cwd(), 'package.json');
|
|
535
|
+
const packageContent = await readFile(packagePath, 'utf8');
|
|
536
|
+
const pkg = JSON.parse(packageContent);
|
|
537
|
+
const deps = {
|
|
538
|
+
...pkg.dependencies,
|
|
539
|
+
...pkg.devDependencies,
|
|
540
|
+
};
|
|
541
|
+
const depsStr = JSON.stringify(deps, Object.keys(deps).sort());
|
|
542
|
+
hash.update(`package:${depsStr}`);
|
|
543
|
+
// 2. Hash del package-lock.json si existe (versiones exactas instaladas)
|
|
544
|
+
try {
|
|
545
|
+
const lockPath = path.join(cwd(), 'package-lock.json');
|
|
546
|
+
const lockContent = await readFile(lockPath, 'utf8');
|
|
547
|
+
const lockData = JSON.parse(lockContent);
|
|
548
|
+
// Solo incluir las versiones instaladas, no todo el lockfile
|
|
549
|
+
const installedVersions = {};
|
|
550
|
+
if (lockData.packages) {
|
|
551
|
+
for (const [pkgPath, pkgInfo] of Object.entries(lockData.packages)) {
|
|
552
|
+
if (pkgPath &&
|
|
553
|
+
pkgPath !== '' &&
|
|
554
|
+
typeof pkgInfo === 'object' &&
|
|
555
|
+
pkgInfo !== null) {
|
|
556
|
+
const pkgName = pkgPath.replace('node_modules/', '');
|
|
557
|
+
if (pkgInfo.version) {
|
|
558
|
+
installedVersions[pkgName] = pkgInfo.version;
|
|
559
|
+
}
|
|
560
|
+
}
|
|
561
|
+
}
|
|
562
|
+
}
|
|
563
|
+
hash.update(`lock:${JSON.stringify(installedVersions, Object.keys(installedVersions).sort())}`);
|
|
564
|
+
}
|
|
565
|
+
catch {
|
|
566
|
+
// Ignorar si no existe package-lock.json
|
|
567
|
+
}
|
|
568
|
+
// 3. ✨ NUEVO: Hash de timestamps críticos de node_modules
|
|
569
|
+
try {
|
|
570
|
+
const nodeModulesPath = path.join(cwd(), 'node_modules');
|
|
571
|
+
const nodeModulesStat = await stat(nodeModulesPath);
|
|
572
|
+
hash.update(`nmtime:${nodeModulesStat.mtimeMs}`);
|
|
573
|
+
// Verificar timestamps de dependencias críticas instaladas
|
|
574
|
+
const criticalDeps = Object.keys(deps).slice(0, 10); // Top 10 para performance
|
|
575
|
+
for (const dep of criticalDeps) {
|
|
576
|
+
try {
|
|
577
|
+
const depPath = path.join(nodeModulesPath, dep);
|
|
578
|
+
const depStat = await stat(depPath);
|
|
579
|
+
hash.update(`${dep}:${depStat.mtimeMs}`);
|
|
580
|
+
}
|
|
581
|
+
catch {
|
|
582
|
+
// Dependencia no instalada o error
|
|
583
|
+
hash.update(`${dep}:missing`);
|
|
584
|
+
}
|
|
585
|
+
}
|
|
586
|
+
}
|
|
587
|
+
catch {
|
|
588
|
+
// node_modules no existe
|
|
589
|
+
hash.update('nmtime:none');
|
|
590
|
+
}
|
|
591
|
+
return hash.digest('hex').substring(0, 16);
|
|
592
|
+
}
|
|
593
|
+
catch (error) {
|
|
594
|
+
// Incluir información del error en el hash para debugging
|
|
595
|
+
return createHash('sha256')
|
|
596
|
+
.update(`error:${error instanceof Error ? error.message : 'unknown'}`)
|
|
597
|
+
.digest('hex')
|
|
598
|
+
.substring(0, 16);
|
|
599
|
+
}
|
|
600
|
+
}
|
|
601
|
+
/**
|
|
602
|
+
* Genera clave de cache granular que incluye todos los factores
|
|
603
|
+
*/
|
|
604
|
+
async generateCacheKey(filePath) {
|
|
605
|
+
const contentHash = await this.generateContentHash(filePath);
|
|
606
|
+
const configHash = this.generateConfigHash();
|
|
607
|
+
const envHash = this.generateEnvHash();
|
|
608
|
+
const dependencyHash = await this.generateDependencyHash();
|
|
609
|
+
// Usar | como separador para evitar problemas con rutas de Windows
|
|
610
|
+
return `${filePath}|${contentHash.substring(0, 12)}|${configHash}|${envHash}|${dependencyHash}`;
|
|
611
|
+
} /**
|
|
612
|
+
* Verifica si una entrada de cache es válida
|
|
613
|
+
*/
|
|
614
|
+
async isValid(filePath) {
|
|
615
|
+
const entry = this.cache.get(filePath);
|
|
616
|
+
if (!entry)
|
|
617
|
+
return false;
|
|
618
|
+
try {
|
|
619
|
+
// Verificar si el archivo de salida existe
|
|
620
|
+
await stat(entry.outputPath);
|
|
621
|
+
// Verificar si el contenido ha cambiado
|
|
622
|
+
const currentContentHash = await this.generateContentHash(filePath);
|
|
623
|
+
if (entry.contentHash !== currentContentHash) {
|
|
624
|
+
this.cache.delete(filePath);
|
|
625
|
+
return false;
|
|
626
|
+
}
|
|
627
|
+
// Verificar si la configuración ha cambiado
|
|
628
|
+
const currentConfigHash = this.generateConfigHash();
|
|
629
|
+
if (entry.configHash !== currentConfigHash) {
|
|
630
|
+
this.cache.delete(filePath);
|
|
631
|
+
return false;
|
|
632
|
+
}
|
|
633
|
+
// Verificar si las variables de entorno han cambiado
|
|
634
|
+
const currentEnvHash = this.generateEnvHash();
|
|
635
|
+
if (entry.envHash !== currentEnvHash) {
|
|
636
|
+
this.cache.delete(filePath);
|
|
637
|
+
return false;
|
|
638
|
+
}
|
|
639
|
+
// Verificar si las dependencias han cambiado
|
|
640
|
+
const currentDependencyHash = await this.generateDependencyHash();
|
|
641
|
+
if (entry.dependencyHash !== currentDependencyHash) {
|
|
642
|
+
this.cache.delete(filePath);
|
|
643
|
+
return false;
|
|
644
|
+
}
|
|
645
|
+
// Verificar tiempo de modificación como backup
|
|
646
|
+
const stats = await stat(filePath);
|
|
647
|
+
if (stats.mtimeMs > entry.mtime) {
|
|
648
|
+
this.cache.delete(filePath);
|
|
649
|
+
return false;
|
|
650
|
+
}
|
|
651
|
+
// Actualizar tiempo de uso para LRU
|
|
652
|
+
entry.lastUsed = Date.now();
|
|
653
|
+
return true;
|
|
654
|
+
}
|
|
655
|
+
catch {
|
|
656
|
+
// Si hay error verificando, eliminar del cache
|
|
657
|
+
this.cache.delete(filePath);
|
|
658
|
+
return false;
|
|
659
|
+
}
|
|
660
|
+
} /**
|
|
661
|
+
* Añade una entrada al cache
|
|
662
|
+
*/
|
|
663
|
+
async set(filePath, outputPath) {
|
|
664
|
+
try {
|
|
665
|
+
const stats = await stat(filePath);
|
|
666
|
+
const contentHash = await this.generateContentHash(filePath);
|
|
667
|
+
const configHash = this.generateConfigHash();
|
|
668
|
+
const envHash = this.generateEnvHash();
|
|
669
|
+
const dependencyHash = await this.generateDependencyHash();
|
|
670
|
+
const entry = {
|
|
671
|
+
contentHash,
|
|
672
|
+
configHash,
|
|
673
|
+
envHash,
|
|
674
|
+
dependencyHash,
|
|
675
|
+
mtime: stats.mtimeMs,
|
|
676
|
+
outputPath,
|
|
677
|
+
lastUsed: Date.now(),
|
|
678
|
+
size: stats.size,
|
|
679
|
+
};
|
|
680
|
+
// Aplicar límites de memoria y entradas antes de agregar
|
|
681
|
+
this.evictIfNeeded(entry.size);
|
|
682
|
+
this.cache.set(filePath, entry);
|
|
683
|
+
this.currentMemoryUsage += entry.size;
|
|
684
|
+
}
|
|
685
|
+
catch (error) {
|
|
686
|
+
// Si hay error, no cachear
|
|
687
|
+
console.warn(`Warning: No se pudo cachear ${filePath}:`, error);
|
|
688
|
+
}
|
|
689
|
+
} /**
|
|
690
|
+
* Aplica política LRU para liberar espacio
|
|
691
|
+
*/
|
|
692
|
+
evictIfNeeded(newEntrySize) {
|
|
693
|
+
// Verificar límite de entradas más agresivamente
|
|
694
|
+
while (this.cache.size >= this.maxEntries * 0.8) {
|
|
695
|
+
// Limpiar cuando llegue al 80%
|
|
696
|
+
this.evictLRU();
|
|
697
|
+
}
|
|
698
|
+
// Verificar límite de memoria más agresivamente
|
|
699
|
+
while (this.currentMemoryUsage + newEntrySize > this.maxMemory * 0.8 && // Limpiar cuando llegue al 80%
|
|
700
|
+
this.cache.size > 0) {
|
|
701
|
+
this.evictLRU();
|
|
702
|
+
}
|
|
703
|
+
// Eviction adicional si la memoria total del proceso es alta
|
|
704
|
+
const memUsage = process.memoryUsage();
|
|
705
|
+
const heapUsedMB = memUsage.heapUsed / (1024 * 1024);
|
|
706
|
+
if (heapUsedMB > 200 && this.cache.size > 50) {
|
|
707
|
+
// Si heap > 200MB, limpiar más agresivamente
|
|
708
|
+
const entriesToRemove = Math.min(this.cache.size - 50, 10);
|
|
709
|
+
for (let i = 0; i < entriesToRemove; i++) {
|
|
710
|
+
this.evictLRU();
|
|
711
|
+
}
|
|
712
|
+
}
|
|
713
|
+
} /**
|
|
714
|
+
* Elimina la entrada menos recientemente usada
|
|
715
|
+
*/
|
|
716
|
+
evictLRU() {
|
|
717
|
+
let oldestKey = '';
|
|
718
|
+
let oldestTime = Infinity;
|
|
719
|
+
for (const [key, entry] of this.cache) {
|
|
720
|
+
if (entry.lastUsed < oldestTime) {
|
|
721
|
+
oldestTime = entry.lastUsed;
|
|
722
|
+
oldestKey = key;
|
|
723
|
+
}
|
|
724
|
+
}
|
|
725
|
+
if (oldestKey) {
|
|
726
|
+
const entry = this.cache.get(oldestKey);
|
|
727
|
+
if (entry) {
|
|
728
|
+
this.currentMemoryUsage -= entry.size;
|
|
729
|
+
this.cache.delete(oldestKey);
|
|
730
|
+
}
|
|
731
|
+
}
|
|
732
|
+
}
|
|
733
|
+
/**
|
|
734
|
+
* Método público para limpiar entradas del cache cuando sea necesario
|
|
735
|
+
*/
|
|
736
|
+
cleanOldEntries(maxEntriesToRemove = 20) {
|
|
737
|
+
let removedCount = 0;
|
|
738
|
+
for (let i = 0; i < maxEntriesToRemove && this.cache.size > 0; i++) {
|
|
739
|
+
const sizeBefore = this.cache.size;
|
|
740
|
+
this.evictLRU();
|
|
741
|
+
if (this.cache.size < sizeBefore) {
|
|
742
|
+
removedCount++;
|
|
743
|
+
}
|
|
744
|
+
else {
|
|
745
|
+
break; // No se pudo remover más entradas
|
|
746
|
+
}
|
|
747
|
+
}
|
|
748
|
+
return removedCount;
|
|
749
|
+
}
|
|
750
|
+
/**
|
|
751
|
+
* Carga el cache desde disco
|
|
752
|
+
*/
|
|
753
|
+
async load(cacheFile) {
|
|
754
|
+
try {
|
|
755
|
+
if (env.cleanCache === 'true') {
|
|
756
|
+
this.cache.clear();
|
|
757
|
+
this.currentMemoryUsage = 0;
|
|
758
|
+
try {
|
|
759
|
+
await unlink(cacheFile);
|
|
760
|
+
}
|
|
761
|
+
catch {
|
|
762
|
+
// Ignorar errores al eliminar el archivo
|
|
763
|
+
}
|
|
764
|
+
return;
|
|
765
|
+
}
|
|
766
|
+
const cacheData = await readFile(cacheFile, 'utf-8');
|
|
767
|
+
const parsed = JSON.parse(cacheData);
|
|
768
|
+
// Validar y cargar entradas del cache
|
|
769
|
+
for (const [key, value] of Object.entries(parsed)) {
|
|
770
|
+
const entry = value;
|
|
771
|
+
if (entry.contentHash && entry.outputPath && entry.mtime) {
|
|
772
|
+
this.cache.set(key, entry);
|
|
773
|
+
this.currentMemoryUsage += entry.size || 0;
|
|
774
|
+
}
|
|
775
|
+
}
|
|
776
|
+
}
|
|
777
|
+
catch {
|
|
778
|
+
// Cache file doesn't exist or is invalid, start fresh
|
|
779
|
+
this.cache.clear();
|
|
780
|
+
this.currentMemoryUsage = 0;
|
|
781
|
+
}
|
|
782
|
+
}
|
|
783
|
+
/**
|
|
784
|
+
* Guarda el cache a disco
|
|
785
|
+
*/
|
|
786
|
+
async save(cacheFile, cacheDir) {
|
|
787
|
+
try {
|
|
788
|
+
await mkdir(cacheDir, { recursive: true });
|
|
789
|
+
const cacheData = Object.fromEntries(this.cache);
|
|
790
|
+
await writeFile(cacheFile, JSON.stringify(cacheData, null, 2));
|
|
791
|
+
}
|
|
792
|
+
catch (error) {
|
|
793
|
+
console.warn('Warning: No se pudo guardar el cache:', error);
|
|
794
|
+
}
|
|
795
|
+
}
|
|
796
|
+
/**
|
|
797
|
+
* Limpia completamente el cache
|
|
798
|
+
*/
|
|
799
|
+
clear() {
|
|
800
|
+
this.cache.clear();
|
|
801
|
+
this.currentMemoryUsage = 0;
|
|
802
|
+
} /**
|
|
803
|
+
* Obtiene la ruta de salida para un archivo cacheado
|
|
804
|
+
*/
|
|
805
|
+
getOutputPath(filePath) {
|
|
806
|
+
const entry = this.cache.get(filePath);
|
|
807
|
+
return entry?.outputPath || '';
|
|
808
|
+
} /**
|
|
809
|
+
* Obtiene estadísticas del cache
|
|
810
|
+
*/
|
|
811
|
+
getStats() {
|
|
812
|
+
return {
|
|
813
|
+
entries: this.cache.size,
|
|
814
|
+
memoryUsage: this.currentMemoryUsage,
|
|
815
|
+
hitRate: 0, // Se calculará externamente
|
|
816
|
+
};
|
|
817
|
+
}
|
|
818
|
+
// ✨ ISSUE #3: Métodos de vigilancia y invalidación cascada
|
|
819
|
+
/**
|
|
820
|
+
* Inicializa vigilancia de package.json y node_modules
|
|
821
|
+
*/
|
|
822
|
+
async startDependencyWatching() {
|
|
823
|
+
if (this.isWatchingDependencies)
|
|
824
|
+
return;
|
|
825
|
+
try {
|
|
826
|
+
// Lazy load chokidar para evitar problemas de importación
|
|
827
|
+
const chokidar = await import('chokidar');
|
|
828
|
+
// Vigilar package.json
|
|
829
|
+
if (await this.fileExists(this.packageJsonPath)) {
|
|
830
|
+
const packageWatcher = chokidar.watch(this.packageJsonPath, {
|
|
831
|
+
persistent: false, // No mantener el proceso vivo
|
|
832
|
+
ignoreInitial: true,
|
|
833
|
+
});
|
|
834
|
+
packageWatcher.on('change', () => {
|
|
835
|
+
logger.info('📦 package.json modificado - invalidando cache de dependencias');
|
|
836
|
+
this.invalidateByDependencyChange();
|
|
837
|
+
});
|
|
838
|
+
this.fileWatchers.set('package.json', packageWatcher);
|
|
839
|
+
}
|
|
840
|
+
// Vigilar node_modules (solo cambios en el directorio raíz para performance)
|
|
841
|
+
if (await this.fileExists(this.nodeModulesPath)) {
|
|
842
|
+
const nodeModulesWatcher = chokidar.watch(this.nodeModulesPath, {
|
|
843
|
+
persistent: false,
|
|
844
|
+
ignoreInitial: true,
|
|
845
|
+
depth: 1, // Solo primer nivel para performance
|
|
846
|
+
ignored: /(^|[/\\])\../, // Ignorar archivos ocultos
|
|
847
|
+
});
|
|
848
|
+
nodeModulesWatcher.on('addDir', (path) => {
|
|
849
|
+
logger.info(`📦 Nueva dependencia instalada: ${path.split(/[/\\]/).pop()}`);
|
|
850
|
+
this.invalidateByDependencyChange();
|
|
851
|
+
});
|
|
852
|
+
nodeModulesWatcher.on('unlinkDir', (path) => {
|
|
853
|
+
logger.info(`📦 Dependencia eliminada: ${path.split(/[/\\]/).pop()}`);
|
|
854
|
+
this.invalidateByDependencyChange();
|
|
855
|
+
});
|
|
856
|
+
this.fileWatchers.set('node_modules', nodeModulesWatcher);
|
|
857
|
+
}
|
|
858
|
+
this.isWatchingDependencies = true;
|
|
859
|
+
logger.info('🔍 Vigilancia de dependencias iniciada');
|
|
860
|
+
}
|
|
861
|
+
catch (error) {
|
|
862
|
+
logger.warn('⚠️ No se pudo iniciar vigilancia de dependencias:', error);
|
|
863
|
+
}
|
|
864
|
+
}
|
|
865
|
+
/**
|
|
866
|
+
* Detiene la vigilancia de dependencias
|
|
867
|
+
*/
|
|
868
|
+
async stopDependencyWatching() {
|
|
869
|
+
for (const [name, watcher] of this.fileWatchers) {
|
|
870
|
+
try {
|
|
871
|
+
await watcher.close();
|
|
872
|
+
logger.info(`🛑 Vigilancia detenida: ${name}`);
|
|
873
|
+
}
|
|
874
|
+
catch (error) {
|
|
875
|
+
logger.warn(`⚠️ Error cerrando watcher ${name}:`, error);
|
|
876
|
+
}
|
|
877
|
+
}
|
|
878
|
+
this.fileWatchers.clear();
|
|
879
|
+
this.isWatchingDependencies = false;
|
|
880
|
+
}
|
|
881
|
+
/**
|
|
882
|
+
* Registra dependencias de un archivo para invalidación cascada
|
|
883
|
+
*/
|
|
884
|
+
registerDependencies(filePath, dependencies) {
|
|
885
|
+
// Limpiar dependencias anteriores
|
|
886
|
+
const oldDeps = this.dependencyGraph.get(filePath);
|
|
887
|
+
if (oldDeps) {
|
|
888
|
+
for (const dep of oldDeps) {
|
|
889
|
+
const reverseDeps = this.reverseDependencyGraph.get(dep);
|
|
890
|
+
if (reverseDeps) {
|
|
891
|
+
reverseDeps.delete(filePath);
|
|
892
|
+
if (reverseDeps.size === 0) {
|
|
893
|
+
this.reverseDependencyGraph.delete(dep);
|
|
894
|
+
}
|
|
895
|
+
}
|
|
896
|
+
}
|
|
897
|
+
}
|
|
898
|
+
// Registrar nuevas dependencias
|
|
899
|
+
const newDeps = new Set(dependencies);
|
|
900
|
+
this.dependencyGraph.set(filePath, newDeps);
|
|
901
|
+
for (const dep of newDeps) {
|
|
902
|
+
if (!this.reverseDependencyGraph.has(dep)) {
|
|
903
|
+
this.reverseDependencyGraph.set(dep, new Set());
|
|
904
|
+
}
|
|
905
|
+
this.reverseDependencyGraph.get(dep).add(filePath);
|
|
906
|
+
}
|
|
907
|
+
}
|
|
908
|
+
/**
|
|
909
|
+
* Invalida cache por cambios en dependencias
|
|
910
|
+
*/
|
|
911
|
+
invalidateByDependencyChange() {
|
|
912
|
+
let invalidatedCount = 0;
|
|
913
|
+
// Invalidar todos los archivos que dependen de dependencias externas
|
|
914
|
+
for (const [filePath] of this.cache) {
|
|
915
|
+
this.cache.delete(filePath);
|
|
916
|
+
invalidatedCount++;
|
|
917
|
+
}
|
|
918
|
+
// Limpiar grafos de dependencias
|
|
919
|
+
this.dependencyGraph.clear();
|
|
920
|
+
this.reverseDependencyGraph.clear();
|
|
921
|
+
this.currentMemoryUsage = 0;
|
|
922
|
+
logger.info(`🗑️ Cache invalidado: ${invalidatedCount} archivos (cambio en dependencias)`);
|
|
923
|
+
}
|
|
924
|
+
/**
|
|
925
|
+
* Invalida cascada cuando un archivo específico cambia
|
|
926
|
+
*/
|
|
927
|
+
invalidateCascade(changedFile) {
|
|
928
|
+
const invalidated = [];
|
|
929
|
+
const toInvalidate = new Set([changedFile]);
|
|
930
|
+
// BFS para encontrar todos los archivos afectados
|
|
931
|
+
const queue = [changedFile];
|
|
932
|
+
while (queue.length > 0) {
|
|
933
|
+
const current = queue.shift();
|
|
934
|
+
const dependents = this.reverseDependencyGraph.get(current);
|
|
935
|
+
if (dependents) {
|
|
936
|
+
for (const dependent of dependents) {
|
|
937
|
+
if (!toInvalidate.has(dependent)) {
|
|
938
|
+
toInvalidate.add(dependent);
|
|
939
|
+
queue.push(dependent);
|
|
940
|
+
}
|
|
941
|
+
}
|
|
942
|
+
}
|
|
943
|
+
}
|
|
944
|
+
// Invalidar archivos
|
|
945
|
+
for (const filePath of toInvalidate) {
|
|
946
|
+
if (this.cache.has(filePath)) {
|
|
947
|
+
const entry = this.cache.get(filePath);
|
|
948
|
+
this.currentMemoryUsage -= entry.size;
|
|
949
|
+
this.cache.delete(filePath);
|
|
950
|
+
invalidated.push(filePath);
|
|
951
|
+
}
|
|
952
|
+
}
|
|
953
|
+
if (invalidated.length > 0) {
|
|
954
|
+
logger.info(`🔄 Invalidación cascada: ${invalidated.length} archivos afectados por ${changedFile}`);
|
|
955
|
+
}
|
|
956
|
+
return invalidated;
|
|
957
|
+
}
|
|
958
|
+
/**
|
|
959
|
+
* Verifica si un archivo existe
|
|
960
|
+
*/
|
|
961
|
+
async fileExists(filePath) {
|
|
962
|
+
try {
|
|
963
|
+
await stat(filePath);
|
|
964
|
+
return true;
|
|
965
|
+
}
|
|
966
|
+
catch {
|
|
967
|
+
return false;
|
|
968
|
+
}
|
|
969
|
+
}
|
|
970
|
+
/**
|
|
971
|
+
* Obtiene estadísticas avanzadas del cache
|
|
972
|
+
*/
|
|
973
|
+
getAdvancedStats() {
|
|
974
|
+
return {
|
|
975
|
+
entries: this.cache.size,
|
|
976
|
+
memoryUsage: this.currentMemoryUsage,
|
|
977
|
+
hitRate: 0,
|
|
978
|
+
dependencyNodes: this.dependencyGraph.size,
|
|
979
|
+
watchingDependencies: this.isWatchingDependencies,
|
|
980
|
+
activeWatchers: this.fileWatchers.size,
|
|
981
|
+
};
|
|
982
|
+
}
|
|
983
|
+
}
|
|
984
|
+
// Instancia global del cache inteligente
|
|
985
|
+
const smartCache = new SmartCompilationCache();
|
|
986
|
+
const CACHE_DIR = path.join(path.resolve(env.PATH_PROY || cwd(), 'compiler'), '.cache');
|
|
987
|
+
const CACHE_FILE = path.join(CACHE_DIR, 'versacompile-cache.json');
|
|
988
|
+
async function loadCache() {
|
|
989
|
+
await smartCache.load(CACHE_FILE);
|
|
990
|
+
// ✨ ISSUE #3: Iniciar vigilancia de dependencias en modo watch
|
|
991
|
+
if (env.WATCH_MODE === 'true' ||
|
|
992
|
+
argv.includes('--watch') ||
|
|
993
|
+
argv.includes('-w')) {
|
|
994
|
+
await smartCache.startDependencyWatching();
|
|
995
|
+
}
|
|
996
|
+
}
|
|
997
|
+
async function saveCache() {
|
|
998
|
+
await smartCache.save(CACHE_FILE, CACHE_DIR);
|
|
999
|
+
}
|
|
1000
|
+
// 🎯 Funciones del Sistema Unificado de Manejo de Errores
|
|
1001
|
+
/**
|
|
1002
|
+
* Registra un error de compilación en el sistema unificado
|
|
1003
|
+
*/
|
|
1004
|
+
function registerCompilationError(file, stage, message, severity = 'error', details, help) {
|
|
1005
|
+
compilationErrors.push({
|
|
1006
|
+
file,
|
|
1007
|
+
stage,
|
|
1008
|
+
message,
|
|
1009
|
+
severity,
|
|
1010
|
+
details,
|
|
1011
|
+
help,
|
|
1012
|
+
timestamp: Date.now(),
|
|
1013
|
+
});
|
|
1014
|
+
}
|
|
1015
|
+
/**
|
|
1016
|
+
* Registra un resultado de compilación (éxitos/errores por etapa)
|
|
1017
|
+
*/
|
|
1018
|
+
function registerCompilationResult(stage, errors, success, files = []) {
|
|
1019
|
+
const existingResult = compilationResults.find(r => r.stage === stage);
|
|
1020
|
+
if (existingResult) {
|
|
1021
|
+
existingResult.errors += errors;
|
|
1022
|
+
existingResult.success += success;
|
|
1023
|
+
existingResult.files.push(...files);
|
|
1024
|
+
}
|
|
1025
|
+
else {
|
|
1026
|
+
compilationResults.push({
|
|
1027
|
+
stage,
|
|
1028
|
+
errors,
|
|
1029
|
+
success,
|
|
1030
|
+
files: [...files],
|
|
1031
|
+
});
|
|
1032
|
+
}
|
|
1033
|
+
}
|
|
1034
|
+
/**
|
|
1035
|
+
* Maneja errores según el modo de compilación
|
|
1036
|
+
*/
|
|
1037
|
+
async function handleCompilationError(error, fileName, stage, mode, isVerbose = false) {
|
|
1038
|
+
const errorMessage = error instanceof Error ? error.message : error;
|
|
1039
|
+
const errorDetails = error instanceof Error ? error.stack : undefined;
|
|
1040
|
+
// Registrar el error en el sistema unificado
|
|
1041
|
+
registerCompilationError(fileName, stage, errorMessage, 'error', errorDetails);
|
|
1042
|
+
registerCompilationResult(stage, 1, 0, [fileName]); // Mostrar error inmediatamente solo en modo individual y watch
|
|
1043
|
+
if (mode === 'individual' || mode === 'watch') {
|
|
1044
|
+
const chalk = await loadChalk();
|
|
1045
|
+
const baseName = path.basename(fileName);
|
|
1046
|
+
const stageColor = await getStageColor(stage);
|
|
1047
|
+
if (isVerbose) {
|
|
1048
|
+
// Modo verbose: Mostrar error completo con contexto
|
|
1049
|
+
logger.error(chalk.red(`❌ Error en etapa ${stageColor(stage)} - ${baseName}:`));
|
|
1050
|
+
logger.error(chalk.red(errorMessage));
|
|
1051
|
+
if (errorDetails && (stage === 'typescript' || stage === 'vue')) {
|
|
1052
|
+
// Mostrar stack trace limitado para TypeScript y Vue
|
|
1053
|
+
const stackLines = errorDetails.split('\n').slice(0, 5);
|
|
1054
|
+
stackLines.forEach(line => {
|
|
1055
|
+
if (line.trim()) {
|
|
1056
|
+
logger.error(chalk.gray(` ${line.trim()}`));
|
|
1057
|
+
}
|
|
1058
|
+
});
|
|
1059
|
+
}
|
|
1060
|
+
}
|
|
1061
|
+
else {
|
|
1062
|
+
// Modo normal: Mostrar error simplificado
|
|
1063
|
+
const firstLine = errorMessage.split('\n')[0];
|
|
1064
|
+
logger.error(chalk.red(`❌ Error en ${stageColor(stage)}: ${baseName}`));
|
|
1065
|
+
logger.error(chalk.red(` ${firstLine}`));
|
|
1066
|
+
logger.info(chalk.yellow(`💡 Usa --verbose para ver detalles completos`));
|
|
1067
|
+
}
|
|
1068
|
+
}
|
|
1069
|
+
// En modo 'all', los errores se acumulan silenciosamente para el resumen final
|
|
1070
|
+
}
|
|
1071
|
+
/**
|
|
1072
|
+
* Registra un éxito de compilación
|
|
1073
|
+
*/
|
|
1074
|
+
function registerCompilationSuccess(fileName, stage) {
|
|
1075
|
+
registerCompilationResult(stage, 0, 1, [fileName]);
|
|
1076
|
+
}
|
|
1077
|
+
/**
|
|
1078
|
+
* Limpia todos los errores y resultados acumulados
|
|
1079
|
+
*/
|
|
1080
|
+
function clearCompilationState() {
|
|
1081
|
+
compilationErrors.length = 0;
|
|
1082
|
+
compilationResults.length = 0;
|
|
1083
|
+
}
|
|
1084
|
+
/**
|
|
1085
|
+
* Muestra un resumen detallado de todos los errores de compilación
|
|
1086
|
+
*/
|
|
1087
|
+
async function displayCompilationSummary(isVerbose = false, totalTime) {
|
|
1088
|
+
const chalk = await loadChalk();
|
|
1089
|
+
if (compilationErrors.length === 0 && compilationResults.length === 0) {
|
|
1090
|
+
logger.info(chalk.green('✅ No hay errores de compilación para mostrar.'));
|
|
1091
|
+
if (totalTime) {
|
|
1092
|
+
logger.info(chalk.bold(`\n⏱️ TIEMPO TOTAL DE COMPILACIÓN: ${totalTime}`));
|
|
1093
|
+
}
|
|
1094
|
+
return;
|
|
1095
|
+
}
|
|
1096
|
+
// 🎨 Header moderno del resumen
|
|
1097
|
+
const summaryLine = '━'.repeat(40);
|
|
1098
|
+
logger.info('');
|
|
1099
|
+
logger.info(chalk.bold.cyan('📊 Resumen de Compilación'));
|
|
1100
|
+
logger.info(chalk.gray(summaryLine)); // ⏱️ Tiempo total con formato elegante
|
|
1101
|
+
if (totalTime) {
|
|
1102
|
+
logger.info(chalk.bold(`⏱️ Tiempo Total: ${chalk.green(totalTime)}`));
|
|
1103
|
+
logger.info('');
|
|
1104
|
+
} // 🔧 Estadísticas por etapa con mejor formato
|
|
1105
|
+
if (compilationResults.length > 0) {
|
|
1106
|
+
logger.info(chalk.bold.blue('🔧 Estadísticas por Etapa:'));
|
|
1107
|
+
for (const result of compilationResults) {
|
|
1108
|
+
const totalFiles = result.success + result.errors;
|
|
1109
|
+
const successRate = totalFiles > 0
|
|
1110
|
+
? Math.round((result.success / totalFiles) * 100)
|
|
1111
|
+
: 0;
|
|
1112
|
+
// Iconos y colores dinámicos por etapa
|
|
1113
|
+
const stageIcon = getStageIcon(result.stage);
|
|
1114
|
+
const statusColor = result.errors === 0 ? chalk.green : chalk.red;
|
|
1115
|
+
const progressBar = createProgressBarWithPercentage(successRate, 20);
|
|
1116
|
+
logger.info(` ${stageIcon} ${chalk.bold(result.stage)}`);
|
|
1117
|
+
logger.info(` ${statusColor('●')} ${result.success}/${totalFiles} archivos ${statusColor(`(${successRate}%)`)}`);
|
|
1118
|
+
logger.info(` ${progressBar}`);
|
|
1119
|
+
if (result.errors > 0) {
|
|
1120
|
+
logger.info(` ${chalk.red('⚠')} ${result.errors} ${result.errors === 1 ? 'error' : 'errores'}`);
|
|
1121
|
+
}
|
|
1122
|
+
logger.info('');
|
|
1123
|
+
}
|
|
1124
|
+
}
|
|
1125
|
+
// Mostrar errores detallados
|
|
1126
|
+
if (compilationErrors.length > 0) {
|
|
1127
|
+
logger.info(chalk.red(`\n❌ Se encontraron ${compilationErrors.length} errores:`));
|
|
1128
|
+
// Agrupar errores por archivo para mejor organización
|
|
1129
|
+
const errorsByFile = new Map();
|
|
1130
|
+
compilationErrors.forEach(error => {
|
|
1131
|
+
if (!errorsByFile.has(error.file)) {
|
|
1132
|
+
errorsByFile.set(error.file, []);
|
|
1133
|
+
}
|
|
1134
|
+
errorsByFile.get(error.file).push(error);
|
|
1135
|
+
});
|
|
1136
|
+
// Mostrar errores por archivo
|
|
1137
|
+
let fileIndex = 1;
|
|
1138
|
+
for (const [filePath, fileErrors] of errorsByFile) {
|
|
1139
|
+
const baseName = path.basename(filePath);
|
|
1140
|
+
const errorCount = fileErrors.filter(e => e.severity === 'error').length;
|
|
1141
|
+
const warningCount = fileErrors.filter(e => e.severity === 'warning').length;
|
|
1142
|
+
logger.info(chalk.cyan(`\n📄 ${fileIndex}. ${baseName}`));
|
|
1143
|
+
logger.info(chalk.gray(` Ruta: ${filePath}`));
|
|
1144
|
+
logger.info(chalk.yellow(` ${errorCount} errores, ${warningCount} advertencias`));
|
|
1145
|
+
for (const error of fileErrors) {
|
|
1146
|
+
const icon = error.severity === 'error' ? '❌' : '⚠️';
|
|
1147
|
+
const stageColor = await getStageColor(error.stage);
|
|
1148
|
+
logger.info(` ${icon} [${stageColor(error.stage)}] ${error.message}`);
|
|
1149
|
+
if (isVerbose && error.details) {
|
|
1150
|
+
// En modo verbose, mostrar detalles adicionales
|
|
1151
|
+
const detailLines = error.details.split('\n').slice(0, 5);
|
|
1152
|
+
detailLines.forEach(line => {
|
|
1153
|
+
if (line.trim()) {
|
|
1154
|
+
logger.info(chalk.gray(` ${line.trim()}`));
|
|
1155
|
+
}
|
|
1156
|
+
});
|
|
1157
|
+
}
|
|
1158
|
+
if (error.help) {
|
|
1159
|
+
logger.info(chalk.blue(` 💡 ${error.help}`));
|
|
1160
|
+
}
|
|
1161
|
+
}
|
|
1162
|
+
fileIndex++;
|
|
1163
|
+
} // 📊 Mostrar totales finales con diseño moderno
|
|
1164
|
+
const totalErrors = compilationErrors.filter(e => e.severity === 'error').length;
|
|
1165
|
+
const totalWarnings = compilationErrors.filter(e => e.severity === 'warning').length;
|
|
1166
|
+
const totalFiles = errorsByFile.size;
|
|
1167
|
+
// Header elegante para estadísticas finales
|
|
1168
|
+
const statLine = '═'.repeat(50);
|
|
1169
|
+
logger.info('');
|
|
1170
|
+
logger.info(chalk.bold.cyan(statLine));
|
|
1171
|
+
logger.info(chalk.bold.cyan(' 📊 RESUMEN FINAL'));
|
|
1172
|
+
logger.info(chalk.bold.cyan(statLine));
|
|
1173
|
+
// Estadísticas con iconos y colores modernos
|
|
1174
|
+
logger.info('');
|
|
1175
|
+
logger.info(chalk.bold('🎯 Resultados:'));
|
|
1176
|
+
logger.info(` 📁 Archivos afectados: ${chalk.cyan.bold(totalFiles)}`);
|
|
1177
|
+
logger.info(` ${totalErrors > 0 ? chalk.red('●') : chalk.green('○')} Errores: ${totalErrors > 0 ? chalk.red.bold(totalErrors) : chalk.green.bold('0')}`);
|
|
1178
|
+
logger.info(` ${totalWarnings > 0 ? chalk.yellow('●') : chalk.green('○')} Advertencias: ${totalWarnings > 0 ? chalk.yellow.bold(totalWarnings) : chalk.green.bold('0')}`);
|
|
1179
|
+
logger.info('');
|
|
1180
|
+
// Estado final con diseño visual atractivo
|
|
1181
|
+
if (totalErrors > 0) {
|
|
1182
|
+
logger.info(chalk.red.bold('🚨 COMPILACIÓN COMPLETADA CON ERRORES'));
|
|
1183
|
+
logger.info(chalk.red(' Por favor revisa y corrige los problemas anteriores.'));
|
|
1184
|
+
}
|
|
1185
|
+
else if (totalWarnings > 0) {
|
|
1186
|
+
logger.info(chalk.yellow.bold('⚠️ COMPILACIÓN COMPLETADA CON ADVERTENCIAS'));
|
|
1187
|
+
logger.info(chalk.yellow(' Considera revisar las advertencias anteriores.'));
|
|
1188
|
+
}
|
|
1189
|
+
else {
|
|
1190
|
+
logger.info(chalk.green.bold('✅ COMPILACIÓN EXITOSA'));
|
|
1191
|
+
logger.info(chalk.green(' ¡Todos los archivos se compilaron sin problemas!'));
|
|
1192
|
+
}
|
|
1193
|
+
logger.info('');
|
|
1194
|
+
logger.info(chalk.bold.cyan(statLine));
|
|
1195
|
+
}
|
|
1196
|
+
else {
|
|
1197
|
+
// Caso exitoso sin errores
|
|
1198
|
+
const successLine = '═'.repeat(50);
|
|
1199
|
+
logger.info('');
|
|
1200
|
+
logger.info(chalk.bold.green(successLine));
|
|
1201
|
+
logger.info(chalk.bold.green(' ✨ ÉXITO'));
|
|
1202
|
+
logger.info(chalk.bold.green(successLine));
|
|
1203
|
+
logger.info('');
|
|
1204
|
+
logger.info(chalk.green.bold('🎉 COMPILACIÓN COMPLETADA EXITOSAMENTE'));
|
|
1205
|
+
logger.info(chalk.green(' ¡No se encontraron errores ni advertencias!'));
|
|
1206
|
+
logger.info('');
|
|
1207
|
+
logger.info(chalk.bold.green(successLine));
|
|
1208
|
+
}
|
|
1209
|
+
logger.info('');
|
|
1210
|
+
}
|
|
1211
|
+
/**
|
|
1212
|
+
* Muestra errores del linter con formato visual moderno y profesional
|
|
1213
|
+
*/
|
|
1214
|
+
async function displayLinterErrors(errors) {
|
|
1215
|
+
const chalk = await loadChalk();
|
|
1216
|
+
// Agrupar errores por archivo
|
|
1217
|
+
const errorsByFile = new Map();
|
|
1218
|
+
errors.forEach(error => {
|
|
1219
|
+
if (!errorsByFile.has(error.file)) {
|
|
1220
|
+
errorsByFile.set(error.file, []);
|
|
1221
|
+
}
|
|
1222
|
+
errorsByFile.get(error.file).push(error);
|
|
1223
|
+
});
|
|
1224
|
+
const totalErrors = errors.filter(e => e.severity === 'error').length;
|
|
1225
|
+
const totalWarnings = errors.filter(e => e.severity === 'warning').length;
|
|
1226
|
+
const totalFiles = errorsByFile.size;
|
|
1227
|
+
// Header estilo moderno con gradiente visual
|
|
1228
|
+
logger.info(chalk.bold.rgb(255, 120, 120)('╭─────────────────────────────────────────────────────────────╮'));
|
|
1229
|
+
logger.info(chalk.bold.rgb(255, 120, 120)('│ ') +
|
|
1230
|
+
chalk.bold.white('🔍 LINTER REPORT') +
|
|
1231
|
+
chalk.bold.rgb(255, 120, 120)(' │'));
|
|
1232
|
+
logger.info(chalk.bold.rgb(255, 120, 120)('╰─────────────────────────────────────────────────────────────╯'));
|
|
1233
|
+
// Resumen con iconos profesionales
|
|
1234
|
+
const errorIcon = totalErrors > 0 ? chalk.red('●') : chalk.green('○');
|
|
1235
|
+
const warningIcon = totalWarnings > 0 ? chalk.yellow('●') : chalk.green('○');
|
|
1236
|
+
logger.info('');
|
|
1237
|
+
logger.info(chalk.bold('📊 Summary:'));
|
|
1238
|
+
logger.info(` ${errorIcon} ${chalk.bold(totalErrors)} ${chalk.red('errors')}`);
|
|
1239
|
+
logger.info(` ${warningIcon} ${chalk.bold(totalWarnings)} ${chalk.yellow('warnings')}`);
|
|
1240
|
+
logger.info(` 📁 ${chalk.bold(totalFiles)} ${chalk.cyan('files')}`);
|
|
1241
|
+
logger.info('');
|
|
1242
|
+
if (totalErrors === 0 && totalWarnings === 0) {
|
|
1243
|
+
logger.info(chalk.green.bold('✨ All checks passed! No issues found.'));
|
|
1244
|
+
return;
|
|
1245
|
+
}
|
|
1246
|
+
// Mostrar errores por archivo con formato elegante
|
|
1247
|
+
let fileIndex = 1;
|
|
1248
|
+
for (const [filePath, fileErrors] of errorsByFile) {
|
|
1249
|
+
await displayFileErrorsGroup(filePath, fileErrors, fileIndex, totalFiles);
|
|
1250
|
+
fileIndex++;
|
|
1251
|
+
if (fileIndex <= totalFiles) {
|
|
1252
|
+
logger.info(chalk.gray('─'.repeat(80))); // Separador entre archivos
|
|
1253
|
+
}
|
|
1254
|
+
}
|
|
1255
|
+
// Footer con estadísticas
|
|
1256
|
+
logger.info('');
|
|
1257
|
+
logger.info(chalk.bold.rgb(255, 120, 120)('╭─────────────────────────────────────────────────────────────╮'));
|
|
1258
|
+
logger.info(chalk.bold.rgb(255, 120, 120)('│ ') +
|
|
1259
|
+
chalk.bold.white(`Found ${totalErrors + totalWarnings} issues in ${totalFiles} files`) +
|
|
1260
|
+
' '.repeat(Math.max(0, 52 -
|
|
1261
|
+
`Found ${totalErrors + totalWarnings} issues in ${totalFiles} files`
|
|
1262
|
+
.length)) +
|
|
1263
|
+
chalk.bold.rgb(255, 120, 120)(' │'));
|
|
1264
|
+
logger.info(chalk.bold.rgb(255, 120, 120)('╰─────────────────────────────────────────────────────────────╯'));
|
|
1265
|
+
}
|
|
1266
|
+
/**
|
|
1267
|
+
* Muestra un grupo de errores para un archivo específico con formato moderno
|
|
1268
|
+
*/
|
|
1269
|
+
async function displayFileErrorsGroup(filePath, fileErrors, _fileIndex, _totalFiles) {
|
|
1270
|
+
const chalk = await loadChalk();
|
|
1271
|
+
// Header del archivo con iconos de estado
|
|
1272
|
+
const errorCount = fileErrors.filter(e => e.severity === 'error').length;
|
|
1273
|
+
const warningCount = fileErrors.filter(e => e.severity === 'warning').length;
|
|
1274
|
+
const statusIcon = errorCount > 0 ? chalk.red('✕') : chalk.yellow('⚠');
|
|
1275
|
+
const fileIcon = filePath.endsWith('.vue')
|
|
1276
|
+
? '🎨'
|
|
1277
|
+
: filePath.endsWith('.ts')
|
|
1278
|
+
? '📘'
|
|
1279
|
+
: filePath.endsWith('.js')
|
|
1280
|
+
? '📜'
|
|
1281
|
+
: '📄';
|
|
1282
|
+
logger.info('');
|
|
1283
|
+
logger.info(chalk.bold(`${statusIcon} ${fileIcon} ${chalk.cyan(path.relative(process.cwd(), filePath))}`));
|
|
1284
|
+
logger.info(chalk.gray(` ${errorCount} errors, ${warningCount} warnings`));
|
|
1285
|
+
logger.info('');
|
|
1286
|
+
// Mostrar cada error con formato elegante
|
|
1287
|
+
for (let i = 0; i < fileErrors.length; i++) {
|
|
1288
|
+
const error = fileErrors[i];
|
|
1289
|
+
await displayModernLinterError(error, filePath, i + 1, fileErrors.length);
|
|
1290
|
+
}
|
|
1291
|
+
}
|
|
1292
|
+
/**
|
|
1293
|
+
* Muestra un error individual con formato visual moderno tipo ESLint/Prettier
|
|
1294
|
+
*/
|
|
1295
|
+
async function displayModernLinterError(error, filePath, errorIndex, totalErrorsInFile) {
|
|
1296
|
+
const chalk = await loadChalk();
|
|
1297
|
+
const fs = await import('node:fs/promises');
|
|
1298
|
+
// Determinar tipo y color del error
|
|
1299
|
+
const isError = error.severity === 'error';
|
|
1300
|
+
const typeColor = isError ? chalk.red : chalk.yellow;
|
|
1301
|
+
const typeIcon = isError ? '✕' : '⚠';
|
|
1302
|
+
const line = error.line || 1;
|
|
1303
|
+
const column = error.column || 1;
|
|
1304
|
+
const ruleId = error.ruleId || error.from || 'unknown';
|
|
1305
|
+
// Línea principal del error con formato moderno
|
|
1306
|
+
const errorHeader = ` ${typeColor(typeIcon)} ${chalk.bold(error.message)}`;
|
|
1307
|
+
const ruleInfo = `${chalk.gray(ruleId)}`;
|
|
1308
|
+
const locationInfo = `${chalk.blue(`${line}:${column}`)}`;
|
|
1309
|
+
logger.info(errorHeader);
|
|
1310
|
+
logger.info(` ${chalk.gray('at')} ${locationInfo} ${chalk.gray('·')} ${ruleInfo}`);
|
|
1311
|
+
// Mostrar código con contexto
|
|
1312
|
+
try {
|
|
1313
|
+
const absolutePath = path.resolve(filePath);
|
|
1314
|
+
const fileContent = await fs.readFile(absolutePath, 'utf-8');
|
|
1315
|
+
const lines = fileContent.split('\n');
|
|
1316
|
+
const lineNum = parseInt(line.toString()) - 1;
|
|
1317
|
+
if (lineNum >= 0 && lineNum < lines.length) {
|
|
1318
|
+
logger.info('');
|
|
1319
|
+
// Mostrar líneas de contexto con numeración elegante
|
|
1320
|
+
const startLine = Math.max(0, lineNum - 1);
|
|
1321
|
+
const endLine = Math.min(lines.length - 1, lineNum + 1);
|
|
1322
|
+
const maxLineNumWidth = (endLine + 1).toString().length;
|
|
1323
|
+
for (let i = startLine; i <= endLine; i++) {
|
|
1324
|
+
const currentLineNum = i + 1;
|
|
1325
|
+
const currentLine = lines[i] || '';
|
|
1326
|
+
const lineNumStr = currentLineNum
|
|
1327
|
+
.toString()
|
|
1328
|
+
.padStart(maxLineNumWidth, ' ');
|
|
1329
|
+
const isErrorLine = i === lineNum;
|
|
1330
|
+
if (isErrorLine) {
|
|
1331
|
+
// Línea con el error - destacada
|
|
1332
|
+
logger.info(` ${chalk.red('>')} ${chalk.gray(lineNumStr)} ${chalk.gray('│')} ${currentLine}`);
|
|
1333
|
+
// Indicador de posición del error
|
|
1334
|
+
const pointer = ' '.repeat(Math.max(0, column - 1)) + typeColor('^');
|
|
1335
|
+
logger.info(` ${chalk.gray(' ')} ${chalk.gray(' '.repeat(maxLineNumWidth))} ${chalk.gray('│')} ${pointer}`);
|
|
1336
|
+
}
|
|
1337
|
+
else {
|
|
1338
|
+
// Líneas de contexto
|
|
1339
|
+
logger.info(` ${chalk.gray(' ')} ${chalk.gray(lineNumStr)} ${chalk.gray('│')} ${chalk.gray(currentLine)}`);
|
|
1340
|
+
}
|
|
1341
|
+
}
|
|
1342
|
+
}
|
|
1343
|
+
}
|
|
1344
|
+
catch {
|
|
1345
|
+
// Si no se puede leer el archivo, mostrar formato simplificado
|
|
1346
|
+
logger.info(` ${chalk.gray('│')} ${chalk.gray('(Unable to read file content)')}`);
|
|
1347
|
+
}
|
|
1348
|
+
// Mostrar ayuda si está disponible
|
|
1349
|
+
if (error.help) {
|
|
1350
|
+
logger.info('');
|
|
1351
|
+
const helpText = error.help.replace(/^Regla \w+: /, '').trim();
|
|
1352
|
+
logger.info(` ${chalk.blue('💡')} ${chalk.blue('Help:')} ${chalk.gray(helpText)}`);
|
|
1353
|
+
}
|
|
1354
|
+
// Separador entre errores (solo si no es el último)
|
|
1355
|
+
if (errorIndex < totalErrorsInFile) {
|
|
1356
|
+
logger.info('');
|
|
1357
|
+
}
|
|
1358
|
+
}
|
|
1359
|
+
/**
|
|
1360
|
+
* Muestra un solo error del linter con formato visual mejorado
|
|
1361
|
+
* @deprecated Use displayModernLinterError instead
|
|
1362
|
+
*/
|
|
1363
|
+
async function _displaySingleLinterError(error, filePath) {
|
|
1364
|
+
const chalk = await loadChalk();
|
|
1365
|
+
const fs = await import('node:fs/promises');
|
|
1366
|
+
const icon = error.severity === 'error' ? '×' : '⚠';
|
|
1367
|
+
const ruleInfo = error.help || '';
|
|
1368
|
+
const line = error.line || 'N/A';
|
|
1369
|
+
const column = error.column || 10; // Columna por defecto si no está disponible
|
|
1370
|
+
// Línea principal del error
|
|
1371
|
+
const mainErrorLine = `${chalk.red(icon)} ${chalk.cyan(`${error.from}(${ruleInfo.replace(/^Regla \w+: /, '')})`)}: ${error.message}`;
|
|
1372
|
+
logger.info(mainErrorLine);
|
|
1373
|
+
// Intentar leer el contenido del archivo para mostrar contexto
|
|
1374
|
+
try {
|
|
1375
|
+
const absolutePath = path.resolve(filePath);
|
|
1376
|
+
const fileContent = await fs.readFile(absolutePath, 'utf-8');
|
|
1377
|
+
const lines = fileContent.split('\n');
|
|
1378
|
+
const lineNum = parseInt(line.toString()) - 1; // Convertir a índice 0-based
|
|
1379
|
+
if (lineNum >= 0 && lineNum < lines.length) {
|
|
1380
|
+
// Mostrar ubicación
|
|
1381
|
+
logger.info(chalk.blue(` ╭─[${filePath}:${line}:${column}]`));
|
|
1382
|
+
// Mostrar líneas de contexto
|
|
1383
|
+
const startLine = Math.max(0, lineNum - 1);
|
|
1384
|
+
const endLine = Math.min(lines.length - 1, lineNum + 1);
|
|
1385
|
+
for (let i = startLine; i <= endLine; i++) {
|
|
1386
|
+
const currentLineNum = i + 1;
|
|
1387
|
+
const currentLine = lines[i] || '';
|
|
1388
|
+
const prefix = currentLineNum.toString().padStart(2, ' ');
|
|
1389
|
+
if (i === lineNum) {
|
|
1390
|
+
// Línea con el error
|
|
1391
|
+
logger.info(chalk.blue(` ${prefix} │ `) + currentLine);
|
|
1392
|
+
// Mostrar el indicador de error
|
|
1393
|
+
const indent = ' '.repeat(prefix.length + 3); // Espacios para alinear
|
|
1394
|
+
const pointer = ' '.repeat(Math.max(0, (column || 1) - 1)) +
|
|
1395
|
+
chalk.red('───────┬──────');
|
|
1396
|
+
logger.info(chalk.blue(indent + '·') + pointer);
|
|
1397
|
+
// Mensaje de ubicación específica
|
|
1398
|
+
const messageIndent = ' '.repeat(Math.max(0, (column || 1) + 6));
|
|
1399
|
+
logger.info(chalk.blue(indent + '·') +
|
|
1400
|
+
messageIndent +
|
|
1401
|
+
chalk.red('╰── ') +
|
|
1402
|
+
chalk.gray(getErrorLocationMessage(error)));
|
|
1403
|
+
}
|
|
1404
|
+
else {
|
|
1405
|
+
// Líneas de contexto
|
|
1406
|
+
logger.info(chalk.blue(` ${prefix} │ `) + chalk.gray(currentLine));
|
|
1407
|
+
}
|
|
1408
|
+
}
|
|
1409
|
+
logger.info(chalk.blue(' ╰────'));
|
|
1410
|
+
}
|
|
1411
|
+
}
|
|
1412
|
+
catch {
|
|
1413
|
+
// Si no se puede leer el archivo, mostrar formato simplificado
|
|
1414
|
+
logger.info(chalk.blue(` ╭─[${filePath}:${line}:${column}]`));
|
|
1415
|
+
logger.info(chalk.blue(' │ ') +
|
|
1416
|
+
chalk.gray('(No se pudo leer el contenido del archivo)'));
|
|
1417
|
+
logger.info(chalk.blue(' ╰────'));
|
|
1418
|
+
}
|
|
1419
|
+
// Mostrar ayuda si está disponible
|
|
1420
|
+
if (error.help) {
|
|
1421
|
+
const helpMessage = error.help.replace(/^Regla \w+: /, '');
|
|
1422
|
+
logger.info(chalk.blue(' help: ') + chalk.yellow(helpMessage));
|
|
1423
|
+
}
|
|
1424
|
+
logger.info(''); // Espacio entre errores
|
|
1425
|
+
}
|
|
1426
|
+
/**
|
|
1427
|
+
* Genera un mensaje descriptivo para la ubicación específica del error
|
|
1428
|
+
*/
|
|
1429
|
+
function getErrorLocationMessage(error) {
|
|
1430
|
+
if (error.message.includes('declared but never used')) {
|
|
1431
|
+
const match = error.message.match(/'([^']+)'/);
|
|
1432
|
+
if (match) {
|
|
1433
|
+
return `'${match[1]}' is declared here`;
|
|
1434
|
+
}
|
|
1435
|
+
}
|
|
1436
|
+
if (error.message.includes('Unexpected var')) {
|
|
1437
|
+
return 'var declaration found here';
|
|
1438
|
+
}
|
|
1439
|
+
if (error.message.includes('never reassigned')) {
|
|
1440
|
+
const match = error.message.match(/'([^']+)'/);
|
|
1441
|
+
if (match) {
|
|
1442
|
+
return `'${match[1]}' is assigned here`;
|
|
1443
|
+
}
|
|
1444
|
+
}
|
|
1445
|
+
return 'error location';
|
|
1446
|
+
}
|
|
1447
|
+
/**
|
|
1448
|
+
* Obtiene el color apropiado para cada etapa de compilación
|
|
1449
|
+
*/
|
|
1450
|
+
async function getStageColor(stage) {
|
|
1451
|
+
const chalk = await loadChalk();
|
|
1452
|
+
switch (stage) {
|
|
1453
|
+
case 'vue':
|
|
1454
|
+
return chalk.green;
|
|
1455
|
+
case 'typescript':
|
|
1456
|
+
return chalk.blue;
|
|
1457
|
+
case 'standardization':
|
|
1458
|
+
return chalk.yellow;
|
|
1459
|
+
case 'minification':
|
|
1460
|
+
return chalk.red;
|
|
1461
|
+
case 'tailwind':
|
|
1462
|
+
return chalk.magenta;
|
|
1463
|
+
case 'file-read':
|
|
1464
|
+
return chalk.gray;
|
|
1465
|
+
default:
|
|
1466
|
+
return chalk.white;
|
|
1467
|
+
}
|
|
1468
|
+
}
|
|
1469
|
+
export function normalizeRuta(ruta) {
|
|
1470
|
+
if (path.isAbsolute(ruta)) {
|
|
1471
|
+
return path.normalize(ruta).replace(/\\/g, '/');
|
|
1472
|
+
}
|
|
1473
|
+
const file = path
|
|
1474
|
+
.normalize(!ruta.startsWith('.') ? './' + ruta : ruta)
|
|
1475
|
+
.replace(/\\/g, '/');
|
|
1476
|
+
const sourceForDist = file.startsWith('./') ? file : `./${file}`;
|
|
1477
|
+
return sourceForDist;
|
|
1478
|
+
}
|
|
1479
|
+
export function getOutputPath(ruta) {
|
|
1480
|
+
const pathSource = env.PATH_SOURCE ?? '';
|
|
1481
|
+
const pathDist = env.PATH_DIST ?? '';
|
|
1482
|
+
if (!pathSource || !pathDist) {
|
|
1483
|
+
return ruta.replace(/\.(vue|ts)$/, '.js');
|
|
1484
|
+
}
|
|
1485
|
+
const normalizedRuta = path.normalize(ruta).replace(/\\/g, '/');
|
|
1486
|
+
const normalizedSource = path.normalize(pathSource).replace(/\\/g, '/');
|
|
1487
|
+
const normalizedDist = path.normalize(pathDist).replace(/\\/g, '/');
|
|
1488
|
+
let outputPath;
|
|
1489
|
+
if (normalizedRuta.includes(normalizedSource)) {
|
|
1490
|
+
const relativePath = normalizedRuta
|
|
1491
|
+
.substring(normalizedRuta.indexOf(normalizedSource) +
|
|
1492
|
+
normalizedSource.length)
|
|
1493
|
+
.replace(/^[/\\]/, '');
|
|
1494
|
+
outputPath = path
|
|
1495
|
+
.join(normalizedDist, relativePath)
|
|
1496
|
+
.replace(/\\/g, '/');
|
|
1497
|
+
}
|
|
1498
|
+
else {
|
|
1499
|
+
const fileName = path.basename(normalizedRuta);
|
|
1500
|
+
outputPath = path.join(normalizedDist, fileName).replace(/\\/g, '/');
|
|
1501
|
+
}
|
|
1502
|
+
if (outputPath.includes('vue') || outputPath.includes('ts')) {
|
|
1503
|
+
return outputPath.replace(/\.(vue|ts)$/, '.js');
|
|
1504
|
+
}
|
|
1505
|
+
else {
|
|
1506
|
+
return outputPath;
|
|
1507
|
+
}
|
|
1508
|
+
}
|
|
1509
|
+
// Optimización para modo watch: debouncing y cache de archivos
|
|
1510
|
+
class WatchModeOptimizer {
|
|
1511
|
+
static instance;
|
|
1512
|
+
fileSystemCache = new Map();
|
|
1513
|
+
debounceTimers = new Map();
|
|
1514
|
+
DEBOUNCE_DELAY = 100; // 100ms debounce
|
|
1515
|
+
static getInstance() {
|
|
1516
|
+
if (!WatchModeOptimizer.instance) {
|
|
1517
|
+
WatchModeOptimizer.instance = new WatchModeOptimizer();
|
|
1518
|
+
}
|
|
1519
|
+
return WatchModeOptimizer.instance;
|
|
1520
|
+
}
|
|
1521
|
+
async compileForWatch(filePath, compileFn) {
|
|
1522
|
+
return new Promise(resolve => {
|
|
1523
|
+
const existingTimer = this.debounceTimers.get(filePath);
|
|
1524
|
+
if (existingTimer) {
|
|
1525
|
+
clearTimeout(existingTimer);
|
|
1526
|
+
}
|
|
1527
|
+
const timer = setTimeout(async () => {
|
|
1528
|
+
this.debounceTimers.delete(filePath);
|
|
1529
|
+
try {
|
|
1530
|
+
const stats = await stat(filePath);
|
|
1531
|
+
const cached = this.fileSystemCache.get(filePath);
|
|
1532
|
+
if (cached && cached.mtime >= stats.mtimeMs) {
|
|
1533
|
+
resolve({ success: true, cached: true });
|
|
1534
|
+
return;
|
|
1535
|
+
} // Configurar worker pool para modo watch
|
|
1536
|
+
const { TypeScriptWorkerPool } = (await import('./typescript-worker-pool.js'));
|
|
1537
|
+
const workerPool = TypeScriptWorkerPool.getInstance();
|
|
1538
|
+
workerPool.setMode('watch');
|
|
1539
|
+
const result = await compileFn(filePath);
|
|
1540
|
+
this.fileSystemCache.set(filePath, {
|
|
1541
|
+
mtime: stats.mtimeMs,
|
|
1542
|
+
});
|
|
1543
|
+
resolve(result);
|
|
1544
|
+
}
|
|
1545
|
+
catch (error) {
|
|
1546
|
+
resolve({ success: false, error });
|
|
1547
|
+
}
|
|
1548
|
+
}, this.DEBOUNCE_DELAY);
|
|
1549
|
+
this.debounceTimers.set(filePath, timer);
|
|
1550
|
+
});
|
|
1551
|
+
}
|
|
1552
|
+
cleanup() {
|
|
1553
|
+
this.debounceTimers.forEach(timer => clearTimeout(timer));
|
|
1554
|
+
this.debounceTimers.clear();
|
|
1555
|
+
this.fileSystemCache.clear();
|
|
1556
|
+
}
|
|
1557
|
+
}
|
|
1558
|
+
async function compileJS(inPath, outPath, mode = 'individual') {
|
|
1559
|
+
const timings = {};
|
|
1560
|
+
// Si la ruta ya es absoluta, no la resolvamos de nuevo
|
|
1561
|
+
inPath = path.isAbsolute(inPath)
|
|
1562
|
+
? normalizeRuta(inPath)
|
|
1563
|
+
: normalizeRuta(path.resolve(inPath)); // 🚀 Usar OptimizedModuleManager para carga optimizada
|
|
1564
|
+
const moduleManager = OptimizedModuleManager.getInstance();
|
|
1565
|
+
// Timing de lectura
|
|
1566
|
+
let start = Date.now();
|
|
1567
|
+
const extension = path.extname(inPath); // Asegurar que el parser esté cargado
|
|
1568
|
+
await moduleManager.ensureModuleLoaded('parser');
|
|
1569
|
+
const getCodeFile = await loadParser();
|
|
1570
|
+
const result = await getCodeFile(inPath);
|
|
1571
|
+
let code = result.code;
|
|
1572
|
+
const error = result.error;
|
|
1573
|
+
timings.fileRead = Date.now() - start;
|
|
1574
|
+
if (error) {
|
|
1575
|
+
await handleCompilationError(error instanceof Error ? error : new Error(String(error)), inPath, 'file-read', mode, env.VERBOSE === 'true');
|
|
1576
|
+
throw new Error(error instanceof Error ? error.message : String(error));
|
|
1577
|
+
}
|
|
1578
|
+
if (!code ||
|
|
1579
|
+
code.trim().length === 0 ||
|
|
1580
|
+
code === 'undefined' ||
|
|
1581
|
+
code === 'null') {
|
|
1582
|
+
await handleCompilationError(new Error('El archivo está vacío o no se pudo leer.'), inPath, 'file-read', mode, env.VERBOSE === 'true');
|
|
1583
|
+
throw new Error('El archivo está vacío o no se pudo leer.');
|
|
1584
|
+
} // Logs detallados en modo verbose
|
|
1585
|
+
const shouldShowDetailedLogs = env.VERBOSE === 'true'; // Compilación de Vue
|
|
1586
|
+
let vueResult;
|
|
1587
|
+
if (extension === '.vue') {
|
|
1588
|
+
start = Date.now();
|
|
1589
|
+
if (shouldShowDetailedLogs) {
|
|
1590
|
+
logger.info(chalk.green(`💚 Precompilando VUE: ${path.basename(inPath)}`));
|
|
1591
|
+
}
|
|
1592
|
+
// Asegurar que el módulo Vue esté cargado
|
|
1593
|
+
await moduleManager.ensureModuleLoaded('vue');
|
|
1594
|
+
const preCompileVue = await loadVue();
|
|
1595
|
+
if (typeof preCompileVue !== 'function') {
|
|
1596
|
+
throw new Error(`loadVue devolvió ${typeof preCompileVue} en lugar de una función para archivo: ${inPath}`);
|
|
1597
|
+
}
|
|
1598
|
+
vueResult = await preCompileVue(code, inPath, env.isPROD === 'true');
|
|
1599
|
+
timings.vueCompile = Date.now() - start;
|
|
1600
|
+
if (vueResult === undefined || vueResult === null) {
|
|
1601
|
+
throw new Error(`preCompileVue devolvió ${vueResult} para archivo: ${inPath}`);
|
|
1602
|
+
}
|
|
1603
|
+
if (vueResult.error) {
|
|
1604
|
+
await handleCompilationError(vueResult.error instanceof Error
|
|
1605
|
+
? vueResult.error
|
|
1606
|
+
: new Error(String(vueResult.error)), inPath, 'vue', mode, env.VERBOSE === 'true');
|
|
1607
|
+
throw new Error(vueResult.error instanceof Error
|
|
1608
|
+
? vueResult.error.message
|
|
1609
|
+
: String(vueResult.error));
|
|
1610
|
+
}
|
|
1611
|
+
registerCompilationSuccess(inPath, 'vue');
|
|
1612
|
+
code = vueResult.data;
|
|
1613
|
+
}
|
|
1614
|
+
if (!code || code.trim().length === 0) {
|
|
1615
|
+
await handleCompilationError(new Error('El código Vue compilado está vacío.'), inPath, 'vue', mode, env.VERBOSE === 'true');
|
|
1616
|
+
throw new Error('El código Vue compilado está vacío.');
|
|
1617
|
+
}
|
|
1618
|
+
// Compilación de TypeScript
|
|
1619
|
+
let tsResult;
|
|
1620
|
+
if (extension === '.ts' || vueResult?.lang === 'ts') {
|
|
1621
|
+
start = Date.now();
|
|
1622
|
+
if (shouldShowDetailedLogs) {
|
|
1623
|
+
logger.info(chalk.blue(`🔄️ Precompilando TS: ${path.basename(inPath)}`));
|
|
1624
|
+
}
|
|
1625
|
+
// Asegurar que el módulo TypeScript esté cargado
|
|
1626
|
+
await moduleManager.ensureModuleLoaded('typescript');
|
|
1627
|
+
const preCompileTS = await loadTypeScript();
|
|
1628
|
+
if (typeof preCompileTS !== 'function') {
|
|
1629
|
+
throw new Error(`loadTypeScript devolvió ${typeof preCompileTS} en lugar de una función para archivo: ${inPath}`);
|
|
1630
|
+
}
|
|
1631
|
+
tsResult = await preCompileTS(code, inPath);
|
|
1632
|
+
timings.tsCompile = Date.now() - start;
|
|
1633
|
+
if (tsResult === undefined || tsResult === null) {
|
|
1634
|
+
throw new Error(`preCompileTS devolvió ${tsResult} para archivo: ${inPath}`);
|
|
1635
|
+
}
|
|
1636
|
+
if (tsResult.error) {
|
|
1637
|
+
if (mode === 'all') {
|
|
1638
|
+
// En modo --all, registrar el error pero continuar la compilación
|
|
1639
|
+
registerCompilationError(inPath, 'typescript', tsResult.error instanceof Error
|
|
1640
|
+
? tsResult.error.message
|
|
1641
|
+
: String(tsResult.error), 'error');
|
|
1642
|
+
}
|
|
1643
|
+
else {
|
|
1644
|
+
await handleCompilationError(tsResult.error, inPath, 'typescript', mode, env.VERBOSE === 'true');
|
|
1645
|
+
throw new Error(tsResult.error instanceof Error
|
|
1646
|
+
? tsResult.error.message
|
|
1647
|
+
: String(tsResult.error));
|
|
1648
|
+
}
|
|
1649
|
+
}
|
|
1650
|
+
else {
|
|
1651
|
+
registerCompilationSuccess(inPath, 'typescript');
|
|
1652
|
+
code = tsResult.data;
|
|
1653
|
+
}
|
|
1654
|
+
}
|
|
1655
|
+
if (!code || code.trim().length === 0) {
|
|
1656
|
+
await handleCompilationError(new Error('El código TypeScript compilado está vacío.'), inPath, 'typescript', mode, env.VERBOSE === 'true');
|
|
1657
|
+
throw new Error('El código TypeScript compilado está vacío.');
|
|
1658
|
+
} // Estandarización
|
|
1659
|
+
if (shouldShowDetailedLogs) {
|
|
1660
|
+
logger.info(chalk.yellow(`💛 Estandarizando: ${path.basename(inPath)}`));
|
|
1661
|
+
}
|
|
1662
|
+
start = Date.now();
|
|
1663
|
+
// Asegurar que el módulo de transformaciones esté cargado
|
|
1664
|
+
await moduleManager.ensureModuleLoaded('transforms');
|
|
1665
|
+
const estandarizaCode = await loadTransforms();
|
|
1666
|
+
const resultSTD = await estandarizaCode(code, inPath);
|
|
1667
|
+
timings.standardization = Date.now() - start;
|
|
1668
|
+
if (resultSTD === undefined || resultSTD === null) {
|
|
1669
|
+
throw new Error(`estandarizaCode devolvió ${resultSTD} para archivo: ${inPath}`);
|
|
1670
|
+
}
|
|
1671
|
+
if (resultSTD.error) {
|
|
1672
|
+
await handleCompilationError(new Error(resultSTD.error), inPath, 'standardization', mode, env.VERBOSE === 'true');
|
|
1673
|
+
throw new Error(resultSTD.error);
|
|
1674
|
+
}
|
|
1675
|
+
registerCompilationSuccess(inPath, 'standardization');
|
|
1676
|
+
code = resultSTD.code;
|
|
1677
|
+
if (!code || code.trim().length === 0) {
|
|
1678
|
+
await handleCompilationError(new Error('El código estandarizado está vacío.'), inPath, 'standardization', mode, env.VERBOSE === 'true');
|
|
1679
|
+
throw new Error('El código estandarizado está vacío.');
|
|
1680
|
+
}
|
|
1681
|
+
// Minificación (solo en producción)
|
|
1682
|
+
if (env.isPROD === 'true') {
|
|
1683
|
+
start = Date.now();
|
|
1684
|
+
if (shouldShowDetailedLogs) {
|
|
1685
|
+
logger.info(chalk.red(`🤖 Minificando: ${path.basename(inPath)}`));
|
|
1686
|
+
}
|
|
1687
|
+
// Asegurar que el módulo de minificación esté cargado
|
|
1688
|
+
await moduleManager.ensureModuleLoaded('minify');
|
|
1689
|
+
const minifyJS = await loadMinify();
|
|
1690
|
+
const resultMinify = await minifyJS(code, inPath, true);
|
|
1691
|
+
timings.minification = Date.now() - start;
|
|
1692
|
+
if (resultMinify === undefined || resultMinify === null) {
|
|
1693
|
+
throw new Error(`minifyJS devolvió ${resultMinify} para archivo: ${inPath}`);
|
|
1694
|
+
}
|
|
1695
|
+
if (resultMinify.error) {
|
|
1696
|
+
await handleCompilationError(resultMinify.error instanceof Error
|
|
1697
|
+
? resultMinify.error
|
|
1698
|
+
: new Error(String(resultMinify.error)), inPath, 'minification', mode, env.VERBOSE === 'true');
|
|
1699
|
+
throw new Error(resultMinify.error instanceof Error
|
|
1700
|
+
? resultMinify.error.message
|
|
1701
|
+
: String(resultMinify.error));
|
|
1702
|
+
}
|
|
1703
|
+
registerCompilationSuccess(inPath, 'minification');
|
|
1704
|
+
code = resultMinify.code;
|
|
1705
|
+
} // Escribir archivo final
|
|
1706
|
+
const destinationDir = path.dirname(outPath);
|
|
1707
|
+
await mkdir(destinationDir, { recursive: true });
|
|
1708
|
+
await writeFile(outPath, code, 'utf-8');
|
|
1709
|
+
// Logs de timing detallados en modo verbose
|
|
1710
|
+
if (shouldShowDetailedLogs) {
|
|
1711
|
+
const totalTime = Object.values(timings).reduce((sum, time) => sum + time, 0);
|
|
1712
|
+
logger.info(chalk.cyan(`⏱️ Timing para ${path.basename(inPath)}:`));
|
|
1713
|
+
if (timings.fileRead)
|
|
1714
|
+
logger.info(chalk.cyan(` 📖 Lectura: ${timings.fileRead}ms`));
|
|
1715
|
+
if (timings.vueCompile)
|
|
1716
|
+
logger.info(chalk.cyan(` 💚 Vue: ${timings.vueCompile}ms`));
|
|
1717
|
+
if (timings.tsCompile)
|
|
1718
|
+
logger.info(chalk.cyan(` 🔄️ TypeScript: ${timings.tsCompile}ms`));
|
|
1719
|
+
if (timings.standardization)
|
|
1720
|
+
logger.info(chalk.cyan(` 💛 Estandarización: ${timings.standardization}ms`));
|
|
1721
|
+
if (timings.minification)
|
|
1722
|
+
logger.info(chalk.cyan(` 🤖 Minificación: ${timings.minification}ms`));
|
|
1723
|
+
logger.info(chalk.cyan(` 🏁 Total: ${totalTime}ms`));
|
|
1724
|
+
}
|
|
1725
|
+
return {
|
|
1726
|
+
error: null,
|
|
1727
|
+
action: 'extension',
|
|
1728
|
+
};
|
|
1729
|
+
}
|
|
1730
|
+
export async function initCompile(ruta, compileTailwind = true, mode = 'individual') {
|
|
1731
|
+
try {
|
|
1732
|
+
// 🚀 Sistema de Carga Inteligente de Módulos
|
|
1733
|
+
const moduleManager = OptimizedModuleManager.getInstance();
|
|
1734
|
+
const fileExtension = path.extname(ruta);
|
|
1735
|
+
const fileExtensions = new Set([fileExtension]);
|
|
1736
|
+
// Inicializar módulos según el contexto
|
|
1737
|
+
await moduleManager.preloadForContext(mode === 'all' ? 'batch' : mode, fileExtensions);
|
|
1738
|
+
// Generar TailwindCSS si está habilitado
|
|
1739
|
+
if (compileTailwind && Boolean(env.TAILWIND)) {
|
|
1740
|
+
await moduleManager.ensureModuleLoaded('tailwind');
|
|
1741
|
+
const generateTailwindCSS = await loadTailwind();
|
|
1742
|
+
const resultTW = await generateTailwindCSS();
|
|
1743
|
+
if (typeof resultTW !== 'boolean') {
|
|
1744
|
+
if (resultTW?.success) {
|
|
1745
|
+
logger.info(`🎨 ${resultTW.message}`);
|
|
1746
|
+
}
|
|
1747
|
+
else {
|
|
1748
|
+
const errorMsg = `${resultTW.message}${resultTW.details ? '\n' + resultTW.details : ''}`;
|
|
1749
|
+
await handleCompilationError(new Error(errorMsg), 'tailwind.config.js', 'tailwind', mode, env.VERBOSE === 'true');
|
|
1750
|
+
// No hacer throw aquí, permitir que la compilación continúe
|
|
1751
|
+
}
|
|
1752
|
+
}
|
|
1753
|
+
}
|
|
1754
|
+
const startTime = Date.now();
|
|
1755
|
+
const file = normalizeRuta(ruta);
|
|
1756
|
+
const outFile = getOutputPath(file);
|
|
1757
|
+
// 🚀 Verificar cache antes de compilar (especialmente importante en modo watch)
|
|
1758
|
+
if (mode === 'watch' || mode === 'individual') {
|
|
1759
|
+
if (await shouldSkipFile(file)) {
|
|
1760
|
+
if (env.VERBOSE === 'true') {
|
|
1761
|
+
logger.info(`⏭️ Archivo omitido (cache): ${path.basename(file)}`);
|
|
1762
|
+
}
|
|
1763
|
+
return {
|
|
1764
|
+
success: true,
|
|
1765
|
+
cached: true,
|
|
1766
|
+
output: smartCache.getOutputPath(file) || outFile,
|
|
1767
|
+
action: 'cached',
|
|
1768
|
+
};
|
|
1769
|
+
}
|
|
1770
|
+
}
|
|
1771
|
+
if (mode === 'individual' && env.VERBOSE === 'true') {
|
|
1772
|
+
logger.info(`🔜 Fuente: ${file}`);
|
|
1773
|
+
}
|
|
1774
|
+
const result = await compileJS(file, outFile, mode);
|
|
1775
|
+
if (result.error) {
|
|
1776
|
+
throw new Error(result.error);
|
|
1777
|
+
}
|
|
1778
|
+
// 🚀 Actualizar cache después de compilación exitosa (especialmente en modo watch)
|
|
1779
|
+
if (mode === 'watch' || mode === 'individual') {
|
|
1780
|
+
await smartCache.set(file, outFile);
|
|
1781
|
+
}
|
|
1782
|
+
const endTime = Date.now();
|
|
1783
|
+
const elapsedTime = showTimingForHumans(endTime - startTime);
|
|
1784
|
+
if (mode === 'individual') {
|
|
1785
|
+
if (env.VERBOSE === 'true') {
|
|
1786
|
+
logger.info(`🔚 Destino: ${outFile}`);
|
|
1787
|
+
logger.info(`⏱️ Tiempo: ${elapsedTime}`);
|
|
1788
|
+
}
|
|
1789
|
+
const chalk = await loadChalk();
|
|
1790
|
+
logger.info(chalk.green(`✅ Compilación exitosa: ${path.basename(file)}`));
|
|
1791
|
+
}
|
|
1792
|
+
return {
|
|
1793
|
+
success: true,
|
|
1794
|
+
output: outFile,
|
|
1795
|
+
action: result.action,
|
|
1796
|
+
};
|
|
1797
|
+
}
|
|
1798
|
+
catch (error) {
|
|
1799
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
1800
|
+
if (env.VERBOSE === 'true') {
|
|
1801
|
+
logger.error(`❌ Error en compilación de ${path.basename(ruta)}: ${errorMessage}`);
|
|
1802
|
+
}
|
|
1803
|
+
return {
|
|
1804
|
+
success: false,
|
|
1805
|
+
output: '',
|
|
1806
|
+
error: errorMessage,
|
|
1807
|
+
};
|
|
1808
|
+
}
|
|
1809
|
+
}
|
|
1810
|
+
// Variable para el último progreso mostrado (evitar spam)
|
|
1811
|
+
let lastProgressUpdate = 0;
|
|
1812
|
+
// Sistema de gestión de progreso persistente (como Jest)
|
|
1813
|
+
class ProgressManager {
|
|
1814
|
+
static instance;
|
|
1815
|
+
progressActive = false;
|
|
1816
|
+
lastProgressLine = '';
|
|
1817
|
+
logBuffer = [];
|
|
1818
|
+
originalConsoleLog;
|
|
1819
|
+
originalConsoleError;
|
|
1820
|
+
originalConsoleWarn;
|
|
1821
|
+
hasProgressLine = false;
|
|
1822
|
+
constructor() {
|
|
1823
|
+
// Guardar referencias originales
|
|
1824
|
+
this.originalConsoleLog = console.log;
|
|
1825
|
+
this.originalConsoleError = console.error;
|
|
1826
|
+
this.originalConsoleWarn = console.warn;
|
|
1827
|
+
}
|
|
1828
|
+
static getInstance() {
|
|
1829
|
+
if (!ProgressManager.instance) {
|
|
1830
|
+
ProgressManager.instance = new ProgressManager();
|
|
1831
|
+
}
|
|
1832
|
+
return ProgressManager.instance;
|
|
1833
|
+
}
|
|
1834
|
+
interceptConsole() {
|
|
1835
|
+
// Interceptar console.log y similares
|
|
1836
|
+
console.log = (...args) => {
|
|
1837
|
+
this.addLog(args.map(arg => String(arg)).join(' '));
|
|
1838
|
+
};
|
|
1839
|
+
console.error = (...args) => {
|
|
1840
|
+
this.addLog(args.map(arg => String(arg)).join(' '));
|
|
1841
|
+
};
|
|
1842
|
+
console.warn = (...args) => {
|
|
1843
|
+
this.addLog(args.map(arg => String(arg)).join(' '));
|
|
1844
|
+
};
|
|
1845
|
+
}
|
|
1846
|
+
restoreConsole() {
|
|
1847
|
+
console.log = this.originalConsoleLog;
|
|
1848
|
+
console.error = this.originalConsoleError;
|
|
1849
|
+
console.warn = this.originalConsoleWarn;
|
|
1850
|
+
}
|
|
1851
|
+
startProgress() {
|
|
1852
|
+
this.progressActive = true;
|
|
1853
|
+
this.logBuffer = [];
|
|
1854
|
+
this.hasProgressLine = false;
|
|
1855
|
+
this.interceptConsole();
|
|
1856
|
+
// 🎨 Header moderno de inicio de compilación
|
|
1857
|
+
const headerLine = '━'.repeat(48);
|
|
1858
|
+
process.stdout.write('\n\x1b[96m' + headerLine + '\x1b[0m\n');
|
|
1859
|
+
process.stdout.write('\x1b[96m│ \x1b[97m\x1b[1m🚀 Iniciando Compilación\x1b[0m\x1b[96m' +
|
|
1860
|
+
' '.repeat(22) +
|
|
1861
|
+
'│\x1b[0m\n');
|
|
1862
|
+
process.stdout.write('\x1b[96m' + headerLine + '\x1b[0m\n');
|
|
1863
|
+
}
|
|
1864
|
+
updateProgress(progressText) {
|
|
1865
|
+
if (!this.progressActive)
|
|
1866
|
+
return;
|
|
1867
|
+
// Si hay logs pendientes, mostrarlos primero
|
|
1868
|
+
if (this.logBuffer.length > 0) {
|
|
1869
|
+
// Si ya hay una línea de progreso, limpiarla primero
|
|
1870
|
+
if (this.hasProgressLine) {
|
|
1871
|
+
process.stdout.write('\r\x1b[K');
|
|
1872
|
+
}
|
|
1873
|
+
// Escribir todos los logs pendientes
|
|
1874
|
+
for (const log of this.logBuffer) {
|
|
1875
|
+
process.stdout.write((this.hasProgressLine ? '\n' : '') + log + '\n');
|
|
1876
|
+
this.hasProgressLine = false;
|
|
1877
|
+
}
|
|
1878
|
+
this.logBuffer = [];
|
|
1879
|
+
} // Escribir separador elegante antes del progreso
|
|
1880
|
+
if (this.hasProgressLine) {
|
|
1881
|
+
process.stdout.write('\r\x1b[K');
|
|
1882
|
+
}
|
|
1883
|
+
else {
|
|
1884
|
+
process.stdout.write('\n\x1b[96m' + '▔'.repeat(50) + '\x1b[0m\n');
|
|
1885
|
+
}
|
|
1886
|
+
// 🎨 Barra de progreso con colores dinámicos
|
|
1887
|
+
const stage = this.getStageFromText(progressText);
|
|
1888
|
+
const { bgColor, textColor, icon } = this.getProgressColors(stage);
|
|
1889
|
+
const progressBar = '█'.repeat(3);
|
|
1890
|
+
const enhancedProgress = `\x1b[${bgColor}m\x1b[${textColor}m ${progressBar} ${icon} ${progressText} ${progressBar} \x1b[0m`;
|
|
1891
|
+
process.stdout.write(enhancedProgress);
|
|
1892
|
+
this.hasProgressLine = true;
|
|
1893
|
+
this.lastProgressLine = progressText;
|
|
1894
|
+
}
|
|
1895
|
+
addLog(message) {
|
|
1896
|
+
if (this.progressActive) {
|
|
1897
|
+
this.logBuffer.push(message);
|
|
1898
|
+
}
|
|
1899
|
+
else {
|
|
1900
|
+
this.originalConsoleLog(message);
|
|
1901
|
+
}
|
|
1902
|
+
}
|
|
1903
|
+
addImmediateLog(message) {
|
|
1904
|
+
if (this.progressActive) {
|
|
1905
|
+
if (this.hasProgressLine) {
|
|
1906
|
+
process.stdout.write('\r\x1b[K');
|
|
1907
|
+
}
|
|
1908
|
+
// Añadir un punto de separación visual para logs inmediatos
|
|
1909
|
+
process.stdout.write('\x1b[90m│\x1b[0m ' + message + '\n');
|
|
1910
|
+
this.hasProgressLine = false;
|
|
1911
|
+
}
|
|
1912
|
+
else {
|
|
1913
|
+
this.originalConsoleLog(message);
|
|
1914
|
+
}
|
|
1915
|
+
}
|
|
1916
|
+
endProgress() {
|
|
1917
|
+
if (this.progressActive) {
|
|
1918
|
+
if (this.hasProgressLine) {
|
|
1919
|
+
process.stdout.write('\n');
|
|
1920
|
+
}
|
|
1921
|
+
// Mostrar barra de progreso final completa antes del separador
|
|
1922
|
+
process.stdout.write('\n\x1b[33m' + '-'.repeat(50) + '\x1b[0m\n');
|
|
1923
|
+
const finalProgressBar = '█'.repeat(3);
|
|
1924
|
+
const finalProgress = `\x1b[42m\x1b[30m ${finalProgressBar} ✅ PROCESO COMPLETADO 100% ${finalProgressBar} \x1b[0m`;
|
|
1925
|
+
process.stdout.write(finalProgress + '\n');
|
|
1926
|
+
// 🎨 Footer moderno de finalización
|
|
1927
|
+
const footerLine = '━'.repeat(48);
|
|
1928
|
+
process.stdout.write('\x1b[92m' + footerLine + '\x1b[0m\n');
|
|
1929
|
+
process.stdout.write('\x1b[92m│ \x1b[97m\x1b[1m✅ ¡Compilación Completada!\x1b[0m\x1b[92m' +
|
|
1930
|
+
' '.repeat(23) +
|
|
1931
|
+
'│\x1b[0m\n');
|
|
1932
|
+
process.stdout.write('\x1b[92m' + footerLine + '\x1b[0m\n\n');
|
|
1933
|
+
// Escribir logs finales pendientes
|
|
1934
|
+
if (this.logBuffer.length > 0) {
|
|
1935
|
+
for (const log of this.logBuffer) {
|
|
1936
|
+
process.stdout.write(log + '\n');
|
|
1937
|
+
}
|
|
1938
|
+
}
|
|
1939
|
+
}
|
|
1940
|
+
this.restoreConsole();
|
|
1941
|
+
this.progressActive = false;
|
|
1942
|
+
this.lastProgressLine = '';
|
|
1943
|
+
this.logBuffer = [];
|
|
1944
|
+
this.hasProgressLine = false;
|
|
1945
|
+
}
|
|
1946
|
+
isActive() {
|
|
1947
|
+
return this.progressActive;
|
|
1948
|
+
}
|
|
1949
|
+
/**
|
|
1950
|
+
* 🎨 Determina la etapa del progreso basándose en el texto
|
|
1951
|
+
*/
|
|
1952
|
+
getStageFromText(text) {
|
|
1953
|
+
if (text.includes('Iniciando') || text.includes('Starting'))
|
|
1954
|
+
return 'start';
|
|
1955
|
+
if (text.includes('Tailwind') || text.includes('CSS'))
|
|
1956
|
+
return 'tailwind';
|
|
1957
|
+
if (text.includes('Recopilando') ||
|
|
1958
|
+
text.includes('archivos') ||
|
|
1959
|
+
text.includes('files'))
|
|
1960
|
+
return 'files';
|
|
1961
|
+
if (text.includes('Compilando') || text.includes('workers'))
|
|
1962
|
+
return 'compile';
|
|
1963
|
+
if (text.includes('cache') || text.includes('Guardando'))
|
|
1964
|
+
return 'cache';
|
|
1965
|
+
if (text.includes('linter') || text.includes('Linter'))
|
|
1966
|
+
return 'linter';
|
|
1967
|
+
if (text.includes('completado') || text.includes('Complete'))
|
|
1968
|
+
return 'complete';
|
|
1969
|
+
return 'default';
|
|
1970
|
+
}
|
|
1971
|
+
/**
|
|
1972
|
+
* 🌈 Obtiene colores dinámicos para cada etapa
|
|
1973
|
+
*/
|
|
1974
|
+
getProgressColors(stage) {
|
|
1975
|
+
const colorSchemes = {
|
|
1976
|
+
start: { bgColor: '45', textColor: '97', icon: '🚀' }, // Cyan brillante
|
|
1977
|
+
tailwind: { bgColor: '105', textColor: '97', icon: '🎨' }, // Magenta
|
|
1978
|
+
files: { bgColor: '43', textColor: '30', icon: '📁' }, // Amarillo
|
|
1979
|
+
compile: { bgColor: '42', textColor: '30', icon: '⚙️' }, // Verde
|
|
1980
|
+
cache: { bgColor: '44', textColor: '97', icon: '💾' }, // Azul
|
|
1981
|
+
linter: { bgColor: '101', textColor: '97', icon: '🔍' }, // Rojo claro
|
|
1982
|
+
complete: { bgColor: '102', textColor: '30', icon: '✅' }, // Verde claro
|
|
1983
|
+
default: { bgColor: '100', textColor: '30', icon: '⏳' }, // Gris claro
|
|
1984
|
+
};
|
|
1985
|
+
const defaultColors = { bgColor: '100', textColor: '30', icon: '⏳' };
|
|
1986
|
+
return colorSchemes[stage] || defaultColors;
|
|
1987
|
+
}
|
|
1988
|
+
}
|
|
1989
|
+
// Función para ejecutar el linter antes de la compilación
|
|
1990
|
+
export async function runLinter(showResult = false) {
|
|
1991
|
+
const linterENV = env.linter;
|
|
1992
|
+
const linterPromises = [];
|
|
1993
|
+
const linterErrors = [];
|
|
1994
|
+
let proceedWithCompilation = true;
|
|
1995
|
+
if (env.ENABLE_LINTER !== 'true') {
|
|
1996
|
+
return true;
|
|
1997
|
+
}
|
|
1998
|
+
if (typeof linterENV === 'string' && linterENV.trim() !== '') {
|
|
1999
|
+
logger.info('🔍 Ejecutando linting...');
|
|
2000
|
+
try {
|
|
2001
|
+
const parsedLinterEnv = JSON.parse(linterENV);
|
|
2002
|
+
if (Array.isArray(parsedLinterEnv)) {
|
|
2003
|
+
// Cargar dependencias de linting de forma lazy
|
|
2004
|
+
const { ESLint, OxLint } = await loadLinter();
|
|
2005
|
+
for (const item of parsedLinterEnv) {
|
|
2006
|
+
if (item.name.toLowerCase() === 'eslint') {
|
|
2007
|
+
logger.info(`🔧 Ejecutando ESLint con config: ${item.configFile || 'por defecto'}`);
|
|
2008
|
+
const eslintPromise = ESLint(item)
|
|
2009
|
+
.then((eslintResult) => {
|
|
2010
|
+
if (eslintResult && eslintResult.json) {
|
|
2011
|
+
// Procesar resultados de ESLint
|
|
2012
|
+
if (Array.isArray(eslintResult.json)) {
|
|
2013
|
+
eslintResult.json.forEach((result) => {
|
|
2014
|
+
const filePath = result.filePath ||
|
|
2015
|
+
result.file ||
|
|
2016
|
+
'archivo no especificado';
|
|
2017
|
+
linterErrors.push({
|
|
2018
|
+
from: 'eslint',
|
|
2019
|
+
line: result.line || 'N/A',
|
|
2020
|
+
column: result.column || 1,
|
|
2021
|
+
file: filePath,
|
|
2022
|
+
message: result.message,
|
|
2023
|
+
severity: result.severity === 2
|
|
2024
|
+
? 'error'
|
|
2025
|
+
: 'warning',
|
|
2026
|
+
help: result.ruleId
|
|
2027
|
+
? `Regla ESLint: ${result.ruleId}`
|
|
2028
|
+
: undefined,
|
|
2029
|
+
});
|
|
2030
|
+
});
|
|
2031
|
+
}
|
|
2032
|
+
else if (eslintResult.json.results &&
|
|
2033
|
+
Array.isArray(eslintResult.json.results)) {
|
|
2034
|
+
eslintResult.json.results.forEach((fileResult) => {
|
|
2035
|
+
if (fileResult.messages &&
|
|
2036
|
+
Array.isArray(fileResult.messages)) {
|
|
2037
|
+
fileResult.messages.forEach((msg) => {
|
|
2038
|
+
const filePath = fileResult.filePath ||
|
|
2039
|
+
fileResult.file ||
|
|
2040
|
+
'archivo no especificado';
|
|
2041
|
+
linterErrors.push({
|
|
2042
|
+
from: 'eslint',
|
|
2043
|
+
line: msg.line ||
|
|
2044
|
+
'N/A',
|
|
2045
|
+
column: msg.column ||
|
|
2046
|
+
1,
|
|
2047
|
+
file: filePath,
|
|
2048
|
+
message: msg.message,
|
|
2049
|
+
severity: msg.severity ===
|
|
2050
|
+
2
|
|
2051
|
+
? 'error'
|
|
2052
|
+
: 'warning',
|
|
2053
|
+
help: msg.ruleId
|
|
2054
|
+
? `Regla ESLint: ${msg.ruleId}`
|
|
2055
|
+
: undefined,
|
|
2056
|
+
});
|
|
2057
|
+
});
|
|
2058
|
+
}
|
|
2059
|
+
});
|
|
2060
|
+
}
|
|
2061
|
+
}
|
|
2062
|
+
})
|
|
2063
|
+
.catch((err) => {
|
|
2064
|
+
logger.error(`❌ Error durante la ejecución de ESLint: ${err.message}`);
|
|
2065
|
+
linterErrors.push({
|
|
2066
|
+
file: item.configFile || 'ESLint Config',
|
|
2067
|
+
message: `Fallo al ejecutar ESLint: ${err.message}`,
|
|
2068
|
+
severity: 'error',
|
|
2069
|
+
});
|
|
2070
|
+
});
|
|
2071
|
+
linterPromises.push(eslintPromise);
|
|
2072
|
+
}
|
|
2073
|
+
else if (item.name.toLowerCase() === 'oxlint') {
|
|
2074
|
+
logger.info(`🔧 Ejecutando OxLint con config: ${item.configFile || 'por defecto'}`);
|
|
2075
|
+
const oxlintPromise = OxLint(item)
|
|
2076
|
+
.then((oxlintResult) => {
|
|
2077
|
+
if (oxlintResult &&
|
|
2078
|
+
oxlintResult['json'] &&
|
|
2079
|
+
Array.isArray(oxlintResult['json']['diagnostics'])) {
|
|
2080
|
+
oxlintResult['json']['diagnostics'].forEach((result) => {
|
|
2081
|
+
const filePath = result.filename ||
|
|
2082
|
+
result.file ||
|
|
2083
|
+
'archivo no especificado';
|
|
2084
|
+
const lineNumber = result.labels &&
|
|
2085
|
+
result.labels[0] &&
|
|
2086
|
+
result.labels[0].span
|
|
2087
|
+
? result.labels[0].span
|
|
2088
|
+
.line ||
|
|
2089
|
+
result.labels[0].span
|
|
2090
|
+
.start?.line
|
|
2091
|
+
: 'N/A';
|
|
2092
|
+
const columnNumber = result.labels &&
|
|
2093
|
+
result.labels[0] &&
|
|
2094
|
+
result.labels[0].span
|
|
2095
|
+
? result.labels[0].span
|
|
2096
|
+
.column ||
|
|
2097
|
+
result.labels[0].span
|
|
2098
|
+
.start?.column
|
|
2099
|
+
: 1;
|
|
2100
|
+
linterErrors.push({
|
|
2101
|
+
from: 'oxlint',
|
|
2102
|
+
line: lineNumber,
|
|
2103
|
+
column: columnNumber,
|
|
2104
|
+
file: filePath,
|
|
2105
|
+
message: result.message,
|
|
2106
|
+
severity: typeof result.severity ===
|
|
2107
|
+
'string'
|
|
2108
|
+
? result.severity.toLowerCase()
|
|
2109
|
+
: 'error',
|
|
2110
|
+
help: result.help ||
|
|
2111
|
+
(result.code
|
|
2112
|
+
? `Regla Oxlint: ${result.code}`
|
|
2113
|
+
: undefined),
|
|
2114
|
+
});
|
|
2115
|
+
});
|
|
2116
|
+
}
|
|
2117
|
+
})
|
|
2118
|
+
.catch((err) => {
|
|
2119
|
+
logger.error(`❌ Error durante la ejecución de OxLint: ${err.message}`);
|
|
2120
|
+
linterErrors.push({
|
|
2121
|
+
file: item.configFile || 'Oxlint Config',
|
|
2122
|
+
message: `Fallo al ejecutar Oxlint: ${err.message}`,
|
|
2123
|
+
severity: 'error',
|
|
2124
|
+
});
|
|
2125
|
+
});
|
|
2126
|
+
linterPromises.push(oxlintPromise);
|
|
2127
|
+
}
|
|
2128
|
+
}
|
|
2129
|
+
}
|
|
2130
|
+
else {
|
|
2131
|
+
logger.warn('⚠️ La configuración de linter no es un array válido.');
|
|
2132
|
+
}
|
|
2133
|
+
await Promise.all(linterPromises);
|
|
2134
|
+
if (showResult) {
|
|
2135
|
+
// Modo --linter: Solo mostrar resultados sin preguntar
|
|
2136
|
+
if (linterErrors.length > 0) {
|
|
2137
|
+
await displayLinterErrors(linterErrors);
|
|
2138
|
+
}
|
|
2139
|
+
else {
|
|
2140
|
+
const chalk = await loadChalk();
|
|
2141
|
+
logger.info(chalk.green('✅ No se encontraron errores ni advertencias de linting.'));
|
|
2142
|
+
}
|
|
2143
|
+
}
|
|
2144
|
+
else {
|
|
2145
|
+
// Modo compilación: Mostrar errores si los hay y preguntar al usuario
|
|
2146
|
+
if (linterErrors.length > 0) {
|
|
2147
|
+
await displayLinterErrors(linterErrors);
|
|
2148
|
+
logger.warn('🚨 Se encontraron errores o advertencias durante el linting.');
|
|
2149
|
+
if (env.yes === 'false') {
|
|
2150
|
+
const result = await promptUser('¿Deseas continuar con la compilación a pesar de los errores de linting? (s/N): ');
|
|
2151
|
+
if (result.toLowerCase() !== 's') {
|
|
2152
|
+
logger.info('🛑 Compilación cancelada por el usuario.');
|
|
2153
|
+
proceedWithCompilation = false;
|
|
2154
|
+
}
|
|
2155
|
+
}
|
|
2156
|
+
}
|
|
2157
|
+
}
|
|
2158
|
+
}
|
|
2159
|
+
catch (parseError) {
|
|
2160
|
+
logger.warn(`Error parseando configuración de linter: ${parseError instanceof Error ? parseError.message : 'Error desconocido'}, omitiendo...`);
|
|
2161
|
+
}
|
|
2162
|
+
}
|
|
2163
|
+
return proceedWithCompilation;
|
|
2164
|
+
}
|
|
2165
|
+
// Función para crear una barra de progreso visual
|
|
2166
|
+
function createProgressBar(current, total, width = 30) {
|
|
2167
|
+
const percentage = Math.round((current / total) * 100);
|
|
2168
|
+
const filled = Math.round((percentage / 100) * width);
|
|
2169
|
+
const empty = width - filled;
|
|
2170
|
+
return `[${'█'.repeat(filled)}${' '.repeat(empty)}] ${percentage}% (${current}/${total})`;
|
|
2171
|
+
}
|
|
2172
|
+
// Función helper para verificar si un archivo debe ser omitido por cache
|
|
2173
|
+
async function shouldSkipFile(filePath) {
|
|
2174
|
+
return await smartCache.isValid(filePath);
|
|
2175
|
+
}
|
|
2176
|
+
// Función para compilar archivos con límite de concurrencia
|
|
2177
|
+
async function compileWithConcurrencyLimit(files, maxConcurrency = 8) {
|
|
2178
|
+
const results = [];
|
|
2179
|
+
const executing = [];
|
|
2180
|
+
const total = files.length;
|
|
2181
|
+
let completed = 0;
|
|
2182
|
+
let skipped = 0;
|
|
2183
|
+
let failed = 0;
|
|
2184
|
+
// Usar el gestor de progreso existente (ya iniciado en initCompileAll)
|
|
2185
|
+
const progressManager = ProgressManager.getInstance();
|
|
2186
|
+
// Variable para controlar el progreso inicial
|
|
2187
|
+
let hasShownInitialProgress = false;
|
|
2188
|
+
// Contador para limpieza periódica de memoria
|
|
2189
|
+
let compilationCounter = 0;
|
|
2190
|
+
const CLEANUP_INTERVAL = 20; // Limpiar cada 20 compilaciones
|
|
2191
|
+
// Función para mostrar progreso
|
|
2192
|
+
function showProgress() {
|
|
2193
|
+
const currentTotal = completed + skipped + failed;
|
|
2194
|
+
const progressBar = createProgressBar(currentTotal, total);
|
|
2195
|
+
const progressPercent = Math.round((currentTotal / total) * 100);
|
|
2196
|
+
// Mostrar progreso inicial cuando se inicie O cuando haya progreso real
|
|
2197
|
+
if ((currentTotal === 0 && !hasShownInitialProgress) ||
|
|
2198
|
+
(progressPercent > lastProgressUpdate + 1 && currentTotal > 0) ||
|
|
2199
|
+
currentTotal === total) {
|
|
2200
|
+
const progressText = `🚀 ${progressBar} [✅ ${completed} | ⏭️ ${skipped} | ❌ ${failed}]`;
|
|
2201
|
+
progressManager.updateProgress(progressText);
|
|
2202
|
+
if (currentTotal === 0) {
|
|
2203
|
+
hasShownInitialProgress = true;
|
|
2204
|
+
}
|
|
2205
|
+
lastProgressUpdate = progressPercent;
|
|
2206
|
+
// NO terminar el progreso aquí - se termina en initCompileAll
|
|
2207
|
+
}
|
|
2208
|
+
}
|
|
2209
|
+
// Mostrar progreso inicial
|
|
2210
|
+
showProgress();
|
|
2211
|
+
for (const file of files) {
|
|
2212
|
+
const promise = (async () => {
|
|
2213
|
+
try {
|
|
2214
|
+
// Log verbose: Iniciando compilación del archivo
|
|
2215
|
+
if (env.VERBOSE === 'true') {
|
|
2216
|
+
logger.info(`🔄 Compilando: ${path.basename(file)}`);
|
|
2217
|
+
}
|
|
2218
|
+
// Verificar cache antes de compilar
|
|
2219
|
+
if (await shouldSkipFile(file)) {
|
|
2220
|
+
skipped++;
|
|
2221
|
+
if (env.VERBOSE === 'true') {
|
|
2222
|
+
logger.info(`⏭️ Archivo omitido (cache): ${path.basename(file)}`);
|
|
2223
|
+
}
|
|
2224
|
+
showProgress();
|
|
2225
|
+
return {
|
|
2226
|
+
success: true,
|
|
2227
|
+
cached: true,
|
|
2228
|
+
output: smartCache.getOutputPath(file),
|
|
2229
|
+
};
|
|
2230
|
+
}
|
|
2231
|
+
const result = await initCompile(file, false, 'batch');
|
|
2232
|
+
// Actualizar cache si la compilación fue exitosa
|
|
2233
|
+
if (result.success && result.output) {
|
|
2234
|
+
await smartCache.set(file, result.output);
|
|
2235
|
+
if (env.VERBOSE === 'true') {
|
|
2236
|
+
logger.info(`✅ Completado: ${path.basename(file)} → ${path.basename(result.output)}`);
|
|
2237
|
+
}
|
|
2238
|
+
}
|
|
2239
|
+
else if (env.VERBOSE === 'true') {
|
|
2240
|
+
logger.info(`❌ Error en: ${path.basename(file)}`);
|
|
2241
|
+
}
|
|
2242
|
+
completed++;
|
|
2243
|
+
compilationCounter++; // Limpieza periódica de memoria
|
|
2244
|
+
if (compilationCounter % CLEANUP_INTERVAL === 0) {
|
|
2245
|
+
// Forzar garbage collection si está disponible
|
|
2246
|
+
try {
|
|
2247
|
+
if (typeof globalThis.gc === 'function') {
|
|
2248
|
+
globalThis.gc();
|
|
2249
|
+
}
|
|
2250
|
+
}
|
|
2251
|
+
catch {
|
|
2252
|
+
// gc no disponible, continuar normalmente
|
|
2253
|
+
}
|
|
2254
|
+
// Limpiar cache si la memoria es alta
|
|
2255
|
+
const memUsage = process.memoryUsage();
|
|
2256
|
+
const heapUsedMB = memUsage.heapUsed / (1024 * 1024);
|
|
2257
|
+
if (heapUsedMB > 300) {
|
|
2258
|
+
// Si el heap supera 300MB
|
|
2259
|
+
const cacheEntries = smartCache.getStats().entries;
|
|
2260
|
+
if (cacheEntries > 50) {
|
|
2261
|
+
console.log(`[Memory] Heap alto (${heapUsedMB.toFixed(1)}MB), limpiando cache...`);
|
|
2262
|
+
// Limpiar entradas más antiguas del cache
|
|
2263
|
+
const removedEntries = smartCache.cleanOldEntries(20);
|
|
2264
|
+
if (removedEntries > 0) {
|
|
2265
|
+
console.log(`[Memory] Se removieron ${removedEntries} entradas del cache`);
|
|
2266
|
+
}
|
|
2267
|
+
}
|
|
2268
|
+
}
|
|
2269
|
+
}
|
|
2270
|
+
showProgress();
|
|
2271
|
+
return result;
|
|
2272
|
+
}
|
|
2273
|
+
catch (error) {
|
|
2274
|
+
failed++;
|
|
2275
|
+
if (env.VERBOSE === 'true') {
|
|
2276
|
+
const errorMsg = error instanceof Error ? error.message : String(error);
|
|
2277
|
+
logger.error(`💥 Falló: ${path.basename(file)} - ${errorMsg}`);
|
|
2278
|
+
}
|
|
2279
|
+
showProgress();
|
|
2280
|
+
return {
|
|
2281
|
+
success: false,
|
|
2282
|
+
error: error instanceof Error ? error.message : String(error),
|
|
2283
|
+
};
|
|
2284
|
+
}
|
|
2285
|
+
})();
|
|
2286
|
+
results.push(promise);
|
|
2287
|
+
executing.push(promise);
|
|
2288
|
+
// ✅ FIX: Esperar correctamente a que termine alguna promesa antes de continuar
|
|
2289
|
+
if (executing.length >= maxConcurrency) {
|
|
2290
|
+
// Esperar a que termine cualquier promesa
|
|
2291
|
+
const completedPromise = await Promise.race(executing);
|
|
2292
|
+
// Remover la promesa completada del array executing
|
|
2293
|
+
const index = executing.indexOf(completedPromise);
|
|
2294
|
+
if (index !== -1) {
|
|
2295
|
+
executing.splice(index, 1);
|
|
2296
|
+
}
|
|
2297
|
+
}
|
|
2298
|
+
// Limpiar promesas completadas del array executing
|
|
2299
|
+
promise.then(() => {
|
|
2300
|
+
const index = executing.indexOf(promise);
|
|
2301
|
+
if (index !== -1) {
|
|
2302
|
+
executing.splice(index, 1);
|
|
2303
|
+
}
|
|
2304
|
+
});
|
|
2305
|
+
}
|
|
2306
|
+
await Promise.all(results);
|
|
2307
|
+
// El progreso ya se termina automáticamente en showProgress() cuando se completa
|
|
2308
|
+
}
|
|
2309
|
+
export async function initCompileAll() {
|
|
2310
|
+
try {
|
|
2311
|
+
// Inicializar el gestor de progreso desde el inicio
|
|
2312
|
+
const progressManager = ProgressManager.getInstance();
|
|
2313
|
+
progressManager.startProgress();
|
|
2314
|
+
// Fase 1: Preparación inicial
|
|
2315
|
+
progressManager.updateProgress('🚀 Iniciando compilación...');
|
|
2316
|
+
// Limpiar estado de compilación anterior
|
|
2317
|
+
clearCompilationState();
|
|
2318
|
+
// Cargar cache al inicio
|
|
2319
|
+
progressManager.updateProgress('📦 Cargando cache...');
|
|
2320
|
+
await loadCache();
|
|
2321
|
+
lastProgressUpdate = 0; // Fase 2: Linting
|
|
2322
|
+
progressManager.updateProgress('🔍 Ejecutando linter...');
|
|
2323
|
+
const shouldContinue = await runLinter(false); // false = mostrar errores y preguntar si hay errores
|
|
2324
|
+
if (!shouldContinue) {
|
|
2325
|
+
// await displayCompilationSummary(env.VERBOSE === 'true');
|
|
2326
|
+
progressManager.endProgress();
|
|
2327
|
+
return;
|
|
2328
|
+
}
|
|
2329
|
+
const startTime = Date.now();
|
|
2330
|
+
const rawPathSource = env.PATH_SOURCE ?? '';
|
|
2331
|
+
const pathDist = env.PATH_DIST ?? '';
|
|
2332
|
+
const normalizedGlobPathSource = rawPathSource.replace(/\\/g, '/');
|
|
2333
|
+
const patterns = [
|
|
2334
|
+
`${normalizedGlobPathSource}/**/*.js`,
|
|
2335
|
+
`${normalizedGlobPathSource}/**/*.vue`,
|
|
2336
|
+
`${normalizedGlobPathSource}/**/*.ts`,
|
|
2337
|
+
`${normalizedGlobPathSource}/**/*.mjs`,
|
|
2338
|
+
`${normalizedGlobPathSource}/**/*.cjs`,
|
|
2339
|
+
];
|
|
2340
|
+
logger.info(`📝 Compilando todos los archivos...`);
|
|
2341
|
+
logger.info(`🔜 Fuente: ${rawPathSource}`);
|
|
2342
|
+
logger.info(`🔚 Destino: ${pathDist}\n`);
|
|
2343
|
+
// Fase 3: TailwindCSS
|
|
2344
|
+
progressManager.updateProgress('🎨 Generando TailwindCSS...');
|
|
2345
|
+
const generateTailwindCSS = await loadTailwind();
|
|
2346
|
+
const resultTW = await generateTailwindCSS();
|
|
2347
|
+
if (typeof resultTW !== 'boolean') {
|
|
2348
|
+
if (resultTW?.success) {
|
|
2349
|
+
logger.info(`🎨 ${resultTW.message}\n`);
|
|
2350
|
+
}
|
|
2351
|
+
else {
|
|
2352
|
+
await handleCompilationError(new Error(`${resultTW.message}${resultTW.details ? '\n' + resultTW.details : ''}`), 'tailwind.config.js', 'tailwind', 'all', env.VERBOSE === 'true');
|
|
2353
|
+
}
|
|
2354
|
+
}
|
|
2355
|
+
// Fase 4: Recopilando archivos
|
|
2356
|
+
progressManager.updateProgress('📁 Recopilando archivos...');
|
|
2357
|
+
const filesToCompile = [];
|
|
2358
|
+
for await (const file of glob(patterns)) {
|
|
2359
|
+
if (file.endsWith('.d.ts')) {
|
|
2360
|
+
continue;
|
|
2361
|
+
}
|
|
2362
|
+
// Usar la ruta tal como viene de glob, sin modificar
|
|
2363
|
+
filesToCompile.push(file);
|
|
2364
|
+
}
|
|
2365
|
+
// ✨ OPTIMIZACIÓN: Determinar concurrencia basada en CPUs y tipo de operación
|
|
2366
|
+
let cpuCount = os.cpus().length;
|
|
2367
|
+
const fileCount = filesToCompile.length;
|
|
2368
|
+
// ✅ FIX: En algunos entornos (Docker, VMs), os.cpus() retorna 1
|
|
2369
|
+
// Establecer un mínimo razonable basado en el tipo de sistema
|
|
2370
|
+
if (cpuCount < 4) {
|
|
2371
|
+
// Probablemente un contenedor o VM mal configurado
|
|
2372
|
+
// Usar un valor conservador pero razonable
|
|
2373
|
+
cpuCount = 4;
|
|
2374
|
+
if (env.VERBOSE === 'true') {
|
|
2375
|
+
logger.warn(`⚠️ Solo se detectó ${os.cpus().length} CPU. Usando ${cpuCount} hilos por defecto.`);
|
|
2376
|
+
}
|
|
2377
|
+
}
|
|
2378
|
+
// ✅ OVERRIDE MANUAL: Permitir al usuario forzar un número de hilos
|
|
2379
|
+
if (process.env.VERSACOMPILER_MAX_THREADS) {
|
|
2380
|
+
const envThreads = parseInt(process.env.VERSACOMPILER_MAX_THREADS, 10);
|
|
2381
|
+
if (!isNaN(envThreads) && envThreads > 0) {
|
|
2382
|
+
cpuCount = envThreads;
|
|
2383
|
+
if (env.VERBOSE === 'true') {
|
|
2384
|
+
logger.info(`🔧 Usando ${cpuCount} hilos (variable de entorno VERSACOMPILER_MAX_THREADS)`);
|
|
2385
|
+
}
|
|
2386
|
+
}
|
|
2387
|
+
}
|
|
2388
|
+
// Obtener memoria total del sistema (no solo heap)
|
|
2389
|
+
const totalMemoryMB = os.totalmem() / (1024 * 1024);
|
|
2390
|
+
const freeMemoryMB = os.freemem() / (1024 * 1024);
|
|
2391
|
+
const memoryUsagePercent = ((totalMemoryMB - freeMemoryMB) / totalMemoryMB) * 100;
|
|
2392
|
+
let maxConcurrency;
|
|
2393
|
+
// ✅ ESTRATEGIA AGRESIVA: Usar TODOS los cores disponibles por defecto
|
|
2394
|
+
// La compilación de archivos es I/O bound, no CPU bound, así que podemos ser agresivos
|
|
2395
|
+
if (memoryUsagePercent > 90) {
|
|
2396
|
+
// Solo si la memoria del SISTEMA está al 90%, reducir hilos
|
|
2397
|
+
maxConcurrency = Math.max(4, Math.floor(cpuCount * 0.5));
|
|
2398
|
+
}
|
|
2399
|
+
else if (fileCount < 5) {
|
|
2400
|
+
// Muy pocos archivos: no tiene sentido más hilos que archivos
|
|
2401
|
+
maxConcurrency = fileCount;
|
|
2402
|
+
}
|
|
2403
|
+
else if (fileCount < 20) {
|
|
2404
|
+
// Pocos archivos: usar todos los CPUs
|
|
2405
|
+
maxConcurrency = cpuCount;
|
|
2406
|
+
}
|
|
2407
|
+
else if (fileCount < 50) {
|
|
2408
|
+
// Archivos moderados: 1.5x los CPUs (I/O permite más hilos)
|
|
2409
|
+
maxConcurrency = Math.floor(cpuCount * 1.5);
|
|
2410
|
+
}
|
|
2411
|
+
else if (fileCount < 200) {
|
|
2412
|
+
// Muchos archivos: 2x los CPUs
|
|
2413
|
+
maxConcurrency = cpuCount * 2;
|
|
2414
|
+
}
|
|
2415
|
+
else {
|
|
2416
|
+
// Proyectos grandes: máxima concurrencia
|
|
2417
|
+
maxConcurrency = Math.min(cpuCount * 3, 48);
|
|
2418
|
+
}
|
|
2419
|
+
// ✅ GARANTIZAR MÍNIMO RAZONABLE: Nunca menos de 4 hilos en proyectos > 10 archivos
|
|
2420
|
+
if (fileCount > 10 && maxConcurrency < 4) {
|
|
2421
|
+
maxConcurrency = 4;
|
|
2422
|
+
if (env.VERBOSE === 'true') {
|
|
2423
|
+
logger.info(`⚡ Ajustando a mínimo de 4 hilos para proyecto de ${fileCount} archivos`);
|
|
2424
|
+
}
|
|
2425
|
+
}
|
|
2426
|
+
// Fase 5: Configurando workers y precargando módulos
|
|
2427
|
+
progressManager.updateProgress('⚙️ Configurando workers...');
|
|
2428
|
+
// ✅ Logging mejorado con información de recursos
|
|
2429
|
+
logger.info(`🚀 Compilando ${fileCount} archivos con concurrencia optimizada (${maxConcurrency} hilos)...`);
|
|
2430
|
+
// ✅ SIEMPRE mostrar info de CPUs/memoria para detectar problemas
|
|
2431
|
+
logger.info(` 📊 CPUs detectados: ${os.cpus().length} (usando: ${cpuCount})`);
|
|
2432
|
+
logger.info(` 💾 Memoria libre: ${freeMemoryMB.toFixed(0)}MB / ${totalMemoryMB.toFixed(0)}MB (${(100 - memoryUsagePercent).toFixed(1)}% libre)`);
|
|
2433
|
+
logger.info(` ⚡ Hilos configurados: ${maxConcurrency} (${(maxConcurrency / cpuCount).toFixed(1)}x CPUs)`);
|
|
2434
|
+
// ⚠️ Warning si los hilos son muy pocos para el tamaño del proyecto
|
|
2435
|
+
const optimalThreads = Math.min(cpuCount * 2, 24);
|
|
2436
|
+
if (fileCount > 50 && maxConcurrency < optimalThreads * 0.5) {
|
|
2437
|
+
const chalk = await loadChalk();
|
|
2438
|
+
logger.warn(chalk.yellow(`⚠️ Solo se usarán ${maxConcurrency} hilos para ${fileCount} archivos.`));
|
|
2439
|
+
logger.info(chalk.yellow(` 💡 Tip: export VERSACOMPILER_MAX_THREADS=${optimalThreads}`));
|
|
2440
|
+
}
|
|
2441
|
+
// ⚠️ ADVERTENCIA: Si los hilos son muy bajos para el tamaño del proyecto
|
|
2442
|
+
if (fileCount > 50 && maxConcurrency < 8) {
|
|
2443
|
+
logger.warn(`⚠️ Solo se usarán ${maxConcurrency} hilos para ${fileCount} archivos.`);
|
|
2444
|
+
logger.warn(` 💡 Tip: Establece VERSACOMPILER_MAX_THREADS para forzar más hilos:`);
|
|
2445
|
+
logger.warn(` 💡 export VERSACOMPILER_MAX_THREADS=16`);
|
|
2446
|
+
}
|
|
2447
|
+
// Precargar módulos ANTES de iniciar la compilación concurrente
|
|
2448
|
+
// Esto evita que múltiples hilos intenten cargar node:crypto simultáneamente
|
|
2449
|
+
const moduleManager = OptimizedModuleManager.getInstance();
|
|
2450
|
+
const fileExtensions = new Set(filesToCompile.map(f => path.extname(f)));
|
|
2451
|
+
await moduleManager.preloadForContext('batch', fileExtensions);
|
|
2452
|
+
// Configurar worker pool para modo batch
|
|
2453
|
+
try {
|
|
2454
|
+
const { TypeScriptWorkerPool } = (await import('./typescript-worker-pool.js'));
|
|
2455
|
+
const workerPool = TypeScriptWorkerPool.getInstance();
|
|
2456
|
+
workerPool.setMode('batch');
|
|
2457
|
+
}
|
|
2458
|
+
catch {
|
|
2459
|
+
// Error silencioso en configuración del pool
|
|
2460
|
+
}
|
|
2461
|
+
// Fase 6: Compilación (el progreso continúa en compileWithConcurrencyLimit)
|
|
2462
|
+
progressManager.updateProgress(`🚀 Iniciando compilación de ${fileCount} archivos...`);
|
|
2463
|
+
await compileWithConcurrencyLimit(filesToCompile, maxConcurrency); // Guardar cache al final
|
|
2464
|
+
progressManager.updateProgress('💾 Guardando cache...');
|
|
2465
|
+
await saveCache();
|
|
2466
|
+
// ✨ FIX #4: Limpiar módulos no usados después de compilación masiva
|
|
2467
|
+
progressManager.updateProgress('🧹 Limpiando módulos no usados...');
|
|
2468
|
+
moduleManager.cleanupUnusedModules();
|
|
2469
|
+
const endTime = Date.now();
|
|
2470
|
+
const elapsedTime = showTimingForHumans(endTime - startTime); // Finalizar progreso
|
|
2471
|
+
progressManager.endProgress();
|
|
2472
|
+
// Mostrar resumen de compilación con tiempo total
|
|
2473
|
+
await displayCompilationSummary(env.VERBOSE === 'true', elapsedTime);
|
|
2474
|
+
}
|
|
2475
|
+
catch (error) {
|
|
2476
|
+
// Asegurar que el progreso termine en caso de error
|
|
2477
|
+
const progressManager = ProgressManager.getInstance();
|
|
2478
|
+
if (progressManager.isActive()) {
|
|
2479
|
+
progressManager.endProgress();
|
|
2480
|
+
}
|
|
2481
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
2482
|
+
logger.error(`🚩 Error al compilar todos los archivos: ${errorMessage}`);
|
|
2483
|
+
// Registrar el error en el sistema unificado
|
|
2484
|
+
await handleCompilationError(error instanceof Error ? error : new Error(String(error)), 'compilación general', 'all', 'all', env.VERBOSE === 'true'); // Mostrar resumen incluso si hay errores generales
|
|
2485
|
+
await displayCompilationSummary(env.VERBOSE === 'true');
|
|
2486
|
+
}
|
|
2487
|
+
}
|
|
2488
|
+
/**
|
|
2489
|
+
* 🎨 Obtiene icono apropiado para cada etapa
|
|
2490
|
+
*/
|
|
2491
|
+
function getStageIcon(stage) {
|
|
2492
|
+
const icons = {
|
|
2493
|
+
vue: '🎨',
|
|
2494
|
+
typescript: '📘',
|
|
2495
|
+
standardization: '💛',
|
|
2496
|
+
minification: '🗜️',
|
|
2497
|
+
tailwind: '🎨',
|
|
2498
|
+
'file-read': '📖',
|
|
2499
|
+
default: '⚙️',
|
|
2500
|
+
};
|
|
2501
|
+
return icons[stage] ?? '⚙️';
|
|
2502
|
+
}
|
|
2503
|
+
/**
|
|
2504
|
+
* Crea una barra de progreso visual con porcentaje
|
|
2505
|
+
*/
|
|
2506
|
+
function createProgressBarWithPercentage(percentage, width) {
|
|
2507
|
+
const filled = Math.round((percentage / 100) * width);
|
|
2508
|
+
const empty = width - filled;
|
|
2509
|
+
// Usar código directo para evitar problemas de importación
|
|
2510
|
+
const greenBar = '\x1b[32m' + '█'.repeat(filled) + '\x1b[0m';
|
|
2511
|
+
const grayBar = '\x1b[90m' + '░'.repeat(empty) + '\x1b[0m';
|
|
2512
|
+
return `${greenBar}${grayBar} ${percentage}%`;
|
|
2513
|
+
}
|
|
2514
|
+
// Función wrapper para compatibilidad con tests
|
|
2515
|
+
// ✨ FIX #5: Con timeout de 60 segundos para compilación individual
|
|
2516
|
+
export async function compileFile(filePath) {
|
|
2517
|
+
return await withTimeout(initCompile(filePath, true, 'individual'), 60000, `compilación de ${path.basename(filePath)}`);
|
|
2518
|
+
}
|
|
2519
|
+
export { WatchModeOptimizer };
|
|
2520
|
+
//# sourceMappingURL=compile.js.map
|