@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.
package/README.md CHANGED
@@ -1,25 +1,21 @@
1
- # XAVVA 🚀 (Windows Only) `v1.6.5`
1
+ # XAVVA 🚀 (Windows Only) `v1.7.0`
2
2
 
3
3
  Xavva é uma CLI de alto desempenho construída com **Bun** para automatizar o ciclo de desenvolvimento de aplicações Java (Maven/Gradle) rodando no Apache Tomcat. Ela foi desenhada especificamente para desenvolvedores que buscam a velocidade de ambientes modernos (como Node.js/Vite) dentro do ecossistema Java Enterprise.
4
4
 
5
- > [!IMPORTANT]
6
- > **Compatibilidade:** Atualmente, o Xavva é exclusivo para **Windows**, utilizando integrações nativas com PowerShell e CMD para automação de browser e gerenciamento de processos.
7
-
8
5
  ## 🛠️ Funcionalidades de Elite
9
6
 
10
7
  - **⚡ Ultra-Fast Hot Swap**: Compilação incremental e injeção direta de arquivos `.class` e recursos (JSP, HTML, CSS, JS) no Tomcat em execução sem necessidade de restart.
8
+ - **📦 Multi-Module Support**: Detecção recursiva de diretórios de classes em projetos complexos, garantindo que o Hot-Reload funcione entre diferentes módulos.
11
9
  - **🛠️ Modo Dev Inteligente**: O comando `xavva dev` ativa hot-reload, logs limpos, debugger (JPDA) e monitoramento de memória em um único fluxo.
12
10
  - **🌐 Live Reload Automático**: Atualiza automaticamente as abas do Chrome ou Edge após o deploy ou sincronização de arquivos, mantendo o foco no código.
13
- - **🔍 API Documentation (Swagger-like)**: O comando `xavva docs` mapeia estaticamente sua API, exibindo endpoints, métodos HTTP e parâmetros (Query, Path, Body) diretamente no terminal.
14
- - **📊 Real-time Log Filtering**: Filtra ruídos excessivos do Tomcat/Jersey/SLF4J, destacando erros Java com dicas de solução e tempo de startup.
15
- - **📈 JVM & Memory Monitor**: Exibe o consumo de RAM (Working Set) do processo do Tomcat em tempo real.
16
- - **🩺 Doctor Mode**: Diagnostica o ambiente (Java, Tomcat, Maven, Gradle) e corrige automaticamente problemas de **Encoding (UTF-8 BOM)** que podem causar falhas silenciosas no Java.
17
- - **🛡️ JAR Audit**: O comando `xavva audit` analisa todas as dependências (`.jar`) da sua aplicação e verifica vulnerabilidades conhecidas (CVEs) usando o banco de dados **OSV.dev**.
11
+ - **🔍 API Documentation (Swagger-like)**: O comando `xavva docs` mapeia estaticamente sua API, exibindo endpoints, métodos HTTP e parâmetros diretamente no terminal.
12
+ - **📊 Real-time Log Filtering**: Filtra ruídos excessivos do Tomcat/Jersey/SLF4J, destacando erros Java com dicas de solução.
13
+ - **📈 JVM & Memory Monitor**: Exibe o consumo de RAM do processo do Tomcat em tempo real.
14
+ - **🩺 Doctor Mode**: Diagnostica o ambiente e corrige automaticamente problemas de **Encoding (UTF-8 BOM)**.
15
+ - **🛡️ JAR Audit**: Analisa todas as dependências (`.jar`) da sua aplicação em busca de vulnerabilidades (CVEs).
18
16
 
19
17
  ## 🚀 Instalação e Uso
20
18
 
