@archznn/xavva 1.6.5 → 1.8.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.
@@ -0,0 +1,78 @@
1
+ import { watch } from "fs";
2
+ import { Logger } from "../utils/ui";
3
+ import { DeployCommand } from "../commands/DeployCommand";
4
+ import type { AppConfig } from "../types/config";
5
+
6
+ export class WatcherService {
7
+ private isDeploying = false;
8
+ private pendingFullBuild = false;
9
+ private coolingFiles = new Set<string>();
10
+ private debounceTimer?: Timer;
11
+
12
+ constructor(private config: AppConfig, private deployCmd: DeployCommand) {}
13
+
14
+ public async start() {
15
+ await this.run(false);
16
+
17
+ watch(process.cwd(), { recursive: true }, async (event, filename) => {
18
+ if (!filename) return;
19
+
20
+ if (this.coolingFiles.has(filename)) return;
21
+ this.coolingFiles.add(filename);
22
+ setTimeout(() => this.coolingFiles.delete(filename), 500);
23
+
24
+ if (this.isIgnored(filename)) return;
25
+
26
+ const isBuildConfig = filename === "pom.xml" || filename === "build.gradle" || filename === "build.gradle.kts";
27
+ const isJava = filename.endsWith(".java") || isBuildConfig;
28
+ const isResource = this.isResourceFile(filename);
29
+
30
+ if (isBuildConfig) {
31
+ Logger.watcher(`Build configuration changed: ${filename}`, 'warn');
32
+ const { BuildCacheService } = await import("./BuildCacheService");
33
+ new BuildCacheService().clearCache();
34
+ this.pendingFullBuild = true;
35
+ }
36
+
37
+ if (isResource && !isJava) {
38
+ await this.deployCmd.syncResource(this.config, filename);
39
+ return;
40
+ }
41
+
42
+ if (!isJava) return;
43
+
44
+ Logger.watcher(filename, 'watch');
45
+ clearTimeout(this.debounceTimer);
46
+
47
+ this.debounceTimer = setTimeout(() => {
48
+ this.run(this.pendingFullBuild ? false : true);
49
+ this.pendingFullBuild = false;
50
+ }, 1000);
51
+ });
52
+ }
53
+
54
+ private async run(incremental = false) {
55
+ if (this.isDeploying) return;
56
+ this.isDeploying = true;
57
+ try {
58
+ await this.deployCmd.execute(this.config, { watch: true, incremental });
59
+ } catch (e) {
60
+ // Error handled by command
61
+ } finally {
62
+ this.isDeploying = false;
63
+ }
64
+ }
65
+
66
+ private isIgnored(filename: string): boolean {
67
+ return filename.includes("target") ||
68
+ filename.includes("build") ||
69
+ filename.includes("node_modules") ||
70
+ filename.split(/[/\\]/).some(part => part.startsWith("."));
71
+ }
72
+
73
+ private isResourceFile(filename: string): boolean {
74
+ return filename.endsWith(".jsp") || filename.endsWith(".html") ||
75
+ filename.endsWith(".css") || filename.endsWith(".js") ||
76
+ filename.endsWith(".xml") || filename.endsWith(".properties");
77
+ }
78
+ }
@@ -11,10 +11,12 @@ export interface ProjectConfig {
11
11
  profile: string;
12
12
  skipBuild: boolean;
13
13
  skipScan: boolean;
14
- cleanLogs: boolean;
14
+ clean: boolean;
15
15
  quiet: boolean;
16
16
  verbose: boolean;
17
17
  debug: boolean;
18
+ debugPort: number;
19
+ cleanLogs: boolean;
18
20
  grep?: string;
19
21
  }
20
22
 
@@ -22,3 +24,30 @@ export interface AppConfig {
22
24
  tomcat: TomcatConfig;
23
25
  project: ProjectConfig;
24
26
  }
