@archznn/xavva 3.1.3 → 3.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.
Files changed (80) hide show
  1. package/README.md +221 -12
  2. package/package.json +3 -2
  3. package/src/commands/AuditCommand.ts +12 -10
  4. package/src/commands/BuildCommand.ts +9 -7
  5. package/src/commands/ChangelogCommand.ts +5 -5
  6. package/src/commands/CleanCommand.ts +242 -0
  7. package/src/commands/CompletionCommand.ts +7 -7
  8. package/src/commands/DbCommand.ts +33 -31
  9. package/src/commands/DeployCommand.ts +252 -229
  10. package/src/commands/DepsCommand.ts +174 -174
  11. package/src/commands/DockerCommand.ts +14 -14
  12. package/src/commands/DoctorCommand.ts +252 -239
  13. package/src/commands/EncodingCommand.ts +19 -19
  14. package/src/commands/HealthCommand.ts +7 -7
  15. package/src/commands/HelpCommand.ts +34 -14
  16. package/src/commands/HistoryCommand.ts +5 -5
  17. package/src/commands/HttpCommand.ts +6 -6
  18. package/src/commands/IdeCommand.ts +313 -0
  19. package/src/commands/InitCommand.ts +26 -25
  20. package/src/commands/LogsCommand.ts +8 -6
  21. package/src/commands/ProfilesCommand.ts +6 -6
  22. package/src/commands/RedoCommand.ts +2 -2
  23. package/src/commands/RunCommand.ts +64 -24
  24. package/src/commands/StartCommand.ts +9 -7
  25. package/src/commands/TestCommand.ts +4 -4
  26. package/src/commands/TomcatCommand.ts +219 -100
  27. package/src/config/versions.ts +111 -9
  28. package/src/di/container.ts +239 -105
  29. package/src/errors/ErrorHandler.ts +23 -19
  30. package/src/errors/errorMessages.ts +235 -0
  31. package/src/index.ts +11 -3
  32. package/src/logging/FileLogger.ts +235 -0
  33. package/src/logging/Logger.ts +545 -0
  34. package/src/logging/OperationLogger.ts +296 -0
  35. package/src/logging/ProgressLogger.ts +187 -0
  36. package/src/logging/TableLogger.ts +246 -0
  37. package/src/logging/colors.ts +167 -0
  38. package/src/logging/constants.ts +176 -0
  39. package/src/logging/formatters.ts +337 -0
  40. package/src/logging/index.ts +93 -0
  41. package/src/logging/types.ts +64 -0
  42. package/src/plugins/PluginManager.ts +325 -0
  43. package/src/plugins/types.ts +82 -0
  44. package/src/services/AuditService.ts +5 -3
  45. package/src/services/BuildService.ts +15 -17
  46. package/src/services/DashboardService.ts +14 -3
  47. package/src/services/DbService.ts +35 -34
  48. package/src/services/DependencyAnalyzerService.ts +18 -18
  49. package/src/services/DependencyCacheService.ts +303 -0
  50. package/src/services/DeployWatcher.ts +127 -23
  51. package/src/services/DockerService.ts +3 -3
  52. package/src/services/EmbeddedTomcatService.ts +13 -12
  53. package/src/services/FileWatcher.ts +15 -7
  54. package/src/services/HttpService.ts +5 -5
  55. package/src/services/LogAnalyzer.ts +26 -22
  56. package/src/services/PerformanceProfiler.ts +267 -0
  57. package/src/services/ProjectService.ts +3 -0
  58. package/src/services/TestService.ts +3 -3
  59. package/src/services/TomcatService.ts +46 -25
  60. package/src/services/tomcat/TomcatBackupManager.ts +330 -0
  61. package/src/services/tomcat/TomcatChecksumVerifier.ts +211 -0
  62. package/src/services/tomcat/TomcatCompatibilityChecker.ts +298 -0
  63. package/src/services/tomcat/TomcatDownloadCache.ts +250 -0
  64. package/src/services/tomcat/TomcatDownloadService.ts +335 -0
  65. package/src/services/tomcat/TomcatInstallerService.ts +474 -0
  66. package/src/services/tomcat/TomcatMirrorManager.ts +181 -0
  67. package/src/services/tomcat/index.ts +36 -0
  68. package/src/services/tomcat/types.ts +120 -0
  69. package/src/types/args.ts +68 -1
  70. package/src/types/configSchema.ts +174 -0
  71. package/src/utils/ChangelogGenerator.ts +11 -11
  72. package/src/utils/LoggerLevel.ts +44 -20
  73. package/src/utils/ProgressBar.ts +87 -46
  74. package/src/utils/argsParser.ts +260 -0
  75. package/src/utils/config.ts +340 -189
  76. package/src/utils/constants.ts +87 -9
  77. package/src/utils/dryRun.ts +192 -0
  78. package/src/utils/processManager.ts +23 -7
  79. package/src/utils/security.ts +293 -0
  80. package/src/utils/ui.ts +299 -428
