@archznn/xavva 2.0.3 → 2.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/src/utils/ui.ts CHANGED
@@ -1,315 +1,241 @@
1
1
  import pkg from "../../package.json";
2
+ import type { DashboardService } from "../services/DashboardService";
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
+ };
2
31
 
3
32
  export class Logger {
4
- public static readonly C = {
5
- reset: "\x1b[0m",
6
- cyan: "\x1b[36m",
7
- green: "\x1b[32m",
8
- yellow: "\x1b[33m",
9
- red: "\x1b[31m",
10
- dim: "\x1b[90m",
11
- bold: "\x1b[1m",
12
- blue: "\x1b[34m",
13
- magenta: "\x1b[35m",
14
- bgRed: "\x1b[41m",
15
- white: "\x1b[37m",
16
- gray: "\x1b[38;5;240m",
17
- lightGray: "\x1b[38;5;248m",
18
- darkGray: "\x1b[38;5;238m"
19
- };
20
-
21
- private static hotswapPluginsCount = 0;
22
- private static lastDomain = "";
23
- private static lastHotswapMsg = "";
24
- private static activeSpinnerMsg = "";
25
- private static dashboard: any = null;
26
-
27
- static setDashboard(dashboard: any) {
28
- this.dashboard = dashboard;
29
- }
30
-
31
- private static write(message: string, isError: boolean = false) {
32
- if (this.dashboard && this.dashboard.isTuiActive()) {
33
- this.dashboard.log(message);
34
- return;
35
- }
36
-
37
- if (this.activeSpinnerMsg) {
38
- process.stdout.write("\r\x1B[K"); // Limpa a linha do spinner
39
- }
40
-
41
- if (isError) {
42
- console.error(message + this.C.reset);
43
- } else {
44
- console.log(message + this.C.reset);
45
- }
46
-
47
- if (this.activeSpinnerMsg) {
48
- // Re-imprime o início da linha do spinner para o próximo frame
49
- process.stdout.write(` ${this.C.cyan}⠋${this.C.reset} ${this.activeSpinnerMsg}...`);
50
- }
51
- }
52
-
53
- static getGitContext(): { branch: string, author: string, hash: string } {
54
- try {
55
- const branch = Bun.spawnSync(["git", "rev-parse", "--abbrev-ref", "HEAD"]).stdout.toString().trim();
56
- const author = Bun.spawnSync(["git", "log", "-1", "--format=%an"]).stdout.toString().trim();
57
- const hash = Bun.spawnSync(["git", "rev-parse", "--short", "HEAD"]).stdout.toString().trim();
58
- return { branch, author, hash };
59
- } catch (e) {
60
- return { branch: "", author: "", hash: "" };
61
- }
62
- }
63
-
64
- static banner(command?: string, profile?: string) {
65
- console.clear();
66
- const git = this.getGitContext();
67
- const name = (process.cwd().split(/[/\\]/).pop() || "PROJECT").toUpperCase();
68
- const version = `v${pkg.version}`;
69
-
70
- const mode = command?.toUpperCase() || "DEPLOY";
71
- const modeColor = mode === "DEV" ? this.C.green : this.C.blue;
72
- const modeIcon = mode === "DEV" ? "⚡" : "🚀";
73
-
74
- console.log("");
75
- 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}`);
76
-
77
- const profileInfo = profile ? ` ${this.C.dim}•${this.C.reset} ${this.C.yellow}♦ ${profile.toUpperCase()}${this.C.reset}` : "";
78
- 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}` : "";
79
- console.log(` ${this.C.dim}📦 ${version}${profileInfo}${gitInfo ? ` ${this.C.dim}•${this.C.reset} ${gitInfo}` : ""}${this.C.reset}`);
80
-
81
- console.log(` ${modeColor}${this.C.bold}⬢ ${modeIcon} ${mode} MODE${this.C.reset}`);
82
- console.log(` ${this.C.dim}─────────────────────────────────────────────────${this.C.reset}`);
83
- }
84
-
85
- static section(title: string) {
86
- this.write(`\n${this.C.bold}${this.C.blue}[${title.toUpperCase()}]${this.C.reset}`);
87
- }
88
-
89
- private static domain(name: string) {
90
- if (this.lastDomain !== name) {
91
- this.write(`\n${this.C.bold}${this.C.blue}[${name.toUpperCase()}]${this.C.reset}`);
92
- this.lastDomain = name;
93
- }
94
- }
95
-
96
- static config(label: string, value: string | number | boolean) {
97
- this.domain("config");
98
- this.info(label, value);
99
- }
100
-
101
- static info(label: string, value: string | number | boolean) {
102
- this.write(` ${this.C.lightGray}${label.padEnd(12)}${this.C.reset} : ${this.C.bold}${value}${this.C.reset}`);
103
- }
104
-
105
- static build(msg: string, status: 'start' | 'success' | 'error' | 'info' = 'success') {
106
- this.domain("build");
107
- const symbol = status === 'start' ? `${this.C.blue}▶` : status === 'success' ? `${this.C.green}✔` : status === 'error' ? `${this.C.red}✖` : `${this.C.dim}ℹ`;
108
- this.write(` ${symbol} ${this.C.reset}${msg}`);
109
- }
110
-
111
- static server(msg: string, status: 'start' | 'success' | 'error' | 'info' = 'info') {
112
- this.domain("server");
113
- const symbol = status === 'start' ? `${this.C.blue}▶` : status === 'success' ? `${this.C.green}✔` : status === 'error' ? `${this.C.red}✖` : `${this.C.dim}ℹ`;
114
- this.write(` ${symbol} ${this.C.reset}${msg}`);
115
- }
116
-
117
- static health(msg: string, status: 'success' | 'error' | 'warn' = 'success') {
118
- this.domain("health");
119
- const symbol = status === 'success' ? `${this.C.green}✔` : status === 'error' ? `${this.C.red}✖` : `${this.C.yellow}⚠`;
120
- this.write(` ${symbol} ${this.C.reset}${msg}`);
121
- }
122
-
123
- static watcher(msg: string, status: 'watch' | 'change' | 'start' | 'success' = 'success') {
124
- this.domain("watcher");
125
- const symbol = status === 'watch' ? `${this.C.magenta}👀` : status === 'change' ? `${this.C.yellow}▲` : status === 'start' ? `${this.C.blue}▶` : `${this.C.green}✔`;
126
- this.write(` ${symbol} ${this.C.reset}${msg}`);
127
- }
128
-
129
- static success(msg: string) { this.write(` ${this.C.green}✔ ${msg}`); }
130
- static error(msg: string) { this.write(` ${this.C.red} ${msg}`, true); }
131
- static warn(msg: string) { this.write(` ${this.C.yellow}⚠ ${msg}`); }
132
- static log(msg: string) { this.write(` ${msg}`); }
133
- static step(msg: string) { this.write(` ${this.C.dim}» ${msg}`); }
134
- static debug(msg: string) { this.write(` ${this.C.magenta}🐛 ${msg}`); }
135
- static process(msg: string) { this.write(` ${this.C.blue}▶ ${msg}`); }
136
- static newline() { this.write(""); }
137
- static dim(msg: string) { this.write(` ${this.C.dim}${msg}${this.C.reset}`); }
138
-
139
- static spinner(msg: string) {
140
- if (this.dashboard && this.dashboard.isTuiActive()) {
141
- this.dashboard.log(`${this.C.cyan}⠋${this.C.reset} ${msg}...`);
142
- return (success = true) => {
143
- if (success) {
144
- this.dashboard.log(`${this.C.green}✔${this.C.reset} ${msg}`);
145
- } else {
146
- this.dashboard.log(`${this.C.red}✖${this.C.reset} Falha em ${msg}`);
147
- }
148
- };
149
- }
150
-
151
- this.activeSpinnerMsg = msg;
152
- const frames = ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"];
153
- let i = 0;
154
- process.stdout.write("\x1B[?25l");
155
-
156
- const timer = setInterval(() => {
157
- process.stdout.write(`\r ${this.C.cyan}${frames[i]}${this.C.reset} ${msg}...`);
158
- i = (i + 1) % frames.length;
159
- }, 80);
160
-
161
- return (success = true) => {
162
- clearInterval(timer);
163
- this.activeSpinnerMsg = "";
164
- process.stdout.write("\r\x1B[K");
165
- process.stdout.write("\x1B[?25h");
166
- if (success) {
167
- this.write(` ${this.C.green}✔${this.C.reset} ${msg}`);
168
- } else {
169
- this.error(`Falha em ${msg}`);
170
- }
171
- };
172
- }
173
-
174
- static isSystemNoise(line: string): boolean {
175
- const noise = [
176
- "Using CATALINA_", "Using JRE_HOME", "Using CLASSPATH", "NOTE: Picked up JDK_JAVA_OPTIONS",
177
- "Command line argument", "VersionLoggerListener", "Scanning for projects...",
178
- "Building ", "--- ", "+++ ", "DEBUG: ", "org.apache.catalina.startup.VersionLoggerListener",
179
- "org.apache.catalina.core.AprLifecycleListener", "org.apache.coyote.AbstractProtocol.init",
180
- "org.apache.catalina.startup.Catalina.load", "Arquivos processados em",
181
- "org.apache.jasper.servlet.TldScanner.scanJars", "Listening for transport dt_socket",
182
- "org.apache.catalina.startup.ExpandWar.expand", "org.apache.catalina.startup.ContextConfig.configureStart",
183
- "SLF4J: ", "org.glassfish.jersey.internal.Errors.logErrors", "contains empty path annotation",
184
- "org.apache.catalina.core.StandardContext.setPath", "milliseconds",
185
- "org.apache.catalina.startup.HostConfig.deployWAR", "org.apache.catalina.startup.HostConfig.deployDirectory",
186
- "Deployment of web application", "Deploying web application archive", "at org.apache",
187
- "Registering directory", "initialized in ClassLoader", "Discovered plugins:",
188
- "enhanced with plugin initialization", "registerJerseyContainer", "JasperLoader@",
189
- "Hotswap ready (Plugins:", "autoHotswap.delay", "watchResources=false",
190
- "org.apache.catalina.webresources.Cache.getResource", "insufficient free space available"
191
- ];
192
- return noise.some(n => line.includes(n));
193
- }
194
-
195
- static isEssential(line: string): boolean {
196
- return line.includes("SEVERE") || line.includes("ERROR") || line.includes("Exception") ||
197
- line.includes("Caused by") || line.includes("at ") || line.includes("... ") ||
198
- line.includes("Server startup in") || line.includes("HOTSWAP AGENT:");
199
- }
200
-
201
- static summarize(line: string): string {
202
- if (this.isSystemNoise(line)) return "";
203
-
204
- const startupMatch = line.match(/Server startup in (\[?)(.*?)(\]?)\s*ms/);
205
- if (startupMatch) {
206
- const time = (parseInt(startupMatch[2]) / 1000).toFixed(1);
207
- this.domain("server");
208
- return `${this.C.green}✔ ${this.C.bold}Server started in ${time}s`;
209
- }
210
-
211
- const deployMatch = line.match(/Deployment of web application archive \[(.*?)\] has finished in \[(.*?)\] ms/);
212
- if (deployMatch) {
213
- this.domain("build");
214
- return `${this.C.green}✔ Artifacts deployed`;
215
- }
216
-
217
- const hotswapPattern = /HOTSWAP AGENT:.*? (INFO|WARN|ERROR|RELOAD) (.*?) - (.*)/;
218
- const hotswapMatch = line.match(hotswapPattern);
219
- if (hotswapMatch) {
220
- const level = hotswapMatch[1];
221
- let msg = hotswapMatch[3];
222
-
223
- if (msg.includes("plugin initialized")) {
224
- this.hotswapPluginsCount++;
225
- return "";
226
- }
227
-
228
- if (msg.includes("redefinition") || msg.includes("reloaded") || level === 'RELOAD') {
229
- if (msg.includes("Reloading classes [")) {
230
- const classes = msg.match(/\[(.*?)\]/)?.[1] || "";
231
- const classCount = classes.split(",").length;
232
- if (classCount > 3) msg = `Reloading ${classCount} classes...`;
233
- }
234
-
235
- if (msg === this.lastHotswapMsg) return "";
236
- this.lastHotswapMsg = msg;
237
-
238
- this.watcher(`Hotswap: ${msg.replace(/Class '.*?'/, (m) => this.C.bold + m + this.C.reset)}`, 'success');
239
- return "";
240
- }
241
-
242
- if (msg.includes("Loading Hotswap agent")) {
243
- this.domain("server");
244
- return `${this.C.blue}▶ ${this.C.reset}Initializing Hotswap Agent ${msg.match(/\d+\.\d+\.\d+/)?.[0] || ""}`;
245
- }
246
-
247
- if (this.hotswapPluginsCount > 0) {
248
- const count = this.hotswapPluginsCount;
249
- this.hotswapPluginsCount = 0;
250
- this.domain("server");
251
- this.write(` ${this.C.green}✔ ${this.C.reset}Hotswap ready (Plugins: ${count} loaded)`);
252
- }
253
-
254
- let color = this.C.cyan;
255
- let symbol = "●";
256
- if (level === "WARN") { color = this.C.yellow; symbol = "▲"; }
257
- else if (level === "ERROR") { color = this.C.red; symbol = "✖"; }
258
-
259
- this.domain("server");
260
- return `${color}${symbol} ${this.C.bold}Hotswap:${this.C.reset} ${msg}`;
261
- }
262
-
263
- if (line.includes("java.lang.UnsupportedOperationException") && (line.includes("add a method") || line.includes("change the schema"))) {
264
- this.domain("watcher");
265
- this.write(` ${this.C.red}✖ ${this.C.bold}Hotswap Falhou:${this.C.reset} Mudança estrutural detectada (novo método/campo).`);
266
- this.write(` ${this.C.yellow}💡 Dica: Sua JVM atual não suporta mudar a estrutura da classe. Reinicie o servidor para aplicar.`);
267
- return "";
268
- }
269
-
270
- const tomcatPattern = /^(\d{2}-\w{3}-\d{4} \d{2}:\d{2}:\d{2}\.\d{3})\s+(INFO|WARNING|SEVERE|ERROR)\s+\[(.*?)\]\s+(.*)$/;
271
- const tMatch = line.match(tomcatPattern);
272
- if (tMatch) {
273
- const label = tMatch[2];
274
- let msg = tMatch[4].trim();
275
- if (this.isSystemNoise(msg)) return "";
276
- let color = this.C.dim;
277
- let symbol = "ℹ";
278
- if (label === "WARNING") { color = this.C.yellow; symbol = "▲"; }
279
- else if (label === "SEVERE" || label === "ERROR") { color = this.C.red; symbol = "✖"; }
280
- msg = msg.replace(/^(org\.apache|com\.sun|java\..*?|org\.glassfish)\.[a-zA-Z0-9.]+\s/, "").trim();
281
- if (!msg) return "";
282
- return `${color}${symbol} ${msg}`;
283
- }
284
-
285
- const compilationErrorMatch = line.match(/^\[ERROR\]\s+(.*\.java):\[(\d+),(\d+)\]\s+(.*)$/);
286
- if (compilationErrorMatch) {
287
- const [_, filePath, row, col, msg] = compilationErrorMatch;
288
- const fileName = filePath.split(/[/\\]/).pop();
289
- 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}`;
290
- }
291
-
292
- const logPattern = /^\[(INFO|WARNING|WARN|SEVERE|ERROR)\]\s+(.*)$/;
293
- const match = line.match(logPattern);
294
- if (match) {
295
- const label = match[1];
296
- let msg = match[2].trim();
297
- if (msg.includes("Total time:") || msg.includes("Finished at:") || msg.includes("Final Memory:") || msg.includes("-----------------------")) return "";
298
- let color = this.C.dim;
299
- let symbol = "ℹ";
300
- if (label === "WARNING") { color = this.C.yellow; symbol = "▲"; }
301
- else if (label === "SEVERE" || label === "ERROR") { color = this.C.red; symbol = "✖"; }
302
- msg = msg.replace(/^(org\.apache|com\.sun|java\..*?)\.[a-zA-Z0-9.]+\s/, "").trim();
303
- if (!msg || msg === "]" || msg.includes("Compilation failure")) return "";
304
- return `${color}${symbol} ${msg}`;
305
- }
306
-
307
- if (line.includes("Exception") || line.includes("Caused by") || line.includes("at ")) {
308
- const trimmed = line.trim();
309
- const color = (trimmed.includes("org.apache") || trimmed.includes("java.base") || trimmed.includes("sun.reflect")) ? this.C.dim : this.C.yellow;
310
- return ` ${color}${trimmed}`;
311
- }
312
-
313
- return "";
314
- }
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 moderno e clean
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
+
48
+ // Linha superior decorativa
49
+ console.log(`${C.gray}┌────────────────────────────────────────────────────────┐${C.reset}`);
50
+
51
+ // Logo e projeto
52
+ console.log(`${C.gray}│${C.reset} ${C.primary}${C.bold}XAVVA${C.reset}${C.gray}.${C.reset}${C.dim}v${pkg.version}${C.reset} ${C.gray}│${C.reset} ${C.white}${C.bold}${name}${C.reset}`);
53
+
54
+ // Info adicional em uma linha
55
+ const parts: string[] = [];
56
+ if (command) parts.push(`${C.primary}${command}${C.reset}`);
57
+ if (profile) parts.push(`${C.warning}profile:${profile}${C.reset}`);
58
+ if (encoding) parts.push(`${C.info}${encoding}${C.reset}`);
59
+ if (git.branch) parts.push(`${C.secondary}git:${git.branch}${C.reset}`);
60
+
61
+ if (parts.length > 0) {
62
+ console.log(`${C.gray}│${C.reset} ${C.dim}mode${C.reset} ${C.gray}│${C.reset} ${parts.join(` ${C.gray}•${C.reset} `)}`);
63
+ }
64
+
65
+ // Linha inferior
66
+ console.log(`${C.gray}└────────────────────────────────────────────────────────┘${C.reset}`);
67
+ console.log();
68
+ }
69
+
70
+ // Seções com divisórias clean
71
+ static section(title: string) {
72
+ console.log(`${C.gray}┌─ ${C.white}${C.bold}${title}${C.reset}`);
73
+ }
74
+
75
+ static endSection() {
76
+ console.log(`${C.gray}└${C.reset}`);
77
+ }
78
+
79
+ // Configurações em formato chave: valor alinhado
80
+ static config(label: string, value: string | number | boolean) {
81
+ const valueStr = String(value);
82
+ const isBool = typeof value === 'boolean';
83
+ const displayValue = isBool
84
+ ? (value ? `${C.successBright}✓${C.reset} ${C.success}enabled${C.reset}` : `${C.gray}○${C.reset} ${C.gray}disabled${C.reset}`)
85
+ : `${C.white}${valueStr}${C.reset}`;
86
+
87
+ console.log(`${C.gray}│${C.reset} ${C.dim}${label.padEnd(12)}${C.reset} ${C.gray}:${C.reset} ${displayValue}`);
88
+ }
89
+
90
+ // Status com ícones minimalistas
91
+ static ready(msg: string) {
92
+ console.log(`${C.gray}│${C.reset} ${C.success}●${C.reset} ${msg}`);
93
+ }
94
+
95
+ static info(label: string, value?: string) {
96
+ if (value) {
97
+ console.log(`${C.gray}│${C.reset} ${C.dim}${label}${C.reset} ${C.gray}:${C.reset} ${C.white}${value}${C.reset}`);
98
+ } else {
99
+ console.log(`${C.gray}│${C.reset} ${C.info}ℹ${C.reset} ${label}`);
100
+ }
101
+ }
102
+
103
+ static success(msg: string) {
104
+ console.log(`${C.gray}│${C.reset} ${C.success}✓${C.reset} ${msg}`);
105
+ }
106
+
107
+ static error(msg: string) {
108
+ console.log(`${C.gray}│${C.reset} ${C.error}✗${C.reset} ${C.error}${msg}${C.reset}`);
109
+ }
110
+
111
+ static warn(msg: string) {
112
+ console.log(`${C.gray}│${C.reset} ${C.warning}⚠${C.reset} ${msg}`);
113
+ }
114
+
115
+ static build(msg: string) {
116
+ console.log(`${C.gray}│${C.reset} ${C.primary}▸${C.reset} ${C.dim}build${C.reset} ${C.gray}:${C.reset} ${msg}`);
117
+ }
118
+
119
+ static server(msg: string) {
120
+ console.log(`${C.gray}│${C.reset} ${C.primary}▸${C.reset} ${C.dim}server${C.reset} ${C.gray}:${C.reset} ${msg}`);
121
+ }
122
+
123
+ static watch(msg: string) {
124
+ console.log(`${C.gray}│${C.reset} ${C.secondary}◉${C.reset} ${C.dim}watch${C.reset} ${C.gray}:${C.reset} ${msg}`);
125
+ }
126
+
127
+ static hotswap(msg: string) {
128
+ console.log(`${C.gray}│${C.reset} ${C.secondary}↻${C.reset} ${C.dim}hotswap${C.reset} ${C.gray}:${C.reset} ${msg}`);
129
+ }
130
+
131
+ // URL formatada de forma destacada
132
+ static url(label: string, url: string) {
133
+ console.log(`${C.gray}│${C.reset} ${C.dim}${label}${C.reset} ${C.gray}:${C.reset} ${C.primaryBright}${C.bold}${url}${C.reset}`);
134
+ }
135
+
136
+ // Spinner moderno
137
+ static spinner(msg: string) {
138
+ if (this.dashboard?.isTuiActive()) {
139
+ return this.dashboard.spinner(msg);
140
+ }
141
+
142
+ const frames = ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"];
143
+ let i = 0;
144
+
145
+ process.stdout.write(`${C.gray}│${C.reset} `);
146
+ process.stdout.write("\x1B[?25l");
147
+
148
+ const timer = setInterval(() => {
149
+ process.stdout.write(`\r${C.gray}│${C.reset} ${C.primary}${frames[i]}${C.reset} ${C.dim}${msg}${C.reset}`);
150
+ i = (i + 1) % frames.length;
151
+ }, 80);
152
+
153
+ return (success = true) => {
154
+ clearInterval(timer);
155
+ process.stdout.write("\x1B[?25h");
156
+ if (success) {
157
+ console.log(`\r${C.gray}│${C.reset} ${C.success}✓${C.reset} ${msg}`);
158
+ } else {
159
+ console.log(`\r${C.gray}│${C.reset} ${C.error}✗${C.reset} ${C.error}${msg}${C.reset}`);
160
+ }
161
+ };
162
+ }
163
+
164
+ // Linha em branco
165
+ static newline() {
166
+ console.log();
167
+ }
168
+
169
+ // Divisória simples
170
+ static divider() {
171
+ console.log(`${C.gray}├────────────────────────────────────────────────────────┤${C.reset}`);
172
+ }
173
+
174
+ // Finalização
175
+ static done() {
176
+ console.log(`${C.gray}└────────────────────────────────────────────────────────┘${C.reset}`);
177
+ console.log();
178
+ }
179
+
180
+ // Helper para contexto git
181
+ static getGitContext(): { branch: string; author: string; hash: string } {
182
+ try {
183
+ const branch = Bun.spawnSync(["git", "rev-parse", "abbrev-ref", "HEAD"]).stdout.toString().trim();
184
+ const author = Bun.spawnSync(["git", "log", "-1", "format=%an"]).stdout.toString().trim();
185
+ const hash = Bun.spawnSync(["git", "rev-parse", "--short", "HEAD"]).stdout.toString().trim();
186
+ return { branch, author, hash };
187
+ } catch {
188
+ return { branch: "", author: "", hash: "" };
189
+ }
190
+ }
191
+
192
+ // Filtros de noise (mantidos)
193
+ static isSystemNoise(line: string): boolean {
194
+ const noise = [
195
+ "Using CATALINA_", "Using JRE_HOME", "Using CLASSPATH",
196
+ "Scanning for projects...", "Building ", "--- ", "+++ ",
197
+ "Arquivos processados em", "milliseconds",
198
+ "SLF4J: ", "Discovered plugins:",
199
+ "enhanced with plugin initialization", "Hotswap ready",
200
+ "autoHotswap.delay", "watchResources=false",
201
+ ];
202
+ return noise.some(n => line.includes(n));
203
+ }
204
+
205
+ // Sumarização de logs do Tomcat (simplificada)
206
+ static summarize(line: string): string {
207
+ if (this.isSystemNoise(line)) return "";
208
+
209
+ // Server startup
210
+ const startupMatch = line.match(/Server startup in.*?([\d,]+)\s*ms/);
211
+ if (startupMatch) {
212
+ const time = startupMatch[1].replace(",", "");
213
+ const seconds = (parseInt(time) / 1000).toFixed(1);
214
+ return `${C.success}ready ${C.gray}in ${C.white}${seconds}s${C.reset}`;
215
+ }
216
+
217
+ // Hotswap
218
+ if (line.includes("HOTSWAP AGENT") && line.includes("RELOAD")) {
219
+ return `${C.secondary}↻ hotswap ${C.gray}detected${C.reset}`;
220
+ }
221
+
222
+ // Erros de compilação
223
+ const compilationError = line.match(/\[ERROR\].*?(\w+\.java):\[(\d+).*?\]\s*(.+)/);
224
+ if (compilationError) {
225
+ const [, file, lineNum, msg] = compilationError;
226
+ return `${C.error}✗ ${C.white}${file}${C.gray}:${lineNum}${C.reset} ${C.gray}${msg.slice(0, 50)}${C.reset}`;
227
+ }
228
+
229
+ // Erros SEVERE
230
+ if (line.includes("SEVERE") || line.includes("Exception")) {
231
+ return `${C.error}✗ ${C.gray}${line.slice(0, 80)}${C.reset}`;
232
+ }
233
+
234
+ // Warnings
235
+ if (line.includes("WARNING")) {
236
+ return `${C.warning}⚠ ${C.gray}${line.slice(0, 80)}${C.reset}`;
237
+ }
238
+
239
+ return "";
240
+ }
315
241
  }