21
- Você pode instalar o Xavva globalmente usando o NPM (requer [Bun](https://bun.sh/) instalado no sistema):
22
-
23
19
  ```bash
24
20
  # Instalação global
25
21
  npm install -g @archznn/xavva
@@ -30,46 +26,16 @@ npx @archznn/xavva dev
30
26
 
31
27
  ## ⚙️ Zero Config & Auto-Detection
32
28
 
33
- O Xavva foi evoluído para um modelo **Zero Config**. Você não precisa mais de arquivos de configuração para começar.
34
-
35
- - **Auto-Detecção:** O Xavva identifica automaticamente se seu projeto usa **Maven** (`pom.xml`) ou **Gradle** (`build.gradle`) ao ser executado na raiz.
36
- - **Ambiente Inteligente:** Ele utiliza as variáveis de ambiente `TOMCAT_HOME` ou `CATALINA_HOME` para localizar o servidor.
37
- - **Prioridade CLI:** Qualquer parâmetro passado via linha de comando (como `--path` ou `--port`) tem precedência total sobre o ambiente.
29
+ O Xavva identifica automaticamente se seu projeto usa **Maven** (`pom.xml`) ou **Gradle** (`build.gradle`) e localiza o Tomcat através das variáveis `TOMCAT_HOME` ou `CATALINA_HOME`.
38
30
 
39
31
  ### Comandos Principais
40
32
 
41
33
  ```bash
42
- # Inicia o modo de desenvolvimento completo (Auto-detecta Maven/Gradle)
43
- xavva dev
44
-
45
- # Define o Tomcat e o Profile manualmente via CLI
46
- xavva dev -p C:\tomcat-9 -P production
47
-
48
- # Exibe a documentação da API
49
- xavva docs
50
-
51
- # Audita vulnerabilidades nas dependências JAR do app
52
- xavva audit
53
-
54
- # Diagnostica o ambiente e limpa arquivos com BOM (UTF-8 signature)
55
- xavva doctor --fix
34
+ xavva dev # Modo desenvolvimento completo com Hot-Reload
35
+ xavva docs # Documentação estática de endpoints
36
+ xavva audit # Auditoria de segurança de dependências
37
+ xavva doctor --fix # Diagnóstico e reparo de ambiente
56
38
  ```
57
39
 
58
- ### Opções Úteis
59
-
60
- - `-p, --path <path>`: Caminho customizado do Tomcat (Sobrescreve TOMCAT_HOME).
61
- - `-P, --profile <nome>`: Define o profile do Maven/Gradle (ex: dev, prod).
62
- - `-t, --tool <maven|gradle>`: Força o uso de uma ferramenta específica.
63
- - `-n, --name <nome>`: Define o nome do contexto da aplicação.
64
- - `-w, --watch`: Ativa o monitoramento de arquivos para hot-reload.
65
- - `-d, --debug`: Habilita o Java Debugger na porta 5005.
66
-
67
- ## 📦 Stack Tecnológica
68
-
69
- - **Runtime:** [Bun](https://bun.sh/) (Engine de alta performance)
70
- - **Linguagem:** [TypeScript](https://www.typescriptlang.org/)
71
- - **Automação:** PowerShell & CMD (Integração nativa Windows)
72
- - **CI/CD:** GitHub Actions para geração de binários multi-plataforma (via Bun Compile)
73
-
74
40
  ---
75
41
  *Desenvolvido para transformar a experiência de desenvolvimento Java Legacy em algo ágil e produtivo.*
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@archznn/xavva",
3
- "version": "1.6.5",
3
+ "version": "1.7.0",
4
4
  "description": "Ultra-fast CLI tool for Java/Tomcat development on Windows with Hot-Reload and Zero Config.",
5
5
  "module": "src/index.ts",
6
6
  "type": "module",
@@ -11,12 +11,10 @@ export class AuditCommand implements Command {
11
11
 
12
12
  let appName = config.project.appName;
13
13
 
14
- // 1. Tentar inferir do diretório atual se não foi passado via config
15
14
  if (!appName) {
16
15
  appName = this.inferFromArtifacts();
17
16
  }
18
17
 
19
- // 2. Se ainda não tem nome, tenta inferir do Tomcat
20
18
  if (!appName) {
21
19
  const webappsPath = path.join(config.tomcat.path, "webapps");
22
20
  if (fs.existsSync(webappsPath)) {
@@ -68,14 +66,12 @@ export class AuditCommand implements Command {
68
66
  }
69
67
 
70
68
  private inferFromArtifacts(): string | undefined {
71
- // Busca .war no target (Maven) ou build/libs (Gradle)
72
69
  const paths = ["target", "build/libs"];
73
70
  for (const p of paths) {
74
71
  const fullPath = path.join(process.cwd(), p);
75
72
  if (fs.existsSync(fullPath)) {
76
73
  const wars = fs.readdirSync(fullPath).filter(f => f.endsWith(".war"));
77
74
  if (wars.length > 0) {
78
- // Retorna o nome do .war mais recente sem a extensão
79
75
  const latest = wars.map(name => ({
80
76
  name,
81
77
  time: fs.statSync(path.join(fullPath, name)).mtimeMs
@@ -32,16 +32,27 @@ export class DeployCommand implements Command {
32
32
  const builder = this.builder || new BuildService(config.project, config.tomcat);
33
33
 
34
34
  if (!incremental) {
35
- Logger.section("Deploy Configuration");
36
- if (config.project.quiet) {
37
- Logger.info("App", `${config.project.appName} (${config.project.buildTool.toUpperCase()}${config.project.profile ? ` - ${config.project.profile}` : ""})`);
38
- Logger.info("Status", `Watch: ${isWatching ? "ON" : "OFF"} | Debug: ${config.project.debug ? "ON" : "OFF"}`);
39
- } else {
40
- Logger.info("Tool", config.project.buildTool.toUpperCase());
41
- Logger.info("App Name", config.project.appName);
42
- if (config.project.profile) Logger.info("Profile", config.project.profile);
43
- Logger.info("Watch Mode", isWatching ? "Active" : "Inactive");
44
- Logger.info("Debug Mode", config.project.debug ? "Active" : "Inactive");
35
+ Logger.config("Runtime", config.project.buildTool.toUpperCase());
36
+ Logger.config("Watch Mode", isWatching ? "ON" : "OFF");
37
+ Logger.config("Debug", config.project.debug ? "ON (Port 5005)" : "OFF");
38
+
39
+ let javaBin = "java";
40
+ if (process.env.JAVA_HOME) {
41
+ const homeBin = path.join(process.env.JAVA_HOME, "bin", "java.exe");
42
+ if (fs.existsSync(homeBin)) javaBin = homeBin;
43
+ }
44
+
45
+ const javaVer = Bun.spawnSync([javaBin, "-version"]);
46
+ const output = (javaVer.stderr.toString() + javaVer.stdout.toString()).toLowerCase();
47
+ const hasDcevm = output.includes("dcevm") ||
48
+ output.includes("jetbrains") ||
49
+ output.includes("trava") ||
50
+ output.includes("jbr");
51
+
52
+ if (!hasDcevm && isWatching) {
53
+ Logger.config("Hot Reload", "Standard (No structural changes)");
54
+ } else if (hasDcevm) {
55
+ Logger.config("Hot Reload", "Advanced (DCEVM Active)");
45
56
  }
46
57
 
47
58
  const srcPath = path.join(process.cwd(), "src");
@@ -49,12 +60,11 @@ export class DeployCommand implements Command {
49
60
  const contextPath = (config.project.appName || "").replace(".war", "");
50
61
  const endpoints = EndpointService.scan(srcPath, contextPath);
51
62
  if (endpoints.length > 0) {
52
- Logger.info("Endpoints", endpoints.length);
63
+ Logger.config("Endpoints", endpoints.length);
53
64
  }
54
65
  }
55
66
  } else {
56
- console.log("");
57
- Logger.warn("Re-deploying detected changes...");
67
+ Logger.watcher("Change detected", "change");
58
68
  }
59
69
 
60
70
  try {
@@ -65,45 +75,64 @@ export class DeployCommand implements Command {
65
75
  }
66
76
 
67
77
  if (!config.project.skipBuild) {
78
+ if (incremental) Logger.watcher("Incremental compilation", "start");
68
79
  await builder.runBuild(incremental);
80
+ if (!incremental) Logger.build("Full project build");
69
81
  }
70
82
 
71
83
  if (incremental) {
72
- const appFolder = await builder.syncClasses();
73
- const actualContextPath = contextPath || appFolder || "";
74
- if (actualContextPath) {
75
- const actualAppUrl = `http://localhost:${config.tomcat.port}/${actualContextPath}`;
76
- await this.reloadBrowser(actualAppUrl);
77
- }
84
+ const actualAppFolder = await builder.syncClasses(true);
85
+ const actualContextPath = contextPath || actualAppFolder || "";
86
+ const actualAppUrl = `http://localhost:${config.tomcat.port}/${actualContextPath}`;
87
+ await this.reloadBrowser(actualAppUrl);
88
+ Logger.watcher("Redeploy completed", "success");
78
89
  return;
79
90
  }