@@ -0,0 +1,267 @@
1
+ /**
2
+ * Profiler de Performance
3
+ *
4
+ * Mede tempo de execução de operações e gera relatórios
5
+ * Uso: xavva build --profile ou xavva deploy --profile
6
+ */
7
+
8
+ import { Logger } from "../logging";
9
+
10
+ interface PhaseTiming {
11
+ phase: string;
12
+ startTime: number;
13
+ endTime?: number;
14
+ duration?: number;
15
+ parent?: string;
16
+ }
17
+
18
+ interface ProfilerConfig {
19
+ enabled: boolean;
20
+ verbose: boolean;
21
+ }
22
+
23
+ export class PerformanceProfiler {
24
+ private timings: Map<string, PhaseTiming> = new Map();
25
+ private completedTimings: PhaseTiming[] = [];
26
+ private stack: string[] = [];
27
+ private config: ProfilerConfig;
28
+ private logger = Logger.getInstance();
29
+ private globalStart: number = 0;
30
+
31
+ constructor(config: Partial<ProfilerConfig> = {}) {
32
+ this.config = {
33
+ enabled: true,
34
+ verbose: false,
35
+ ...config,
36
+ };
37
+ }
38
+
39
+ /**
40
+ * Inicia profiling global
41
+ */
42
+ start(): void {
43
+ if (!this.config.enabled) return;
44
+ this.globalStart = performance.now();
45
+ this.timings.clear();
46
+ this.completedTimings = [];
47
+ this.stack = [];
48
+ }
49
+
50
+ /**
51
+ * Inicia uma fase
52
+ */
53
+ startPhase(phase: string): void {
54
+ if (!this.config.enabled) return;
55
+
56
+ const parent = this.stack.length > 0 ? this.stack[this.stack.length - 1] : undefined;
57
+
58
+ const timing: PhaseTiming = {
59
+ phase,
60
+ startTime: performance.now(),
61
+ parent,
62
+ };
63
+
64
+ this.timings.set(phase, timing);
65
+ this.stack.push(phase);
66
+
67
+ if (this.config.verbose) {
68
+ this.logger.debug(`[Profiler] Iniciando: ${phase}`);
69
+ }
70
+ }
71
+
72
+ /**
73
+ * Finaliza uma fase
74
+ */
75
+ endPhase(phase?: string): void {
76
+ if (!this.config.enabled) return;
77
+
78
+ const phaseName = phase || this.stack.pop();
79
+ if (!phaseName) return;
80
+
81
+ const timing = this.timings.get(phaseName);
82
+ if (!timing) return;
83
+
84
+ timing.endTime = performance.now();
85
+ timing.duration = timing.endTime - timing.startTime;
86
+
87
+ this.completedTimings.push({ ...timing });
88
+ this.timings.delete(phaseName);
89
+
90
+ if (this.config.verbose) {
91
+ this.logger.debug(`[Profiler] Finalizado: ${phaseName} (${timing.duration.toFixed(2)}ms)`);
92
+ }
93
+ }
94
+
95
+ /**
96
+ * Executa função dentro de uma fase
97
+ */
98
+ async withPhase<T>(phase: string, fn: () => Promise<T>): Promise<T> {
99
+ this.startPhase(phase);
100
+ try {
101
+ return await fn();
102
+ } finally {
103
+ this.endPhase(phase);
104
+ }
105
+ }
106
+
107
+ /**
108
+ * Executa função síncrona dentro de uma fase
109
+ */
110
+ withPhaseSync<T>(phase: string, fn: () => T): T {
111
+ this.startPhase(phase);
112
+ try {
113
+ return fn();
114
+ } finally {
115
+ this.endPhase(phase);
116
+ }
117
+ }
118
+
119
+ /**
120
+ * Gera relatório de performance
121
+ */
122
+ generateReport(): void {
123
+ if (!this.config.enabled) return;
124
+
125
+ const totalTime = performance.now() - this.globalStart;
126
+
127
+ this.logger.newline();
128
+ this.logger.section("📊 Profile de Performance");
129
+
130
+ // Ordena por duração (decrescente)
131
+ const sorted = [...this.completedTimings].sort((a, b) =>
132
+ (b.duration || 0) - (a.duration || 0)
133
+ );
134
+
135
+ // Agrupa por nível
136
+ const rootPhases = sorted.filter(t => !t.parent);
137
+
138
+ for (const phase of rootPhases) {
139
+ this.printPhase(phase, sorted, 0, totalTime);
140
+ }
141
+
142
+ // Linha de total
143
+ this.logger.divider();
144
+ this.printTimingLine("TOTAL", totalTime, totalTime, 0, true);
145
+
146
+ // Estatísticas
147
+ this.printStats();
148
+ }
149
+
150
+ private printPhase(phase: PhaseTiming, all: PhaseTiming[], level: number, total: number): void {
151
+ this.printTimingLine(phase.phase, phase.duration || 0, total, level);
152
+
153
+ // Filhos
154
+ const children = all.filter(t => t.parent === phase.phase);
155
+ for (const child of children) {
156
+ this.printPhase(child, all, level + 1, total);
157
+ }
158
+ }
159
+
160
+ private printTimingLine(name: string, duration: number, total: number, level: number, isTotal = false): void {
161
+ const indent = " ".repeat(level);
162
+ const percent = total > 0 ? (duration / total) * 100 : 0;
163
+ const bar = this.renderBar(percent);
164
+
165
+ const timeStr = this.formatDuration(duration);
166
+ const percentStr = percent.toFixed(1).padStart(5);
167
+
168
+ const nameFormatted = isTotal
169
+ ? `${name}`
170
+ : `${indent}${name}`;
171
+
172
+ this.logger.info(`${nameFormatted.padEnd(25)} ${timeStr.padStart(10)} ${bar} ${percentStr}%`);
173
+ }
174
+
175
+ private renderBar(percent: number): string {
176
+ const width = 20;
177
+ const filled = Math.round((percent / 100) * width);
178
+ const empty = width - filled;
179
+
180
+ const bar = "█".repeat(filled) + "░".repeat(empty);
181
+ return `[${bar}]`;
182
+ }
183
+
184
+ private formatDuration(ms: number): string {
185
+ if (ms < 1000) {
186
+ return `${ms.toFixed(0)}ms`;
187
+ } else if (ms < 60000) {
188
+ return `${(ms / 1000).toFixed(2)}s`;
189
+ } else {
190
+ const mins = Math.floor(ms / 60000);
191
+ const secs = ((ms % 60000) / 1000).toFixed(1);
192
+ return `${mins}m ${secs}s`;
193
+ }
194
+ }
195
+
196
+ private printStats(): void {
197
+ const times = this.completedTimings.map(t => t.duration || 0);
198
+
199
+ if (times.length === 0) return;
200
+
201
+ const avg = times.reduce((a, b) => a + b, 0) / times.length;
202
+ const max = Math.max(...times);
203
+ const min = Math.min(...times);
204
+
205
+ this.logger.newline();
206
+ this.logger.info("📈 Estatísticas:");
207
+ this.logger.info(` Fases: ${times.length}`);
208
+ this.logger.info(` Média: ${this.formatDuration(avg)}`);
209
+ this.logger.info(` Mín: ${this.formatDuration(min)}`);
210
+ this.logger.info(` Máx: ${this.formatDuration(max)}`);
211
+ }
212
+
213
+ /**
214
+ * Exporta dados para JSON
215
+ */
216
+ exportJSON(): object {
217
+ const totalTime = performance.now() - this.globalStart;
218
+
219
+ return {
220
+ totalTime,
221
+ phases: this.completedTimings.map(t => ({
222
+ phase: t.phase,
223
+ duration: t.duration,
224
+ parent: t.parent,
225
+ })),
226
+ timestamp: new Date().toISOString(),
227
+ };
228
+ }
229
+
230
+ /**
231
+ * Limpa dados
232
+ */
233
+ reset(): void {
234
+ this.timings.clear();
235
+ this.completedTimings = [];
236
+ this.stack = [];
237
+ this.globalStart = 0;
238
+ }
239
+
240
+ /**
241
+ * Verifica se profiling está ativo
242
+ */
243
+ isEnabled(): boolean {
244
+ return this.config.enabled;
245
+ }
246
+
247
+ /**
248
+ * Ativa/desativa profiling
249
+ */
250
+ setEnabled(enabled: boolean): void {
251
+ this.config.enabled = enabled;
252
+ }
253
+ }
254
+
255
+ // Singleton global
256
+ let globalProfiler: PerformanceProfiler | null = null;
257
+
258
+ export function getProfiler(config?: Partial<ProfilerConfig>): PerformanceProfiler {
259
+ if (!globalProfiler) {
260
+ globalProfiler = new PerformanceProfiler(config);
261
+ }
262
+ return globalProfiler;
263
+ }
264
+
265
+ export function resetProfiler(): void {
266
+ globalProfiler = null;
267
+ }
@@ -1,8 +1,11 @@
1
1
  import { existsSync, readdirSync, statSync, readFileSync } from "fs";
