@archznn/xavva 3.1.2 → 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 +43 -14
  9. package/src/commands/DeployCommand.ts +252 -229
  10. package/src/commands/DepsCommand.ts +174 -174
  11. package/src/commands/DockerCommand.ts +35 -4
  12. package/src/commands/DoctorCommand.ts +252 -239
  13. package/src/commands/EncodingCommand.ts +26 -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 +27 -1
  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 +25 -1
  26. package/src/commands/TomcatCommand.ts +232 -88
  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 +20 -6
  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
package/src/utils/ui.ts CHANGED
@@ -1,407 +1,369 @@
1
+ /**
2
+ * UI Utils - Adaptador para o novo sistema de logging
3
+ *
4
+ * Mantém compatibilidade com código existente enquanto usa
5
+ * internamente o novo sistema de logging em src/logging/
6
+ *
7
+ * @deprecated Use o novo sistema de logging em src/logging/
8
+ */
9
+
1
10
  import pkg from "../../package.json";
2
11
  import type { DashboardService } from "../services/DashboardService";
12
+ import { Logger, Colors, Icons, colorize, stripAnsi, padText } from "../logging";
13
+ import { LAYOUT, NOISE_PATTERNS, ESSENTIAL_PATTERNS, SPINNER_FRAMES, SPINNER_INTERVAL } from "../logging/constants";
3
14
 