80
91
 
81
- Logger.step("Cleaning webapps and cache");
82
- tomcat.clearWebapps();
92
+ Logger.build("Webapps cleaned");
93
+ const artifactInfo = await builder.deployToWebapps();
94
+ Logger.build("Artifacts generated");
95
+
96
+ const finalContextPath = contextPath || artifactInfo.finalName.replace(".war", "");
97
+ const appWebappPath = path.join(config.tomcat.path, "webapps", finalContextPath);
98
+
99
+ if (!fs.existsSync(appWebappPath)) fs.mkdirSync(appWebappPath, { recursive: true });
100
+
101
+ try {
102
+ Bun.spawnSync(["jar", "xf", artifactInfo.path], { cwd: appWebappPath });
103
+ Logger.build("Artifacts deployed");
104
+ } catch (e: any) {
105
+ const extractCmd = `powershell -command "Expand-Archive -Path '${artifactInfo.path}' -DestinationPath '${appWebappPath}' -Force"`;
106
+ Bun.spawnSync(["powershell", "-command", extractCmd]);
107
+ Logger.build("Artifacts deployed (legacy mode)");
108
+ }
83
109
 
84
- Logger.step("Moving artifacts to webapps");
85
- const artifact = await builder.deployToWebapps();
110
+ const webInfClassesDir = path.join(appWebappPath, "WEB-INF", "classes");
111
+ if (!fs.existsSync(webInfClassesDir)) fs.mkdirSync(webInfClassesDir, { recursive: true });
86
112
 