27
+
28
+ export interface CLIArguments {
29
+ path?: string;
30
+ tool?: string;
31
+ name?: string;
32
+ port?: string;
33
+ "no-build"?: boolean;
34
+ scan?: boolean;
35
+ clean?: boolean;
36
+ quiet?: boolean;
37
+ help?: boolean;
38
+ version?: boolean;
39
+ debug?: boolean;
40
+ watch?: boolean;
41
+ profile?: string;
42
+ grep?: string;
43
+ verbose?: boolean;
44
+ dp?: string;
45
+ fix?: boolean;
46
+ incremental?: boolean;
47
+ }
48
+
49
+ export interface CommandContext {
50
+ config: AppConfig;
51
+ positionals: string[];
52
+ values: CLIArguments;
53
+ }
@@ -1,10 +1,10 @@
1
1
  import { parseArgs } from "util";
2
2
  import path from "path";
3
3
  import fs from "fs";
4
- import type { AppConfig } from "../types/config";
4
+ import type { AppConfig, CLIArguments, CommandContext } from "../types/config";
5
5
 
6
6
  export class ConfigManager {
7
- static async load(): Promise<{ config: AppConfig, positionals: string[], values: any }> {
7
+ static async load(): Promise<CommandContext> {
8
8
  const args = Bun.argv.slice(Bun.argv[0].endsWith("bun.exe") || Bun.argv[0].endsWith("bun") ? 2 : 1);
9
9
 
10
10
  const { values, positionals } = parseArgs({
@@ -25,12 +25,14 @@ export class ConfigManager {
25
25
  profile: { type: "string", short: "P" },
26
26
  grep: { type: "string", short: "G" },
27
27
  verbose: { type: "boolean", short: "V" },
28
+ dp: { type: "string" },
28
29
  fix: { type: "boolean" },
29
30
  },
30
31
  strict: false,
31
32
  allowPositionals: true,
32
33
  });
33
34
 
35
+ const cliValues = values as CLIArguments;
34
36
  const isDev = positionals.includes("dev");
35
37
  const isRun = positionals.includes("run") || positionals.includes("debug");
36
38
 
@@ -47,30 +49,32 @@ export class ConfigManager {
47
49
 
48
50
  const config: AppConfig = {
49
51
  tomcat: {
50
- path: String(values.path || envTomcatPath),
51
- port: parseInt(String(values.port || "8080")),
52
+ path: String(cliValues.path || envTomcatPath),
53
+ port: parseInt(String(cliValues.port || "8080")),
52
54
  webapps: "webapps",
53
- grep: values.grep ? String(values.grep) : "",
55
+ grep: cliValues.grep ? String(cliValues.grep) : "",
54
56
  },
55
57
  project: {
56
- appName: values.name ? String(values.name) : "",
57
- buildTool: (values.tool as "maven" | "gradle") || detectedTool,
58
- profile: String(values.profile || ""),
59
- skipBuild: !!values["no-build"],
60
- skipScan: values.scan !== undefined ? !values.scan : true,
61
- cleanLogs: !!(values.clean || isDev),
62
- quiet: !!(values.quiet || isDev),
63
- verbose: !!values.verbose,
64
- debug: !!(values.debug || isDev || isRun),
65
- grep: runClass || (values.grep ? String(values.grep) : ""),
58
+ appName: cliValues.name ? String(cliValues.name) : "",
59
+ buildTool: (cliValues.tool as "maven" | "gradle") || detectedTool,
60
+ profile: String(cliValues.profile || ""),
61
+ skipBuild: !!cliValues["no-build"],
62
+ skipScan: cliValues.scan !== undefined ? !cliValues.scan : true,
63
+ clean: !!cliValues.clean,
64
+ cleanLogs: cliValues.verbose ? false : true,
65
+ quiet: cliValues.verbose ? false : true,
66
+ verbose: !!cliValues.verbose,
67
+ debug: !!(cliValues.debug || isDev || isRun),
68
+ debugPort: parseInt(String(cliValues.dp || "5005")),
69
+ grep: runClass || (cliValues.grep ? String(cliValues.grep) : ""),
66
70
  }
67
71
  };
68
72
 
69
- if (isDev) values.watch = true;
73
+ if (isDev) cliValues.watch = true;
70
74
 
71
75
  this.ensureGitIgnore();
72
76
 
73
- return { config, positionals, values };
77
+ return { config, positionals, values: cliValues };
74
78
  }
75
79
 
76
80
  private static detectBuildTool(): "maven" | "gradle" {
@@ -80,7 +84,7 @@ export class ConfigManager {
80
84
  if (fs.existsSync(path.join(process.cwd(), "build.gradle")) || fs.existsSync(path.join(process.cwd(), "build.gradle.kts"))) {
81
85
  return "gradle";
82
86
  }
83
- return "maven"; // Default to maven if not found
87
+ return "maven";
84
88
  }
85
89
 
86
90
  private static ensureGitIgnore() {
package/src/utils/ui.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  import pkg from "../../package.json";
2
2
 
3
3
  export class Logger {
4
- private static readonly C = {
4
+ public static readonly C = {
5
5
  reset: "\x1b[0m",
6
6
  cyan: "\x1b[36m",
7
7
  green: "\x1b[32m",
@@ -9,60 +9,124 @@ export class Logger {
9
9
  red: "\x1b[31m",
10
10
  dim: "\x1b[90m",
11
11
  bold: "\x1b[1m",
12
- blue: "\x1b[34m"
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"
13
19
  };
14
20
 
15
- static getGitContext(): string {
21
+ private static hotswapPluginsCount = 0;
22
+ private static lastDomain = "";
23
+ private static lastHotswapMsg = "";
24
+ private static activeSpinnerMsg = "";
25
+
26
+ private static write(message: string, isError: boolean = false) {
27
+ if (this.activeSpinnerMsg) {
28
+ process.stdout.write("\r\x1B[K"); // Limpa a linha do spinner
29
+ }
30
+
31
+ if (isError) {
32
+ console.error(message + this.C.reset);
33
+ } else {
34
+ console.log(message + this.C.reset);
35
+ }
36
+
37
+ if (this.activeSpinnerMsg) {
38
+ // Re-imprime o início da linha do spinner para o próximo frame
39
+ process.stdout.write(` ${this.C.cyan}⠋${this.C.reset} ${this.activeSpinnerMsg}...`);
40
+ }
41
+ }
42
+
43
+ static getGitContext(): { branch: string, author: string, hash: string } {
16
44
  try {
17
45
  const branch = Bun.spawnSync(["git", "rev-parse", "--abbrev-ref", "HEAD"]).stdout.toString().trim();
18
46
  const author = Bun.spawnSync(["git", "log", "-1", "--format=%an"]).stdout.toString().trim();
19
- return branch ? `${this.C.blue} ${branch}${this.C.reset} ${this.C.dim}(${author})${this.C.reset}` : "";
47
+ const hash = Bun.spawnSync(["git", "rev-parse", "--short", "HEAD"]).stdout.toString().trim();
48
+ return { branch, author, hash };
20
49
  } catch (e) {
21
- return "";
50
+ return { branch: "", author: "", hash: "" };
22
51
  }
23
52
  }
24
53
 
25
54
  static banner(command?: string) {
26
55
  console.clear();
27
56
  const git = this.getGitContext();
28
- console.log(`${this.C.cyan}
29
- ${this.C.bold}XAVVA ${this.C.reset}${this.C.dim}v${pkg.version}${this.C.reset} ${git}
30
- ${this.C.dim}──────────────────────────${this.C.reset}`);
31
- if (command) {
32
- console.log(` ${this.C.yellow}${this.C.bold}MODO: ${command.toUpperCase()}${this.C.reset}\n`);
33
- }
57
+ const name = (process.cwd().split(/[/\\]/).pop() || "PROJECT").toUpperCase();
58
+ const version = `v${pkg.version}`;
59
+
60
+ const mode = command?.toUpperCase() || "DEPLOY";
61
+ const modeColor = mode === "DEV" ? this.C.green : this.C.blue;
62
+ const modeIcon = mode === "DEV" ? "⚡" : "🚀";
63
+
64
+ console.log("");
65
+ 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}`);
66
+
67
+ 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}` : "";
68
+ console.log(` ${this.C.dim}📦 ${version}${gitInfo ? ` ${this.C.dim}•${this.C.reset} ${gitInfo}` : ""}${this.C.reset}`);
69
+
70
+ console.log(` ${modeColor}${this.C.bold}⬢ ${modeIcon} ${mode} MODE${this.C.reset}`);
71
+ console.log(` ${this.C.dim}─────────────────────────────────────────────────${this.C.reset}`);
34
72
  }
35
73
 
36
74
  static section(title: string) {
37
- console.log(`\n ${this.C.bold}${this.C.blue}${title.toUpperCase()} ${this.C.reset}`);
38
- console.log(` ${this.C.dim}──────────────────────────${this.C.reset}`);
75
+ this.write(`\n${this.C.bold}${this.C.blue}[${title.toUpperCase()}]${this.C.reset}`);
39
76
  }
40
77
 
41
- static info(label: string, value: string | number | boolean) {
42
- console.log(` ${this.C.cyan}${label.padEnd(12)}${this.C.reset} ${this.C.bold}${value}${this.C.reset}`);
78
+ private static domain(name: string) {
79
+ if (this.lastDomain !== name) {
80
+ this.write(`\n${this.C.bold}${this.C.blue}[${name.toUpperCase()}]${this.C.reset}`);
81
+ this.lastDomain = name;
82
+ }
43
83
  }
44
84
 
45
- static success(msg: string) {
46
- console.log(`\n ${this.C.green}✔ ${msg}${this.C.reset}`);
85
+ static config(label: string, value: string | number | boolean) {
86
+ this.domain("config");
87
+ this.info(label, value);
47
88
  }
48
89
 
49
- static error(msg: string) {
50
- console.error(`\n ${this.C.red} ${msg}${this.C.reset}`);
90
+ static info(label: string, value: string | number | boolean) {
91
+ this.write(` ${this.C.lightGray}${label.padEnd(12)}${this.C.reset} : ${this.C.bold}${value}${this.C.reset}`);
51
92
  }
52
93
 
53
- static warn(msg: string) {
54
- console.log(` ${this.C.yellow}⚠ ${msg}${this.C.reset}`);
94
+ static build(msg: string, status: 'start' | 'success' | 'error' | 'info' = 'success') {
95
+ this.domain("build");
96
+ const symbol = status === 'start' ? `${this.C.blue}▶` : status === 'success' ? `${this.C.green}✔` : status === 'error' ? `${this.C.red}✖` : `${this.C.dim}ℹ`;
97
+ this.write(` ${symbol} ${this.C.reset}${msg}`);
55
98
  }
56
99
 
57
- static log(msg: string) {
58
- console.log(` ${msg}`);
100
+ static server(msg: string, status: 'start' | 'success' | 'error' | 'info' = 'info') {
101
+ this.domain("server");
102
+ const symbol = status === 'start' ? `${this.C.blue}▶` : status === 'success' ? `${this.C.green}✔` : status === 'error' ? `${this.C.red}✖` : `${this.C.dim}ℹ`;
103
+ this.write(` ${symbol} ${this.C.reset}${msg}`);
59
104
  }
60
105
 
61
- static step(msg: string) {
62
- console.log(` ${this.C.dim}➜ ${msg}${this.C.reset}`);
106
+ static health(msg: string, status: 'success' | 'error' | 'warn' = 'success') {
107
+ this.domain("health");
108
+ const symbol = status === 'success' ? `${this.C.green}✔` : status === 'error' ? `${this.C.red}✖` : `${this.C.yellow}⚠`;
109
+ this.write(` ${symbol} ${this.C.reset}${msg}`);
63
110
  }
64
111
 
112
+ static watcher(msg: string, status: 'watch' | 'change' | 'start' | 'success' = 'success') {
113
+ this.domain("watcher");
114
+ const symbol = status === 'watch' ? `${this.C.magenta}👀` : status === 'change' ? `${this.C.yellow}▲` : status === 'start' ? `${this.C.blue}▶` : `${this.C.green}✔`;
115
+ this.write(` ${symbol} ${this.C.reset}${msg}`);
116
+ }
117
+
118
+ static success(msg: string) { this.write(` ${this.C.green}✔ ${msg}`); }
119
+ static error(msg: string) { this.write(` ${this.C.red}✖ ${msg}`, true); }
120
+ static warn(msg: string) { this.write(` ${this.C.yellow}⚠ ${msg}`); }
121
+ static log(msg: string) { this.write(` ${msg}`); }
122
+ static step(msg: string) { this.write(` ${this.C.dim}» ${msg}`); }
123
+ static debug(msg: string) { this.write(` ${this.C.magenta}🐛 ${msg}`); }
124
+ static process(msg: string) { this.write(` ${this.C.blue}▶ ${msg}`); }
125
+ static newline() { this.write(""); }
126
+ static dim(msg: string) { this.write(` ${this.C.dim}${msg}${this.C.reset}`); }
127
+
65
128
  static spinner(msg: string) {
129
+ this.activeSpinnerMsg = msg;
66
130
  const frames = ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"];
67
131
  let i = 0;
68
132
  process.stdout.write("\x1B[?25l");
@@ -74,121 +138,153 @@ export class Logger {
74
138
 
75
139
  return (success = true) => {
76
140
  clearInterval(timer);
141
+ this.activeSpinnerMsg = "";
77
142
  process.stdout.write("\r\x1B[K");
78
143
  process.stdout.write("\x1B[?25h");
79
144
  if (success) {
80
- console.log(` ${this.C.green}✔${this.C.reset} ${msg}`);
145
+ this.write(` ${this.C.green}✔${this.C.reset} ${msg}`);
146
+ } else {
147
+ this.error(`Falha em ${msg}`);
81
148
  }
82
149
  };
83
150
  }
84
151
 
85
152
  static isSystemNoise(line: string): boolean {
86
153
  const noise = [
87
- "Using CATALINA_",
88
- "Using JRE_HOME",
89
- "Using CLASSPATH",
90
- "NOTE: Picked up JDK_JAVA_OPTIONS",
91
- "Command line argument",
92
- "VersionLoggerListener",
93
- "Scanning for projects...",
94
- "Building ",
95
- "--- ",
96
- "+++ ",
97
- "DEBUG: ",
98
- "org.apache.catalina.startup.VersionLoggerListener",
99
- "org.apache.catalina.core.AprLifecycleListener",
100
- "org.apache.coyote.AbstractProtocol.init",
101
- "org.apache.catalina.startup.Catalina.load",
102
- "Arquivos processados em",
103
- "org.apache.jasper.servlet.TldScanner.scanJars",
104
- "Listening for transport dt_socket",
105
- "org.apache.catalina.startup.ExpandWar.expand",
106
- "org.apache.catalina.startup.ContextConfig.configureStart",
107
- "SLF4J: ",
108
- "org.glassfish.jersey.internal.Errors.logErrors",
109
- "contains empty path annotation",
110
- "org.apache.catalina.core.StandardContext.setPath",
111
- "milliseconds"
154
+ "Using CATALINA_", "Using JRE_HOME", "Using CLASSPATH", "NOTE: Picked up JDK_JAVA_OPTIONS",
155
+ "Command line argument", "VersionLoggerListener", "Scanning for projects...",
156
+ "Building ", "--- ", "+++ ", "DEBUG: ", "org.apache.catalina.startup.VersionLoggerListener",
157
+ "org.apache.catalina.core.AprLifecycleListener", "org.apache.coyote.AbstractProtocol.init",
158
+ "org.apache.catalina.startup.Catalina.load", "Arquivos processados em",
159
+ "org.apache.jasper.servlet.TldScanner.scanJars", "Listening for transport dt_socket",
160
+ "org.apache.catalina.startup.ExpandWar.expand", "org.apache.catalina.startup.ContextConfig.configureStart",
161
+ "SLF4J: ", "org.glassfish.jersey.internal.Errors.logErrors", "contains empty path annotation",
162
+ "org.apache.catalina.core.StandardContext.setPath", "milliseconds",
163
+ "org.apache.catalina.startup.HostConfig.deployWAR", "org.apache.catalina.startup.HostConfig.deployDirectory",
164
+ "Deployment of web application", "Deploying web application archive", "at org.apache",
165
+ "Registering directory", "initialized in ClassLoader", "Discovered plugins:",
166
+ "enhanced with plugin initialization", "registerJerseyContainer", "JasperLoader@",
167
+ "Hotswap ready (Plugins:", "autoHotswap.delay", "watchResources=false"
112
168
  ];
113
169
  return noise.some(n => line.includes(n));
114
170
  }
115
171
 
116
172
  static isEssential(line: string): boolean {
117
- return line.includes("SEVERE") ||
118
- line.includes("ERROR") ||
119
- line.includes("Exception") ||
120
- line.includes("Caused by") ||
121
- line.includes("at ") ||
122
- line.includes("... ") ||
123
- line.includes("Server startup in");
173
+ return line.includes("SEVERE") || line.includes("ERROR") || line.includes("Exception") ||
174
+ line.includes("Caused by") || line.includes("at ") || line.includes("... ") ||
175
+ line.includes("Server startup in") || line.includes("HOTSWAP AGENT:");
124
176
  }
125
177
 
126
178
  static summarize(line: string): string {
179
+ if (this.isSystemNoise(line)) return "";
180
+
127
181
  const startupMatch = line.match(/Server startup in (\[?)(.*?)(\]?)\s*ms/);
128
182
  if (startupMatch) {
129
- const time = startupMatch[2] || "???";
130
- return `\n ${this.C.green}${this.C.bold}🚀 TOMCAT PRONTO EM ${time}ms${this.C.reset}\n`;
183
+ const time = (parseInt(startupMatch[2]) / 1000).toFixed(1);
184
+ this.domain("server");
185
+ return `${this.C.green}✔ ${this.C.bold}Server started in ${time}s`;
186
+ }
187
+
188
+ const deployMatch = line.match(/Deployment of web application archive \[(.*?)\] has finished in \[(.*?)\] ms/);
189
+ if (deployMatch) {
190
+ this.domain("build");
191
+ return `${this.C.green}✔ Artifacts deployed`;
192
+ }
193
+
194
+ const hotswapPattern = /HOTSWAP AGENT:.*? (INFO|WARN|ERROR|RELOAD) (.*?) - (.*)/;
195
+ const hotswapMatch = line.match(hotswapPattern);
196
+ if (hotswapMatch) {
197
+ const level = hotswapMatch[1];
198
+ let msg = hotswapMatch[3];
199
+
200
+ if (msg.includes("plugin initialized")) {
201
+ this.hotswapPluginsCount++;
202
+ return "";
203
+ }
204
+
205
+ if (msg.includes("redefinition") || msg.includes("reloaded") || level === 'RELOAD') {
206
+ if (msg.includes("Reloading classes [")) {
207
+ const classes = msg.match(/\[(.*?)\]/)?.[1] || "";
208
+ const classCount = classes.split(",").length;
209
+ if (classCount > 3) msg = `Reloading ${classCount} classes...`;
210
+ }
211
+
212
+ if (msg === this.lastHotswapMsg) return "";
213
+ this.lastHotswapMsg = msg;
214
+
215
+ this.watcher(`Hotswap: ${msg.replace(/Class '.*?'/, (m) => this.C.bold + m + this.C.reset)}`, 'success');
216
+ return "";
217
+ }
218
+
219
+ if (msg.includes("Loading Hotswap agent")) {
220
+ this.domain("server");
221
+ return `${this.C.blue}▶ ${this.C.reset}Initializing Hotswap Agent ${msg.match(/\d+\.\d+\.\d+/)?.[0] || ""}`;
222
+ }
223
+
224
+ if (this.hotswapPluginsCount > 0) {
225
+ const count = this.hotswapPluginsCount;
226
+ this.hotswapPluginsCount = 0;
227
+ this.domain("server");
228
+ this.write(` ${this.C.green}✔ ${this.C.reset}Hotswap ready (Plugins: ${count} loaded)`);
229
+ }
230
+
231
+ let color = this.C.cyan;
232
+ let symbol = "●";
233
+ if (level === "WARN") { color = this.C.yellow; symbol = "▲"; }
234
+ else if (level === "ERROR") { color = this.C.red; symbol = "✖"; }
235
+
236
+ this.domain("server");
237
+ return `${color}${symbol} ${this.C.bold}Hotswap:${this.C.reset} ${msg}`;
238
+ }
239
+
240
+ if (line.includes("java.lang.UnsupportedOperationException") && (line.includes("add a method") || line.includes("change the schema"))) {
241
+ this.domain("watcher");
242
+ this.write(` ${this.C.red}✖ ${this.C.bold}Hotswap Falhou:${this.C.reset} Mudança estrutural detectada (novo método/campo).`);
243
+ this.write(` ${this.C.yellow}💡 Dica: Sua JVM atual não suporta mudar a estrutura da classe. Reinicie o servidor para aplicar.`);
244
+ return "";
131
245
  }
132
246
 
133
- if (line.match(/^\[(INFO|WARN|ERROR)\]\s*$/) || line.includes("--- maven-") || line.includes("--- bpo-")) return "";
134
- if (line.includes("Scanning for projects...") || line.includes("Building bpo-consig") || line.includes("--- ")) return "";
247
+ const tomcatPattern = /^(\d{2}-\w{3}-\d{4} \d{2}:\d{2}:\d{2}\.\d{3})\s+(INFO|WARNING|SEVERE|ERROR)\s+\[(.*?)\]\s+(.*)$/;
248
+ const tMatch = line.match(tomcatPattern);
249
+ if (tMatch) {
250
+ const label = tMatch[2];
251
+ let msg = tMatch[4].trim();
252
+ if (this.isSystemNoise(msg)) return "";
253
+ let color = this.C.dim;
254
+ let symbol = "ℹ";
255
+ if (label === "WARNING") { color = this.C.yellow; symbol = "▲"; }
256
+ else if (label === "SEVERE" || label === "ERROR") { color = this.C.red; symbol = "✖"; }
257
+ msg = msg.replace(/^(org\.apache|com\.sun|java\..*?|org\.glassfish)\.[a-zA-Z0-9.]+\s/, "").trim();
258
+ if (!msg) return "";
259
+ return `${color}${symbol} ${msg}`;
260
+ }
135
261
 
136
262
  const compilationErrorMatch = line.match(/^\[ERROR\]\s+(.*\.java):\[(\d+),(\d+)\]\s+(.*)$/);
137
263
  if (compilationErrorMatch) {
138
264
  const [_, filePath, row, col, msg] = compilationErrorMatch;
139
265
  const fileName = filePath.split(/[/\\]/).pop();
140
-
141
- let contextTip = "";
142
- if (msg.includes("unmappable character") || msg.includes("encoding")) {
143
- contextTip = `\n ${this.C.yellow}💡 Dica: Erro de encoding detectado. O arquivo parece usar um charset (como UTF-8) diferente do configurado no Maven.${this.C.reset}`;
144
- } else if (msg.includes("illegal character")) {
145
- if (msg.includes("\\u00bb") || msg.includes("\\u00bf") || msg.includes("\\u00ef")) {
146
- contextTip = `\n ${this.C.yellow}💡 Dica: UTF-8 BOM detectado! O arquivo tem caracteres invisíveis no início que o Java não aceita. Use 'xavva doctor' para corrigir.${this.C.reset}`;
147
- } else {
148
- contextTip = `\n ${this.C.yellow}💡 Dica: Caractere invisível ou inválido. Tente remover espaços ou quebras de linha estranhas no topo do arquivo.${this.C.reset}`;
149
- }
150
- } else if (msg.includes("cannot find symbol")) {
151
- contextTip = `\n ${this.C.yellow}💡 Dica: Símbolo não encontrado. Verifique se o import está correto ou se a dependência existe.${this.C.reset}`;
152
- }
153
-
154
- return ` ${this.C.red}${this.C.bold}✖ 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}\n ${this.C.red}➜ ${this.C.reset}${msg}${contextTip}\n`;
266
+ 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}`;
155
267
  }
156
268
 
157
269
  const logPattern = /^\[(INFO|WARNING|WARN|SEVERE|ERROR)\]\s+(.*)$/;
158
270
  const match = line.match(logPattern);
159
-
160
271
  if (match) {
161
272
  const label = match[1];
162
- let msg = match[2];
163
-
273
+ let msg = match[2].trim();
164
274
  if (msg.includes("Total time:") || msg.includes("Finished at:") || msg.includes("Final Memory:") || msg.includes("-----------------------")) return "";
165
-
166
- let color = "";
167
- let prefix = "";
168
-
169
- if (label === "INFO") {
170
- color = this.C.dim;
171
- prefix = "ℹ";
172
- } else if (label === "WARNING" || label === "WARN") {
173
- color = this.C.yellow;
174
- prefix = "⚠";
175
- } else if (label === "SEVERE" || label === "ERROR") {
176
- color = this.C.red;
177
- prefix = "✘";
178
- }
179
-
275
+ let color = this.C.dim;
276
+ let symbol = "";
277
+ if (label === "WARNING") { color = this.C.yellow; symbol = ""; }
278
+ else if (label === "SEVERE" || label === "ERROR") { color = this.C.red; symbol = "✖"; }
180
279
  msg = msg.replace(/^(org\.apache|com\.sun|java\..*?)\.[a-zA-Z0-9.]+\s/, "").trim();
181
280
  if (!msg || msg === "]" || msg.includes("Compilation failure")) return "";
182
-
183
- return ` ${color}${prefix} ${msg}${this.C.reset}`;
281
+ return `${color}${symbol} ${msg}`;
184
282
  }
185
283
 
186
- if (line.includes("Exception") || line.includes("at ") || line.includes("Caused by")) {
284
+ if (line.includes("Exception") || line.includes("Caused by") || line.includes("at ")) {
187
285
  const trimmed = line.trim();
188
- if (trimmed.includes("org.apache") || trimmed.includes("java.base") || trimmed.includes("sun.reflect")) {
189
- return ` ${this.C.dim}${trimmed}${this.C.reset}`;
190
- }
191
- return ` ${this.C.yellow}${trimmed}${this.C.reset}`;
286
+ const color = (trimmed.includes("org.apache") || trimmed.includes("java.base") || trimmed.includes("sun.reflect")) ? this.C.dim : this.C.yellow;
287
+ return ` ${color}${trimmed}`;
192
288
  }
193
289
 
194
290
  return "";