@archznn/xavva 2.4.0 → 2.6.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/README.md CHANGED
@@ -1,8 +1,8 @@
1
1
  # XAVVA CLI 🚀
2
2
 
3
- > Ultra-fast development toolkit for Java Enterprise (Tomcat) on Windows
3
+ > Ultra-fast development toolkit for Java Enterprise (Tomcat) on Windows, Linux & macOS
4
4
 
5
- [![Version](https://img.shields.io/badge/version-2.4.0-blue.svg)](https://github.com/leorsousa05/Xavva)
5
+ [![Version](https://img.shields.io/badge/version-2.6.0-blue.svg)](https://github.com/leorsousa05/Xavva)
6
6
  [![License](https://img.shields.io/badge/license-MIT-green.svg)](LICENSE)
7
7
 
8
8
  Xavva is a high-performance CLI built with **Bun** that transforms the Java/Tomcat development experience. It brings modern development workflows (like Node.js/Vite) to the Java Enterprise ecosystem with hot-reload, smart logging, and automated deployment.
@@ -25,7 +25,7 @@ Xavva is a high-performance CLI built with **Bun** that transforms the Java/Tomc
25
25
 
26
26
  ## 📦 Installation
27
27
 
28
- ```powershell
28
+ ```bash
29
29
  # Via NPM
30
30
  npm install -g @archznn/xavva
31
31
 
@@ -196,12 +196,14 @@ Create `xavva.json` in your project root:
196
196
  "tui": false
197
197
  },
198
198
  "tomcat": {
199
- "path": "C:/apache-tomcat",
199
+ "path": "/home/user/apache-tomcat",
200
200
  "port": 8080
201
201
  }
202
202
  }
203
203
  ```
204
204
 
205
+ > **Note:** On Windows use `"path": "C:/apache-tomcat"` format.
206
+
205
207
  ### CLI Options
206
208
 
207
209
  | Option | Description |
@@ -224,6 +226,25 @@ Create `xavva.json` in your project root:
224
226
 
225
227
  ---
226
228
 
229
+ ## 💻 Platform Support
230
+
231
+ Xavva works on all major platforms:
232
+
233
+ | Platform | Status | Notes |
234
+ |----------|--------|-------|
235
+ | Windows | ✅ Full | PowerShell for system integration |
236
+ | Linux | ✅ Full | Bash/Zsh auto-configuration |
237
+ | macOS | ✅ Full | Native terminal support |
238
+
239
+ ### Requirements
240
+
241
+ - **Bun** runtime (latest version)
242
+ - **Java** 11 or higher (JDK)
243
+ - **Maven** 3.6+ or **Gradle** 7+
244
+ - **Git** (optional, for version info)
245
+
246
+ ---
247
+
227
248
  ## 🏗️ Architecture
228
249
 
229
250
  Xavva uses a modular service-oriented architecture:
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@archznn/xavva",
3
- "version": "2.4.0",
4
- "description": "Ultra-fast CLI tool for Java/Tomcat development on Windows with Hot-Reload and Zero Config.",
3
+ "version": "2.6.0",
4
+ "description": "Ultra-fast CLI tool for Java/Tomcat development with Hot-Reload and Zero Config. Supports Windows, Linux and macOS.",
5
5
  "module": "src/index.ts",
6
6
  "type": "module",
7
7
  "bin": {
@@ -19,6 +19,8 @@
19
19
  "gradle",
20
20
  "hot-reload",
21
21
  "windows",
22
+ "linux",
23
+ "macos",
22
24
  "cli",
23
25
  "bun"
24
26
  ],
@@ -7,6 +7,11 @@ import { TomcatService } from "../services/TomcatService";
7
7
  import { Logger } from "../utils/ui";
8
8
  import { EndpointService } from "../services/EndpointService";
9
9
  import { BrowserService } from "../services/BrowserService";
10
+ import {
11
+ getJavaPath,
12
+ getWarExtractCommand,
13
+ isWindows,
14
+ } from "../utils/platform";
10
15
 
11
16
  export class DeployCommand implements Command {
12
17
  constructor(private tomcat: TomcatService, private builder: BuildService) {}
@@ -14,6 +19,7 @@ export class DeployCommand implements Command {
14
19
  async execute(config: AppConfig, args?: CLIArguments): Promise<void> {
15
20
  const incremental = args?.watch && args?.incremental;
16
21
  const isWatching = !!args?.watch;
22
+ const changedFiles = args?.changedFiles;
17
23
  const tomcat = this.tomcat;
18
24
  const builder = this.builder;
19
25
 
@@ -46,11 +52,11 @@ export class DeployCommand implements Command {
46
52
  }
47
53
 
48
54
  if (incremental) {
49
- const actualAppFolder = await builder.syncClasses();
55
+ const actualAppFolder = await builder.syncClasses(changedFiles);
50
56
  const actualContextPath = contextPath || actualAppFolder || "";
51
57
  const actualAppUrl = `http://localhost:${config.tomcat.port}/${actualContextPath}`;
52
58
  await BrowserService.reload(actualAppUrl);
53
- Logger.success("redeploy completed");
59
+ Logger.success(`redeploy completed (${changedFiles?.length || 'all'} file(s))`);
54
60
  return;
55
61
  }
56
62
 
@@ -77,15 +83,28 @@ export class DeployCommand implements Command {
77
83
  Bun.spawnSync(["jar", "xf", artifactInfo.path], { cwd: appWebappPath });
78
84
  Logger.server("extracted WAR");
79
85
  } catch (e) {
80
- const extractCmd = `Expand-Archive -Path $env:ARTIFACT_PATH -DestinationPath $env:DEST_PATH -Force`;
81
- Bun.spawnSync(["powershell", "-command", extractCmd], {
82
- env: {
83
- ...process.env,
84
- ARTIFACT_PATH: artifactInfo.path,
85
- DEST_PATH: appWebappPath
86
+ // Fallback para extração com jar (funciona em todas as plataformas)
87
+ if (isWindows()) {
88
+ const extractCmd = `Expand-Archive -Path $env:ARTIFACT_PATH -DestinationPath $env:DEST_PATH -Force`;
89
+ Bun.spawnSync(["powershell", "-command", extractCmd], {
90
+ env: {
91
+ ...process.env,
92
+ ARTIFACT_PATH: artifactInfo.path,
93
+ DEST_PATH: appWebappPath
94
+ }
95
+ });
96
+ } else {
97
+ // Linux/Mac: usa unzip ou jar
98
+ try {
99
+ Bun.spawnSync(["unzip", "-q", "-o", artifactInfo.path, "-d", appWebappPath]);
100
+ } catch {
101
+ // Fallback final para jar
102
+ Bun.spawnSync(getWarExtractCommand(artifactInfo.path, appWebappPath), {
103
+ cwd: appWebappPath
104
+ });
86
105
  }
87
- });
88
- Logger.server("extracted WAR (legacy)");
106
+ }
107
+ Logger.server("extracted WAR (fallback)");
89
108
  }
90
109
  } else {
91
110
  Logger.server("webapp up to date");
@@ -98,7 +117,7 @@ export class DeployCommand implements Command {
98
117
  const finalAppUrl = `http://localhost:${config.tomcat.port}/${finalContextPath}`;
99
118
 
100
119
  tomcat.onReady = async () => {
101
- await this.handleServerReady(config, finalAppUrl, finalContextPath, tomcat, incremental);
120
+ await this.handleServerReady(config, finalAppUrl, finalContextPath, tomcat, !!incremental);
102
121
  };
103
122
 
104
123
  tomcat.start(config, isWatching);
@@ -116,13 +135,7 @@ export class DeployCommand implements Command {
116
135
  Logger.config("watch", isWatching);
117
136
  Logger.config("debug", config.project.debug ? `port ${config.project.debugPort}` : false);
118
137
 
119
- let javaBin = "java";
120
- if (process.env.JAVA_HOME) {
121
- const homeBin = path.join(process.env.JAVA_HOME, "bin", "java.exe");
122
- if (fs.existsSync(homeBin)) javaBin = homeBin;
123
- }
124
-
125
- const javaVer = Bun.spawnSync([javaBin, "-version"]);
138
+ const javaVer = Bun.spawnSync([getJavaPath(), "-version"]);
126
139
  const output = (javaVer.stderr.toString() + javaVer.stdout.toString()).toLowerCase();
127
140
  const hasDcevm = ["dcevm", "jetbrains", "trava", "jbr"].some(v => output.includes(v));
128
141
 
@@ -4,6 +4,17 @@ import { Logger } from "../utils/ui";
4
4
  import fs from "fs";
5
5
  import path from "path";
6
6
  import os from "os";
7
+ import {
8
+ getCatalinaScript,
9
+ getWhichCommand,
10
+ getJbrDownloadUrl,
11
+ getTarExtractCommand,
12
+ getJavaPath,
13
+ getJavaBinary,
14
+ isWindows,
15
+ isLinux,
16
+ isMacOS,
17
+ } from "../utils/platform";
7
18
 
8
19
  export class DoctorCommand implements Command {
9
20
  async execute(config: AppConfig, values: CLIArguments = {}): Promise<void> {
@@ -25,7 +36,7 @@ export class DoctorCommand implements Command {
25
36
 
26
37
  if (!jvmInfo.dcevm) {
27
38
  Logger.log(
28
- ` ${Logger.C.yellow}💡 Dica: Sua JVM não suporta mudanças estruturais (novos métodos/campos).${Logger.C.reset}`,
39
+ ` ${Logger.C.warning}💡 Dica: Sua JVM não suporta mudanças estruturais (novos métodos/campos).${Logger.C.reset}`,
29
40
  );
30
41
  if (values.fix) {
31
42
  await this.installDCEVM();
@@ -41,12 +52,12 @@ export class DoctorCommand implements Command {
41
52
 
42
53
  if (tomcatOk) {
43
54
  const binOk = fs.existsSync(
44
- path.join(config.tomcat.path, "bin", "catalina.bat"),
55
+ path.join(config.tomcat.path, "bin", getCatalinaScript()),
45
56
  );
46
57
  this.check(
47
58
  "Tomcat Bin",
48
59
  binOk,
49
- binOk ? "OK" : "catalina.bat não encontrado",
60
+ binOk ? "OK" : `${getCatalinaScript()} não encontrado`,
50
61
  );
51
62
  }
52
63
 
@@ -211,10 +222,7 @@ export class DoctorCommand implements Command {
211
222
 
212
223
  private checkBinary(name: string): boolean {
213
224
  try {
214
- const proc = Bun.spawnSync([
215
- process.platform === "win32" ? "where" : "which",
216
- name,
217
- ]);
225
+ const proc = Bun.spawnSync(getWhichCommand(name));
218
226
  return proc.exitCode === 0;
219
227
  } catch {
220
228
  return false;
@@ -224,11 +232,7 @@ export class DoctorCommand implements Command {
224
232
  private checkJVM(): { name: string, dcevm: boolean } {
225
233
  try {
226
234
  // Tentar primeiro o binário do JAVA_HOME para evitar cache do Path
227
- let javaBin = "java";
228
- if (process.env.JAVA_HOME) {
229
- const homeBin = path.join(process.env.JAVA_HOME, "bin", "java.exe");
230
- if (fs.existsSync(homeBin)) javaBin = homeBin;
231
- }
235
+ let javaBin = getJavaPath();
232
236
 
233
237
  const proc = Bun.spawnSync([javaBin, "-version"]);
234
238
  const output = (proc.stderr.toString() + proc.stdout.toString()).toLowerCase();
@@ -251,8 +255,8 @@ export class DoctorCommand implements Command {
251
255
  Logger.section("Instalação do JetBrains Runtime (JBR 21)");
252
256
  Logger.log("Baixando JDK moderna com DCEVM nativo (JBR 21 SDK)...");
253
257
 
254
- // URL para o JetBrains Runtime 21 SDK Windows x64
255
- const url = "https://cache-redirector.jetbrains.com/intellij-jbr/jbrsdk-21.0.6-windows-x64-b895.97.tar.gz";
258
+ // URL para o JetBrains Runtime 21 SDK (multiplataforma)
259
+ const url = getJbrDownloadUrl("21");
256
260
  const installDir = path.join(os.homedir(), ".xavva", "jdk-dcevm");
257
261
 
258
262
  // Limpar instalação anterior se existir
@@ -272,21 +276,15 @@ export class DoctorCommand implements Command {
272
276
 
273
277
  Logger.success("Download concluído. Extraindo binários...");
274
278
 
275
- // Usar PowerShell para extrair .tar.gz (nativo no Windows 10/11)
276
- const extractCmd = `tar -xzf $env:TAR_PATH -C $env:INSTALL_DIR`;
277
- Bun.spawnSync(["powershell", "-command", extractCmd], {
278
- env: {
279
- ...process.env,
280
- TAR_PATH: tarPath,
281
- INSTALL_DIR: installDir
282
- }
283
- });
279
+ // Extrair .tar.gz usando comando apropriado para a plataforma
280
+ const extractCmd = getTarExtractCommand(tarPath, installDir);
281
+ Bun.spawnSync(extractCmd);
284
282
 
285
283
  fs.rmSync(tarPath);
286
284
 
287
- // Busca recursiva para encontrar onde está o bin/java.exe
285
+ // Busca recursiva para encontrar onde está o bin/java
288
286
  const findJdkRoot = (dir: string): string | null => {
289
- if (fs.existsSync(path.join(dir, "bin", "java.exe"))) return dir;
287
+ if (fs.existsSync(path.join(dir, "bin", getJavaBinary()))) return dir;
290
288
  const subdirs = fs.readdirSync(dir, { withFileTypes: true })
291
289
  .filter(d => d.isDirectory())
292
290
  .map(d => path.join(dir, d.name));
@@ -299,55 +297,110 @@ export class DoctorCommand implements Command {
299
297
 
300
298
  const jdkPath = findJdkRoot(installDir) || installDir;
301
299
  const binPath = path.join(jdkPath, "bin");
302
-
303
- Logger.process("Configurando variáveis de ambiente do SISTEMA...");
304
-
305
- const setEnvCmd = `
306
- $jdk = $env:JDK_PATH;
307
- $bin = $env:BIN_PATH;
308
- try {
309
- [Environment]::SetEnvironmentVariable('JAVA_HOME', $jdk, 'Machine');
310
- $pathVar = [Environment]::GetEnvironmentVariable('Path', 'Machine');
311
- $paths = $pathVar -split ';' | Where-Object { $_ -ne '' };
312
- $normalizedBin = $bin.TrimEnd('\\').ToLower();
313
-
314
- $exists = $false;
315
- foreach ($p in $paths) {
316
- if ($p.TrimEnd('\\').ToLower() -eq $normalizedBin) { $exists = $true; break; }
317
- }
318
-
319
- if (-not $exists) {
320
- $newPath = "$bin;" + $pathVar;
321
- [Environment]::SetEnvironmentVariable('Path', $newPath, 'Machine');
322
- }
323
- Write-Output "OK";
324
- } catch {
325
- Write-Error $_.Exception.Message;
326
- }
327
- `.replace(/\n/g, ' ');
328
- const result = Bun.spawnSync(["powershell", "-command", setEnvCmd], {
329
- env: {
330
- ...process.env,
331
- JDK_PATH: jdkPath,
332
- BIN_PATH: binPath
333
- }
334
- });
335
- const output = result.stdout.toString() + result.stderr.toString();
336
-
337
- if (output.includes("ACCESS_DENIED")) {
338
- Logger.error("Falha ao configurar variáveis do SISTEMA (Acesso Negado).");
339
- Logger.warn("Dica: Execute o terminal como ADMINISTRADOR para permitir esta alteração.");
340
- Logger.info("JAVA_HOME manual", jdkPath);
341
- } else {
342
- Logger.success(`DCEVM configurado no SISTEMA com sucesso!`);
343
- Logger.info("JAVA_HOME", jdkPath);
344
- }
345
-
346
- Logger.newline();
347
- Logger.warn("IMPORTANTE: Reinicie seu terminal (ou o VS Code) para as mudanças surtirem efeito.");
348
- } catch (e) {
349
-
350
- Logger.error(`Falha na instalação: ${e.message}`);
351
- }
352
- }
300
+
301
+ if (isWindows()) {
302
+ await this.configureWindowsEnv(jdkPath, binPath);
303
+ } else {
304
+ await this.configureUnixEnv(jdkPath, binPath);
305
+ }
306
+ } catch (e: any) {
307
+ Logger.error(`Falha na instalação: ${e.message}`);
308
+ }
309
+ }
310
+
311
+ private async configureWindowsEnv(jdkPath: string, binPath: string) {
312
+ Logger.process("Configurando variáveis de ambiente do SISTEMA...");
313
+
314
+ const setEnvCmd = `
315
+ $jdk = $env:JDK_PATH;
316
+ $bin = $env:BIN_PATH;
317
+ try {
318
+ [Environment]::SetEnvironmentVariable('JAVA_HOME', $jdk, 'Machine');
319
+ $pathVar = [Environment]::GetEnvironmentVariable('Path', 'Machine');
320
+ $paths = $pathVar -split ';' | Where-Object { $_ -ne '' };
321
+ $normalizedBin = $bin.TrimEnd('\\\\').ToLower();
322
+
323
+ $exists = $false;
324
+ foreach ($p in $paths) {
325
+ if ($p.TrimEnd('\\\\').ToLower() -eq $normalizedBin) { $exists = $true; break; }
326
+ }
327
+
328
+ if (-not $exists) {
329
+ $newPath = "$bin;" + $pathVar;
330
+ [Environment]::SetEnvironmentVariable('Path', $newPath, 'Machine');
331
+ }
332
+ Write-Output "OK";
333
+ } catch {
334
+ Write-Error $_.Exception.Message;
335
+ }
336
+ `.replace(/\n/g, ' ');
337
+
338
+ const result = Bun.spawnSync(["powershell", "-command", setEnvCmd], {
339
+ env: {
340
+ ...process.env,
341
+ JDK_PATH: jdkPath,
342
+ BIN_PATH: binPath
343
+ }
344
+ });
345
+ const output = result.stdout.toString() + result.stderr.toString();
346
+
347
+ if (output.includes("ACCESS_DENIED")) {
348
+ Logger.error("Falha ao configurar variáveis do SISTEMA (Acesso Negado).");
349
+ Logger.warn("Dica: Execute o terminal como ADMINISTRADOR para permitir esta alteração.");
350
+ Logger.info("JAVA_HOME manual", jdkPath);
351
+ } else {
352
+ Logger.success(`DCEVM configurado no SISTEMA com sucesso!`);
353
+ Logger.info("JAVA_HOME", jdkPath);
354
+ }
355
+
356
+ Logger.newline();
357
+ Logger.warn("IMPORTANTE: Reinicie seu terminal (ou o VS Code) para as mudanças surtirem efeito.");
358
+ }
359
+
360
+ private async configureUnixEnv(jdkPath: string, binPath: string) {
361
+ Logger.process("Configurando variáveis de ambiente...");
362
+
363
+ const shell = process.env.SHELL || "/bin/bash";
364
+ let rcFile = path.join(os.homedir(), ".bashrc");
365
+
366
+ if (shell.includes("zsh")) {
367
+ rcFile = path.join(os.homedir(), ".zshrc");
368
+ } else if (shell.includes("fish")) {
369
+ rcFile = path.join(os.homedir(), ".config/fish/config.fish");
370
+ }
371
+
372
+ // Verifica se já existe JAVA_HOME configurado
373
+ const exportLine = `export JAVA_HOME="${jdkPath}"`;
374
+ const pathLine = `export PATH="${binPath}:$PATH"`;
375
+
376
+ let rcContent = "";
377
+ if (fs.existsSync(rcFile)) {
378
+ rcContent = fs.readFileSync(rcFile, "utf8");
379
+ }
380
+
381
+ // Remove linhas antigas do Xavva JBR
382
+ const lines = rcContent.split("\n");
383
+ const filteredLines = lines.filter(line =>
384
+ !line.includes("# Xavva JBR") &&
385
+ !line.includes("JAVA_HOME=") &&
386
+ !line.includes("# Added by Xavva")
387
+ );
388
+
389
+ // Adiciona novas configurações
390
+ filteredLines.push("# Added by Xavva - JetBrains Runtime (DCEVM)");
391
+ filteredLines.push(exportLine);
392
+ filteredLines.push(pathLine);
393
+
394
+ fs.writeFileSync(rcFile, filteredLines.join("\n") + "\n");
395
+
396
+ Logger.success(`DCEVM configurado em ${rcFile}`);
397
+ Logger.info("JAVA_HOME", jdkPath);
398
+ Logger.newline();
399
+ Logger.warn("IMPORTANTE: Execute 'source " + rcFile + "' ou reinicie seu terminal para aplicar permanentemente.");
400
+ Logger.info("Dica", "Para esta sessão, o JAVA_HOME já foi configurado temporariamente.");
401
+
402
+ // Configura JAVA_HOME temporariamente para o processo atual
403
+ process.env.JAVA_HOME = jdkPath;
404
+ process.env.PATH = `${binPath}:${process.env.PATH}`;
405
+ }
353
406
  }
@@ -60,7 +60,7 @@ export class LogsCommand implements Command {
60
60
  currentSize = newStats.size;
61
61
  } else if (newStats.size < currentSize) {
62
62
  currentSize = newStats.size;
63
- dashboard.log(Logger.C.yellow + "Arquivo de log foi resetado/rotacionado.");
63
+ dashboard.log(Logger.C.warning + "Arquivo de log foi resetado/rotacionado.");
64
64
  }
65
65
  }
66
66
  });
@@ -5,6 +5,14 @@ import path from "path";
5
5
  import fs from "fs";
6
6
  import { glob } from "glob";
7
7
  import readline from "readline";
8
+ import {
9
+ getJavaPath,
10
+ getMavenCommand,
11
+ getGradleCommand,
12
+ getClasspathSeparator,
13
+ normalizeClasspathPath,
14
+ isWindows,
15
+ } from "../utils/platform";
8
16
 
9
17
  export class RunCommand implements Command {
10
18
  async execute(config: AppConfig, args?: CLIArguments): Promise<void> {
@@ -36,7 +44,8 @@ export class RunCommand implements Command {
36
44
  const { localCp, dependencyCp } = await this.getClasspath(config);
37
45
  const pathingJar = await this.createPathingJar(dependencyCp);
38
46
 
39
- const finalCp = `${localCp};${pathingJar}`;
47
+ const sep = getClasspathSeparator();
48
+ const finalCp = `${localCp}${sep}${pathingJar}`;
40
49
 
41
50
  const javaArgs = [
42
51
  "-classpath", finalCp,
@@ -60,7 +69,7 @@ export class RunCommand implements Command {
60
69
  Logger.warn(`🚀 Executando ${className}...`);
61
70
  }
62
71
 
63
- const bin = process.env.JAVA_HOME ? path.join(process.env.JAVA_HOME, "bin", "java.exe") : "java";
72
+ const bin = getJavaPath();
64
73
 
65
74
  const proc = Bun.spawn([bin, ...javaArgs], {
66
75
  stdout: "inherit",
@@ -220,9 +229,10 @@ export class RunCommand implements Command {
220
229
  const xavvaDir = path.join(process.cwd(), ".xavva");
221
230
  const jarPath = path.join(xavvaDir, "classpath.jar");
222
231
 
223
- const paths = dependencyCp.split(";").filter(p => p.trim());
232
+ const sep = getClasspathSeparator();
233
+ const paths = dependencyCp.split(sep).filter(p => p.trim());
224
234
  const relativePaths = paths.map(p => {
225
- let rel = path.relative(xavvaDir, p).replace(/\\/g, "/");
235
+ let rel = normalizeClasspathPath(path.relative(xavvaDir, p));
226
236
  if (fs.existsSync(p) && fs.statSync(p).isDirectory() && !rel.endsWith("/")) rel += "/";
227
237
  // Robust URL encoding for Class-Path as per Java Spec
228
238
  return encodeURI(rel)
@@ -296,10 +306,9 @@ export class RunCommand implements Command {
296
306
  const stopSpinner = Logger.spinner("Generating project classpath");
297
307
  try {
298
308
  if (config.project.buildTool === "maven") {
299
- const mvnCmd = process.platform === "win32" ? "mvn.cmd" : "mvn";
300
- Bun.spawnSync([mvnCmd, "dependency:build-classpath", `-Dmdep.outputFile=${cpFile}`]);
309
+ Bun.spawnSync([getMavenCommand(), "dependency:build-classpath", `-Dmdep.outputFile=${cpFile}`]);
301
310
  } else if (config.project.buildTool === "gradle") {
302
- const gradleCmd = process.platform === "win32" ? "gradle.bat" : "gradle";
311
+ const gradleCmd = getGradleCommand();
303
312
  const initScriptPath = path.join(xavvaDir, "init-cp.gradle");
304
313
  const normalizedCpFile = cpFile.replace(/\\/g, "/");
305
314
  const initScriptContent = `
@@ -335,9 +344,10 @@ export class RunCommand implements Command {
335
344
 
336
345
  let dependencyCp = fs.existsSync(cpFile) ? fs.readFileSync(cpFile, "utf8").trim() : "";
337
346
 
338
- // Normalize platform specific separators to semicolon for consistency
339
- if (path.delimiter !== ";") {
340
- dependencyCp = dependencyCp.split(path.delimiter).join(";");
347
+ // Normalize platform specific separators para o separador consistente
348
+ const sep = getClasspathSeparator();
349
+ if (path.delimiter !== sep) {
350
+ dependencyCp = dependencyCp.split(path.delimiter).join(sep);
341
351
  }
342
352
 
343
353
  const localFolders = [
@@ -356,7 +366,7 @@ export class RunCommand implements Command {
356
366
  const localCp = localFolders
357
367
  .map(p => path.join(process.cwd(), p))
358
368
  .filter(p => fs.existsSync(p))
359
- .join(";");
369
+ .join(getClasspathSeparator());
360
370
 
361
371
  return { localCp, dependencyCp };
362
372
  }
package/src/index.ts CHANGED
@@ -93,7 +93,7 @@ async function main() {
93
93
  // Registrar ação de restart manual na TUI
94
94
  if (dashboard.isTuiActive()) {
95
95
  dashboard.onAction("r", () => {
96
- dashboard.log(Logger.C.yellow + "Restart manual solicitado via TUI...");
96
+ dashboard.log(Logger.C.warning + "Restart manual solicitado via TUI...");
97
97
  deployCmd.execute(config, false, true); // Executa deploy completo mas mantém o watch
98
98
  });
99
99
  }
@@ -1,15 +1,22 @@
1
1
  import { Logger } from "../utils/ui";
2
+ import { isWindows, getOpenBrowserArgs } from "../utils/platform";
2
3
 
3
4
  export class BrowserService {
4
5
  /**
5
- * Recarrega a aba ativa do browser (Chrome ou Edge) no Windows.
6
+ * Recarrega a aba ativa do browser (Chrome ou Edge).
7
+ * Nota: No Linux/Mac, esta funcionalidade é limitada devido às restrições
8
+ * de automação de GUI. Recomenda-se usar extensões de Live Reload.
6
9
  */
7
10
  public static async reload(url: string) {
8
- if (process.platform !== 'win32') return;
9
-
10
11
  // Pequeno delay para garantir que o Tomcat processou o novo contexto
11
12
  await new Promise(r => setTimeout(r, 800));
12
13
 
14
+ if (!isWindows()) {
15
+ // No Linux/Mac, tenta notificar via Browser Sync ou similar se disponível
16
+ // Por enquanto, apenas loga (o usuário pode usar extensões de Live Reload)
17
+ return;
18
+ }
19
+
13
20
  const psCommand = `
14
21
  $shell = New-Object -ComObject WScript.Shell
15
22
  $process = Get-Process | Where-Object { $_.MainWindowTitle -match "Chrome" -or $_.MainWindowTitle -match "Edge" } | Select-Object -First 1
@@ -31,11 +38,7 @@ export class BrowserService {
31
38
  * Abre a URL no browser padrão do sistema.
32
39
  */
33
40
  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
- }
41
+ const args = getOpenBrowserArgs(url);
42
+ Bun.spawn(args);
40
43
  }
41
44
  }