@archznn/xavva 1.6.5 → 1.7.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.
@@ -5,85 +5,248 @@ import fs from "fs";
5
5
  import path from "path";
6
6
 
7
7
  export class DoctorCommand implements Command {
8
- async execute(config: AppConfig, values: any = {}): Promise<void> {
9
- Logger.section("Xavva Doctor - Ambiente");
8
+ async execute(config: AppConfig, values: any = {}): Promise<void> {
9
+ Logger.section("Xavva Doctor - Ambiente");
10
10
 
11
- this.check("JAVA_HOME", !!process.env.JAVA_HOME, process.env.JAVA_HOME || "Não definido");
12
-
13
- const tomcatOk = fs.existsSync(config.tomcat.path);
14
- this.check("Tomcat Path", tomcatOk, config.tomcat.path);
15
-
16
- if (tomcatOk) {
17
- const binOk = fs.existsSync(path.join(config.tomcat.path, "bin", "catalina.bat"));
18
- this.check("Tomcat Bin", binOk, binOk ? "OK" : "catalina.bat não encontrado");
19
- }
11
+ this.check(
12
+ "JAVA_HOME",
13
+ !!process.env.JAVA_HOME,
14
+ process.env.JAVA_HOME || "Não definido",
15
+ );
20
16
 
21
- const mvnOk = this.checkBinary("mvn");
22
- this.check("Maven", mvnOk, mvnOk ? "Disponível" : "Não encontrado no PATH");
17
+ const jvmInfo = this.checkJVM();
18
+ this.check(
19
+ "JVM Type",
20
+ jvmInfo.dcevm,
21
+ jvmInfo.name +
22
+ (jvmInfo.dcevm ? " (Advanced Hot Reload OK)" : " (Standard)"),
23
+ );
23
24
 
24
- const gradleOk = this.checkBinary("gradle") || this.checkBinary("gradlew");
25
- this.check("Gradle", gradleOk, gradleOk ? "Disponível" : "Não encontrado no PATH");
25
+ if (!jvmInfo.dcevm) {
26
+ Logger.log(
27
+ ` ${Logger.C.yellow}💡 Dica: Sua JVM não suporta mudanças estruturais (novos métodos/campos).${Logger.C.reset}`,
28
+ );
29
+ if (values.fix) {
30
+ await this.installDCEVM();
31
+ } else {
32
+ Logger.log(
33
+ ` ${Logger.C.cyan}Use 'xavva doctor --fix' para baixar uma JDK com DCEVM integrado.${Logger.C.reset}`,
34
+ );
35
+ }
36
+ }
26
37
 
27
- const gitOk = this.checkBinary("git");
28
- this.check("Git", gitOk, gitOk ? "Disponível" : "Não encontrado no PATH");
38
+ const tomcatOk = fs.existsSync(config.tomcat.path);
39
+ this.check("Tomcat Path", tomcatOk, config.tomcat.path);
29
40
 
30
- Logger.section("Xavva Doctor - Integridade de Arquivos");
31
- await this.checkBOM(values.fix);
41
+ if (tomcatOk) {
42
+ const binOk = fs.existsSync(
43
+ path.join(config.tomcat.path, "bin", "catalina.bat"),
44
+ );
45
+ this.check(
46
+ "Tomcat Bin",
47
+ binOk,
48
+ binOk ? "OK" : "catalina.bat não encontrado",
49
+ );
50
+ }
32
51
 
33
- console.log("");
34
- }
52
+ const mvnOk = this.checkBinary("mvn");
53
+ this.check("Maven", mvnOk, mvnOk ? "Disponível" : "Não encontrado no PATH");
35
54
 
36
- private async checkBOM(fix: boolean) {
37
- const srcPath = path.join(process.cwd(), "src");
38
- if (!fs.existsSync(srcPath)) return;
39
-
40
- const filesWithBOM: string[] = [];
41
- const scan = (dir: string) => {
42
- const list = fs.readdirSync(dir, { withFileTypes: true });
43
- for (const item of list) {
44
- const res = path.resolve(dir, item.name);
45
- if (item.isDirectory()) {
46
- scan(res);
47
- } else if (item.name.endsWith(".java")) {
48
- const buffer = fs.readFileSync(res);
49
- if (buffer[0] === 0xEF && buffer[1] === 0xBB && buffer[2] === 0xBF) {
50
- filesWithBOM.push(res);
51
- }
52
- }
53
- }
54
- };
55
-
56
- scan(srcPath);
57
-
58
- if (filesWithBOM.length > 0) {
59
- this.check("Encoding BOM", false, `${filesWithBOM.length} arquivos com BOM (UTF-8 com assinatura)`);
60
- if (fix) {
61
- for (const file of filesWithBOM) {
62
- const buffer = fs.readFileSync(file);
63
- const cleanBuffer = buffer.subarray(3);
64
- fs.writeFileSync(file, cleanBuffer);
65
- console.log(` \x1b[32m✔\x1b[0m Corrigido: ${path.basename(file)}`);
66
- }
67
- Logger.success("BOM removido de todos os arquivos!");
68
- } else {
69
- Logger.warn("Use 'xavva doctor --fix' para remover o BOM automaticamente.");
70
- }
71
- } else {
72
- this.check("Encoding BOM", true, "Nenhum arquivo com BOM detectado.");
73
- }
74
- }
55
+ const gradleOk = this.checkBinary("gradle") || this.checkBinary("gradlew");
56
+ this.check(
57
+ "Gradle",
58
+ gradleOk,
59
+ gradleOk ? "Disponível" : "Não encontrado no PATH",
60
+ );
75
61
 
76
- private check(label: string, ok: boolean, detail: string) {
77
- const icon = ok ? "\x1b[32m✔\x1b[0m" : "\x1b[31m✘\x1b[0m";
78
- console.log(` ${icon} ${label.padEnd(15)} ${detail}`);
79
- }
62
+ const gitOk = this.checkBinary("git");
63
+ this.check("Git", gitOk, gitOk ? "Disponível" : "Não encontrado no PATH");
64
+
65
+ Logger.section("Xavva Doctor - Integridade de Arquivos");
66
+ await this.checkBOM(values.fix);
67
+
68
+ console.log("");
69
+ }
70
+
71
+ private async checkBOM(fix: boolean) {
72
+ const srcPath = path.join(process.cwd(), "src");
73
+ if (!fs.existsSync(srcPath)) return;
74
+
75
+ const filesWithBOM: string[] = [];
76
+ const scan = (dir: string) => {
77
+ const list = fs.readdirSync(dir, { withFileTypes: true });
78
+ for (const item of list) {
79
+ const res = path.resolve(dir, item.name);
80
+ if (item.isDirectory()) {
81
+ scan(res);
82
+ } else if (item.name.endsWith(".java")) {
83
+ const buffer = fs.readFileSync(res);
84
+ if (buffer[0] === 0xef && buffer[1] === 0xbb && buffer[2] === 0xbf) {
85
+ filesWithBOM.push(res);
86
+ }
87
+ }
88
+ }
89
+ };
80
90
 
81
- private checkBinary(name: string): boolean {
91
+ scan(srcPath);
92
+
93
+ if (filesWithBOM.length > 0) {
94
+ this.check(
95
+ "Encoding BOM",
96
+ false,
97
+ `${filesWithBOM.length} arquivos com BOM (UTF-8 com assinatura)`,
98
+ );
99
+ if (fix) {
100
+ for (const file of filesWithBOM) {
101
+ const buffer = fs.readFileSync(file);
102
+ const cleanBuffer = buffer.subarray(3);
103
+ fs.writeFileSync(file, cleanBuffer);
104
+ console.log(
105
+ ` \x1b[32m✔\x1b[0m Corrigido: ${path.basename(file)}`,
106
+ );
107
+ }
108
+ Logger.success("BOM removido de todos os arquivos!");
109
+ } else {
110
+ Logger.warn(
111
+ "Use 'xavva doctor --fix' para remover o BOM automaticamente.",
112
+ );
113
+ }
114
+ } else {
115
+ this.check("Encoding BOM", true, "Nenhum arquivo com BOM detectado.");
116
+ }
117
+ }
118
+
119
+ private check(label: string, ok: boolean, detail: string) {
120
+ const icon = ok ? "\x1b[32m✔\x1b[0m" : "\x1b[31m✘\x1b[0m";
121
+ console.log(` ${icon} ${label.padEnd(15)} ${detail}`);
122
+ }
123
+
124
+ private checkBinary(name: string): boolean {
125
+ try {
126
+ const proc = Bun.spawnSync([
127
+ process.platform === "win32" ? "where" : "which",
128
+ name,
129
+ ]);
130
+ return proc.exitCode === 0;
131
+ } catch {
132
+ return false;
133
+ }
134
+ }
135
+
136
+ private checkJVM(): { name: string, dcevm: boolean } {
82
137
  try {
83
- const proc = Bun.spawnSync([process.platform === 'win32' ? "where" : "which", name]);
84
- return proc.exitCode === 0;
138
+ // Tentar primeiro o binário do JAVA_HOME para evitar cache do Path
139
+ let javaBin = "java";
140
+ if (process.env.JAVA_HOME) {
141
+ const homeBin = path.join(process.env.JAVA_HOME, "bin", "java.exe");
142
+ if (fs.existsSync(homeBin)) javaBin = homeBin;
143
+ }
144
+
145
+ const proc = Bun.spawnSync([javaBin, "-version"]);
146
+ const output = (proc.stderr.toString() + proc.stdout.toString()).toLowerCase();
147
+ const isDcevm = output.includes("dcevm") ||
148
+ output.includes("jetbrains") ||
149
+ output.includes("trava") ||
150
+ output.includes("jbr");
151
+
152
+ const versionMatch = output.match(/version "(.*?)"/);
153
+ return {
154
+ name: versionMatch ? `Java ${versionMatch[1]}` : "Java Desconhecido",
155
+ dcevm: isDcevm
156
+ };
85
157
  } catch {
86
- return false;
158
+ return { name: "Não encontrada", dcevm: false };
87
159
  }
88
160
  }
161
+
162
+ private async installDCEVM() {
163
+ Logger.section("Instalação do JetBrains Runtime (JBR 21)");
164
+ Logger.log("Baixando JDK moderna com DCEVM nativo (JBR 21 SDK)...");
165
+
166
+ // URL para o JetBrains Runtime 21 SDK Windows x64
167
+ const url = "https://cache-redirector.jetbrains.com/intellij-jbr/jbrsdk-21.0.6-windows-x64-b895.97.tar.gz";
168
+ const installDir = path.join(require("os").homedir(), ".xavva", "jdk-dcevm");
169
+
170
+ // Limpar instalação anterior se existir
171
+ if (fs.existsSync(installDir)) {
172
+ try {
173
+ fs.rmSync(installDir, { recursive: true, force: true });
174
+ } catch (e) {}
175
+ }
176
+ fs.mkdirSync(installDir, { recursive: true });
177
+
178
+ try {
179
+ const response = await fetch(url);
180
+ if (!response.ok) throw new Error("Erro ao baixar JBR (verifique sua conexão)");
181
+ const buffer = await response.arrayBuffer();
182
+ const tarPath = path.join(installDir, "jbr.tar.gz");
183
+ fs.writeFileSync(tarPath, Buffer.from(buffer));
184
+
185
+ Logger.success("Download concluído. Extraindo binários...");
186
+
187
+ // Usar PowerShell para extrair .tar.gz (nativo no Windows 10/11)
188
+ const extractCmd = `powershell -command "tar -xzf '${tarPath}' -C '${installDir}'"`;
189
+ Bun.spawnSync(["powershell", "-command", extractCmd]);
190
+
191
+ fs.rmSync(tarPath);
192
+
193
+ // Busca recursiva para encontrar onde está o bin/java.exe
194
+ const findJdkRoot = (dir: string): string | null => {
195
+ if (fs.existsSync(path.join(dir, "bin", "java.exe"))) return dir;
196
+ const subdirs = fs.readdirSync(dir, { withFileTypes: true })
197
+ .filter(d => d.isDirectory())
198
+ .map(d => path.join(dir, d.name));
199
+ for (const subdir of subdirs) {
200
+ const found = findJdkRoot(subdir);
201
+ if (found) return found;
202
+ }
203
+ return null;
204
+ };
205
+
206
+ const jdkPath = findJdkRoot(installDir) || installDir;
207
+
208
+ Logger.process("Configurando variáveis de ambiente do SISTEMA...");
209
+
210
+ const setEnvCmd = `
211
+ $jdk = '${jdkPath}';
212
+ $bin = '${path.join(jdkPath, "bin")}';
213
+ try {
214
+ [Environment]::SetEnvironmentVariable('JAVA_HOME', $jdk, 'Machine');
215
+ $pathVar = [Environment]::GetEnvironmentVariable('Path', 'Machine');
216
+ $paths = $pathVar -split ';' | Where-Object { $_ -ne '' };
217
+ $normalizedBin = $bin.TrimEnd('\\').ToLower();
218
+
219
+ $exists = $false;
220
+ foreach ($p in $paths) {
221
+ if ($p.TrimEnd('\\').ToLower() -eq $normalizedBin) { $exists = $true; break; }
222
+ }
223
+
224
+ if (-not $exists) {
225
+ $newPath = "$bin;" + $pathVar;
226
+ [Environment]::SetEnvironmentVariable('Path', $newPath, 'Machine');
227
+ }
228
+ Write-Output "OK";
229
+ } catch {
230
+ Write-Error $_.Exception.Message;
231
+ }
232
+ `.replace(/\n/g, ' ');
233
+ const result = Bun.spawnSync(["powershell", "-command", setEnvCmd]);
234
+ const output = result.stdout.toString() + result.stderr.toString();
235
+
236
+ if (output.includes("ACCESS_DENIED")) {
237
+ Logger.error("Falha ao configurar variáveis do SISTEMA (Acesso Negado).");
238
+ Logger.warn("Dica: Execute o terminal como ADMINISTRADOR para permitir esta alteração.");
239
+ Logger.info("JAVA_HOME manual", jdkPath);
240
+ } else {
241
+ Logger.success(`DCEVM configurado no SISTEMA com sucesso!`);
242
+ Logger.info("JAVA_HOME", jdkPath);
243
+ }
244
+
245
+ Logger.newline();
246
+ Logger.warn("IMPORTANTE: Reinicie seu terminal (ou o VS Code) para as mudanças surtirem efeito.");
247
+ } catch (e: any) {
248
+
249
+ Logger.error(`Falha na instalação: ${e.message}`);
250
+ }
251
+ }
89
252
  }
@@ -49,7 +49,8 @@ export class RunCommand implements Command {
49
49
 
50
50
  if (this.debug) {
51
51
  Logger.warn(`🚀 Aguardando debugger na porta 5005 para ${className}...`);
52
- Logger.log(`${"\x1b[36m"}Dica:${"\x1b[0m"} No VS Code ou IntelliJ, use 'Attach to Remote JVM' na porta 5005.\n`);
52
+ Logger.log(`${Logger.C.cyan}Dica:${Logger.C.reset} No VS Code ou IntelliJ, use 'Attach to Remote JVM' na porta 5005.`);
53
+ Logger.newline();
53
54
  } else {
54
55
  Logger.warn(`🚀 Executando ${className}...`);
55
56
  }
@@ -140,7 +141,7 @@ export class RunCommand implements Command {
140
141
  });
141
142
 
142
143
  return new Promise((resolve) => {
143
- readline.question(`\n Escolha a classe (1-${uniqueClasses.length}) ou [C]ancelar: `, (answer: string) => {
144
+ readline.question(` Escolha a classe (1-${uniqueClasses.length}) ou [C]ancelar: `, (answer: string) => {
144
145
  readline.close();
145
146
  const idx = parseInt(answer) - 1;
146
147
  if (!isNaN(idx) && uniqueClasses[idx]) {
@@ -175,7 +176,7 @@ export class RunCommand implements Command {
175
176
  });
176
177
 
177
178
  return new Promise((resolve) => {
178
- readline.question(`\n Escolha a classe (1-${Math.min(history.length, 5)}) ou [C]ancelar: `, (answer: string) => {
179
+ readline.question(` Escolha a classe (1-${Math.min(history.length, 5)}) ou [C]ancelar: `, (answer: string) => {
179
180
  readline.close();
180
181
  if (!answer.trim()) {
181
182
  resolve(history[0]);
@@ -15,7 +15,7 @@ export class StartCommand implements Command {
15
15
  Logger.step("Checking ports");
16
16
  await tomcat.killConflict();
17
17
  Logger.step("Starting Tomcat");
18
- tomcat.start(config.project.cleanLogs, config.project.debug, config.project.skipScan, config.project.quiet);
18
+ tomcat.start(config, false);
19
19
 
20
20
  await new Promise(() => {});
21
21
  } catch (error: any) {
package/src/index.ts CHANGED
@@ -20,7 +20,7 @@ async function main() {
20
20
  const { config, positionals, values } = await ConfigManager.load();
21
21
 
22
22
  if (values.version) {
23
- console.log(`v${pkg.version}`);
23
+ Logger.log(`v${pkg.version}`);
24
24
  process.exit(0);
25
25
  }
26
26
 
@@ -66,7 +66,7 @@ async function main() {
66
66
  await handleDeploy(config, values);
67
67
  break;
68
68
  default:
69
- console.error(`Comando desconhecido: ${commandName}`);
69
+ Logger.error(`Comando desconhecido: ${commandName}`);
70
70
  new HelpCommand().execute(config);
71
71
  process.exit(1);
72
72
  }
@@ -93,9 +93,15 @@ async function handleDeploy(config: any, values: any) {
93
93
  await run(false);
94
94
 
95
95
  let debounceTimer: Timer;
96
+ const coolingFiles = new Set<string>();
97
+
96
98
  watch(process.cwd(), { recursive: true }, async (event, filename) => {
97
99
  if (!filename) return;
98
100
 
101
+ if (coolingFiles.has(filename)) return;
102
+ coolingFiles.add(filename);
103
+ setTimeout(() => coolingFiles.delete(filename), 500);
104
+
99
105
  const isJava = filename.endsWith(".java") || filename === "pom.xml" || filename === "build.gradle";
100
106
  const isResource = filename.endsWith(".jsp") || filename.endsWith(".html") ||
101
107
  filename.endsWith(".css") || filename.endsWith(".js") ||
@@ -116,21 +122,34 @@ async function handleDeploy(config: any, values: any) {
116
122
  if (isJsp) {
117
123
  const parts = filename.split(/[/\\]/);
118
124
  const webappIndex = parts.indexOf("webapp");
119
- if (webappIndex !== -1) {
120
- const relPath = parts.slice(webappIndex + 1).join("/");
125
+ const webContentIndex = parts.indexOf("WebContent");
126
+ const rootIndex = webappIndex !== -1 ? webappIndex : webContentIndex;
127
+
128
+ if (rootIndex !== -1) {
129
+ const relPath = parts.slice(rootIndex + 1).join("/");
121
130
  isPrivate = relPath.startsWith("WEB-INF") || relPath.startsWith("META-INF");
122
- const contextPath = (config.project.appName || "").replace(".war", "");
131
+
132
+ let contextPath = (config.project.appName || "").replace(".war", "");
133
+ if (!contextPath) {
134
+ const webappsPath = require("path").join(config.tomcat.path, "webapps");
135
+ if (require("fs").existsSync(webappsPath)) {
136
+ const folders = require("fs").readdirSync(webappsPath, { withFileTypes: true })
137
+ .filter((dirent: any) => dirent.isDirectory() && !["ROOT", "manager", "host-manager", "docs"].includes(dirent.name));
138
+ if (folders.length === 1) contextPath = folders[0].name;
139
+ }
140
+ }
141
+
123
142
  jspUrl = `http://localhost:${config.tomcat.port}${contextPath ? "/" + contextPath : ""}/${relPath}`;
124
143
  }
125
144
  }
126
145
 
127
146
  if (isJsp && isPrivate) {
128
- console.log(`\n ${"\x1b[33m"}🔒${"\x1b[0m"} JSP Privado alterado (WEB-INF): ${filename}`);
129
- console.log(` ${"\x1b[90m"}Nota: Este arquivo não é acessível via URL direta.${"\x1b[0m"}`);
147
+ Logger.watcher(`JSP Private (WEB-INF): ${filename}`, 'change');
148
+ Logger.dim(`Nota: Este arquivo não é acessível via URL direta.`);
130
149
  } else if (isJsp && jspUrl) {
131
- console.log(`\n ${"\x1b[32m"}📄${"\x1b[0m"} JSP Atualizado: ${"\x1b[4m"}${jspUrl}${"\x1b[0m"}`);
150
+ Logger.watcher(`JSP Updated: ${jspUrl}`, 'success');
132
151
  } else {
133
- console.log(`\n ${"\x1b[35m"}⚡${"\x1b[0m"} Recurso alterado: ${filename}`);
152
+ Logger.watcher(`Resource altered: ${filename}`, 'change');
134
153
  }
135
154
 
136
155
  await deployCmd.syncResource(config, filename);
@@ -139,7 +158,7 @@ async function handleDeploy(config: any, values: any) {
139
158
 
140
159
  if (!isJava) return;
141
160
 
142
- console.log(`\n ${"\x1b[33m"}👀${"\x1b[0m"} Alteração detectada em: ${filename}`);
161
+ Logger.watcher(filename, 'watch');
143
162
  clearTimeout(debounceTimer);
144
163
 
145
164
  debounceTimer = setTimeout(() => {
@@ -34,7 +34,6 @@ export class AuditService {
34
34
 
35
35
  const stopSpinner = Logger.spinner(`Auditando ${jars.length} dependências`);
36
36
 
37
- // Process in chunks to avoid overwhelming the API
38
37
  const chunkSize = 10;
39
38
  for (let i = 0; i < jars.length; i += chunkSize) {
40
39
  const chunk = jars.slice(i, i + chunkSize);
@@ -52,7 +51,6 @@ export class AuditService {
52
51
  const info = await this.extractJarInfo(jarPath);
53
52
 
54
53
  if (!info.artifactId || !info.version) {
55
- // Fallback to filename parsing if pom.properties is missing
56
54
  const match = jarName.match(/(.+)-([\d\.]+.*)\.jar/);
57
55
  if (match) {
58
56
  info.artifactId = info.artifactId || match[1];
@@ -70,8 +68,6 @@ export class AuditService {
70
68
  }
71
69
 
72
70
  private async extractJarInfo(jarPath: string): Promise<{ groupId?: string, artifactId?: string, version?: string }> {
73
- // We use PowerShell to quickly peek inside the JAR for pom.properties
74
- // This is faster than extracting the whole JAR
75
71
  const normalizedPath = jarPath.split(path.sep).join("/");
76
72
  const psCommand = `
77
73
  Add-Type -AssemblyName System.IO.Compression.FileSystem
@@ -137,7 +133,6 @@ export class AuditService {
137
133
  private extractSeverity(vuln: any): string {
138
134
  if (vuln.database_specific?.severity) return vuln.database_specific.severity;
139
135
  if (vuln.advisories?.[0]?.url?.includes("github.com/advisories")) {
140
- // Try to infer from details if common keywords exist
141
136
  const d = (vuln.details || "").toLowerCase();
142
137
  if (d.includes("critical")) return "CRITICAL";
143
138
  if (d.includes("high")) return "HIGH";
@@ -51,7 +51,7 @@ export class BuildService {
51
51
  if (proc.exitCode !== 0) {
52
52
  if (!this.projectConfig.verbose) {
53
53
  const err = await new Response(proc.stderr).text();
54
- console.log(err);
54
+ Logger.log(err);
55
55
  }
56
56
  Logger.error(`${this.projectConfig.buildTool.toUpperCase()} build failed!`);
57
57
  throw new Error("Falha no build do Java!");
@@ -79,7 +79,7 @@ export class BuildService {
79
79
  errorCount++;
80
80
  if (errorCount > maxErrors && !this.projectConfig.verbose) {
81
81
  if (errorCount === maxErrors + 1) {
82
- console.log(`\n ${"\x1b[31m"}... e mais erros ocultos. Use -V para ver todos.${"\x1b[0m"}`);
82
+ Logger.warn("... e mais erros ocultos. Use -V para ver todos.");
83
83
  }
84
84
  continue;
85
85
  }
@@ -92,7 +92,7 @@ export class BuildService {
92
92
  }
93
93
 
94
94
  const summarized = Logger.summarize(cleanLine);
95
- if (summarized) console.log(summarized);
95
+ if (summarized) Logger.log(summarized);
96
96
  }
97
97
  }
98
98
  }
@@ -105,7 +105,7 @@ export class BuildService {
105
105
 
106
106
  if (!appFolder && fs.existsSync(webappsPath)) {
107
107
  const folders = fs.readdirSync(webappsPath, { withFileTypes: true })
108
- .filter((dirent: any) => dirent.isDirectory() && !["ROOT", "manager", "host-manager", "docs"].includes(dirent.name));
108
+ .filter((dirent: any) => dirent.isDirectory() && !["ROOT", "manager", "host-manager", "docs", "examples"].includes(dirent.name));
109
109
 
110
110
  if (folders.length === 1) {
111
111
  appFolder = folders[0].name;
@@ -124,7 +124,6 @@ export class BuildService {
124
124
  if (!fs.existsSync(sourceDir)) return null;
125
125
 
126
126
  if (!appFolder || !fs.existsSync(destDir)) {
127
- Logger.warn("Pasta descompactada no Tomcat não encontrada. Hot Swap impossível.");
128
127
  return null;
129
128
  }
130
129
 
@@ -145,11 +144,10 @@ export class BuildService {
145
144
  };
146
145
 
147
146
  copyDir(sourceDir, destDir);
148
- Logger.success("Classes swapped in running Tomcat");
149
147
  return appFolder;
150
148
  }
151
149
 
152
- async deployToWebapps(): Promise<string> {
150
+ async deployToWebapps(): Promise<{ path: string, finalName: string }> {
153
151
  const destDir = path.join(this.tomcatConfig.path, this.tomcatConfig.webapps);
154
152
 
155
153
  Logger.step("Searching for generated artifacts");
@@ -187,12 +185,10 @@ export class BuildService {
187
185
  Logger.info("Artifact", warFile.name);
188
186
  if (this.projectConfig.appName) Logger.info("Deploy as", finalName);
189
187
  } else {
190
- const displayName = this.projectConfig.appName ? `${this.projectConfig.appName}` : warFile.name.replace(".war", "");
191
- process.stdout.write(` ${"\x1b[90m"}➜${"\x1b[0m"} Deploying ${"\x1b[1m"}${displayName}${"\x1b[0m"}...\n`);
188
+ Logger.process(`Deploying ${this.projectConfig.appName ? this.projectConfig.appName : warFile.name.replace(".war", "")}...`);
192
189
  }
193
190
 
194
- copyFileSync(warFile.path, path.join(destDir, finalName));
195
191
  this.inferredAppName = finalName.replace(".war", "");
196
- return finalName;
192
+ return { path: warFile.path, finalName };
197
193
  }
198
194
  }
@@ -33,11 +33,9 @@ export class EndpointService {
33
33
  const endpoints: ApiEndpoint[] = [];
34
34
  const className = fileName.replace(".java", "");
35
35
 
36
- // Find class-level mapping
37
36
  const classPathMatch = content.match(/@(Path|RequestMapping)\s*\(\s*["'](.*?)["']\s*\)/);
38
37
  const basePath = classPathMatch ? this.normalizePath(classPathMatch[2]) : "";
39
38
 
40
- // Common mapping annotations
41
39
  const methodRegex = /@(GET|POST|PUT|DELETE|PATCH|Path|RequestMapping|GetMapping|PostMapping|PutMapping|DeleteMapping|PatchMapping)\s*(\(\s*["'](.*?)["']\s*\))?\s*([\s\S]*?)\s+([a-zA-Z0-9_]+)\s*\(([\s\S]*?)\)/g;
42
40
 
43
41
  let match;
@@ -98,7 +96,6 @@ export class EndpointService {
98
96
  for (const p of individualParams) {
99
97
  const trimmed = p.trim();
100
98
 
101
- // Check for annotations
102
99
  const pathParam = trimmed.match(/@PathParam\s*\(\s*["'](.*?)["']\s*\)\s*(\w+)\s+(\w+)/);
103
100
  const pathVariable = trimmed.match(/@PathVariable\s*\(\s*["'](.*?)["']\s*\)\s*(\w+)\s+(\w+)/);
104
101
  const queryParam = trimmed.match(/@QueryParam\s*\(\s*["'](.*?)["']\s*\)\s*(\w+)\s+(\w+)/);