@archznn/xavva 1.7.0 → 1.8.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.
@@ -63,11 +63,98 @@ export class DoctorCommand implements Command {
63
63
  this.check("Git", gitOk, gitOk ? "Disponível" : "Não encontrado no PATH");
64
64
 
65
65
  Logger.section("Xavva Doctor - Integridade de Arquivos");
66
+ await this.checkJarIntegrity(values.fix, config);
66
67
  await this.checkBOM(values.fix);
67
68
 
68
69
  console.log("");
69
70
  }
70
71
 
72
+ private async checkJarIntegrity(fix: boolean, config: AppConfig) {
73
+ const searchPaths = [
74
+ path.join(process.cwd(), "target"),
75
+ path.join(process.cwd(), "build"),
76
+ path.join(config.tomcat.path, "webapps")
77
+ ].filter(p => fs.existsSync(p));
78
+
79
+ const corruptedJars: string[] = [];
80
+ const zipEndSignature = Buffer.from([0x50, 0x4b, 0x05, 0x06]);
81
+
82
+ const scan = (dir: string) => {
83
+ try {
84
+ const list = fs.readdirSync(dir, { withFileTypes: true });
85
+ for (const item of list) {
86
+ const res = path.resolve(dir, item.name);
87
+ if (item.isDirectory()) {
88
+ if (item.name === "node_modules" || item.name === ".git") continue;
89
+ scan(res);
90
+ } else if (item.name.endsWith(".jar")) {
91
+ try {
92
+ const stats = fs.statSync(res);
93
+ if (stats.size < 22) {
94
+ corruptedJars.push(res);
95
+ continue;
96
+ }
97
+
98
+ // Lê os últimos 1024 bytes para encontrar a assinatura EOCD do ZIP
99
+ const readSize = Math.min(stats.size, 1024);
100
+ const buffer = Buffer.alloc(readSize);
101
+ const fd = fs.openSync(res, "r");
102
+ fs.readSync(fd, buffer, 0, readSize, stats.size - readSize);
103
+ fs.closeSync(fd);
104
+
105
+ if (!buffer.includes(zipEndSignature)) {
106
+ corruptedJars.push(res);
107
+ }
108
+ } catch (e) {
109
+ corruptedJars.push(res);
110
+ }
111
+ }
112
+ }
113
+ } catch (e) {}
114
+ };
115
+
116
+ for (const p of searchPaths) {
117
+ scan(p);
118
+ }
119
+
120
+ if (corruptedJars.length > 0) {
121
+ this.check(
122
+ "Integridade JAR",
123
+ false,
124
+ `${corruptedJars.length} arquivos corrompidos detectados.`,
125
+ );
126
+ if (fix) {
127
+ for (const file of corruptedJars) {
128
+ try {
129
+ fs.unlinkSync(file);
130
+ console.log(` \x1b[32m✔\x1b[0m Removido: ${path.basename(file)}`);
131
+ } catch (e) {}
132
+ }
133
+ Logger.success("JARs corrompidos removidos! Eles serão reconstruídos no próximo build.");
134
+
135
+ // Limpar cache do Tomcat
136
+ Logger.process("Limpando cache do Tomcat (work/temp)...");
137
+ const tomcatWork = path.join(config.tomcat.path, "work");
138
+ const tomcatTemp = path.join(config.tomcat.path, "temp");
139
+ [tomcatWork, tomcatTemp].forEach(p => {
140
+ try {
141
+ if (fs.existsSync(p)) {
142
+ fs.rmSync(p, { recursive: true, force: true });
143
+ fs.mkdirSync(p);
144
+ }
145
+ } catch (e) {}
146
+ });
147
+ Logger.success("Cache do Tomcat limpo com sucesso.");
148
+ } else {
149
+ Logger.warn(
150
+ "Use 'xavva doctor --fix' para remover os JARs corrompidos e limpar o cache.",
151
+ );
152
+ }
153
+ } else {
154
+ this.check("Integridade JAR", true, "Todos os arquivos JAR parecem íntegros.");
155
+ }
156
+ }
157
+
71
158
  private async checkBOM(fix: boolean) {
72
159
  const srcPath = path.join(process.cwd(), "src");
73
160
  if (!fs.existsSync(srcPath)) return;
@@ -185,8 +272,14 @@ export class DoctorCommand implements Command {
185
272
  Logger.success("Download concluído. Extraindo binários...");
186
273
 
187
274
  // 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]);
275
+ const extractCmd = `tar -xzf $env:TAR_PATH -C $env:INSTALL_DIR`;
276
+ Bun.spawnSync(["powershell", "-command", extractCmd], {
277
+ env: {
278
+ ...process.env,
279
+ TAR_PATH: tarPath,
280
+ INSTALL_DIR: installDir
281
+ }
282
+ });
190
283
 
191
284
  fs.rmSync(tarPath);
192
285
 
@@ -204,12 +297,13 @@ export class DoctorCommand implements Command {
204
297
  };
205
298
 
206
299
  const jdkPath = findJdkRoot(installDir) || installDir;
300
+ const binPath = path.join(jdkPath, "bin");
207
301
 
208
302
  Logger.process("Configurando variáveis de ambiente do SISTEMA...");
209
303
 
210
304
  const setEnvCmd = `
211
- $jdk = '${jdkPath}';
212
- $bin = '${path.join(jdkPath, "bin")}';
305
+ $jdk = $env:JDK_PATH;
306
+ $bin = $env:BIN_PATH;
213
307
  try {
214
308
  [Environment]::SetEnvironmentVariable('JAVA_HOME', $jdk, 'Machine');
215
309
  $pathVar = [Environment]::GetEnvironmentVariable('Path', 'Machine');
@@ -230,7 +324,13 @@ export class DoctorCommand implements Command {
230
324
  Write-Error $_.Exception.Message;
231
325
  }
232
326
  `.replace(/\n/g, ' ');
233
- const result = Bun.spawnSync(["powershell", "-command", setEnvCmd]);
327
+ const result = Bun.spawnSync(["powershell", "-command", setEnvCmd], {
328
+ env: {
329
+ ...process.env,
330
+ JDK_PATH: jdkPath,
331
+ BIN_PATH: binPath
332
+ }
333
+ });
234
334
  const output = result.stdout.toString() + result.stderr.toString();
235
335
 
236
336
  if (output.includes("ACCESS_DENIED")) {
@@ -24,7 +24,8 @@ Comandos principais:
24
24
 
25
25
  Opções:
26
26
  -w, --watch 👀 Hot Reload: monitora arquivos e redeploya automaticamente.
27
- -d, --debug 🐞 Habilita debug Java (JPDA) na porta 5005.
27
+ -d, --debug 🐞 Habilita debug Java (JPDA).
28
+ --dp [porta] 🔌 Porta do Debugger (padrão: 5005).
28
29
  -c, --clean 🧹 Logs coloridos e simplificados (recomendado).
29
30
  -q, --quiet 🤫 Mostra apenas mensagens essenciais nos logs.
30
31
  -V, --verbose 📣 Mostra logs completos do Maven/Gradle.
@@ -1,19 +1,17 @@
1
1
  import type { Command } from "./Command";
2
- import type { AppConfig } from "../types/config";
2
+ import type { AppConfig, CLIArguments } from "../types/config";
3
3
  import { Logger } from "../utils/ui";
4
- import { spawn } from "child_process";
5
4
  import path from "path";
6
5
 
7
6
  export class RunCommand implements Command {
8
- constructor(private debug: boolean = true) {}
9
-
10
- async execute(config: AppConfig): Promise<void> {
7
+ async execute(config: AppConfig, args?: CLIArguments): Promise<void> {
8
+ const isDebug = args?.debug !== false; // Default to true if not specified, matching previous behavior
11
9
  let className = config.project.grep;
12
10
 
13
11
  if (!className) {
14
12
  className = await this.loadFromHistory();
15
13
  if (!className) {
16
- Logger.error(`Uso: xavva ${this.debug ? "debug" : "run"} NomeDaClasse`);
14
+ Logger.error(`Uso: xavva ${isDebug ? "debug" : "run"} NomeDaClasse`);
17
15
  return;
18
16
  }
19
17
  }
@@ -26,7 +24,7 @@ export class RunCommand implements Command {
26
24
 
27
25
  this.saveToHistory(className);
28
26
 
29
- if (this.debug) {
27
+ if (isDebug) {
30
28
  Logger.section(`Interactive Debug: ${className}`);
31
29
  } else {
32
30
  Logger.section(`Running: ${className}`);
@@ -37,17 +35,17 @@ export class RunCommand implements Command {
37
35
 
38
36
  const finalCp = `${localCp};${pathingJar}`;
39
37
 
40
- const args = [
38
+ const javaArgs = [
41
39
  "-classpath", finalCp,
42
40
  ];
43
41
 
44
- if (this.debug) {
45
- args.push("-agentlib:jdwp=transport=dt_socket,server=y,suspend=y,address=5005");
42
+ if (isDebug) {
43
+ javaArgs.push("-agentlib:jdwp=transport=dt_socket,server=y,suspend=y,address=5005");
46
44
  }
47
45
 
48
- args.push(className);
46
+ javaArgs.push(className);
49
47
 
50
- if (this.debug) {
48
+ if (isDebug) {
51
49
  Logger.warn(`🚀 Aguardando debugger na porta 5005 para ${className}...`);
52
50
  Logger.log(`${Logger.C.cyan}Dica:${Logger.C.reset} No VS Code ou IntelliJ, use 'Attach to Remote JVM' na porta 5005.`);
53
51
  Logger.newline();
@@ -55,19 +53,20 @@ export class RunCommand implements Command {
55
53
  Logger.warn(`🚀 Executando ${className}...`);
56
54
  }
57
55
 
58
- const bin = "java";
56
+ const bin = process.env.JAVA_HOME ? path.join(process.env.JAVA_HOME, "bin", "java.exe") : "java";
59
57
 
60
- return new Promise((resolve) => {
61
- const child = spawn(bin, args, {
62
- stdio: "inherit",
63
- shell: true
64
- });
65
-
66
- child.on("exit", () => {
67
- Logger.log(`Sessão de ${this.debug ? "debug" : "execução"} encerrada.`);
68
- resolve();
69
- });
58
+ const proc = Bun.spawn([bin, ...javaArgs], {
59
+ stdout: "inherit",
60
+ stderr: "inherit",
61
+ stdin: "inherit",
62
+ env: {
63
+ ...process.env,
64
+ JAVA_OPTS: "-Xms256m -Xmx1024m"
65
+ } as any
70
66
  });
67
+
68
+ await proc.exited;
69
+ Logger.log(`Sessão de ${isDebug ? "debug" : "execução"} encerrada.`);
71
70
  }
72
71
 
73
72
  private async discoverClass(simpleName: string): Promise<string | null> {
@@ -222,22 +221,62 @@ export class RunCommand implements Command {
222
221
  const paths = dependencyCp.split(";").filter(p => p.trim());
223
222
  const relativePaths = paths.map(p => {
224
223
  let rel = path.relative(xavvaDir, p).replace(/\\/g, "/");
225
- if (fs.statSync(p).isDirectory() && !rel.endsWith("/")) rel += "/";
226
- return rel;
224
+ if (fs.existsSync(p) && fs.statSync(p).isDirectory() && !rel.endsWith("/")) rel += "/";
225
+ // Robust URL encoding for Class-Path as per Java Spec
226
+ return encodeURI(rel)
227
+ .replace(/#/g, '%23')
228
+ .replace(/\?/g, '%3F')
229
+ .replace(/%5B/g, '[')
230
+ .replace(/%5D/g, ']');
227
231
  }).join(" ");
228
232
 
229
- let wrappedCp = "";
230
- const maxLen = 70;
231
- for (let i = 0; i < relativePaths.length; i += maxLen) {
232
- const chunk = relativePaths.substring(i, i + maxLen);
233
- if (i === 0) {
234
- wrappedCp += chunk;
233
+ const header = "Class-Path: ";
234
+ let manifestContent = "Manifest-Version: 1.0\r\n";
235
+
236
+ let currentLine = header;
237
+ const parts = relativePaths.split(" ");
238
+
239
+ for (let i = 0; i < parts.length; i++) {
240
+ const part = parts[i] + (i < parts.length - 1 ? " " : "");
241
+
242
+ // Se adicionar o próximo 'part' exceder 70 bytes (margem de segurança antes do CRLF)
243
+ if (Buffer.from(currentLine + part).length > 70) {
244
+ // Se a parte em si for muito longa, precisamos quebrá-la
245
+ if (Buffer.from(" " + part).length > 70) {
246
+ let remainingPart = part;
247
+ while (remainingPart.length > 0) {
248
+ const spaceLeft = 70 - Buffer.from(currentLine).length;
249
+
250
+ // Encontra quantos caracteres de 'remainingPart' cabem no espaço restante
251
+ let fitCount = 0;
252
+ let fitBytes = 0;
253
+ for (let j = 0; j < remainingPart.length; j++) {
254
+ const charBytes = Buffer.from(remainingPart[j]).length;
255
+ if (fitBytes + charBytes > spaceLeft) break;
256
+ fitBytes += charBytes;
257
+ fitCount++;
258
+ }
259
+
260
+ if (fitCount > 0) {
261
+ currentLine += remainingPart.substring(0, fitCount);
262
+ remainingPart = remainingPart.substring(fitCount);
263
+ }
264
+
265
+ if (remainingPart.length > 0) {
266
+ manifestContent += currentLine + "\r\n";
267
+ currentLine = " ";
268
+ }
269
+ }
270
+ } else {
271
+ manifestContent += currentLine + "\r\n";
272
+ currentLine = " " + part;
273
+ }
235
274
  } else {
236
- wrappedCp += "\r\n " + chunk;
275
+ currentLine += part;
237
276
  }
238
277
  }
278
+ manifestContent += currentLine + "\r\n\r\n";
239
279
 
240
- const manifestContent = `Manifest-Version: 1.0\r\nClass-Path: ${wrappedCp}\r\n\r\n`;
241
280
  const manifestPath = path.join(xavvaDir, "MANIFEST.MF");
242
281
  fs.writeFileSync(manifestPath, manifestContent);
243
282
 
@@ -255,22 +294,59 @@ export class RunCommand implements Command {
255
294
  if (!fs.existsSync(cpFile)) {
256
295
  const stopSpinner = Logger.spinner("Generating project classpath");
257
296
  try {
258
- if (config.project.buildTool === 'maven') {
297
+ if (config.project.buildTool === "maven") {
259
298
  Bun.spawnSync(["mvn", "dependency:build-classpath", `-Dmdep.outputFile=${cpFile}`]);
299
+ } else if (config.project.buildTool === "gradle") {
300
+ const initScriptPath = path.join(xavvaDir, "init-cp.gradle");
301
+ const normalizedCpFile = cpFile.replace(/\\/g, "/");
302
+ const initScriptContent = `
303
+ allprojects {
304
+ afterEvaluate { project ->
305
+ if (project.plugins.hasPlugin('java')) {
306
+ tasks.register('printClasspath') {
307
+ doLast {
308
+ def cp = project.sourceSets.main.runtimeClasspath.asPath
309
+ def file = new File("${normalizedCpFile}")
310
+ if (!file.exists()) {
311
+ file.text = cp
312
+ } else {
313
+ file.text = file.text + File.pathSeparator + cp
314
+ }
315
+ }
316
+ }
317
+ }
318
+ }
319
+ }
320
+ `.trim().replace(/^ {24}/gm, ""); // Remove excess indentation
321
+ fs.writeFileSync(initScriptPath, initScriptContent);
322
+ Bun.spawnSync(["gradle", "-q", "printClasspath", "-I", initScriptPath]);
323
+ if (fs.existsSync(initScriptPath)) fs.unlinkSync(initScriptPath);
260
324
  } else {
261
325
  fs.writeFileSync(cpFile, ".");
262
326
  }
263
- } catch (e) {}
327
+ } catch (e) {
328
+ Logger.error(`Falha ao gerar classpath: ${e}`);
329
+ }
264
330
  stopSpinner();
265
331
  }
266
332
 
267
- const dependencyCp = fs.existsSync(cpFile) ? fs.readFileSync(cpFile, "utf8").trim() : "";
333
+ let dependencyCp = fs.existsSync(cpFile) ? fs.readFileSync(cpFile, "utf8").trim() : "";
268
334
 
335
+ // Normalize platform specific separators to semicolon for consistency
336
+ if (path.delimiter !== ";") {
337
+ dependencyCp = dependencyCp.split(path.delimiter).join(";");
338
+ }
339
+
269
340
  const localFolders = [
270
341
  "target/classes",
271
342
  "target/test-classes",
272
343
  "build/classes/java/main",
273
344
  "build/classes/java/test",
345
+ "build/classes/kotlin/main",
346
+ "build/resources/main",
347
+ "build/resources/test",
348
+ "bin/main",
349
+ "bin/test",
274
350
  "."
275
351
  ];
276
352
 
@@ -4,8 +4,10 @@ import { TomcatService } from "../services/TomcatService";
4
4
  import { Logger } from "../utils/ui";
5
5
 
6
6
  export class StartCommand implements Command {
7
+ constructor(private tomcat: TomcatService) {}
8
+
7
9
  async execute(config: AppConfig): Promise<void> {
8
- const tomcat = new TomcatService(config.tomcat);
10
+ const tomcat = this.tomcat;
9
11
 
10
12
  Logger.section("Start Only");
11
13
  Logger.info("Port", config.tomcat.port);
package/src/index.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  #!/usr/bin/env bun
2
- import { watch } from "fs";
3
2
  import { ConfigManager } from "./utils/config";
3
+ import { CommandRegistry } from "./commands/CommandRegistry";
4
4
  import { BuildCommand } from "./commands/BuildCommand";
5
5
  import { DeployCommand } from "./commands/DeployCommand";
6
6
  import { StartCommand } from "./commands/StartCommand";
@@ -10,11 +10,17 @@ import { RunCommand } from "./commands/RunCommand";
10
10
  import { LogsCommand } from "./commands/LogsCommand";
11
11
  import { DocsCommand } from "./commands/DocsCommand";
12
12
  import { AuditCommand } from "./commands/AuditCommand";
13
+
14
+ import { ProjectService } from "./services/ProjectService";
13
15
  import { TomcatService } from "./services/TomcatService";
14
- import { EndpointService } from "./services/EndpointService";
16
+ import { BuildService } from "./services/BuildService";
17
+ import { AuditService } from "./services/AuditService";
18
+ import { WatcherService } from "./services/WatcherService";
19
+ import { BuildCacheService } from "./services/BuildCacheService";
20
+
15
21
  import pkg from "../package.json";
16
22
  import { Logger } from "./utils/ui";
17
- import path from "path";
23
+ import type { AppConfig, CLIArguments } from "./types/config";
18
24
 
19
25
  async function main() {
20
26
  const { config, positionals, values } = await ConfigManager.load();
@@ -32,142 +38,45 @@ async function main() {
32
38
  }
33
39
 
34
40
  if (values.help) {
35
- new HelpCommand().execute(config);
41
+ new HelpCommand().execute(config, values);
36
42
  process.exit(0);
37
43
  }
38
44
 
39
- switch (commandName) {
40
- case "build":
41
- await new BuildCommand().execute(config);
42
- break;
43
- case "start":
44
- await new StartCommand().execute(config);
45
- break;
46
- case "doctor":
47
- await new DoctorCommand().execute(config, values);
48
- break;
49
- case "run":
50
- await new RunCommand(false).execute(config);
51
- break;
52
- case "debug":
53
- await new RunCommand(true).execute(config);
54
- break;
55
- case "logs":
56
- await new LogsCommand().execute(config);
57
- break;
58
- case "docs":
59
- await new DocsCommand().execute(config);
60
- break;
61
- case "audit":
62
- await new AuditCommand().execute(config);
63
- break;
64
- case "dev":
65
- case "deploy":
66
- await handleDeploy(config, values);
67
- break;
68
- default:
69
- Logger.error(`Comando desconhecido: ${commandName}`);
70
- new HelpCommand().execute(config);
71
- process.exit(1);
72
- }
73
- }
45
+ // 1. Instanciar Serviços (Injeção de Dependência)
46
+ const projectService = new ProjectService(config.project);
47
+ const buildCacheService = new BuildCacheService();
48
+ const buildService = new BuildService(config.project, config.tomcat, projectService, buildCacheService);
49
+ const tomcatService = new TomcatService(config.tomcat);
50
+ tomcatService.setProjectService(projectService);
51
+ const auditService = new AuditService(config.tomcat);
74
52
 
75
- async function handleDeploy(config: any, values: any) {
76
- const tomcat = new TomcatService(config.tomcat);
77
- const deployCmd = new DeployCommand(tomcat);
53
+ // 2. Registrar Comandos
54
+ const registry = new CommandRegistry();
78
55
 
79
- if (values.watch) {
80
- let isDeploying = false;
81
-
82
- const run = async (incremental = false) => {
83
- if (isDeploying) return;
84
- isDeploying = true;
85
- try {
86
- await deployCmd.execute(config, incremental, true);
87
- } catch (e) {
88
- } finally {
89
- isDeploying = false;
90
- }
91
- };
92
-
93
- await run(false);
94
-
95
- let debounceTimer: Timer;
96
- const coolingFiles = new Set<string>();
97
-
98
- watch(process.cwd(), { recursive: true }, async (event, filename) => {
99
- if (!filename) return;
100
-
101
- if (coolingFiles.has(filename)) return;
102
- coolingFiles.add(filename);
103
- setTimeout(() => coolingFiles.delete(filename), 500);
104
-
105
- const isJava = filename.endsWith(".java") || filename === "pom.xml" || filename === "build.gradle";
106
- const isResource = filename.endsWith(".jsp") || filename.endsWith(".html") ||
107
- filename.endsWith(".css") || filename.endsWith(".js") ||
108
- filename.endsWith(".xml") || filename.endsWith(".properties");
109
-
110
- const isIgnored = filename.includes("target") ||
111
- filename.includes("build") ||
112
- filename.includes("node_modules") ||
113
- filename.split(/[/\\]/).some(part => part.startsWith("."));
114
-
115
- if (isIgnored) return;
116
-
117
- if (isResource && !isJava) {
118
- const isJsp = filename.endsWith(".jsp");
119
- let jspUrl = "";
120
- let isPrivate = false;
121
-
122
- if (isJsp) {
123
- const parts = filename.split(/[/\\]/);
124
- const webappIndex = parts.indexOf("webapp");
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("/");
130
- isPrivate = relPath.startsWith("WEB-INF") || relPath.startsWith("META-INF");
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
-
142
- jspUrl = `http://localhost:${config.tomcat.port}${contextPath ? "/" + contextPath : ""}/${relPath}`;
143
- }
144
- }
145
-
146
- if (isJsp && isPrivate) {
147
- Logger.watcher(`JSP Private (WEB-INF): ${filename}`, 'change');
148
- Logger.dim(`Nota: Este arquivo não é acessível via URL direta.`);
149
- } else if (isJsp && jspUrl) {
150
- Logger.watcher(`JSP Updated: ${jspUrl}`, 'success');
151
- } else {
152
- Logger.watcher(`Resource altered: ${filename}`, 'change');
153
- }
154
-
155
- await deployCmd.syncResource(config, filename);
156
- return;
157
- }
158
-
159
- if (!isJava) return;
160
-
161
- Logger.watcher(filename, 'watch');
162
- clearTimeout(debounceTimer);
163
-
164
- debounceTimer = setTimeout(() => {
165
- run(true);
166
- }, 1000);
167
- });
168
-
56
+ const deployCmd = new DeployCommand(tomcatService, buildService);
57
+
58
+ registry.register("build", new BuildCommand(buildService));
59
+ registry.register("start", new StartCommand(tomcatService));
60
+ registry.register("doctor", new DoctorCommand());
61
+ registry.register("run", new RunCommand());
62
+ registry.register("debug", new RunCommand());
63
+ registry.register("logs", new LogsCommand());
64
+ registry.register("docs", new DocsCommand());
65
+ registry.register("audit", new AuditCommand(auditService));
66
+ registry.register("deploy", deployCmd);
67
+ registry.register("dev", deployCmd);
68
+
69
+ // Caso especial: Watch Mode para Deploy/Dev
70
+ if ((commandName === "deploy" || commandName === "dev") && values.watch) {
71
+ const watcher = new WatcherService(config, deployCmd);
72
+ await watcher.start();
169
73
  } else {
170
- await deployCmd.execute(config, false, false);
74
+ // 3. Executar do Registro
75
+ // Ajusta flags baseadas no nome do comando para comandos compartilhados
76
+ if (commandName === "debug") values.debug = true;
77
+ if (commandName === "run") values.debug = false;
78
+
79
+ await registry.execute(commandName, config, values);
171
80
  }
172
81
  }
173
82
 
@@ -71,7 +71,7 @@ export class AuditService {
71
71
  const normalizedPath = jarPath.split(path.sep).join("/");
72
72
  const psCommand = `
73
73
  Add-Type -AssemblyName System.IO.Compression.FileSystem
74
- $zip = [System.IO.Compression.ZipFile]::OpenRead("${normalizedPath}")
74
+ $zip = [System.IO.Compression.ZipFile]::OpenRead($env:JAR_PATH)
75
75
  $entry = $zip.Entries | Where-Object { $_.FullName -match "pom.properties$" } | Select-Object -First 1
76
76
  if ($entry) {
77
77
  $stream = $entry.Open()
@@ -85,7 +85,12 @@ export class AuditService {
85
85
  `;
86
86
 
87
87
  try {
88
- const proc = Bun.spawn(["powershell", "-command", psCommand]);
88
+ const proc = Bun.spawn(["powershell", "-command", psCommand], {
89
+ env: {
90
+ ...process.env,
91
+ JAR_PATH: normalizedPath
92
+ }
93
+ });
89
94
  const output = await new Response(proc.stdout).text();
90
95
 
91
96
  const groupId = output.match(/groupId=(.*)/)?.[1]?.trim();
@@ -0,0 +1,41 @@
1
+ import { Logger } from "../utils/ui";
2
+
3
+ export class BrowserService {
4
+ /**
5
+ * Recarrega a aba ativa do browser (Chrome ou Edge) no Windows.
6
+ */
7
+ public static async reload(url: string) {
8
+ if (process.platform !== 'win32') return;
9
+
10
+ // Pequeno delay para garantir que o Tomcat processou o novo contexto
11
+ await new Promise(r => setTimeout(r, 800));
12
+
13
+ const psCommand = `
14
+ $shell = New-Object -ComObject WScript.Shell
15
+ $process = Get-Process | Where-Object { $_.MainWindowTitle -match "Chrome" -or $_.MainWindowTitle -match "Edge" } | Select-Object -First 1
16
+ if ($process) {
17
+ $shell.AppActivate($process.Id)
18
+ Sleep -m 100
19
+ $shell.SendKeys("{F5}")
20
+ }
21
+ `;
22
+
23
+ try {
24
+ Bun.spawn(["powershell", "-command", psCommand]);
25
+ } catch (e) {
26
+ Logger.warn("Não foi possível recarregar o browser automaticamente.");
27
+ }
28
+ }
29
+
30
+ /**
31
+ * Abre a URL no browser padrão do sistema.
32
+ */
33
+ public static open(url: string) {
34
+ if (process.platform === 'win32') {
35
+ Bun.spawn(["cmd", "/c", "start", url]);
36
+ } else {
37
+ const start = process.platform === 'darwin' ? 'open' : 'xdg-open';
38
+ Bun.spawn([start, url]);
39
+ }
40
+ }
41
+ }