4
- // Paleta de cores moderna e minimalista
5
- const C = {
6
- reset: "\x1b[0m",
7
- bold: "\x1b[1m",
8
- dim: "\x1b[2m",
9
- italic: "\x1b[3m",
10
-
11
- // Cores principais
12
- primary: "\x1b[36m", // Cyan
13
- primaryBright: "\x1b[96m", // Bright Cyan
14
- secondary: "\x1b[35m", // Magenta
15
-
16
- // Estados
17
- success: "\x1b[32m", // Green
18
- successBright: "\x1b[92m", // Bright Green
19
- warning: "\x1b[33m", // Yellow
20
- warningBright: "\x1b[93m", // Bright Yellow
21
- error: "\x1b[31m", // Red
22
- errorBright: "\x1b[91m", // Bright Red
23
- info: "\x1b[34m", // Blue
24
-
25
- // Neutros
26
- white: "\x1b[37m",
27
- gray: "\x1b[90m",
28
- lightGray: "\x1b[37m",
29
- darkGray: "\x1b[38;5;240m",
30
- };
31
-
32
- export class Logger {
33
- public static readonly C = C;
15
+ // Re-exporta Cores para compatibilidade
16
+ export const C = Colors;
17
+
18
+ // Largura das colunas para alinhamento
19
+ export const COL = LAYOUT.columns;
20
+
21
+ /**
22
+ * @deprecated Use Logger de src/logging/ diretamente
23
+ *
24
+ * Classe Logger legada - adaptador para o novo sistema
25
+ */
26
+ export class LoggerLegacy {
34
27
  private static dashboard: DashboardService | null = null;
35
28
  private static activeSpinner: { stop: (success?: boolean) => void } | null = null;
36
- private static lastDomain = "";
29
+ private static sectionOpen = false;
30
+ private static logger = Logger.getInstance();
37
31
 
38
32
  static setDashboard(dashboard: DashboardService) {
39
33
  this.dashboard = dashboard;
34
+ // Também configura no novo logger se necessário
35
+ }
36
+
37
+ // Helper: remove ANSI codes para calcular tamanho
38
+ private static plain(s: string): string {
39
+ return stripAnsi(s);
40
+ }
41
+
42
+ // Helper: pad com consideração de ANSI codes
43
+ private static pad(s: string, len: number): string {
44
+ return padText(s, len);
40
45
  }
41
46
 
42
- // Banner completo com informações do ambiente
47
+ /**
48
+ * Banner clean e moderno
49
+ */
43
50
  static banner(command?: string, profile?: string, encoding?: string) {
44
51
  console.clear();
45
- const git = this.getGitContext();
46
52
  const name = process.cwd().split(/[/\\]/).pop() || "project";
47
53
  const now = new Date().toLocaleTimeString('pt-BR', { hour: '2-digit', minute: '2-digit' });
48
- const W = 52; // Largura interna do box (espaço entre os ║)
49
-
50
- // Remove ANSI codes para calcular tamanho
51
- const plain = (s: string) => s.replace(/\x1b\[\d+m/g, '');
52
-
53
- // Cria uma linha com conteúdo alinhado à esquerda
54
- const row = (content: string) => {
55
- const pad = W - plain(content).length;
56
- return `${C.gray}║${C.reset} ${content}${' '.repeat(Math.max(0, pad))}${C.gray}║${C.reset}`;
57
- };
58
-
59
- // Linha superior
60
- console.log(`${C.gray}╔══════════════════════════════════════════════════════╗${C.reset}`);
61
-
62
- // Linha 1: XAVVA v2.2.0 + hora alinhada à direita
63
- const verPlain = `XAVVA v${pkg.version}`; // "XAVVA v2.2.0" = 12 chars
64
- const timePlain = now; // 5 chars
65
- const gap1 = W - verPlain.length - timePlain.length - 1; // -1 para deixar 1 espaço antes do ║
66
- const line1Content = `${C.primary}${C.bold}XAVVA${C.reset}${C.gray} v${pkg.version}${C.reset}${' '.repeat(Math.max(1, gap1))}${C.dim}${now}${C.reset}`;
67
- console.log(row(line1Content));
54
+ const git = this.getGitContext();
68
55
 
69
- // Linha 2: Nome do projeto
70
- console.log(row(`${C.white}${C.bold}${name}${C.reset}`));
56
+ // Header linha única
57
+ const left = `${C.primary}${C.bold}xavva${C.reset} v${pkg.version}`;
58
+ const center = `${C.white}${C.bold}${name}${C.reset}`;
59
+ const right = `${C.dim}${now}${C.reset}`;
71
60
 
72
- // Linha 3: Git info
73
- if (git.branch) {
74
- const gitStatus = this.getGitStatus();
75
- const dirty = gitStatus.dirty ? '*' : '';
76
- const author = git.author ? git.author.split(' ')[0].slice(0, 10) : '';
77
- const hash = git.hash ? git.hash.slice(0, 7) : '';
78
- const branchDisplay = git.branch.slice(0, 20); // Limita branch
79
- const gitLine = `${C.gray}git:${C.reset}${C.secondary}${branchDisplay}${dirty}${C.reset} ${C.dim}${hash}${C.reset} ${C.gray}by${C.reset} ${C.dim}${author}${C.reset}`;
80
- console.log(row(gitLine));
81
- }
61
+ console.log();
62
+ console.log(` ${left} ${center} ${right}`);
82
63
 
83
- // Divisor
84
- console.log(`${C.gray}╠══════════════════════════════════════════════════════╣${C.reset}`);
64
+ // Linha divisória simples
65
+ console.log(` ${C.darkGray}${'─'.repeat(60)}${C.reset}`);
85
66
 
86
- // Config: mode, profile, java, encoding
87
- const cfg: string[] = [];
88
- if (command) cfg.push(`${C.primary}${command}${C.reset}`);
89
- if (profile) cfg.push(`${C.warning}${profile}${C.reset}`);
90
- const jv = this.getJavaVersion();
91
- if (jv) cfg.push(`${C.info}java:${jv}${C.reset}`);
92
- if (encoding) cfg.push(`${C.gray}${encoding}${C.reset}`);
67
+ // Contexto em uma linha
68
+ const ctx: string[] = [];
69
+ if (command) ctx.push(`${C.primary}${command}${C.reset}`);
70
+ if (profile) ctx.push(`${C.warning}${profile}${C.reset}`);
71
+ const java = this.getJavaVersion();
72
+ if (java) ctx.push(`java:${C.dim}${java}${C.reset}`);
73
+ if (git.branch) ctx.push(`${C.gray}git:${git.branch}${git.dirty ? '*' : ''}${C.reset}`);
93
74
 
94
- if (cfg.length) {
95
- const sep = `${C.gray} │ ${C.reset}`;
96
- const cfgLine = `${C.dim}mode${C.reset} : ${cfg.join(sep)}`;
97
- console.log(row(cfgLine));
75
+ if (ctx.length > 0) {
76
+ console.log(` ${ctx.join(' ')}`);
98
77
  }
99
78
 
100
- // Memory
101
- const mem = process.memoryUsage();
102
- const mb = Math.round((mem.heapUsed || mem.rss || 0) / 1024 / 1024);
103
- console.log(row(`${C.dim}mem${C.reset} : ${mb}MB ${C.gray}heap${C.reset}`));
79
+ console.log();
104
80
 
105
- // OS
106
- const plat = process.platform === 'win32' ? 'windows' : process.platform;
107
- console.log(row(`${C.dim}os${C.reset} : ${plat} ${C.gray}|${C.reset} ${process.arch}`));
81
+ // Cabeçalho da tabela
82
+ console.log(` ${C.bold}${this.pad('NOME', COL.name)}${this.pad('STATUS', COL.status)}INFO${C.reset}`);
83
+ console.log(` ${C.darkGray}${'─'.repeat(60)}${C.reset}`);
108
84
 
109
- // Rodapé
110
- console.log(`${C.gray}╚══════════════════════════════════════════════════════╝${C.reset}`);
111
- console.log();
85
+ this.sectionOpen = true;
112
86
  }
113
87
 
114
- private static getGitStatus(): { dirty: boolean; modified: number } {
115
- try {
116
- const result = Bun.spawnSync(["git", "status", "--porcelain"]);
117
- const lines = result.stdout.toString().trim().split('\n').filter(l => l.trim());
118
- return { dirty: lines.length > 0, modified: lines.length };
119
- } catch {
120
- return { dirty: false, modified: 0 };
121
- }
88
+ /**
89
+ * Status lateral estilo docker-compose
90
+ */
91
+ static status(name: string, status: 'pending' | 'running' | 'done' | 'error' | 'warning', info?: string) {
92
+ this.logger.status(name, status, info);
122
93
  }
123
94
 
124
- private static getJavaVersion(): string | null {
125
- try {
126
- const javaBin = process.env.JAVA_HOME
127
- ? `${process.env.JAVA_HOME}/bin/java`
128
- : 'java';
129
- const result = Bun.spawnSync([javaBin, "-version"]);
130
- const output = (result.stderr?.toString() || result.stdout?.toString() || '');
131
- const match = output.match(/version "?(\d+(?:\.\d+)?)/);
132
- if (match) {
133
- const v = match[1];
134
- // Check for DCEVM
135
- if (output.toLowerCase().includes('dcevm') || output.toLowerCase().includes('jbr')) {
136
- return `${v}+dcevm`;
137
- }
138
- return v;
139
- }
140
- return null;
141
- } catch {
142
- return null;
143
- }
95
+ /**
96
+ * Seção de arquivos (para watch mode)
97
+ */
98
+ static filesSection(title: string = "Changes") {
99
+ this.logger.section(title);
100
+ }
101
+
102
+ /**
103
+ * Arquivo individual na seção
104
+ */
105
+ static file(name: string, action: 'changed' | 'compiled' | 'synced' | 'error', path?: string) {
106
+ this.logger.file(name, action, path);
107
+ }
108
+
109
+ /**
110
+ * Resultado/sumário
111
+ */
112
+ static summary(text: string) {
113
+ this.logger.step(text);
114
+ }
115
+
116
+ /**
117
+ * URL formatada
118
+ */
119
+ static url(label: string, url: string) {
120
+ this.logger.url(label, url);
121
+ }
122
+
123
+ /**
124
+ * Divisor simples
125
+ */
126
+ static divider() {
127
+ this.logger.divider();
128
+ }
129
+
130
+ /**
131
+ * Finalização
132
+ */
133
+ static done() {
134
+ this.logger.newline();
144
135
  }
145
136
 
146
- // Seções com divisórias clean
137
+ // ========== Métodos legados - mantidos para compatibilidade ==========
138
+
147
139
  static section(title: string) {
148
- console.log(`${C.gray}┌─ ${C.white}${C.bold}${title}${C.reset}`);
140
+ this.logger.section(title);
149
141
  }
150
142
 
151
143
  static endSection() {
152
- console.log(`${C.gray}└${C.reset}`);
144
+ this.logger.newline();
153
145
  }
154
146
 
155
- // Configurações em formato chave: valor alinhado
156
147
  static config(label: string, value: string | number | boolean) {
157
- const valueStr = String(value);
158
- const isBool = typeof value === 'boolean';
159
- const displayValue = isBool
160
- ? (value ? `${C.successBright}✓${C.reset} ${C.success}enabled${C.reset}` : `${C.gray}○${C.reset} ${C.gray}disabled${C.reset}`)
161
- : `${C.white}${valueStr}${C.reset}`;
162
-
163
- console.log(`${C.gray}│${C.reset} ${C.dim}${label.padEnd(12)}${C.reset} ${C.gray}:${C.reset} ${displayValue}`);
148
+ this.logger.config(label, value);
164
149
  }
165
150
 
166
- // Status com ícones minimalistas
167
151
  static ready(msg: string) {
168
- console.log(`${C.gray}│${C.reset} ${C.success}●${C.reset} ${msg}`);
152
+ this.logger.ready(msg);
169
153
  }
170
154
 
171
155
  static info(label: string, value?: string) {
172
156
  if (value) {
173
- console.log(`${C.gray}│${C.reset} ${C.dim}${label}${C.reset} ${C.gray}:${C.reset} ${C.white}${value}${C.reset}`);
157
+ this.logger.info(`${label}: ${value}`);
174
158
  } else {
175
- console.log(`${C.gray}│${C.reset} ${C.info}ℹ${C.reset} ${label}`);
159
+ this.logger.info(label);
176
160
  }
177
161
  }
178
162
 
179
163
  static success(msg: string) {
180
- console.log(`${C.gray}│${C.reset} ${C.success}✓${C.reset} ${msg}`);
164
+ this.logger.success(msg);
181
165
  }
182
166
 
183
167
  static error(msg: string) {
184
- console.log(`${C.gray}│${C.reset} ${C.error}✗${C.reset} ${C.error}${msg}${C.reset}`);
168
+ this.logger.error(msg);
185
169
  }
186
170
 
187
171
  static warn(msg: string) {
188
- console.log(`${C.gray}│${C.reset} ${C.warning}⚠${C.reset} ${msg}`);
172
+ this.logger.warn(msg);
189
173
  }
190
174
 
191
175
  static debug(msg: string) {
192
- console.log(`${C.gray}│${C.reset} ${C.gray}🐛 ${msg}${C.reset}`);
176
+ this.logger.debug(msg);
193
177
  }
194
178
 
195
179
  static step(msg: string) {
196
- console.log(`${C.gray}│${C.reset} ${C.gray}▸ ${msg}${C.reset}`);
180
+ this.logger.step(msg);
197
181
  }
198
182
 
199
183
  static log(msg: string) {
200
184
  console.log(msg);
201
185
  }
202
186
 
203
- static dim(msg: string) {
204
- console.log(`${C.dim}${msg}${C.reset}`);
187
+ static newline() {
188
+ this.logger.newline();
205
189
  }
206
190
 
207
- static newline() {
208
- console.log();
191
+ // Watch mode - arquivo modificado (legado)
192
+ static fileChanged(filepath: string) {
193
+ const filename = filepath.split(/[/\\]/).pop() || filepath;
194
+ this.file(filename, 'changed');
195
+ }
196
+
197
+ // Watch mode - arquivo compilado/sincronizado (legado)
198
+ static fileSynced(filename: string, action: 'compiled' | 'synced' | 'reloaded' = 'synced') {
199
+ const map = { compiled: 'compiled', synced: 'synced', reloaded: 'reloaded' };
200
+ this.file(filename, action === 'reloaded' ? 'synced' : action, `[${map[action]}]`);
201
+ }
202
+
203
+ // Watch mode - batch de arquivos (legado)
204
+ static filesBatch(count: number, action: string) {
205
+ this.logger.summary(`${count} arquivo(s) ${action}`);
209
206
  }
210
207
 
211
208
  static watcher(msg: string, _type?: string) {
212
- console.log(`${C.gray}│${C.reset} ${C.secondary}◉${C.reset} ${C.dim}watch${C.reset} ${C.gray}:${C.reset} ${msg}`);
209
+ this.logger.debug(`watch: ${msg}`);
213
210
  }
214
211
 
215
212
  static watch(msg: string) {
216
- console.log(`${C.gray}│${C.reset} ${C.secondary}◉${C.reset} ${C.dim}watch${C.reset} ${C.gray}:${C.reset} ${msg}`);
213
+ this.logger.debug(`watch: ${msg}`);
217
214
  }
218
215
 
219
216
  static process(msg: string) {
220
- console.log(`${C.gray}│${C.reset} ${C.primary}▸${C.reset} ${C.dim}process${C.reset} ${C.gray}:${C.reset} ${msg}`);
217
+ this.logger.status('process', 'running', msg);
221
218
  }
222
219
 
223
220
  static build(msg: string) {
224
- console.log(`${C.gray}│${C.reset} ${C.primary}▸${C.reset} ${C.dim}build${C.reset} ${C.gray}:${C.reset} ${msg}`);
221
+ if (msg.includes('completed') || msg.includes('done') || msg.includes('concluído')) {
222
+ this.logger.status('build', 'done', msg);
223
+ } else if (msg.includes('compil')) {
224
+ this.logger.status('build', 'running', msg);
225
+ } else {
226
+ this.logger.status('build', 'pending', msg);
227
+ }
225
228
  }
226
229
 
227
230
  static server(msg: string) {
228
- console.log(`${C.gray}│${C.reset} ${C.primary}▸${C.reset} ${C.dim}server${C.reset} ${C.gray}:${C.reset} ${msg}`);
231
+ if (msg.includes('ready') || msg.includes('done') || msg.includes('pronto')) {
232
+ this.logger.status('server', 'done', msg);
233
+ } else {
234
+ this.logger.status('server', 'running', msg);
235
+ }
229
236
  }
230
237
 
231
238
  static hotswap(msg: string) {
232
- console.log(`${C.gray}│${C.reset} ${C.secondary}↻${C.reset} ${C.dim}hotswap${C.reset} ${C.gray}:${C.reset} ${msg}`);
233
- }
234
-
235
- // URL formatada de forma destacada
236
- static url(label: string, url: string) {
237
- console.log(`${C.gray}│${C.reset} ${C.dim}${label}${C.reset} ${C.gray}:${C.reset} ${C.primaryBright}${C.bold}${url}${C.reset}`);
239
+ this.logger.debug(`hotswap: ${msg}`);
238
240
  }
239
241
 
240
- // Spinner moderno
242
+ /**
243
+ * Spinner moderno
244
+ */
241
245
  static spinner(msg: string) {
242
246
  if (this.dashboard?.isTuiActive()) {
243
247
  return this.dashboard.spinner(msg);
244
248
  }
245
249
 
246
- const frames = ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"];
250
+ // Usa spinner ASCII simples para melhor compatibilidade com Windows
251
+ const frames = ['|', '/', '-', '\\'];
247
252
  let i = 0;
248
253
 
249
- process.stdout.write(`${C.gray}│${C.reset} `);
254
+ process.stdout.write(" ");
250
255
  process.stdout.write("\x1B[?25l");
251
256
 
252
257
  const timer = setInterval(() => {
253
- process.stdout.write(`\r${C.gray}│${C.reset} ${C.primary}${frames[i]}${C.reset} ${C.dim}${msg}${C.reset}`);
258
+ process.stdout.write(`\r ${C.primary}${frames[i]}${C.reset} ${C.dim}${msg}${C.reset}`);
254
259
  i = (i + 1) % frames.length;
255
- }, 80);
260
+ }, SPINNER_INTERVAL);
256
261
 
257
262
  return (success = true) => {
258
263
  clearInterval(timer);
259
264
  process.stdout.write("\x1B[?25h");
260
265
  if (success) {
261
- console.log(`\r${C.gray}│${C.reset} ${C.success}✓${C.reset} ${msg}`);
266
+ console.log(`\r ${C.success}✓${C.reset} ${msg}`);
262
267
  } else {
263
- console.log(`\r${C.gray}│${C.reset} ${C.error}✗${C.reset} ${C.error}${msg}${C.reset}`);
268
+ console.log(`\r ${C.error}✗${C.reset} ${C.error}${msg}${C.reset}`);
264
269
  }
265
270
  };
266
271
  }
267
272
 
268
- // Divisória simples
269
- static divider() {
270
- console.log(`${C.gray}├────────────────────────────────────────────────────────┤${C.reset}`);
271
- }
273
+ // ========== Helpers privados ==========
272
274
 
273
- // Finalização
274
- static done() {
275
- console.log(`${C.gray}└────────────────────────────────────────────────────────┘${C.reset}`);
276
- console.log();
275
+ private static getGitStatus(): { dirty: boolean; modified: number } {
276
+ try {
277
+ const result = Bun.spawnSync(["git", "status", "--porcelain"]);
278
+ const lines = result.stdout.toString().trim().split('\n').filter(l => l.trim());
279
+ return { dirty: lines.length > 0, modified: lines.length };
280
+ } catch {
281
+ return { dirty: false, modified: 0 };
282
+ }
277
283
  }
278
284
 
279
- // Helper para contexto git
280
- static getGitContext(): { branch: string; author: string; hash: string } {
285
+ private static getGitContext(): { branch: string; dirty: boolean } {
281
286
  try {
282
287
  const branch = Bun.spawnSync(["git", "rev-parse", "--abbrev-ref", "HEAD"]).stdout.toString().trim();
283
- const author = Bun.spawnSync(["git", "log", "-1", "--format=%an"]).stdout.toString().trim();
284
- const hash = Bun.spawnSync(["git", "rev-parse", "--short", "HEAD"]).stdout.toString().trim();
285
- return { branch, author, hash };
288
+ const status = this.getGitStatus();
289
+ return { branch, dirty: status.dirty };
286
290
  } catch {
287
- return { branch: "", author: "", hash: "" };
291
+ return { branch: "", dirty: false };
288
292
  }
289
293
  }
290
294
 
291
- // Filtros de noise (mantidos)
292
- // Controle de rate limiting para hotswap
293
- private static lastHotswapTime = 0;
294
- private static hotswapCount = 0;
295
+ private static getJavaVersion(): string | null {
296
+ try {
297
+ const javaBin = process.env.JAVA_HOME
298
+ ? `${process.env.JAVA_HOME}/bin/java`
299
+ : 'java';
300
+ const result = Bun.spawnSync([javaBin, "-version"]);
301
+ const output = (result.stderr?.toString() || result.stdout?.toString() || '');
302
+ const match = output.match(/version "?(\d+(?:\.\d+)?)/);
303
+ if (match) {
304
+ const v = match[1];
305
+ if (output.toLowerCase().includes('dcevm') || output.toLowerCase().includes('jbr')) {
306
+ return `${v}+dcevm`;
307
+ }
308
+ return v;
309
+ }
310
+ return null;
311
+ } catch {
312
+ return null;
313
+ }
314
+ }
295
315
 
316
+ // ========== Log Formatting (mantidos para compatibilidade) ==========
317
+
296
318
  static isSystemNoise(line: string): boolean {
297
- const noise = [
298
- "Using CATALINA_", "Using JRE_HOME", "Using CLASSPATH",
299
- "Scanning for projects...", "Building ", "--- ", "+++ ",
300
- "Arquivos processados em", "milliseconds",
301
- "SLF4J: ", "Discovered plugins:",
302
- "enhanced with plugin initialization", "Hotswap ready",
303
- "autoHotswap.delay", "watchResources=false",
304
- // HotswapAgent noise
305
- "TreeWatcherNIO", "HOTSWAP AGENT", "org.hotswap.agent",
306
- // Jersey/JAX-RS noise
307
- "org.glassfish.jersey", "The (sub)resource method",
308
- // Tomcat noise
309
- "org.apache.catalina", "org.apache.jasper",
310
- ];
311
- return noise.some(n => line.includes(n));
319
+ return NOISE_PATTERNS.system.some(n => line.includes(n));
312
320
  }
313
321
 
314
322
  static isEssential(line: string): boolean {
315
- return line.includes("SEVERE") || line.includes("ERROR") || line.includes("Exception") ||
316
- line.includes("Caused by") || line.includes("at ") || line.includes("... ") ||
317
- line.includes("Server startup in") || line.includes("HOTSWAP AGENT:");
323
+ return ESSENTIAL_PATTERNS.some(pattern => line.includes(pattern));
318
324
  }
319
325
 
320
- // Sumarização de logs do Tomcat (simplificada)
321
326
  static summarize(line: string): string {
322
327
  if (this.isSystemNoise(line)) return "";
323
328
 
324
- // Server startup
325
329
  const startupMatch = line.match(/Server startup in.*?([\d,]+)\s*ms/);
326
330
  if (startupMatch) {
327
331
  const time = startupMatch[1].replace(",", "");
328
332
  const seconds = (parseInt(time) / 1000).toFixed(1);
329
- return `${C.success}ready ${C.gray}in ${C.white}${seconds}s${C.reset}`;
333
+ return `${C.success}pronto ${C.gray}em ${C.white}${seconds}s${C.reset}`;
330
334
  }
331
335
 
332
- // Hotswap com rate limiting (evita spam)
333
336
  if (line.includes("HOTSWAP AGENT") && line.includes("RELOAD")) {
334
- const now = Date.now();
335
- if (now - this.lastHotswapTime < 2000) {
336
- this.hotswapCount++;
337
- return ""; // Silencia se dentro de 2s
338
- }
339
- this.lastHotswapTime = now;
340
- const count = this.hotswapCount > 0 ? ` ${C.gray}(${this.hotswapCount} more)${C.reset}` : "";
341
- this.hotswapCount = 0;
342
- return `${C.secondary}↻ hotswap${C.reset}${count}`;
337
+ return `${C.primary}↻ hotswap${C.reset}`;
343
338
  }
344
339
 
345
- // Erros de compilação
346
340
  const compilationError = line.match(/\[ERROR\].*?(\w+\.java):\[(\d+).*?\]\s*(.+)/);
347
341
  if (compilationError) {
348
342
  const [, file, lineNum, msg] = compilationError;
349
- return `${C.error}✗ ${C.white}${file}${C.gray}:${lineNum}${C.reset} ${C.gray}${msg.slice(0, 50)}${C.reset}`;
343
+ return `${C.error}✗ ${C.white}${file}${C.gray}:${lineNum}${C.reset} ${C.gray}${msg.slice(0, 80)}${C.reset}`;
350
344
  }
351
345
 
352
- // Erros SEVERE
353
346
  if (line.includes("SEVERE") || line.includes("Exception")) {
354
- return `${C.error}✗ ${C.gray}${line.slice(0, 80)}${C.reset}`;
347
+ return `${C.error}✗ ${C.gray}${line.slice(0, 200)}${C.reset}`;
355
348
  }
356
349
 
357
- // Warnings (filtra noise conhecido)
350
+ // WARNING/ADVERTÊNCIA: mostra no modo verbose, não no modo normal
351
+ // (removido do summarize para não aparecer em modo não-verbose)
358
352
  if (line.includes("WARNING") || line.includes("ADVERTÊNCIA")) {
359
- if (this.isSystemNoise(line)) return "";
360
- return `${C.warning}⚠ ${C.gray}${line.slice(0, 80)}${C.reset}`;
353
+ return ""; // Silenciado no modo normal - use --verbose para ver
361
354
  }
362
355
 
363
356
  return "";
364
357
  }
365
358
 
366
- // ========== Tomcat Log Formatting ==========
367
-
368
- private static tomcatNoisePatterns = [
369
- /^Using CATALINA_/,
370
- /^Using JRE_HOME/,
371
- /^Using CLASSPATH/,
372
- /^Using CATALINA_OPTS/,
373
- /^NOTE: Picked up JDK_JAVA_OPTIONS/,
374
- /^HOTSWAP AGENT:.*Plugin.*initialized in ClassLoader/,
375
- /^HOTSWAP AGENT:.*Registering directory/,
376
- /^HOTSWAP AGENT:.*WARNING.*TreeWatcherNIO.*Unable to watch/,
377
- /^HOTSWAP AGENT:.*INFO.*TreeWatcherNIO/,
378
- /^HOTSWAP AGENT:.*INFO.*PluginRegistry.*Discovered plugins/,
379
- /^HOTSWAP AGENT:.*INFO.*HotswapAgent.*Loading Hotswap agent/,
380
- /^HOTSWAP AGENT:.*INFO.*TomcatPlugin.*Tomcat plugin initialized/,
381
- /^\d{2}-[A-Za-z]+-\d{4}\s+\d{2}:\d{2}:\d{2}\.\d+\s+(INFORMAÇÕES|INFO)\s+\[main\].*VersionLoggerListener/,
382
- /^\d{2}-[A-Za-z]+-\d{4}\s+\d{2}:\d{2}:\d{2}\.\d+\s+(INFORMAÇÕES|INFO)\s+\[main\].*AprLifecycleListener/,
383
- /^\d{2}-[A-Za-z]+-\d{4}\s+\d{2}:\d{2}:\d{2}\.\d+\s+(INFORMAÇÕES|INFO)\s+\[main\].*Command line argument/,
384
- /^\d{2}-[A-Za-z]+-\d{4}\s+\d{2}:\d{2}:\d{2}\.\d+\s+(INFORMAÇÕES|INFO)\s+\[main\].*CATALINA_BASE/,
385
- /^\d{2}-[A-Za-z]+-\d{4}\s+\d{2}:\d{2}:\d{2}\.\d+\s+(INFORMAÇÕES|INFO)\s+\[main\].*CATALINA_HOME/,
386
- /^\d{2}-[A-Za-z]+-\d{4}\s+\d{2}:\d{2}:\d{2}\.\d+\s+(INFORMAÇÕES|INFO)\s+\[main\].*Server version/,
387
- /^\d{2}-[A-Za-z]+-\d{4}\s+\d{2}:\d{2}:\d{2}\.\d+\s+(INFORMAÇÕES|INFO)\s+\[main\].*Server built/,
388
- /^\d{2}-[A-Za-z]+-\d{4}\s+\d{2}:\d{2}:\d{2}\.\d+\s+(INFORMAÇÕES|INFO)\s+\[main\].*OS Name/,
389
- /^\d{2}-[A-Za-z]+-\d{4}\s+\d{2}:\d{2}:\d{2}\.\d+\s+(INFORMAÇÕES|INFO)\s+\[main\].*OS Version/,
390
- /^\d{2}-[A-Za-z]+-\d{4}\s+\d{2}:\d{2}:\d{2}\.\d+\s+(INFORMAÇÕES|INFO)\s+\[main\].*Architecture/,
391
- /^\d{2}-[A-Za-z]+-\d{4}\s+\d{2}:\d{2}:\d{2}\.\d+\s+(INFORMAÇÕES|INFO)\s+\[main\].*Java Home/,
392
- /^\d{2}-[A-Za-z]+-\d{4}\s+\d{2}:\d{2}:\d{2}\.\d+\s+(INFORMAÇÕES|INFO)\s+\[main\].*JVM Version/,
393
- /^\d{2}-[A-Za-z]+-\d{4}\s+\d{2}:\d{2}:\d{2}\.\d+\s+(INFORMAÇÕES|INFO)\s+\[main\].*JVM Vendor/,
394
- /^\d{2}-[A-Za-z]+-\d{4}\s+\d{2}:\d{2}:\d{2}\.\d+\s+(INFORMAÇÕES|INFO)\s+\[main\].*Loaded Apache Tomcat Native/,
395
- /^\d{2}-[A-Za-z]+-\d{4}\s+\d{2}:\d{2}:\d{2}\.\d+\s+(INFORMAÇÕES|INFO)\s+\[main\].*APR capabilities/,
396
- /^\d{2}-[A-Za-z]+-\d{4}\s+\d{2}:\d{2}:\d{2}\.\d+\s+(INFORMAÇÕES|INFO)\s+\[main\].*APR\/OpenSSL configuration/,
397
- /^\d{2}-[A-Za-z]+-\d{4}\s+\d{2}:\d{2}:\d{2}\.\d+\s+(INFORMAÇÕES|INFO)\s+\[main\].*OpenSSL successfully initialized/,
398
- /^\d{2}-[A-Za-z]+-\d{4}\s+\d{2}:\d{2}:\d{2}\.\d+\s+(INFORMAÇÕES|INFO)\s+\[main\].*Server initialization in/,
399
- /^\d{2}-[A-Za-z]+-\d{4}\s+\d{2}:\d{2}:\d{2}\.\d+\s+(INFORMAÇÕES|INFO)\s+\[main\].*Starting service/,
400
- /^\d{2}-[A-Za-z]+-\d{4}\s+\d{2}:\d{2}:\d{2}\.\d+\s+(INFORMAÇÕES|INFO)\s+\[main\].*Starting Servlet engine/,
401
- /^ParallelWebappClassLoader/,
359
+ private static tomcatNoisePatterns = NOISE_PATTERNS.tomcat;
360
+
361
+ // Padrões que são noise em modo não-verbose, mas úteis no verbose
362
+ private static tomcatVerbosePatterns = [
363
+ /^HOTSWAP AGENT:/,
402
364
  /^context:/,
403
365
  /^delegate:/,
404
- /^-+> Parent Classloader/,
366
+ /^----------> Parent Classloader:/,
405
367
  /^java\.net\.URLClassLoader/,
406
368
  ];
407
369
 
@@ -409,238 +371,147 @@ export class Logger {
409
371
  return this.tomcatNoisePatterns.some(p => p.test(line));
410
372
  }
411
373
 
374
+ // Logs que são noise em modo normal, mas úteis no verbose
375
+ static isTomcatVerboseLog(line: string): boolean {
376
+ return this.tomcatVerbosePatterns.some(p => p.test(line));
377
+ }
378
+
412
379
  static formatTomcatLog(line: string): string {
413
380
  const cleanLine = line.trim();
414
381
  if (!cleanLine) return "";
415
-
416
- // Silencia noise completamente
417
382
  if (this.isTomcatNoise(cleanLine)) return "";
418
383
 
419
- // HOTSWAP AGENT: Loading Hotswap agent X.X.X
420
- if (cleanLine.includes("HOTSWAP AGENT") && cleanLine.includes("Loading Hotswap agent")) {
421
- const versionMatch = cleanLine.match(/Loading Hotswap agent ([\d.]+)/);
422
- if (versionMatch) {
423
- return `${C.gray}│${C.reset} ${C.secondary}↻${C.reset} ${C.dim}HotswapAgent v${versionMatch[1]}${C.reset}`;
384
+ // Server startup
385
+ if (cleanLine.includes("Server startup in")) {
386
+ const match = cleanLine.match(/Server startup in.*?([\d,]+)\s*ms/);
387
+ if (match) {
388
+ const seconds = (parseInt(match[1].replace(",", "")) / 1000).toFixed(1);
389
+ return ` ${C.success}✓ server${C.reset} ${C.dim}pronto em ${seconds}s${C.reset}`;
424
390
  }
425
391
  }
426
392
 
427
- // HOTSWAP AGENT: Discovered plugins
428
- if (cleanLine.includes("HOTSWAP AGENT") && cleanLine.includes("Discovered plugins")) {
429
- return `${C.gray}│${C.reset} ${C.secondary}↻${C.reset} ${C.dim}plugins loaded${C.reset}`;
430
- }
431
-
432
- // Server initialization
433
- const initMatch = cleanLine.match(/Server initialization in \[(\d+)\] milliseconds/);
434
- if (initMatch) {
435
- return `${C.gray}│${C.reset} ${C.success}●${C.reset} ${C.dim}initialized in ${initMatch[1]}ms${C.reset}`;
436
- }
437
-
438
- // Starting service [Catalina]
439
- const serviceMatch = cleanLine.match(/Starting service \[(\w+)\]/);
440
- if (serviceMatch) {
441
- return `${C.gray}│${C.reset} ${C.primary}▸${C.reset} ${C.dim}starting ${serviceMatch[1].toLowerCase()}${C.reset}`;
442
- }
443
-
444
- // Starting Servlet engine
445
- if (cleanLine.includes("Starting Servlet engine")) {
446
- const versionMatch = cleanLine.match(/Apache Tomcat\/([\d.]+)/);
447
- if (versionMatch) {
448
- return `${C.gray}│${C.reset} ${C.primary}▸${C.reset} ${C.dim}Tomcat ${versionMatch[1]}${C.reset}`;
393
+ // Stack trace - linhas começando com "at "
394
+ if (cleanLine.startsWith("at ") && cleanLine.includes("(")) {
395
+ const match = cleanLine.match(/at\s+([\w.$]+)\.(\w+)\s*\(([^:]+):?(\d*)\)/);
396
+ if (match) {
397
+ const [, className, method, file, lineNum] = match;
398
+ const shortClass = className.split('.').pop() || className;
399
+ return ` ${C.dim}at ${C.gray}${shortClass}.${method}(${file}${lineNum ? ':' + lineNum : ''})${C.reset}`;
449
400
  }
401
+ return ` ${C.dim}${cleanLine.slice(0, 100)}${C.reset}`;
450
402
  }
451
403
 
452
- // Deploy directory
453
- const deployMatch = cleanLine.match(/deployDirectory.*webapps[\\/]([^'"\]]+)/);
454
- if (deployMatch) {
455
- return `${C.gray}│${C.reset} ${C.primary}▸${C.reset} ${C.dim}deploying ${deployMatch[1]}${C.reset}`;
456
- }
457
-
458
- // Server startup in ( já temos no summarize mas reforça aqui )
459
- const startupMatch = cleanLine.match(/Server startup in.*?([\d,]+)\s*ms/);
460
- if (startupMatch) {
461
- const time = startupMatch[1].replace(",", "");
462
- const seconds = (parseInt(time) / 1000).toFixed(1);
463
- return `${C.gray}│${C.reset} ${C.success}●${C.reset} ${C.dim}ready in ${C.white}${seconds}s${C.reset}`;
404
+ // Exception/Caused by
405
+ if (cleanLine.includes("Exception:") || cleanLine.includes("Caused by:")) {
406
+ const isCausedBy = cleanLine.includes("Caused by:");
407
+ const prefix = isCausedBy ? "Caused by: " : "";
408
+ const color = isCausedBy ? C.warning : C.error;
409
+ return ` ${color}${isCausedBy ? '↳' : '✗'} ${prefix}${cleanLine.slice(prefix.length, 120)}${C.reset}`;
464
410
  }
465
411
 
466
- // Tomcat versão info (sumarizado)
467
- const tomcatVersionMatch = cleanLine.match(/Server version number:\s+([\d.]+)/);
468
- if (tomcatVersionMatch) {
469
- return `${C.gray}│${C.reset} ${C.dim}Tomcat ${tomcatVersionMatch[1]}${C.reset}`;
412
+ // HOTSWAP AGENT logs
413
+ if (cleanLine.startsWith("HOTSWAP AGENT:")) {
414
+ const match = cleanLine.match(/HOTSWAP AGENT:\s+([\d:.]+)\s+(\w+)\s+\(([^)]+)\)\s+-\s*(.+)/);
415
+ if (match) {
416
+ const [, time, level, source, message] = match;
417
+ const levelColor = level === "ERROR" ? C.error : level === "WARN" ? C.warning : C.primary;
418
+ return ` ${C.primary}↻${C.reset} ${C.dim}[${time}]${C.reset} ${levelColor}${level}${C.reset} ${C.gray}${message.slice(0, 80)}${C.reset}`;
419
+ }
420
+ return ` ${C.primary}↻${C.reset} ${C.gray}${cleanLine.slice(15, 120)}${C.reset}`;
470
421
  }
471
422
 
472
- // JVM info sumarizada
473
- const jvmMatch = cleanLine.match(/JVM Version:\s+([\d._]+)/);
474
- if (jvmMatch) {
475
- return `${C.gray}│${C.reset} ${C.dim}JVM ${jvmMatch[1]}${C.reset}`;
423
+ // Context classloader logs
424
+ if (cleanLine.startsWith("context:") || cleanLine.startsWith("delegate:") || cleanLine.startsWith("'.") || cleanLine.startsWith("java.net.URLClassLoader")) {
425
+ return ` ${C.dim}◆ ${cleanLine.slice(0, 100)}${C.reset}`;
476
426
  }
477
427
 
478
- // Protocol handler init
479
- const protocolMatch = cleanLine.match(/Initializing ProtocolHandler \["(.+?)"\]/);
480
- if (protocolMatch) {
481
- return `${C.gray}│${C.reset} ${C.primary}▸${C.reset} ${C.dim}${protocolMatch[1]}${C.reset}`;
428
+ if (cleanLine.includes("Parent Classloader:")) {
429
+ return ` ${C.dim}◆ ${cleanLine.slice(0, 100)}${C.reset}`;
482
430
  }
483
431
 
484
- // Erros e warnings que passaram pelo filtro
432
+ // SEVERE/ERROR
485
433
  if (cleanLine.includes("SEVERE") || cleanLine.includes("ERROR")) {
486
- return `${C.gray}│${C.reset} ${C.error}✗${C.reset} ${C.gray}${cleanLine.slice(0, 80)}${C.reset}`;
434
+ return ` ${C.error}✗${C.reset} ${C.gray}${cleanLine.slice(0, 120)}${C.reset}`;
487
435
  }
488
436
 
437
+ // WARNING/ADVERTÊNCIA
489
438
  if (cleanLine.includes("WARNING") || cleanLine.includes("ADVERTÊNCIA")) {
490
- return `${C.gray}│${C.reset} ${C.warning}⚠${C.reset} ${C.gray}${cleanLine.slice(0, 80)}${C.reset}`;
439
+ return ` ${C.warning}!${C.reset} ${C.gray}${cleanLine.slice(0, 120)}${C.reset}`;
491
440
  }
492
441
 
493
- // Outros logs INFO - mostra resumido
494
- const infoMatch = cleanLine.match(/(?:INFORMAÇÕES|INFO)\s+\[.*?\]\s+(.+)/);
495
- if (infoMatch) {
496
- const msg = infoMatch[1].trim();
497
- if (msg.length > 0 && !this.isTomcatNoise(msg)) {
498
- return `${C.gray}│${C.reset} ${C.dim}${msg.slice(0, 70)}${C.reset}`;
499
- }
442
+ // SLF4J logs
443
+ if (cleanLine.startsWith("SLF4J:")) {
444
+ return ` ${C.warning}⚠${C.reset} ${C.gray}${cleanLine.slice(6, 120)}${C.reset}`;
500
445
  }
501
446
 
502
- return "";
447
+ // No modo verbose, retorna a linha formatada simples para não perder logs
448
+ return ` ${C.gray}${cleanLine.slice(0, 120)}${C.reset}`;
503
449
  }
504
450
 
505
- // ========== Build Log Formatting (Maven/Gradle) ==========
506
-
507
- private static buildNoisePatterns = [
508
- /^\[INFO\]\s+Scanning for projects/,
509
- /^\[INFO\]\s+Using the MultiThreadedBuilder/,
510
- /^\[INFO\]\s+---\s+.*\s+---$/,
511
- /^\[INFO\]\s+T+E+\s*$/,
512
- /^\[INFO\]\s+BUILD\s+SUCCESS/i,
513
- /^\[INFO\]\s+BUILD\s+FAILURE/i,
514
- /^\[INFO\]\s+Total time:/,
515
- /^\[INFO\]\s+Finished at:/,
516
- /^\[INFO\]\s+Final Memory:/,
517
- /^\[INFO\]\s+http:\/\/cwiki\.apache\.org/,
518
- /^\[INFO\]\s+-> \[Help 1\]/,
519
- /^\[INFO\]\s+Re-run Maven using/,
520
- /^\[INFO\]\s+To see the full stack trace/,
521
- /^\[INFO\]\s+For more information about the errors/,
522
- /^\[ERROR\]\s+To see the full stack trace/,
523
- /^\[ERROR\]\s+Re-run Maven using/,
524
- /^\[ERROR\]\s+For more information/,
525
- /^\[ERROR\]\s+-> \[Help 1\]/,
526
- /^\[WARNING\]\s+It is highly recommended/,
527
- /^\[WARNING\]\s+For this reason, future Maven/,
528
- /^\[WARNING\]\s+\[HELP, sysprop:version/,
529
- /^\[WARNING\]\s+Some problems were encountered/,
530
- /^\[WARNING\]\s+'dependencies\.dependency/,
531
- /Building .*war/,
532
- /from pom\.xml/,
533
- /\[ war \]/,
534
- /^\s*$/,
535
- ];
536
-
537
- private static gradleNoisePatterns = [
538
- /^> Task/,
539
- /^Download/,
540
- /^Expiring/,
541
- /^BUILD/,
542
- /^\d+ actionable task/,
543
- ];
451
+ private static buildNoisePatterns = NOISE_PATTERNS.build;
544
452
 
545
453
  static isBuildNoise(line: string): boolean {
546
- return this.buildNoisePatterns.some(p => p.test(line)) ||
547
- this.gradleNoisePatterns.some(p => p.test(line));
454
+ return this.buildNoisePatterns.some(p => p.test(line));
548
455
  }
549
456
 
550
- // Acumulador de erro de compilação (para pegar mensagens multi-linha)
551
- private static pendingError: { file: string; line: string; msg: string } | null = null;
552
-
553
- static formatBuildLog(line: string, buildTool: 'maven' | 'gradle' = 'maven'): string {
457
+ static formatBuildLog(line: string, _buildTool: 'maven' | 'gradle' = 'maven'): string {
554
458
  const cleanLine = line.trim();
555
459
  if (!cleanLine) return "";
460
+ if (this.isBuildNoise(cleanLine)) return "";
556
461
 
557
- // Maven: [ERROR] /path/file.java:[123,45] error message
558
- const mavenErrorMatch = cleanLine.match(/^\[ERROR\]\s+(.+\.java):\[(\d+),\d+\]\s*(.+)/);
559
- if (mavenErrorMatch) {
560
- const [, file, lineNum, msg] = mavenErrorMatch;
462
+ // ERROR de compilação com arquivo e linha
463
+ const errorMatch = cleanLine.match(/^\[ERROR\]\s+(.+\.java):\[(\d+),\d+\]\s*(.+)/);
464
+ if (errorMatch) {
465
+ const [, file, lineNum, msg] = errorMatch;
561
466
  const shortFile = file.split(/[/\\]/).pop() || file;
562
- return `${C.gray}│${C.reset} ${C.error}✗${C.reset} ${C.white}${shortFile}${C.gray}:${lineNum}${C.reset} ${C.error}${msg.slice(0, 60)}${C.reset}`;
467
+ return ` ${C.error}✗${C.reset} ${C.white}${shortFile}${C.gray}:${lineNum}${C.reset} ${C.error}${msg.slice(0, 80)}${C.reset}`;
563
468
  }
564
469
 
565
- // Maven: [ERROR] COMPILATION ERROR / BUILD FAILURE (título)
566
- if (cleanLine.match(/^\[(ERROR|INFO)\]\s+(COMPILATION ERROR|BUILD FAILURE)/)) {
567
- return `${C.gray}│${C.reset} ${C.error} COMPILATION FAILED${C.reset}`;
470
+ // ERROR genérico
471
+ if (cleanLine.startsWith("[ERROR]")) {
472
+ return ` ${C.error}✗${C.reset} ${C.gray}${cleanLine.slice(7, 120)}${C.reset}`;
568
473
  }
569
474
 
570
- // Maven: [WARNING] 'dependencies.dependency...'
571
- if (cleanLine.match(/^\[WARNING\]\s+'dependencies\.dependency/)) {
572
- const match = cleanLine.match(/'dependencies\.dependency\.[\w:]+'\s+(.+)/);
573
- if (match) {
574
- return `${C.gray}│${C.reset} ${C.warning}⚠${C.reset} ${C.gray}${match[1].slice(0, 60)}${C.reset}`;
575
- }
475
+ // WARNING
476
+ if (cleanLine.startsWith("[WARNING]")) {
477
+ return ` ${C.warning}!${C.reset} ${C.gray}${cleanLine.slice(9, 120)}${C.reset}`;
576
478
  }
577
479
 
578
- // Maven: [WARNING] The POM for ... is invalid
579
- const invalidPomMatch = cleanLine.match(/^\[WARNING\]\s+The POM for (.+?) is invalid/);
580
- if (invalidPomMatch) {
581
- return `${C.gray}│${C.reset} ${C.warning}⚠${C.reset} ${C.gray}Invalid POM: ${invalidPomMatch[1].slice(0, 50)}${C.reset}`;
582
- }
583
-
584
- // Maven: [INFO] Compiling N source files
480
+ // Compiling
585
481
  const compilingMatch = cleanLine.match(/^\[INFO\]\s+Compiling\s+(\d+)\s+source/);
586
482
  if (compilingMatch) {
587
- return `${C.gray}│${C.reset} ${C.primary}▸${C.reset} ${C.dim}compiling ${C.white}${compilingMatch[1]}${C.reset} ${C.dim}files${C.reset}`;
588
- }
589
-
590
- // Maven: [INFO] /path/file.java: Some input files use or override a deprecated API
591
- const deprecatedMatch = cleanLine.match(/^\[INFO\]\s+(.+\.java):\s+Some input files use (.+)/);
592
- if (deprecatedMatch) {
593
- const shortFile = deprecatedMatch[1].split(/[/\\]/).pop() || deprecatedMatch[1];
594
- const type = deprecatedMatch[2].includes('removal') ? 'deprecated (removal)' : 'deprecated';
595
- return `${C.gray}│${C.reset} ${C.warning}⚠${C.reset} ${C.gray}${shortFile} uses ${type}${C.reset}`;
596
- }
597
-
598
- // Maven: [INFO] Changes detected - recompiling
599
- if (cleanLine.match(/^\[INFO\]\s+Changes detected/)) {
600
- return `${C.gray}│${C.reset} ${C.primary}▸${C.reset} ${C.dim}changes detected, recompiling${C.reset}`;
601
- }
602
-
603
- // Maven: [INFO] Copying N resources
604
- const resourcesMatch = cleanLine.match(/^\[INFO\]\s+Copying\s+(\d+)\s+resources?/);
605
- if (resourcesMatch) {
606
- return `${C.gray}│${C.reset} ${C.dim}copying ${C.white}${resourcesMatch[1]}${C.reset} ${C.dim}resources${C.reset}`;
483
+ return ` ${C.primary}●${C.reset} ${C.dim}compilando ${C.white}${compilingMatch[1]}${C.reset} ${C.dim}arquivos${C.reset}`;
607
484
  }
608
485
 
609
- // Maven: [INFO] Deleting ...
610
- const deletingMatch = cleanLine.match(/^\[INFO\]\s+Deleting\s+(.+)/);
611
- if (deletingMatch) {
612
- return `${C.gray}│${C.reset} ${C.dim}cleaning target directory${C.reset}`;
613
- }
614
-
615
- // Maven: [INFO] skip non existing resourceDirectory
616
- if (cleanLine.match(/^\[INFO\]\s+skip non existing/)) {
617
- return ""; // Silencia
486
+ // Copying resources
487
+ if (cleanLine.includes("Copying") && cleanLine.includes("resource")) {
488
+ const match = cleanLine.match(/Copying\s+(\d+)\s+resource/);
489
+ if (match) {
490
+ return ` ${C.dim}→ copiando ${match[1]} recursos${C.reset}`;
491
+ }
618
492
  }
619
493
 
620
- // Gradle: > Task :name
621
- const gradleTaskMatch = cleanLine.match(/^> Task :(.+)/);
622
- if (gradleTaskMatch) {
623
- return `${C.gray}│${C.reset} ${C.primary}▸${C.reset} ${C.dim}${gradleTaskMatch[1]}${C.reset}`;
494
+ // Building project info
495
+ if (cleanLine.includes("Building ") && cleanLine.includes("<")) {
496
+ const match = cleanLine.match(/Building\s+(.+)/);
497
+ if (match) {
498
+ return ` ${C.primary}●${C.reset} ${C.white}${match[1]}${C.reset}`;
499
+ }
624
500
  }
625
501
 
626
- // Gradle errors
627
- const gradleErrorMatch = cleanLine.match(/^(.+\.java):(\d+):\s*(error|warning):\s*(.+)/);
628
- if (gradleErrorMatch) {
629
- const [, file, lineNum, level, msg] = gradleErrorMatch;
630
- const shortFile = file.split(/[/\\]/).pop() || file;
631
- const icon = level === 'error' ? C.error + '✗' + C.reset : C.warning + '⚠' + C.reset;
632
- return `${C.gray}│${C.reset} ${icon} ${C.white}${shortFile}${C.gray}:${lineNum}${C.reset} ${C.error}${msg.slice(0, 60)}${C.reset}`;
502
+ // Recompile with -X
503
+ if (cleanLine.includes("Recompile with -Xlint")) {
504
+ return ` ${C.warning}⚠${C.reset} ${C.dim}recompile com -Xlint para detalhes${C.reset}`;
633
505
  }
634
506
 
635
- // Se não for nenhum padrão conhecido e não for noise, retorna formatado como info
636
- if (!this.isBuildNoise(cleanLine)) {
637
- // Remove prefixos [INFO], [WARNING], [ERROR] genéricos
638
- const clean = cleanLine.replace(/^\[(INFO|WARNING|ERROR)\]\s*/, '');
639
- if (clean.length > 0 && !this.isBuildNoise(clean)) {
640
- return `${C.gray}│${C.reset} ${C.dim}${clean.slice(0, 70)}${C.reset}`;
641
- }
507
+ // Some input files use unchecked or unsafe operations
508
+ if (cleanLine.includes("Some input files use") || cleanLine.includes("unchecked or unsafe")) {
509
+ return ` ${C.warning}⚠${C.reset} ${C.gray}${cleanLine.slice(0, 100)}${C.reset}`;
642
510
  }
643
511
 
644
512
  return "";
645
513
  }
646
514
  }
515
+
516
+ // Exporta Logger como padrão para compatibilidade
517
+ export { LoggerLegacy as Logger };