87
- const finalContextPath = contextPath || artifact.replace(".war", "");
113
+ const xavvaProps = path.join(process.cwd(), ".xavva", "hotswap-agent.properties");
114
+ if (fs.existsSync(xavvaProps)) {
115
+ fs.copyFileSync(xavvaProps, path.join(webInfClassesDir, "hotswap-agent.properties"));
116
+ }
117
+
88
118
  const finalAppUrl = `http://localhost:${config.tomcat.port}/${finalContextPath}`;
89
119
 
90
120
  tomcat.onReady = async () => {
91
- Logger.step(`Checking health at ${finalAppUrl}`);
92
-
93
121
  try {
94
122
  await new Promise(r => setTimeout(r, 1500));
95
-
96
123
  const response = await fetch(finalAppUrl);
97
124
  if (response.status < 500) {
98
125
  const memory = await tomcat.getMemoryUsage();
99
- Logger.success(`App is UP! (Status: ${response.status} | RAM: ${memory})`);
126
+ Logger.health(finalAppUrl, "success");
127
+ Logger.health(`Status ${response.status}`, "success");
128
+ Logger.health(`Memory ${memory}`, "success");
100
129
 
101
130
  if (!config.project.quiet) {
102
131
  const endpoints = EndpointService.scan(path.join(process.cwd(), "src"), finalContextPath);
103
132
  if (endpoints.length > 0) {
104
- console.log(`\n ${"\x1b[36m"}◈ ENDPOINT MAP:${"\x1b[0m"}`);
105
- endpoints.forEach(e => console.log(` ${"\x1b[90m"}➜${"\x1b[0m"} http://localhost:${config.tomcat.port}${e.fullPath}`));
106
- console.log("");
133
+ Logger.newline();
134
+ Logger.log(`${Logger.C.cyan} ENDPOINT MAP:${Logger.C.reset}`);
135
+ endpoints.forEach(e => Logger.log(`${Logger.C.dim}➜ ${Logger.C.reset}http://localhost:${config.tomcat.port}${e.fullPath}`));
107
136
  }
108
137
  }
109
138
 
@@ -118,14 +147,14 @@ export class DeployCommand implements Command {
118
147
  }
119
148
  }
120
149
  } else {
121
- Logger.warn(`App is starting, but returned status ${response.status}. Check your logs.`);
150
+ Logger.health(`App returned status ${response.status}`, "warn");
122
151
  }
123
152
  } catch (e) {
124
- Logger.error(`Health check failed: Could not connect to ${finalAppUrl}`);
153
+ Logger.health(`Could not connect to ${finalAppUrl}`, "error");
125
154
  }
126
155
  };
127
156
 
