@archznn/xavva 2.5.0 → 2.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.
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.
@@ -20,12 +20,13 @@ Xavva is a high-performance CLI built with **Bun** that transforms the Java/Tomc
20
20
  - 🔧 **Auto-Healing** — Automatic diagnosis and repair of common issues
21
21
  - 🐱 **Embedded Tomcat** — Auto-install Tomcat, no manual setup needed
22
22
  - 📦 **WAR Generation** — Build as .war file or exploded directory
23
+ - 🔤 **Encoding Converter** — Convert file encodings (UTF-8, Windows-1252, ISO-8859-1) and fix mojibake
23
24
 
24
25
  ---
25
26
 
26
27
  ## 📦 Installation
27
28
 
28
- ```powershell
29
+ ```bash
29
30
  # Via NPM
30
31
  npm install -g @archznn/xavva
31
32
 
@@ -56,6 +57,9 @@ xavva deps --update-safe
56
57
  # Check for security vulnerabilities
57
58
  xavva audit
58
59
 
60
+ # Convert file encoding (UTF-8 → Windows-1252)
61
+ xavva encoding convert --to cp1252 --backup src/main/java/
62
+
59
63
  # Use embedded Tomcat (auto-install)
60
64
  xavva dev --yes
61
65
  ```
@@ -91,6 +95,7 @@ xavva dev --yes
91
95
  | `xavva profiles` | List available Maven/Gradle profiles |
92
96
  | `xavva docs` | Generate endpoint documentation |
93
97
  | `xavva tomcat` | Manage embedded Tomcat installations |
98
+ | `xavva encoding` | Convert file encodings (UTF-8, CP1252, ISO-8859-1) |
94
99
 
95
100
  ---
96
101
 
@@ -126,6 +131,56 @@ xavva dev --tomcat-version 9.0.115
126
131
 
127
132
  ---
128
133
 
134
+ ## 🔤 File Encoding
135
+
136
+ The `xavva encoding` command helps you convert file encodings and fix mojibake (corrupted characters):
137
+
138
+ ```bash
139
+ # Detect encoding of a file
140
+ xavva encoding detect src/main/java/MyClass.java
141
+
142
+ # Convert from UTF-8 to Windows-1252 (with backup)
143
+ xavva encoding convert --from utf-8 --to cp1252 --backup src/main/java/
144
+
145
+ # Convert a single file
146
+ xavva encoding convert --to cp1252 --backup src/main/java/MyClass.java
147
+
148
+ # Fix mojibake (e.g., "A��o" → "Ação")
149
+ xavva encoding fix src/main/java/MyClass.java
150
+
151
+ # List encodings of all files in src/
152
+ xavva encoding list
153
+
154
+ # Simulate conversion without modifying files
155
+ xavva encoding convert --from utf-8 --to cp1252 --dry-run src/
156
+ ```
157
+
158
+ ### Supported Encodings
159
+
160
+ - **utf-8** / **utf8** — UTF-8 (default)
161
+ - **windows-1252** / **cp1252** — Windows CP1252 (ANSI)
162
+ - **iso-8859-1** / **latin1** — ISO-8859-1 (Latin-1)
163
+
164
+ ### Configuration
165
+
166
+ Set default encoding in `xavva.json`:
167
+
168
+ ```json
169
+ {
170
+ "project": {
171
+ "encoding": "cp1252"
172
+ }
173
+ }
174
+ ```
175
+
176
+ Then use without `--to`:
177
+
178
+ ```bash
179
+ xavva encoding convert --backup src/main/java/
180
+ ```
181
+
182
+ ---
183
+
129
184
  ## 🔍 Dependency Analysis
130
185
 
131
186
  The `xavva deps` command provides comprehensive dependency analysis:
@@ -196,12 +251,14 @@ Create `xavva.json` in your project root:
196
251
  "tui": false
197
252
  },
198
253
  "tomcat": {
199
- "path": "C:/apache-tomcat",
254
+ "path": "/home/user/apache-tomcat",
200
255
  "port": 8080
201
256
  }
202
257
  }
203
258
  ```
