@archznn/xavva 2.1.0 → 2.2.1

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/src/utils/ui.ts CHANGED
@@ -1,317 +1,646 @@
1
1
  import pkg from "../../package.json";
2
2
  import type { DashboardService } from "../services/DashboardService";
3
3
 
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
+
4
32
  export class Logger {
5
- public static readonly C = {
6
- reset: "\x1b[0m",
7
- cyan: "\x1b[36m",
8
- green: "\x1b[32m",
9
- yellow: "\x1b[33m",
10
- red: "\x1b[31m",
11
- dim: "\x1b[90m",
12
- bold: "\x1b[1m",
13
- blue: "\x1b[34m",
14
- magenta: "\x1b[35m",
15
- bgRed: "\x1b[41m",
16
- white: "\x1b[37m",
17
- gray: "\x1b[38;5;240m",
18
- lightGray: "\x1b[38;5;248m",
19
- darkGray: "\x1b[38;5;238m"
20
- };
21
-
22
- private static hotswapPluginsCount = 0;
23
- private static lastDomain = "";
24
- private static lastHotswapMsg = "";
25
- private static activeSpinnerMsg = "";
26
- private static dashboard: DashboardService | null = null;
27
-
28
- static setDashboard(dashboard: DashboardService) {
29
- this.dashboard = dashboard;
30
- }
31
-
32
- private static write(message: string, isError: boolean = false) {
33
- if (this.dashboard && this.dashboard.isTuiActive()) {
34
- this.dashboard.log(message);
35
- return;
36
- }
37
-
38
- if (this.activeSpinnerMsg) {
39
- process.stdout.write("\r\x1B[K"); // Limpa a linha do spinner
40
- }
41
-
42
- if (isError) {
43
- console.error(message + this.C.reset);
44
- } else {
45
- console.log(message + this.C.reset);
46
- }
47
-
48
- if (this.activeSpinnerMsg) {
49
- // Re-imprime o início da linha do spinner para o próximo frame
50
- process.stdout.write(` ${this.C.cyan}⠋${this.C.reset} ${this.activeSpinnerMsg}...`);
51
- }
52
- }
53
-
54
- static getGitContext(): { branch: string, author: string, hash: string } {
55
- try {
56
- const branch = Bun.spawnSync(["git", "rev-parse", "--abbrev-ref", "HEAD"]).stdout.toString().trim();
57
- const author = Bun.spawnSync(["git", "log", "-1", "--format=%an"]).stdout.toString().trim();
58
- const hash = Bun.spawnSync(["git", "rev-parse", "--short", "HEAD"]).stdout.toString().trim();
59
- return { branch, author, hash };
60
- } catch (e) {
61
- return { branch: "", author: "", hash: "" };
62
- }
63
- }
64
-
65
- static banner(command?: string, profile?: string, encoding?: string) {
66
- console.clear();
67
- const git = this.getGitContext();
68
- const name = (process.cwd().split(/[/\\]/).pop() || "PROJECT").toUpperCase();
69
- const version = `v${pkg.version}`;
70
-
71
- const mode = command?.toUpperCase() || "DEPLOY";
72
- const modeColor = mode === "DEV" ? this.C.green : this.C.blue;
73
- const modeIcon = mode === "DEV" ? "⚡" : "🚀";
74
-
75
- console.log("");
76
- console.log(` ${this.C.bold}${this.C.cyan}X A V V A${this.C.reset} ${this.C.dim}─${this.C.reset} ${this.C.bold}${this.C.white}${name}${this.C.reset}`);
77
-
78
- const profileInfo = profile ? ` ${this.C.dim}•${this.C.reset} ${this.C.yellow}♦ ${profile.toUpperCase()}${this.C.reset}` : "";
79
- const encodingInfo = encoding ? ` ${this.C.dim}•${this.C.reset} ${this.C.cyan} ${encoding.toUpperCase()}${this.C.reset}` : "";
80
- const gitInfo = git.branch ? `${this.C.magenta}🌿 ${git.branch}${this.C.reset} ${this.C.dim}•${this.C.reset} ${this.C.yellow}${git.hash}${this.C.reset}` : "";
81
- console.log(` ${this.C.dim}📦 ${version}${profileInfo}${encodingInfo}${gitInfo ? ` ${this.C.dim}•${this.C.reset} ${gitInfo}` : ""}${this.C.reset}`);
82
-
83
- console.log(` ${modeColor}${this.C.bold}⬢ ${modeIcon} ${mode} MODE${this.C.reset}`);
84
- console.log(` ${this.C.dim}─────────────────────────────────────────────────${this.C.reset}`);
85
- }
86
-
87
- static section(title: string) {
88
- this.write(`\n${this.C.bold}${this.C.blue}[${title.toUpperCase()}]${this.C.reset}`);
89
- }
90
-
91
- private static domain(name: string) {
92
- if (this.lastDomain !== name) {
93
- this.write(`\n${this.C.bold}${this.C.blue}[${name.toUpperCase()}]${this.C.reset}`);
94
- this.lastDomain = name;
95
- }
96
- }
97
-
98
- static config(label: string, value: string | number | boolean) {
99
- this.domain("config");
100
- this.info(label, value);
101
- }
102
-
103
- static info(label: string, value: string | number | boolean) {
104
- this.write(` ${this.C.lightGray}${label.padEnd(12)}${this.C.reset} : ${this.C.bold}${value}${this.C.reset}`);
105
- }
106
-
107
- static build(msg: string, status: 'start' | 'success' | 'error' | 'info' = 'success') {
108
- this.domain("build");
109
- const symbol = status === 'start' ? `${this.C.blue}▶` : status === 'success' ? `${this.C.green}✔` : status === 'error' ? `${this.C.red}✖` : `${this.C.dim}ℹ`;
110
- this.write(` ${symbol} ${this.C.reset}${msg}`);
111
- }
112
-
113
- static server(msg: string, status: 'start' | 'success' | 'error' | 'info' = 'info') {
114
- this.domain("server");
115
- const symbol = status === 'start' ? `${this.C.blue}▶` : status === 'success' ? `${this.C.green}✔` : status === 'error' ? `${this.C.red}✖` : `${this.C.dim}ℹ`;
116
- this.write(` ${symbol} ${this.C.reset}${msg}`);
117
- }
118
-
119
- static health(msg: string, status: 'success' | 'error' | 'warn' = 'success') {
120
- this.domain("health");
121
- const symbol = status === 'success' ? `${this.C.green}✔` : status === 'error' ? `${this.C.red}✖` : `${this.C.yellow}⚠`;
122
- this.write(` ${symbol} ${this.C.reset}${msg}`);
123
- }
124
-
125
- static watcher(msg: string, status: 'watch' | 'change' | 'start' | 'success' = 'success') {
126
- this.domain("watcher");
127
- const symbol = status === 'watch' ? `${this.C.magenta}👀` : status === 'change' ? `${this.C.yellow}▲` : status === 'start' ? `${this.C.blue}▶` : `${this.C.green}✔`;
128
- this.write(` ${symbol} ${this.C.reset}${msg}`);
129
- }
130
-
131
- static success(msg: string) { this.write(` ${this.C.green}✔ ${msg}`); }
132
- static error(msg: string) { this.write(` ${this.C.red} ${msg}`, true); }
133
- static warn(msg: string) { this.write(` ${this.C.yellow}${msg}`); }
134
- static log(msg: string) { this.write(` ${msg}`); }
135
- static step(msg: string) { this.write(` ${this.C.dim}» ${msg}`); }
136
- static debug(msg: string) { this.write(` ${this.C.magenta}🐛 ${msg}`); }
137
- static process(msg: string) { this.write(` ${this.C.blue}▶ ${msg}`); }
138
- static newline() { this.write(""); }
139
- static dim(msg: string) { this.write(` ${this.C.dim}${msg}${this.C.reset}`); }
140
-
141
- static spinner(msg: string) {
142
- if (this.dashboard && this.dashboard.isTuiActive()) {
143
- this.dashboard.log(`${this.C.cyan}⠋${this.C.reset} ${msg}...`);
144
- return (success = true) => {
145
- if (success) {
146
- this.dashboard.log(`${this.C.green}✔${this.C.reset} ${msg}`);
147
- } else {
148
- this.dashboard.log(`${this.C.red}✖${this.C.reset} Falha em ${msg}`);
149
- }
150
- };
151
- }
152
-
153
- this.activeSpinnerMsg = msg;
154
- const frames = ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"];
155
- let i = 0;
156
- process.stdout.write("\x1B[?25l");
157
-
158
- const timer = setInterval(() => {
159
- process.stdout.write(`\r ${this.C.cyan}${frames[i]}${this.C.reset} ${msg}...`);
160
- i = (i + 1) % frames.length;
161
- }, 80);
162
-
163
- return (success = true) => {
164
- clearInterval(timer);
165
- this.activeSpinnerMsg = "";
166
- process.stdout.write("\r\x1B[K");
167
- process.stdout.write("\x1B[?25h");
168
- if (success) {
169
- this.write(` ${this.C.green}✔${this.C.reset} ${msg}`);
170
- } else {
171
- this.error(`Falha em ${msg}`);
172
- }
173
- };
174
- }
175
-
176
- static isSystemNoise(line: string): boolean {
177
- const noise = [
178
- "Using CATALINA_", "Using JRE_HOME", "Using CLASSPATH", "NOTE: Picked up JDK_JAVA_OPTIONS",
179
- "Command line argument", "VersionLoggerListener", "Scanning for projects...",
180
- "Building ", "--- ", "+++ ", "DEBUG: ", "org.apache.catalina.startup.VersionLoggerListener",
181
- "org.apache.catalina.core.AprLifecycleListener", "org.apache.coyote.AbstractProtocol.init",
182
- "org.apache.catalina.startup.Catalina.load", "Arquivos processados em",
183
- "org.apache.jasper.servlet.TldScanner.scanJars", "Listening for transport dt_socket",
184
- "org.apache.catalina.startup.ExpandWar.expand", "org.apache.catalina.startup.ContextConfig.configureStart",
185
- "SLF4J: ", "org.glassfish.jersey.internal.Errors.logErrors", "contains empty path annotation",
186
- "org.apache.catalina.core.StandardContext.setPath", "milliseconds",
187
- "org.apache.catalina.startup.HostConfig.deployWAR", "org.apache.catalina.startup.HostConfig.deployDirectory",
188
- "Deployment of web application", "Deploying web application archive", "at org.apache",
189
- "Registering directory", "initialized in ClassLoader", "Discovered plugins:",
190
- "enhanced with plugin initialization", "registerJerseyContainer", "JasperLoader@",
191
- "Hotswap ready (Plugins:", "autoHotswap.delay", "watchResources=false",
192
- "org.apache.catalina.webresources.Cache.getResource", "insufficient free space available"
193
- ];
194
- return noise.some(n => line.includes(n));
195
- }
196
-
197
- static isEssential(line: string): boolean {
198
- return line.includes("SEVERE") || line.includes("ERROR") || line.includes("Exception") ||
199
- line.includes("Caused by") || line.includes("at ") || line.includes("... ") ||
200
- line.includes("Server startup in") || line.includes("HOTSWAP AGENT:");
201
- }
202
-
203
- static summarize(line: string): string {
204
- if (this.isSystemNoise(line)) return "";
205
-
206
- const startupMatch = line.match(/Server startup in (\[?)(.*?)(\]?)\s*ms/);
207
- if (startupMatch) {
208
- const time = (parseInt(startupMatch[2]) / 1000).toFixed(1);
209
- this.domain("server");
210
- return `${this.C.green}✔ ${this.C.bold}Server started in ${time}s`;
211
- }
212
-
213
- const deployMatch = line.match(/Deployment of web application archive \[(.*?)\] has finished in \[(.*?)\] ms/);
214
- if (deployMatch) {
215
- this.domain("build");
216
- return `${this.C.green}✔ Artifacts deployed`;
217
- }
218
-
219
- const hotswapPattern = /HOTSWAP AGENT:.*? (INFO|WARN|ERROR|RELOAD) (.*?) - (.*)/;
220
- const hotswapMatch = line.match(hotswapPattern);
221
- if (hotswapMatch) {
222
- const level = hotswapMatch[1];
223
- let msg = hotswapMatch[3];
224
-
225
- if (msg.includes("plugin initialized")) {
226
- this.hotswapPluginsCount++;
227
- return "";
228
- }
229
-
230
- if (msg.includes("redefinition") || msg.includes("reloaded") || level === 'RELOAD') {
231
- if (msg.includes("Reloading classes [")) {
232
- const classes = msg.match(/\[(.*?)\]/)?.[1] || "";
233
- const classCount = classes.split(",").length;
234
- if (classCount > 3) msg = `Reloading ${classCount} classes...`;
235
- }
236
-
237
- if (msg === this.lastHotswapMsg) return "";
238
- this.lastHotswapMsg = msg;
239
-
240
- this.watcher(`Hotswap: ${msg.replace(/Class '.*?'/, (m) => this.C.bold + m + this.C.reset)}`, 'success');
241
- return "";
242
- }
243
-
244
- if (msg.includes("Loading Hotswap agent")) {
245
- this.domain("server");
246
- return `${this.C.blue}▶ ${this.C.reset}Initializing Hotswap Agent ${msg.match(/\d+\.\d+\.\d+/)?.[0] || ""}`;
247
- }
248
-
249
- if (this.hotswapPluginsCount > 0) {
250
- const count = this.hotswapPluginsCount;
251
- this.hotswapPluginsCount = 0;
252
- this.domain("server");
253
- this.write(` ${this.C.green}✔ ${this.C.reset}Hotswap ready (Plugins: ${count} loaded)`);
254
- }
255
-
256
- let color = this.C.cyan;
257
- let symbol = "●";
258
- if (level === "WARN") { color = this.C.yellow; symbol = "▲"; }
259
- else if (level === "ERROR") { color = this.C.red; symbol = ""; }
260
-
261
- this.domain("server");
262
- return `${color}${symbol} ${this.C.bold}Hotswap:${this.C.reset} ${msg}`;
263
- }
264
-
265
- if (line.includes("java.lang.UnsupportedOperationException") && (line.includes("add a method") || line.includes("change the schema"))) {
266
- this.domain("watcher");
267
- this.write(` ${this.C.red}✖ ${this.C.bold}Hotswap Falhou:${this.C.reset} Mudança estrutural detectada (novo método/campo).`);
268
- this.write(` ${this.C.yellow}💡 Dica: Sua JVM atual não suporta mudar a estrutura da classe. Reinicie o servidor para aplicar.`);
269
- return "";
270
- }
271
-
272
- const tomcatPattern = /^(\d{2}-\w{3}-\d{4} \d{2}:\d{2}:\d{2}\.\d{3})\s+(INFO|WARNING|SEVERE|ERROR)\s+\[(.*?)\]\s+(.*)$/;
273
- const tMatch = line.match(tomcatPattern);
274
- if (tMatch) {
275
- const label = tMatch[2];
276
- let msg = tMatch[4].trim();
277
- if (this.isSystemNoise(msg)) return "";
278
- let color = this.C.dim;
279
- let symbol = "ℹ";
280
- if (label === "WARNING") { color = this.C.yellow; symbol = "▲"; }
281
- else if (label === "SEVERE" || label === "ERROR") { color = this.C.red; symbol = "✖"; }
282
- msg = msg.replace(/^(org\.apache|com\.sun|java\..*?|org\.glassfish)\.[a-zA-Z0-9.]+\s/, "").trim();
283
- if (!msg) return "";
284
- return `${color}${symbol} ${msg}`;
285
- }
286
-
287
- const compilationErrorMatch = line.match(/^\[ERROR\]\s+(.*\.java):\[(\d+),(\d+)\]\s+(.*)$/);
288
- if (compilationErrorMatch) {
289
- const [_, filePath, row, col, msg] = compilationErrorMatch;
290
- const fileName = filePath.split(/[/\\]/).pop();
291
- return `${this.C.red}✖ ERROR ${this.C.reset}${this.C.dim}em ${this.C.reset}${this.C.bold}${fileName}${this.C.reset}${this.C.dim}:${row}${this.C.reset} ${this.C.red}➜ ${this.C.reset}${msg}`;
292
- }
293
-
294
- const logPattern = /^\[(INFO|WARNING|WARN|SEVERE|ERROR)\]\s+(.*)$/;
295
- const match = line.match(logPattern);
296
- if (match) {
297
- const label = match[1];
298
- let msg = match[2].trim();
299
- if (msg.includes("Total time:") || msg.includes("Finished at:") || msg.includes("Final Memory:") || msg.includes("-----------------------")) return "";
300
- let color = this.C.dim;
301
- let symbol = "ℹ";
302
- if (label === "WARNING") { color = this.C.yellow; symbol = "▲"; }
303
- else if (label === "SEVERE" || label === "ERROR") { color = this.C.red; symbol = "✖"; }
304
- msg = msg.replace(/^(org\.apache|com\.sun|java\..*?)\.[a-zA-Z0-9.]+\s/, "").trim();
305
- if (!msg || msg === "]" || msg.includes("Compilation failure")) return "";
306
- return `${color}${symbol} ${msg}`;
307
- }
308
-
309
- if (line.includes("Exception") || line.includes("Caused by") || line.includes("at ")) {
310
- const trimmed = line.trim();
311
- const color = (trimmed.includes("org.apache") || trimmed.includes("java.base") || trimmed.includes("sun.reflect")) ? this.C.dim : this.C.yellow;
312
- return ` ${color}${trimmed}`;
313
- }
314
-
315
- return "";
316
- }
33
+ public static readonly C = C;
34
+ private static dashboard: DashboardService | null = null;
35
+ private static activeSpinner: { stop: (success?: boolean) => void } | null = null;
36
+ private static lastDomain = "";
37
+
38
+ static setDashboard(dashboard: DashboardService) {
39
+ this.dashboard = dashboard;
40
+ }
41
+
42
+ // Banner completo com informações do ambiente
43
+ static banner(command?: string, profile?: string, encoding?: string) {
44
+ console.clear();
45
+ const git = this.getGitContext();
46
+ const name = process.cwd().split(/[/\\]/).pop() || "project";
47
+ 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));
68
+
69
+ // Linha 2: Nome do projeto
70
+ console.log(row(`${C.white}${C.bold}${name}${C.reset}`));
71
+
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
+ }
82
+
83
+ // Divisor
84
+ console.log(`${C.gray}╠══════════════════════════════════════════════════════╣${C.reset}`);
85
+
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}`);
93
+
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));
98
+ }
99
+
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}`));
104
+
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}`));
108
+
109
+ // Rodapé
110
+ console.log(`${C.gray}╚══════════════════════════════════════════════════════╝${C.reset}`);
111
+ console.log();
112
+ }
113
+
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
+ }
122
+ }
123
+
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
+ }
144
+ }
145
+
146
+ // Seções com divisórias clean
147
+ static section(title: string) {
148
+ console.log(`${C.gray}┌─ ${C.white}${C.bold}${title}${C.reset}`);
149
+ }
150
+
151
+ static endSection() {
152
+ console.log(`${C.gray}└${C.reset}`);
153
+ }
154
+
155
+ // Configurações em formato chave: valor alinhado
156
+ 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}`);
164
+ }
165
+
166
+ // Status com ícones minimalistas
167
+ static ready(msg: string) {
168
+ console.log(`${C.gray}│${C.reset} ${C.success}●${C.reset} ${msg}`);
169
+ }
170
+
171
+ static info(label: string, value?: string) {
172
+ if (value) {
173
+ console.log(`${C.gray}│${C.reset} ${C.dim}${label}${C.reset} ${C.gray}:${C.reset} ${C.white}${value}${C.reset}`);
174
+ } else {
175
+ console.log(`${C.gray}│${C.reset} ${C.info}ℹ${C.reset} ${label}`);
176
+ }
177
+ }
178
+
179
+ static success(msg: string) {
180
+ console.log(`${C.gray}│${C.reset} ${C.success}✓${C.reset} ${msg}`);
181
+ }
182
+
183
+ static error(msg: string) {
184
+ console.log(`${C.gray}│${C.reset} ${C.error}✗${C.reset} ${C.error}${msg}${C.reset}`);
185
+ }
186
+
187
+ static warn(msg: string) {
188
+ console.log(`${C.gray}│${C.reset} ${C.warning}⚠${C.reset} ${msg}`);
189
+ }
190
+
191
+ static debug(msg: string) {
192
+ console.log(`${C.gray}│${C.reset} ${C.gray}🐛 ${msg}${C.reset}`);
193
+ }
194
+
195
+ static step(msg: string) {
196
+ console.log(`${C.gray}│${C.reset} ${C.gray}▸ ${msg}${C.reset}`);
197
+ }
198
+
199
+ static log(msg: string) {
200
+ console.log(msg);
201
+ }
202
+
203
+ static dim(msg: string) {
204
+ console.log(`${C.dim}${msg}${C.reset}`);
205
+ }
206
+
207
+ static newline() {
208
+ console.log();
209
+ }
210
+
211
+ 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}`);
213
+ }
214
+
215
+ 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}`);
217
+ }
218
+
219
+ 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}`);
221
+ }
222
+
223
+ 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}`);
225
+ }
226
+
227
+ 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}`);
229
+ }
230
+
231
+ 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}`);
238
+ }
239
+
240
+ // Spinner moderno
241
+ static spinner(msg: string) {
242
+ if (this.dashboard?.isTuiActive()) {
243
+ return this.dashboard.spinner(msg);
244
+ }
245
+
246
+ const frames = ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"];
247
+ let i = 0;
248
+
249
+ process.stdout.write(`${C.gray}│${C.reset} `);
250
+ process.stdout.write("\x1B[?25l");
251
+
252
+ const timer = setInterval(() => {
253
+ process.stdout.write(`\r${C.gray}│${C.reset} ${C.primary}${frames[i]}${C.reset} ${C.dim}${msg}${C.reset}`);
254
+ i = (i + 1) % frames.length;
255
+ }, 80);
256
+
257
+ return (success = true) => {
258
+ clearInterval(timer);
259
+ process.stdout.write("\x1B[?25h");
260
+ if (success) {
261
+ console.log(`\r${C.gray}│${C.reset} ${C.success}✓${C.reset} ${msg}`);
262
+ } else {
263
+ console.log(`\r${C.gray}│${C.reset} ${C.error}✗${C.reset} ${C.error}${msg}${C.reset}`);
264
+ }
265
+ };
266
+ }
267
+
268
+ // Divisória simples
269
+ static divider() {
270
+ console.log(`${C.gray}├────────────────────────────────────────────────────────┤${C.reset}`);
271
+ }
272
+
273
+ // Finalização
274
+ static done() {
275
+ console.log(`${C.gray}└────────────────────────────────────────────────────────┘${C.reset}`);
276
+ console.log();
277
+ }
278
+
279
+ // Helper para contexto git
280
+ static getGitContext(): { branch: string; author: string; hash: string } {
281
+ try {
282
+ 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 };
286
+ } catch {
287
+ return { branch: "", author: "", hash: "" };
288
+ }
289
+ }
290
+
291
+ // Filtros de noise (mantidos)
292
+ // Controle de rate limiting para hotswap
293
+ private static lastHotswapTime = 0;
294
+ private static hotswapCount = 0;
295
+
296
+ 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));
312
+ }
313
+
314
+ 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:");
318
+ }
319
+
320
+ // Sumarização de logs do Tomcat (simplificada)
321
+ static summarize(line: string): string {
322
+ if (this.isSystemNoise(line)) return "";
323
+
324
+ // Server startup
325
+ const startupMatch = line.match(/Server startup in.*?([\d,]+)\s*ms/);
326
+ if (startupMatch) {
327
+ const time = startupMatch[1].replace(",", "");
328
+ const seconds = (parseInt(time) / 1000).toFixed(1);
329
+ return `${C.success}ready ${C.gray}in ${C.white}${seconds}s${C.reset}`;
330
+ }
331
+
332
+ // Hotswap com rate limiting (evita spam)
333
+ 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}`;
343
+ }
344
+
345
+ // Erros de compilação
346
+ const compilationError = line.match(/\[ERROR\].*?(\w+\.java):\[(\d+).*?\]\s*(.+)/);
347
+ if (compilationError) {
348
+ 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}`;
350
+ }
351
+
352
+ // Erros SEVERE
353
+ if (line.includes("SEVERE") || line.includes("Exception")) {
354
+ return `${C.error}✗ ${C.gray}${line.slice(0, 80)}${C.reset}`;
355
+ }
356
+
357
+ // Warnings (filtra noise conhecido)
358
+ 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}`;
361
+ }
362
+
363
+ return "";
364
+ }
365
+
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/,
402
+ /^context:/,
403
+ /^delegate:/,
404
+ /^-+> Parent Classloader/,
405
+ /^java\.net\.URLClassLoader/,
406
+ ];
407
+
408
+ static isTomcatNoise(line: string): boolean {
409
+ return this.tomcatNoisePatterns.some(p => p.test(line));
410
+ }
411
+
412
+ static formatTomcatLog(line: string): string {
413
+ const cleanLine = line.trim();
414
+ if (!cleanLine) return "";
415
+
416
+ // Silencia noise completamente
417
+ if (this.isTomcatNoise(cleanLine)) return "";
418
+
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}`;
424
+ }
425
+ }
426
+
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}`;
449
+ }
450
+ }
451
+
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}`;
464
+ }
465
+
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}`;
470
+ }
471
+
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}`;
476
+ }
477
+
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}`;
482
+ }
483
+
484
+ // Erros e warnings que passaram pelo filtro
485
+ 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}`;
487
+ }
488
+
489
+ 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}`;
491
+ }
492
+
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
+ }
500
+ }
501
+
502
+ return "";
503
+ }
504
+
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
+ ];
544
+
545
+ static isBuildNoise(line: string): boolean {
546
+ return this.buildNoisePatterns.some(p => p.test(line)) ||
547
+ this.gradleNoisePatterns.some(p => p.test(line));
548
+ }
549
+
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 {
554
+ const cleanLine = line.trim();
555
+ if (!cleanLine) return "";
556
+
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;
561
+ 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}`;
563
+ }
564
+
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}`;
568
+ }
569
+
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
+ }
576
+ }
577
+
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
585
+ const compilingMatch = cleanLine.match(/^\[INFO\]\s+Compiling\s+(\d+)\s+source/);
586
+ 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}`;
607
+ }
608
+
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
618
+ }
619
+
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}`;
624
+ }
625
+
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}`;
633
+ }
634
+
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
+ }
642
+ }
643
+
644
+ return "";
645
+ }
317
646
  }