128
- tomcat.start(config.project.cleanLogs, config.project.debug, config.project.skipScan, config.project.quiet);
157
+ tomcat.start(config, isWatching);
129
158
  } catch (error: any) {
130
159
  Logger.error(error.message);
131
160
  throw error;
@@ -133,26 +162,40 @@ export class DeployCommand implements Command {
133
162
  }
134
163
 
135
164
  async syncResource(config: AppConfig, filename: string): Promise<void> {
136
- const appName = config.project.appName || "";
137
- const explodedPath = path.join(config.tomcat.path, "webapps", appName);
165
+ const contextPath = (config.project.appName || "").replace(".war", "");
166
+
167
+ const webappsPath = path.join(config.tomcat.path, "webapps");
168
+ let appFolder = contextPath;
138
169
 
139
- if (!fs.existsSync(explodedPath)) {
170
+ if (!appFolder && fs.existsSync(webappsPath)) {
171
+ const folders = fs.readdirSync(webappsPath, { withFileTypes: true })
172
+ .filter(dirent => dirent.isDirectory() && !["ROOT", "manager", "host-manager", "docs"].includes(dirent.name));
173
+ if (folders.length === 1) appFolder = folders[0].name;
174
+ }
175
+
176
+ const explodedPath = path.join(webappsPath, appFolder);
177
+
178
+ if (!appFolder || !fs.existsSync(explodedPath)) {
140
179
  return;
141
180
  }
142
181
 
143
182
  const parts = filename.split(/[/\\]/);
144
183
  const webappIndex = parts.indexOf("webapp");
184
+ const webContentIndex = parts.indexOf("WebContent");
185
+ const rootIndex = webappIndex !== -1 ? webappIndex : webContentIndex;
145
186
 
146
- if (webappIndex !== -1) {
147
- const relPath = parts.slice(webappIndex + 1).join(path.sep);
187
+ if (rootIndex !== -1) {
188
+ const relPath = parts.slice(rootIndex + 1).join(path.sep);
148
189
  const targetPath = path.join(explodedPath, relPath);
149
190
 
150
191
  try {
192
+ const targetDir = path.dirname(targetPath);
193
+ if (!fs.existsSync(targetDir)) fs.mkdirSync(targetDir, { recursive: true });
194
+
151
195
  fs.copyFileSync(filename, targetPath);
152
196
  if (!config.project.quiet) Logger.success(`Synced ${path.basename(filename)} directly to Tomcat!`);
153
197
 
154
- const contextPath = config.project.appName || "";
155
- const appUrl = `http://localhost:${config.tomcat.port}/${contextPath}`;
198
+ const appUrl = `http://localhost:${config.tomcat.port}/${appFolder}`;
156
199
  await this.reloadBrowser(appUrl);
157
200
  } catch (e) {
158
201
  Logger.error(`Failed to sync resource: ${filename}`);
@@ -49,12 +49,12 @@ export class DocsCommand implements Command {
49
49
 
50
50
  private renderEndpoint(ep: ApiEndpoint, port: number) {
51
51
  const methodColors: Record<string, string> = {
52
- GET: "\x1b[32m", // Green
53
- POST: "\x1b[33m", // Yellow
54
- PUT: "\x1b[34m", // Blue
55
- DELETE: "\x1b[31m", // Red
56
- PATCH: "\x1b[35m", // Magenta
57
- ALL: "\x1b[37m" // White
52
+ GET: "\x1b[32m",
53
+ POST: "\x1b[33m",
54
+ PUT: "\x1b[34m",
55
+ DELETE: "\x1b[31m",
56
+ PATCH: "\x1b[35m",
57
+ ALL: "\x1b[37m"
58
58
  };
59
59
 
60
60
  const color = methodColors[ep.method] || "\x1b[37m";
@@ -75,11 +75,11 @@ export class DocsCommand implements Command {
75
75
 
76
76
  private renderParameter(param: ApiParam) {
77
77
  const sourceColors: Record<string, string> = {
78
- PATH: "\x1b[35m", // Magenta
79
- QUERY: "\x1b[36m", // Cyan
80
- BODY: "\x1b[33m", // Yellow
81
- HEADER: "\x1b[32m", // Green
82
- FORM: "\x1b[34m" // Blue
78
+ PATH: "\x1b[35m",
79
+ QUERY: "\x1b[36m",
80
+ BODY: "\x1b[33m",
81
+ HEADER: "\x1b[32m",
82
+ FORM: "\x1b[34m"
83
83
  };
84
84
 
85
85
  const color = sourceColors[param.source] || "\x1b[37m";