204
259
 
260
+ > **Note:** On Windows use `"path": "C:/apache-tomcat"` format.
261
+
205
262
  ### CLI Options
206
263
 
207
264
  | Option | Description |
@@ -224,6 +281,25 @@ Create `xavva.json` in your project root:
224
281
 
225
282
  ---
226
283
 
284
+ ## 💻 Platform Support
285
+
286
+ Xavva works on all major platforms:
287
+
288
+ | Platform | Status | Notes |
289
+ |----------|--------|-------|
290
+ | Windows | ✅ Full | PowerShell for system integration |
291
+ | Linux | ✅ Full | Bash/Zsh auto-configuration |
292
+ | macOS | ✅ Full | Native terminal support |
293
+
294
+ ### Requirements
295
+
296
+ - **Bun** runtime (latest version)
297
+ - **Java** 11 or higher (JDK)
298
+ - **Maven** 3.6+ or **Gradle** 7+
299
+ - **Git** (optional, for version info)
300
+
301
+ ---
302
+
227
303
  ## 🏗️ Architecture
228
304
 
229
305
  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.5.0",
4
- "description": "Ultra-fast CLI tool for Java/Tomcat development on Windows with Hot-Reload and Zero Config.",
3
+ "version": "2.7.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) {}
@@ -78,15 +83,28 @@ export class DeployCommand implements Command {
78
83
  Bun.spawnSync(["jar", "xf", artifactInfo.path], { cwd: appWebappPath });
79
84
  Logger.server("extracted WAR");
80
85
  } catch (e) {
81
- const extractCmd = `Expand-Archive -Path $env:ARTIFACT_PATH -DestinationPath $env:DEST_PATH -Force`;
82
- Bun.spawnSync(["powershell", "-command", extractCmd], {
83
- env: {
84
- ...process.env,
85
- ARTIFACT_PATH: artifactInfo.path,
86
- 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
+ });
87
105
  }
88
- });
89
- Logger.server("extracted WAR (legacy)");
106
+ }
107
+ Logger.server("extracted WAR (fallback)");
90
108
  }
91
109
  } else {
92
110
  Logger.server("webapp up to date");
@@ -117,13 +135,7 @@ export class DeployCommand implements Command {
117
135
  Logger.config("watch", isWatching);
118
136
  Logger.config("debug", config.project.debug ? `port ${config.project.debugPort}` : false);
119
137
 
120
- let javaBin = "java";
121
- if (process.env.JAVA_HOME) {
122
- const homeBin = path.join(process.env.JAVA_HOME, "bin", "java.exe");
123
- if (fs.existsSync(homeBin)) javaBin = homeBin;
124
- }
125
-
126
- const javaVer = Bun.spawnSync([javaBin, "-version"]);
138
+ const javaVer = Bun.spawnSync([getJavaPath(), "-version"]);
127
139
  const output = (javaVer.stderr.toString() + javaVer.stdout.toString()).toLowerCase();
128
140
  const hasDcevm = ["dcevm", "jetbrains", "trava", "jbr"].some(v => output.includes(v));
129
141
 
@@ -248,10 +260,11 @@ export class DeployCommand implements Command {
248
260
  if (!fs.existsSync(targetDir)) fs.mkdirSync(targetDir, { recursive: true });
249
261
 
250
262
  fs.copyFileSync(filename, targetPath);
251
- if (!config.project.quiet) Logger.success(`Synced ${path.basename(filename)} directly to Tomcat!`);
263
+ Logger.success(`${path.basename(filename)} updated Tomcat`);
252
264
 
253
265
  const appUrl = `http://localhost:${config.tomcat.port}/${appFolder}`;
254
266
  await BrowserService.reload(appUrl);
267
+ Logger.ready(`Browser reloaded`);
255
268
  } catch (e) {
256
269
  Logger.error(`Failed to sync resource: ${filename}`);
257
270
  }
@@ -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
  }