2
2
  import path from "path";
3
3
  import type { ProjectConfig } from "../types/config";
4
+ import { Logger } from "../logging";
4
5
 
5
6
  export class ProjectService {
7
+ private logger = Logger.getInstance();
8
+
6
9
  constructor(private config: ProjectConfig) {}
7
10
 
8
11
  getBuildOutputDir(): string {
@@ -3,7 +3,7 @@
3
3
  * Suporta Maven e Gradle com modo watch
4
4
  */
5
5
 
6
- import { Logger } from "../utils/ui";
6
+ import { Logger, C } from "../utils/ui";
7
7
  import { spawn } from "child_process";
8
8
  import { watch, type FSWatcher } from "fs";
9
9
  import path from "path";
@@ -287,8 +287,8 @@ export class TestService {
287
287
  }
288
288
 
289
289
  Logger.info("Total", result.totalTests);
290
- Logger.info("Passed", `${Logger.C.success}${result.passed}${Logger.C.reset}`);
291
- Logger.info("Failed", result.failed > 0 ? `${Logger.C.error}${result.failed}${Logger.C.reset}` : "0");
290
+ Logger.info("Passed", `${C.success}${result.passed}${C.reset}`);
291
+ Logger.info("Failed", result.failed > 0 ? `${C.error}${result.failed}${C.reset}` : "0");
292
292
  Logger.info("Skipped", result.skipped);
293
293
  Logger.info("Duration", `${(result.duration / 1000).toFixed(2)}s`);
294
294
 
@@ -1,7 +1,9 @@
1
1
  import type { TomcatConfig, AppConfig } from "../types";
2
2
  import { getHotswapAgentUrl, VERSIONS } from "../config/versions";
3
3
  import { NetworkError } from "../errors/XavvaError";
4
- import { Logger } from "../utils/ui";
4
+ import { Logger } from "../logging";
5
+ import { Logger as LoggerLegacy } from "../utils/ui";
6
+ import { NOISE_PATTERNS } from "../logging/constants";
5
7
  import { ProgressBar, ThemedSpinner } from "../utils/ProgressBar";
6
8
  import type { Subprocess } from "bun";
7
9
  import { ProjectService } from "./ProjectService";
@@ -20,11 +22,12 @@ import {
20
22
  export class TomcatService {
21
23
  private activeConfig: TomcatConfig;
22
24
  private currentProcess: Subprocess | null = null;
23
- private stopStartupSpinner?: (success?: boolean) => void;
25
+ private startupSpinner?: { stop: (success?: boolean) => void; update: (msg: string) => void };
24
26
  public onReady?: () => void;
25
27
  private pid: number | null = null;
26
28
  private projectService: ProjectService | null = null;
27
29
  private hasReadyBeenCalled: boolean = false;
30
+ private logger = Logger.getInstance();
28
31
 
29
32
  constructor(customConfig: TomcatConfig) {
30
33
  this.activeConfig = customConfig;
@@ -82,7 +85,7 @@ export class TomcatService {
82
85
  }
83
86
 
84
87
  if (pid) {
85
- Logger.step(`Freeing port ${this.activeConfig.port}`);
88
+ this.logger.step(`Liberando porta ${this.activeConfig.port}`);
86
89
  const killCmd = getKillCommand(pid);
87
90
  Bun.spawnSync(killCmd);
88
91
  }
@@ -126,13 +129,13 @@ export class TomcatService {
126
129
 
127
130
  await Promise.all(tasks);
128
131
  } catch (e) {
129
- Logger.warn("Não foi possível limpar totalmente a pasta webapps ou cache.");
132
+ this.logger.warn("Não foi possível limpar totalmente a pasta webapps ou cache.");
130
133
  }
131
134
  }
132
135
 
133
136
  stop() {
134
137
  if (this.currentProcess) {
135
- Logger.warn("Stopping active server...");
138
+ this.logger.warn("Parando servidor ativo...");
136
139
  this.currentProcess.kill();
137
140
  this.currentProcess = null;
138
141
  }
@@ -210,7 +213,7 @@ export class TomcatService {
210
213
  Logger.success(`HotswapAgent v${VERSIONS.HOTSWAP_AGENT.VERSION} instalado!`);
211
214
  return agentPath;
212
215
  } catch (e: any) {
213
- Logger.warn("Falha ao baixar HotswapAgent. Usando hot swap padrão da JVM.");
216
+ this.logger.warn("Falha ao baixar HotswapAgent. Usando hot swap padrão da JVM.");
214
217
 
215
218
  // Limpa arquivo parcial se existir
216
219
  if (existsSync(agentPath)) {
@@ -314,13 +317,13 @@ export class TomcatService {
314
317
  }
315
318
 
316
319
  if (config.project.debug) {
317
- Logger.debug(`Java Debugger habilitado na porta ${config.project.debugPort}`);
320
+ this.logger.debug(`Porta do debugger ${config.project.debugPort}`);
318
321
  env.JPDA_ADDRESS = String(config.project.debugPort);
319
322
  env.JPDA_TRANSPORT = "dt_socket";
320
323
  }
321
324
 
322
325
  if ((config.project.cleanLogs || config.project.quiet) && !config.project.verbose) {
323
- this.stopStartupSpinner = Logger.spinner("Starting Tomcat server");
326
+ this.startupSpinner = this.logger.spinner("Iniciando servidor Tomcat");
324
327
  }
325
328
 
326
329
  this.currentProcess = Bun.spawn([binPath, ...args], {
@@ -359,9 +362,9 @@ export class TomcatService {
359
362
  // Detect startup completion
360
363
  if (cleanLine.includes("Server startup in") || cleanLine.includes("SEVERE") || cleanLine.includes("Exception")) {
361
364
  const isSuccess = cleanLine.includes("Server startup in");
362
- if (this.stopStartupSpinner) {
363
- this.stopStartupSpinner(isSuccess);
364
- this.stopStartupSpinner = undefined;
365
+ if (this.startupSpinner) {
366
+ this.startupSpinner.stop(isSuccess);
367
+ this.startupSpinner = undefined;
365
368
  }
366
369
  if (isSuccess && this.onReady && !this.hasReadyBeenCalled) {
367
370
  this.hasReadyBeenCalled = true;
@@ -371,10 +374,12 @@ export class TomcatService {
371
374
 
372
375
  // Verbose: formata logs do Tomcat
373
376
  if (verbose) {
374
- if (Logger.isTomcatNoise(cleanLine)) {
375
- continue; // Silencia noise completamente
377
+ // No modo verbose, só filtra o noise básico (CATALINA_, JRE_HOME, etc)
378
+ // Logs do HOTSWAP e outros são mostrados
379
+ if (LoggerLegacy.isTomcatNoise(cleanLine) && !LoggerLegacy.isTomcatVerboseLog(cleanLine)) {
380
+ continue; // Silencia noise básico
376
381
  }
377
- const formatted = Logger.formatTomcatLog(cleanLine);
382
+ const formatted = LoggerLegacy.formatTomcatLog(cleanLine);
378
383
  if (formatted) {
379
384
  console.log(formatted);
380
385
  }
@@ -384,30 +389,46 @@ export class TomcatService {
384
389
  // Clean mode: filtra noise
385
390
  if (clean) {
386
391
  // Sempre filtra noise do sistema
387
- if (Logger.isSystemNoise(cleanLine)) continue;
392
+ if (NOISE_PATTERNS.system.some(n => cleanLine.includes(n))) continue;
388
393
 
389
394
  // Quiet mode: só mostra essencial
390
- if (quiet && !Logger.isEssential(cleanLine)) {
395
+ if (quiet && !LoggerLegacy.isEssential(cleanLine)) {
391
396
  if (cleanLine.includes("INFO") && !cleanLine.includes("ERROR")) continue;
392
397
  }
393
398
 
394
399
  // Grep filter
395
400
  if (grep && !cleanLine.toLowerCase().includes(grep.toLowerCase())) {
396
- if (!Logger.isEssential(cleanLine)) continue;
401
+ if (!LoggerLegacy.isEssential(cleanLine)) continue;
397
402
  }
398
403
 
399
- const summarized = Logger.summarize(cleanLine);
400
- if (summarized) {
401
- // mostra se não for vazio (rate limiting)
402
- if (summarized.trim()) Logger.log(summarized);
404
+ const summarized = LoggerLegacy.summarize(cleanLine);
405
+ if (summarized && summarized.trim()) {
406
+ // Para o spinner antes de mostrar log para não quebrar a interface
407
+ if (this.startupSpinner) {
408
+ this.startupSpinner.stop(true);
409
+ this.startupSpinner = undefined;
410
+ }
411
+ console.log(summarized);
403
412
  }
404
413
  } else {
405
- // Non-clean: mostra tudo mas formata
406
- if (Logger.isSystemNoise(cleanLine)) {
407
- // Silencia completamente noise em non-clean também
414
+ // Modo normal (não verbose, não clean): filtra mais agressivamente
415
+ // mostra logs essenciais e resumos
416
+ if (NOISE_PATTERNS.system.some(n => cleanLine.includes(n))) {
408
417
  continue;
409
418
  }
410
- Logger.log(cleanLine);
419
+
420
+ // No modo normal, só mostra se for essencial ou tiver resumo
421
+ const summarized = LoggerLegacy.summarize(cleanLine);
422
+ if (summarized && summarized.trim()) {
423
+ // Para o spinner antes de mostrar log
424
+ if (this.startupSpinner) {
425
+ this.startupSpinner.stop(true);
426
+ this.startupSpinner = undefined;
427
+ }
428
+ console.log(summarized);
429
+ }
430
+ // Silencia logs não essenciais no modo normal
431
+ // (WARNING, ADVERTÊNCIA, INFO genérico, etc. só aparecem no verbose)
411
432
  }
412
433
  }
413
434
  }