@archznn/xavva 2.0.3 → 2.2.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 +1 -78
- package/package.json +1 -1
- package/src/commands/AuditCommand.ts +3 -2
- package/src/commands/BuildCommand.ts +5 -3
- package/src/commands/CommandRegistry.ts +9 -5
- package/src/commands/DeployCommand.ts +36 -30
- package/src/commands/DepsCommand.ts +123 -0
- package/src/commands/DoctorCommand.ts +5 -4
- package/src/commands/HelpCommand.ts +94 -40
- package/src/commands/RunCommand.ts +13 -12
- package/src/commands/StartCommand.ts +5 -3
- package/src/index.ts +15 -5
- package/src/services/AuditService.ts +30 -4
- package/src/services/BuildCacheService.ts +2 -1
- package/src/services/BuildService.ts +8 -1
- package/src/services/DashboardService.ts +181 -118
- package/src/services/DependencyAnalyzerService.ts +538 -0
- package/src/services/ProjectService.ts +3 -3
- package/src/services/TomcatService.ts +11 -11
- package/src/services/WatcherService.ts +3 -2
- package/src/types/config.ts +4 -0
- package/src/utils/config.ts +7 -2
- package/src/utils/constants.ts +54 -0
- package/src/utils/processManager.ts +145 -0
- package/src/utils/ui.ts +237 -311
package/README.md
CHANGED
|
@@ -1,78 +1 @@
|
|
|
1
|
-
# XAVVA
|
|
2
|
-
|
|
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
|
-
|
|
5
|
-
---
|
|
6
|
-
|
|
7
|
-
## 🛠️ Por que Xavva?
|
|
8
|
-
|
|
9
|
-
Desenvolver para Java/Tomcat tradicionalmente envolve ciclos lentos de `clean install`, `war deploy` e restarts de servidor. O Xavva quebra esse paradigma ao introduzir um fluxo de **Hot-Reload incremental**, onde apenas o que mudou é enviado ao servidor.
|
|
10
|
-
|
|
11
|
-
### ⚡ Funcionalidades de Elite
|
|
12
|
-
|
|
13
|
-
- **Interactive Dashboard (TUI)**: Um painel em tempo real (`--tui`) com métricas de sistema, status do servidor e atalhos rápidos (Restart, Clear, Quit).
|
|
14
|
-
- **Smart Log Analyzer**: Logs inteligentes que escondem ruídos do framework (Stack Folding) e destacam a causa raiz de erros Java.
|
|
15
|
-
- **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 restart.
|
|
16
|
-
- **Gradle & Maven Native**: Suporte robusto para ambos os ecossistemas, incluindo extração automática de classpath para execução de classes standalone (`run`/`debug`).
|
|
17
|
-
- **Segurança & Robustez**: Auditoria de dependências (`.jar`) e execução protegida contra *Command Injection* no PowerShell.
|
|
18
|
-
- **Pathing JAR (Windows)**: Contorna limites de caracteres do Windows em classpaths gigantes.
|
|
19
|
-
- **Auto-Healing**: Diagnóstico e reparo automático de problemas comuns de ambiente.
|
|
20
|
-
|
|
21
|
-
---
|
|
22
|
-
|
|
23
|
-
## 🚀 Começo Rápido
|
|
24
|
-
|
|
25
|
-
### Instalação
|
|
26
|
-
```powershell
|
|
27
|
-
# Instalação global via NPM
|
|
28
|
-
npm install -g @archznn/xavva
|
|
29
|
-
|
|
30
|
-
# Iniciar em modo Dashboard (TUI)
|
|
31
|
-
xavva dev --tui
|
|
32
|
-
```
|
|
33
|
-
|
|
34
|
-
---
|
|
35
|
-
|
|
36
|
-
## 📖 Referência de Comandos
|
|
37
|
-
|
|
38
|
-
O Xavva 2.0 utiliza uma arquitetura modular de comandos e serviços.
|
|
39
|
-
|
|
40
|
-
### 1. Modo Desenvolvimento (`xavva dev`)
|
|
41
|
-
O comando principal para o dia a dia. Ativa o monitoramento de arquivos e o Hot-Reload.
|
|
42
|
-
- **Flags úteis**:
|
|
43
|
-
- `--tui`: Ativa o Dashboard interativo no terminal.
|
|
44
|
-
- `--no-build`: Pula o build inicial.
|
|
45
|
-
- `--watch`: Ativa o modo de observação de arquivos (padrão em `dev`).
|
|
46
|
-
- `--port 8081`: Define uma porta específica para o Tomcat.
|
|
47
|
-
|
|
48
|
-
### 2. Configuração de Projeto (`xavva.json`)
|
|
49
|
-
Crie um arquivo `xavva.json` na raiz do seu projeto para salvar suas configurações:
|
|
50
|
-
```json
|
|
51
|
-
{
|
|
52
|
-
"project": {
|
|
53
|
-
"appName": "meu-app",
|
|
54
|
-
"buildTool": "maven",
|
|
55
|
-
"tui": true
|
|
56
|
-
},
|
|
57
|
-
"tomcat": {
|
|
58
|
-
"port": 8080
|
|
59
|
-
}
|
|
60
|
-
}
|
|
61
|
-
```
|
|
62
|
-
|
|
63
|
-
### 3. Execução de Classes (`xavva run` / `xavva debug`)
|
|
64
|
-
Executa classes Java standalone (`public static void main`) com resolução automática de dependências.
|
|
65
|
-
|
|
66
|
-
---
|
|
67
|
-
|
|
68
|
-
## 🏗️ Arquitetura Xavva 2.0
|
|
69
|
-
|
|
70
|
-
O Xavva foi refatorado para uma arquitetura de **Injeção de Dependências** e **Serviços Centralizados**:
|
|
71
|
-
|
|
72
|
-
- **DashboardService**: Gerenciamento de interface TUI e interatividade.
|
|
73
|
-
- **LogAnalyzer**: Processamento inteligente de logs e stack traces.
|
|
74
|
-
- **ProjectService**: Inteligência centralizada para descoberta de diretórios e artefatos.
|
|
75
|
-
- **CommandRegistry**: Despacho modular de comandos.
|
|
76
|
-
|
|
77
|
-
---
|
|
78
|
-
*Desenvolvido para transformar o legado em produtivo. 🚀*
|
|
1
|
+
# XAVVA CLI 🚀> Ultra-fast development toolkit for Java Enterprise (Tomcat) on Windows[](https://github.com/leorsousa05/Xavva)[](LICENSE)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.---## ✨ Features- ⚡ **Hot Reload** — Incremental compilation and class injection without server restart- 📊 **Interactive Dashboard** — Real-time TUI with system metrics and shortcuts- 🧠 **Smart Log Analyzer** — Stack trace folding and root cause highlighting- 🔒 **Security Audit** — Automated vulnerability scanning via OSV.dev- 📦 **Dependency Analysis** — Detect conflicts and outdated dependencies- 🎯 **Maven & Gradle** — Native support for both build tools- 🔧 **Auto-Healing** — Automatic diagnosis and repair of common issues---## 📦 Installation```powershell# Via NPMnpm install -g @archznn/xavva# Or run directly with Bunbunx @archznn/xavva dev```---## 🚀 Quick Start```bash# Start development mode with dashboardxavva dev --tui# Deploy to Tomcatxavva deploy# Analyze dependencies for issuesxavva deps# Check for security vulnerabilitiesxavva audit```---## 📖 Commands### Core Development| Command | Description ||---------|-------------|| `xavva dev` | Full development mode (build + deploy + watch + debug) || `xavva deploy` | Build and deploy application to Tomcat || `xavva build` | Compile project only || `xavva start` | Start Tomcat server only |### Code Execution| Command | Description ||---------|-------------|| `xavva run <class>` | Execute a Java class with automatic classpath || `xavva debug <class>` | Debug a Java class (port 5005) |### Analysis & Monitoring| Command | Description ||---------|-------------|| `xavva logs` | Stream and analyze Tomcat logs in real-time || `xavva deps` | **Analyze dependencies** — detect conflicts, find updates || `xavva audit` | Security audit of JAR files via OSV.dev || `xavva doctor` | Diagnose environment issues (JAVA_HOME, DCEVM) || `xavva profiles` | List available Maven/Gradle profiles || `xavva docs` | Generate endpoint documentation |---## 🔍 Dependency AnalysisThe `xavva deps` command provides comprehensive dependency analysis:```bash# Basic analysisxavva deps# With verbose output for debuggingxavva deps --verbose# Show fix suggestions for conflictsxavva deps --fix# Export report as JSONxavva deps --output report.json# Fail on critical conflicts (useful in CI/CD)xavva deps --strict```### What it detects:- ⚠️ **Version Conflicts** — Same dependency with different versions- ⬆️ **Available Updates** — Newer versions in Maven Central- 🔴 **Major Updates** — Breaking changes that need attention- 📊 **Statistics** — Direct vs transitive dependencies### Sample output:```══════════════════════════════════════════════════════════📊 DEPENDENCY ANALYSIS══════════════════════════════════════════════════════════Statistics: Total: 183 dependencies Direct: 45 | Transitive: 138⚠️ VERSION CONFLICTS (2) ✖ com.fasterxml.jackson.core:jackson-databind Versions: 2.13.0, 2.12.6⬆️ UPDATES AVAILABLE (5) ↑ org.postgresql:postgresql 42.2.5 → 42.7.1⚠️ MAJOR UPDATES (1) ! org.springframework.boot:spring-boot-starter 2.5.0 → 3.1.0```---## ⚙️ ConfigurationCreate `xavva.json` in your project root:```json{ "project": { "appName": "my-application", "buildTool": "maven", "profile": "dev", "tui": false }, "tomcat": { "path": "C:/apache-tomcat", "port": 8080 }}```### CLI Options| Option | Description ||--------|-------------|| `-p, --path <path>` | Tomcat installation path || `-t, --tool <tool>` | Build tool: `maven` or `gradle` || `-n, --name <name>` | Application name (WAR context) || `--port <port>` | Tomcat port (default: 8080) || `-P, --profile <prof>` | Maven/Gradle profile || `-e, --encoding <enc>` | Source encoding (utf8, cp1252) || `-w, --watch` | Enable file watching || `--tui` | Interactive dashboard mode || `-d, --debug` | Enable JPDA debugger || `-c, --clean` | Clean logs before start || `-s, --no-build` | Skip initial build || `-V, --verbose` | Detailed output |---## 🏗️ ArchitectureXavva uses a modular service-oriented architecture:- **DashboardService** — TUI management and interactivity- **LogAnalyzer** — Intelligent log processing- **DependencyAnalyzerService** — Dependency conflict detection- **ProjectService** — Project structure discovery- **BuildService** — Maven/Gradle integration- **TomcatService** — Server lifecycle management---## 🤝 ContributingContributions are welcome! Please feel free to submit a Pull Request.---## 📄 LicenseMIT License — see [LICENSE](LICENSE) for details.---<p align="center"> <sub>Built with ❤️ for Java developers who miss modern tooling</sub></p>
|
package/package.json
CHANGED
|
@@ -60,8 +60,9 @@ export class AuditCommand implements Command {
|
|
|
60
60
|
Logger.info("Total de Falhas", totalVulns);
|
|
61
61
|
Logger.info("Relatório gerado via", "OSV.dev (Open Source Vulnerability Database)");
|
|
62
62
|
|
|
63
|
-
} catch (e
|
|
64
|
-
|
|
63
|
+
} catch (e) {
|
|
64
|
+
const message = e instanceof Error ? e.message : String(e);
|
|
65
|
+
Logger.error(message);
|
|
65
66
|
}
|
|
66
67
|
}
|
|
67
68
|
|
|
@@ -2,6 +2,7 @@ import type { Command } from "./Command";
|
|
|
2
2
|
import type { AppConfig } from "../types/config";
|
|
3
3
|
import { BuildService } from "../services/BuildService";
|
|
4
4
|
import { Logger } from "../utils/ui";
|
|
5
|
+
import { ProcessManager } from "../utils/processManager";
|
|
5
6
|
|
|
6
7
|
export class BuildCommand implements Command {
|
|
7
8
|
constructor(private buildService: BuildService) {}
|
|
@@ -14,9 +15,10 @@ export class BuildCommand implements Command {
|
|
|
14
15
|
try {
|
|
15
16
|
await this.buildService.runBuild();
|
|
16
17
|
Logger.success("Build completed successfully!");
|
|
17
|
-
} catch (error
|
|
18
|
-
|
|
19
|
-
|
|
18
|
+
} catch (error) {
|
|
19
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
20
|
+
Logger.error(message);
|
|
21
|
+
await ProcessManager.getInstance().shutdown(1);
|
|
20
22
|
}
|
|
21
23
|
}
|
|
22
24
|
}
|
|
@@ -2,6 +2,7 @@ import type { AppConfig, CLIArguments } from "../types/config";
|
|
|
2
2
|
import type { Command } from "./Command";
|
|
3
3
|
import { Logger } from "../utils/ui";
|
|
4
4
|
import { HelpCommand } from "./HelpCommand";
|
|
5
|
+
import { ProcessManager } from "../utils/processManager";
|
|
5
6
|
|
|
6
7
|
export class CommandRegistry {
|
|
7
8
|
private commands = new Map<string, Command>();
|
|
@@ -18,19 +19,22 @@ export class CommandRegistry {
|
|
|
18
19
|
return this.commands.get(name);
|
|
19
20
|
}
|
|
20
21
|
|
|
21
|
-
async execute(name: string, config: AppConfig, args: CLIArguments) {
|
|
22
|
+
async execute(name: string, config: AppConfig, args: CLIArguments): Promise<void> {
|
|
22
23
|
const command = this.commands.get(name);
|
|
24
|
+
const processManager = ProcessManager.getInstance();
|
|
25
|
+
|
|
23
26
|
if (!command) {
|
|
24
27
|
Logger.error(`Comando desconhecido: ${name}`);
|
|
25
28
|
await new HelpCommand().execute(config);
|
|
26
|
-
|
|
29
|
+
await processManager.shutdown(2);
|
|
27
30
|
}
|
|
28
31
|
|
|
29
32
|
try {
|
|
30
33
|
await command.execute(config, args);
|
|
31
|
-
} catch (error
|
|
32
|
-
|
|
33
|
-
|
|
34
|
+
} catch (error) {
|
|
35
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
36
|
+
Logger.error(`Erro ao executar comando '${name}': ${message}`);
|
|
37
|
+
await processManager.shutdown(1);
|
|
34
38
|
}
|
|
35
39
|
}
|
|
36
40
|
}
|
|
@@ -20,7 +20,7 @@ export class DeployCommand implements Command {
|
|
|
20
20
|
if (!incremental) {
|
|
21
21
|
this.logConfiguration(config, isWatching);
|
|
22
22
|
} else {
|
|
23
|
-
Logger.
|
|
23
|
+
Logger.watch("Change detected");
|
|
24
24
|
}
|
|
25
25
|
|
|
26
26
|
try {
|
|
@@ -31,16 +31,16 @@ export class DeployCommand implements Command {
|
|
|
31
31
|
await tomcat.clearWebapps();
|
|
32
32
|
|
|
33
33
|
if (!config.project.skipBuild) {
|
|
34
|
-
Logger.
|
|
34
|
+
Logger.build("compiling...");
|
|
35
35
|
await builder.runBuild(incremental);
|
|
36
36
|
}
|
|
37
37
|
|
|
38
38
|
if (!config.project.skipBuild) {
|
|
39
|
-
Logger.build("
|
|
39
|
+
Logger.build("completed");
|
|
40
40
|
}
|
|
41
41
|
} else {
|
|
42
42
|
if (!config.project.skipBuild) {
|
|
43
|
-
Logger.
|
|
43
|
+
Logger.build("incremental compile...");
|
|
44
44
|
await builder.runBuild(incremental);
|
|
45
45
|
}
|
|
46
46
|
}
|
|
@@ -50,13 +50,13 @@ export class DeployCommand implements Command {
|
|
|
50
50
|
const actualContextPath = contextPath || actualAppFolder || "";
|
|
51
51
|
const actualAppUrl = `http://localhost:${config.tomcat.port}/${actualContextPath}`;
|
|
52
52
|
await BrowserService.reload(actualAppUrl);
|
|
53
|
-
Logger.
|
|
53
|
+
Logger.success("redeploy completed");
|
|
54
54
|
return;
|
|
55
55
|
}
|
|
56
56
|
|
|
57
|
-
Logger.
|
|
57
|
+
Logger.server("cleaning webapps...");
|
|
58
58
|
const artifactInfo = await builder.deployToWebapps();
|
|
59
|
-
Logger.
|
|
59
|
+
Logger.server("artifacts ready");
|
|
60
60
|
|
|
61
61
|
const finalContextPath = contextPath || artifactInfo.finalName.replace(".war", "");
|
|
62
62
|
const appWebappPath = path.join(config.tomcat.path, "webapps", finalContextPath);
|
|
@@ -65,7 +65,7 @@ export class DeployCommand implements Command {
|
|
|
65
65
|
// Se é um diretório (exploded), sincronizamos o conteúdo total para a pasta do webapps
|
|
66
66
|
if (!fs.existsSync(appWebappPath)) fs.mkdirSync(appWebappPath, { recursive: true });
|
|
67
67
|
await builder.syncExploded(artifactInfo.path, appWebappPath);
|
|
68
|
-
Logger.
|
|
68
|
+
Logger.server("synced exploded directory");
|
|
69
69
|
} else {
|
|
70
70
|
if (!fs.existsSync(appWebappPath)) fs.mkdirSync(appWebappPath, { recursive: true });
|
|
71
71
|
|
|
@@ -75,7 +75,7 @@ export class DeployCommand implements Command {
|
|
|
75
75
|
if (!webappStat || artifactStat.mtimeMs > webappStat.mtimeMs) {
|
|
76
76
|
try {
|
|
77
77
|
Bun.spawnSync(["jar", "xf", artifactInfo.path], { cwd: appWebappPath });
|
|
78
|
-
Logger.
|
|
78
|
+
Logger.server("extracted WAR");
|
|
79
79
|
} catch (e) {
|
|
80
80
|
const extractCmd = `Expand-Archive -Path $env:ARTIFACT_PATH -DestinationPath $env:DEST_PATH -Force`;
|
|
81
81
|
Bun.spawnSync(["powershell", "-command", extractCmd], {
|
|
@@ -85,10 +85,10 @@ export class DeployCommand implements Command {
|
|
|
85
85
|
DEST_PATH: appWebappPath
|
|
86
86
|
}
|
|
87
87
|
});
|
|
88
|
-
Logger.
|
|
88
|
+
Logger.server("extracted WAR (legacy)");
|
|
89
89
|
}
|
|
90
90
|
} else {
|
|
91
|
-
Logger.
|
|
91
|
+
Logger.server("webapp up to date");
|
|
92
92
|
}
|
|
93
93
|
}
|
|
94
94
|
|
|
@@ -102,17 +102,19 @@ export class DeployCommand implements Command {
|
|
|
102
102
|
};
|
|
103
103
|
|
|
104
104
|
tomcat.start(config, isWatching);
|
|
105
|
-
} catch (error
|
|
106
|
-
|
|
105
|
+
} catch (error) {
|
|
106
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
107
|
+
Logger.error(message);
|
|
107
108
|
throw error;
|
|
108
109
|
}
|
|
109
110
|
}
|
|
110
111
|
|
|
111
112
|
private logConfiguration(config: AppConfig, isWatching: boolean) {
|
|
112
|
-
Logger.
|
|
113
|
-
|
|
114
|
-
Logger.config("
|
|
115
|
-
Logger.config("
|
|
113
|
+
Logger.section("Configuration");
|
|
114
|
+
Logger.config("runtime", config.project.buildTool.toLowerCase());
|
|
115
|
+
if (config.project.profile) Logger.config("profile", config.project.profile);
|
|
116
|
+
Logger.config("watch", isWatching);
|
|
117
|
+
Logger.config("debug", config.project.debug ? `port ${config.project.debugPort}` : false);
|
|
116
118
|
|
|
117
119
|
let javaBin = "java";
|
|
118
120
|
if (process.env.JAVA_HOME) {
|
|
@@ -125,9 +127,9 @@ export class DeployCommand implements Command {
|
|
|
125
127
|
const hasDcevm = ["dcevm", "jetbrains", "trava", "jbr"].some(v => output.includes(v));
|
|
126
128
|
|
|
127
129
|
if (!hasDcevm && isWatching) {
|
|
128
|
-
Logger.config("
|
|
130
|
+
Logger.config("hotswap", "standard");
|
|
129
131
|
} else if (hasDcevm) {
|
|
130
|
-
Logger.config("
|
|
132
|
+
Logger.config("hotswap", "dcevm");
|
|
131
133
|
}
|
|
132
134
|
|
|
133
135
|
const srcPath = path.join(process.cwd(), "src");
|
|
@@ -135,9 +137,10 @@ export class DeployCommand implements Command {
|
|
|
135
137
|
const contextPath = (config.project.appName || "").replace(".war", "");
|
|
136
138
|
const endpoints = EndpointService.scan(srcPath, contextPath);
|
|
137
139
|
if (endpoints.length > 0) {
|
|
138
|
-
Logger.config("
|
|
140
|
+
Logger.config("endpoints", endpoints.length);
|
|
139
141
|
}
|
|
140
142
|
}
|
|
143
|
+
Logger.endSection();
|
|
141
144
|
}
|
|
142
145
|
|
|
143
146
|
private injectContextConfiguration(appPath: string) {
|
|
@@ -170,9 +173,12 @@ export class DeployCommand implements Command {
|
|
|
170
173
|
const response = await fetch(url);
|
|
171
174
|
if (response.status < 500) {
|
|
172
175
|
const memory = await tomcat.getMemoryUsage();
|
|
173
|
-
Logger.
|
|
174
|
-
Logger.
|
|
175
|
-
Logger.
|
|
176
|
+
Logger.divider();
|
|
177
|
+
Logger.ready("Server ready");
|
|
178
|
+
Logger.url("Local", url);
|
|
179
|
+
Logger.info("Status", `${response.status}`);
|
|
180
|
+
Logger.info("Memory", memory);
|
|
181
|
+
Logger.done();
|
|
176
182
|
|
|
177
183
|
if (!config.project.quiet) {
|
|
178
184
|
this.showEndpointMap(config.tomcat.port, context);
|
|
@@ -184,32 +190,32 @@ export class DeployCommand implements Command {
|
|
|
184
190
|
BrowserService.open(url);
|
|
185
191
|
}
|
|
186
192
|
} else {
|
|
187
|
-
Logger.
|
|
193
|
+
Logger.warn(`App returned status ${response.status}`);
|
|
188
194
|
}
|
|
189
195
|
} catch (e) {
|
|
190
|
-
Logger.
|
|
196
|
+
Logger.error(`Could not connect to ${url}`);
|
|
191
197
|
}
|
|
192
198
|
}
|
|
193
199
|
|
|
194
200
|
private showEndpointMap(port: number, context: string) {
|
|
195
201
|
const endpoints = EndpointService.scan(path.join(process.cwd(), "src"), context);
|
|
196
202
|
if (endpoints.length > 0) {
|
|
197
|
-
Logger.
|
|
198
|
-
Logger.log(`${Logger.C.cyan}◈ ENDPOINT MAP:${Logger.C.reset}`);
|
|
203
|
+
Logger.section("Endpoints");
|
|
199
204
|
|
|
200
205
|
const apis = endpoints.filter(e => e.className !== "JSP");
|
|
201
206
|
const jsps = endpoints.filter(e => e.className === "JSP");
|
|
202
207
|
|
|
203
208
|
if (apis.length > 0) {
|
|
204
209
|
const uniqueApiUrls = [...new Set(apis.map(e => `http://localhost:${port}${e.fullPath}`))];
|
|
205
|
-
uniqueApiUrls.forEach(url => Logger.
|
|
210
|
+
uniqueApiUrls.forEach(url => Logger.info("", url));
|
|
206
211
|
}
|
|
207
212
|
|
|
208
213
|
if (jsps.length > 0) {
|
|
209
|
-
Logger.
|
|
214
|
+
Logger.info("JSPs", "");
|
|
210
215
|
const uniqueJspUrls = [...new Set(jsps.map(e => `http://localhost:${port}${e.fullPath}`))];
|
|
211
|
-
uniqueJspUrls.forEach(url => Logger.
|
|
216
|
+
uniqueJspUrls.forEach(url => Logger.info("", ` ${url}`));
|
|
212
217
|
}
|
|
218
|
+
Logger.endSection();
|
|
213
219
|
}
|
|
214
220
|
}
|
|
215
221
|
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
import type { Command } from "./Command";
|
|
2
|
+
import type { AppConfig, CLIArguments } from "../types/config";
|
|
3
|
+
import type { DependencyAnalysisResult } from "../services/DependencyAnalyzerService";
|
|
4
|
+
import { DependencyAnalyzerService } from "../services/DependencyAnalyzerService";
|
|
5
|
+
import { AuditService } from "../services/AuditService";
|
|
6
|
+
import { Logger } from "../utils/ui";
|
|
7
|
+
import { ProcessManager } from "../utils/processManager";
|
|
8
|
+
import fs from "fs";
|
|
9
|
+
|
|
10
|
+
export class DepsCommand implements Command {
|
|
11
|
+
private config!: AppConfig;
|
|
12
|
+
|
|
13
|
+
async execute(config: AppConfig, args?: CLIArguments): Promise<void> {
|
|
14
|
+
this.config = config;
|
|
15
|
+
const analyzer = new DependencyAnalyzerService(config.project);
|
|
16
|
+
analyzer.setVerbose(!!args?.verbose);
|
|
17
|
+
|
|
18
|
+
Logger.section("Análise de Dependências");
|
|
19
|
+
Logger.info("Ferramenta", config.project.buildTool.toUpperCase());
|
|
20
|
+
Logger.info("Diretório", process.cwd());
|
|
21
|
+
|
|
22
|
+
const spinner = Logger.spinner("Analisando dependências...");
|
|
23
|
+
|
|
24
|
+
try {
|
|
25
|
+
const result = await analyzer.analyze();
|
|
26
|
+
spinner();
|
|
27
|
+
|
|
28
|
+
// Se não encontrou dependências, mostrar ajuda
|
|
29
|
+
if (result.dependencies.length === 0) {
|
|
30
|
+
Logger.warn("Nenhuma dependência encontrada!");
|
|
31
|
+
Logger.info("Possíveis causas:", "");
|
|
32
|
+
Logger.log(" • Projeto não foi compilado ainda (execute: mvn compile)");
|
|
33
|
+
Logger.log(" • Maven não está no PATH");
|
|
34
|
+
Logger.log(" • Arquivo pom.xml/build.gradle não encontrado");
|
|
35
|
+
Logger.log(" • Erro de parsing no arquivo de configuração");
|
|
36
|
+
Logger.newline();
|
|
37
|
+
Logger.log(`${Logger.C.cyan}Dica:${Logger.C.reset} Execute com --verbose para mais detalhes`);
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// Verificar vulnerabilidades se solicitado
|
|
42
|
+
if (args?.["scan"] !== false) {
|
|
43
|
+
Logger.step("Verificando vulnerabilidades");
|
|
44
|
+
// Integração com AuditService para check de vulnerabilidades
|
|
45
|
+
// nas dependências do projeto
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// Exibir relatório
|
|
49
|
+
const report = analyzer.generateReport(result);
|
|
50
|
+
console.log(report);
|
|
51
|
+
|
|
52
|
+
// Ações adicionais baseadas em flags
|
|
53
|
+
if (args?.["fix"]) {
|
|
54
|
+
await this.suggestFixes(result, config.project.buildTool);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// Exportar resultado se solicitado
|
|
58
|
+
if (args?.["output"]) {
|
|
59
|
+
this.exportReport(result, args["output"] as string);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// Sair com erro se houver conflitos críticos
|
|
63
|
+
const hasErrors = result.conflicts.some(c => c.severity === "error");
|
|
64
|
+
if (hasErrors && args?.["strict"]) {
|
|
65
|
+
await ProcessManager.getInstance().shutdown(1);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
} catch (error) {
|
|
69
|
+
spinner(false);
|
|
70
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
71
|
+
Logger.error(`Falha na análise: ${message}`);
|
|
72
|
+
throw error;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
private async suggestFixes(result: DependencyAnalysisResult, buildTool: string): Promise<void> {
|
|
77
|
+
if (result.conflicts.length === 0) {
|
|
78
|
+
Logger.success("Nenhum conflito para resolver!");
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
Logger.newline();
|
|
83
|
+
Logger.section("Sugestões de Correção");
|
|
84
|
+
|
|
85
|
+
for (const conflict of result.conflicts) {
|
|
86
|
+
Logger.log(`\n${Logger.C.cyan}${conflict.groupId}:${conflict.artifactId}${Logger.C.reset}`);
|
|
87
|
+
|
|
88
|
+
if (buildTool === "maven") {
|
|
89
|
+
Logger.log(" Adicione ao pom.xml:");
|
|
90
|
+
Logger.log(` ${Logger.C.dim}<dependencyManagement>${Logger.C.reset}`);
|
|
91
|
+
Logger.log(` ${Logger.C.dim} <dependencies>${Logger.C.reset}`);
|
|
92
|
+
Logger.log(` ${Logger.C.dim} <dependency>${Logger.C.reset}`);
|
|
93
|
+
Logger.log(` ${Logger.C.dim} <groupId>${conflict.groupId}</groupId>${Logger.C.reset}`);
|
|
94
|
+
Logger.log(` ${Logger.C.dim} <artifactId>${conflict.artifactId}</artifactId>${Logger.C.reset}`);
|
|
95
|
+
Logger.log(` ${Logger.C.dim} <version>${conflict.versions[conflict.versions.length - 1]}</version>${Logger.C.reset}`);
|
|
96
|
+
Logger.log(` ${Logger.C.dim} </dependency>${Logger.C.reset}`);
|
|
97
|
+
Logger.log(` ${Logger.C.dim} </dependencies>${Logger.C.reset}`);
|
|
98
|
+
Logger.log(` ${Logger.C.dim}</dependencyManagement>${Logger.C.reset}`);
|
|
99
|
+
} else {
|
|
100
|
+
Logger.log(" Adicione ao build.gradle:");
|
|
101
|
+
Logger.log(` ${Logger.C.dim}implementation("${conflict.groupId}:${conflict.artifactId}:${conflict.versions[conflict.versions.length - 1]}")${Logger.C.reset}`);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
private exportReport(
|
|
107
|
+
result: DependencyAnalysisResult,
|
|
108
|
+
outputPath: string
|
|
109
|
+
): void {
|
|
110
|
+
const pkg = JSON.parse(fs.readFileSync(path.join(__dirname, "../../package.json"), "utf-8"));
|
|
111
|
+
const data = {
|
|
112
|
+
timestamp: new Date().toISOString(),
|
|
113
|
+
tool: "xavva",
|
|
114
|
+
version: pkg.version,
|
|
115
|
+
result
|
|
116
|
+
};
|
|
117
|
+
|
|
118
|
+
fs.writeFileSync(outputPath, JSON.stringify(data, null, 2));
|
|
119
|
+
Logger.success(`Relatório exportado para: ${outputPath}`);
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
import path from "path";
|
|
@@ -1,11 +1,12 @@
|
|
|
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
4
|
import fs from "fs";
|
|
5
5
|
import path from "path";
|
|
6
|
+
import os from "os";
|
|
6
7
|
|
|
7
8
|
export class DoctorCommand implements Command {
|
|
8
|
-
async execute(config: AppConfig, values:
|
|
9
|
+
async execute(config: AppConfig, values: CLIArguments = {}): Promise<void> {
|
|
9
10
|
Logger.section("Xavva Doctor - Ambiente");
|
|
10
11
|
|
|
11
12
|
this.check(
|
|
@@ -252,7 +253,7 @@ export class DoctorCommand implements Command {
|
|
|
252
253
|
|
|
253
254
|
// URL para o JetBrains Runtime 21 SDK Windows x64
|
|
254
255
|
const url = "https://cache-redirector.jetbrains.com/intellij-jbr/jbrsdk-21.0.6-windows-x64-b895.97.tar.gz";
|
|
255
|
-
const installDir = path.join(
|
|
256
|
+
const installDir = path.join(os.homedir(), ".xavva", "jdk-dcevm");
|
|
256
257
|
|
|
257
258
|
// Limpar instalação anterior se existir
|
|
258
259
|
if (fs.existsSync(installDir)) {
|
|
@@ -344,7 +345,7 @@ export class DoctorCommand implements Command {
|
|
|
344
345
|
|
|
345
346
|
Logger.newline();
|
|
346
347
|
Logger.warn("IMPORTANTE: Reinicie seu terminal (ou o VS Code) para as mudanças surtirem efeito.");
|
|
347
|
-
} catch (e
|
|
348
|
+
} catch (e) {
|
|
348
349
|
|
|
349
350
|
Logger.error(`Falha na instalação: ${e.message}`);
|
|
350
351
|
}
|
|
@@ -4,46 +4,100 @@ import pkg from "../../package.json";
|
|
|
4
4
|
|
|
5
5
|
export class HelpCommand implements Command {
|
|
6
6
|
async execute(_config: AppConfig, _args?: CLIArguments): Promise<void> {
|
|
7
|
+
const version = pkg.version;
|
|
8
|
+
|
|
7
9
|
console.log(`
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
10
|
+
${this.c("cyan", "▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓")}
|
|
11
|
+
${this.c("cyan", "▓")} ${this.c("cyan", "▓")}
|
|
12
|
+
${this.c("cyan", "▓")} ${this.c("bold", "XAVVA")} ${this.c("dim", `v${version}`)} ${this.c("gray", "— Java/Tomcat Dev CLI")} ${this.c("cyan", "▓")}
|
|
13
|
+
${this.c("cyan", "▓")} ${this.c("cyan", "▓")}
|
|
14
|
+
${this.c("cyan", "▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓")}
|
|
15
|
+
|
|
16
|
+
${this.c("yellow", "USAGE")}
|
|
17
|
+
xavva <command> [options]
|
|
18
|
+
|
|
19
|
+
${this.c("yellow", "COMMANDS")}
|
|
20
|
+
${this.c("green", "dev")} Start development mode (build + deploy + watch)
|
|
21
|
+
${this.c("green", "deploy")} Build and deploy to Tomcat (default)
|
|
22
|
+
${this.c("green", "build")} Compile project only
|
|
23
|
+
${this.c("green", "start")} Start Tomcat server only
|
|
24
|
+
${this.c("green", "run")} <class> Run a Java class with automatic classpath
|
|
25
|
+
${this.c("green", "debug")} <class> Debug a Java class (port 5005)
|
|
26
|
+
${this.c("green", "logs")} Stream and analyze Tomcat logs
|
|
27
|
+
${this.c("green", "deps")} Analyze dependencies (conflicts, updates)
|
|
28
|
+
${this.c("green", "audit")} Security audit of JAR files
|
|
29
|
+
${this.c("green", "doctor")} Diagnose and fix environment issues
|
|
30
|
+
${this.c("green", "profiles")} List available Maven/Gradle profiles
|
|
31
|
+
${this.c("green", "docs")} Generate endpoint documentation
|
|
32
|
+
|
|
33
|
+
${this.c("yellow", "OPTIONS")}
|
|
34
|
+
${this.c("cyan", "-p, --path")} <path> Tomcat installation path
|
|
35
|
+
${this.c("cyan", "-t, --tool")} <tool> Build tool: maven | gradle
|
|
36
|
+
${this.c("cyan", "-n, --name")} <name> Application name (WAR context)
|
|
37
|
+
${this.c("cyan", "--port")} <port> Tomcat port (default: 8080)
|
|
38
|
+
${this.c("cyan", "-P, --profile")} <prof> Maven/Gradle profile
|
|
39
|
+
${this.c("cyan", "-e, --encoding")} <enc> Source encoding (utf8, cp1252)
|
|
40
|
+
|
|
41
|
+
${this.c("cyan", "-w, --watch")} Enable file watching (hot reload)
|
|
42
|
+
${this.c("cyan", "--tui")} Interactive dashboard mode
|
|
43
|
+
${this.c("cyan", "-d, --debug")} Enable JPDA debugger
|
|
44
|
+
${this.c("cyan", "--dp")} <port> Debugger port (default: 5005)
|
|
45
|
+
|
|
46
|
+
${this.c("cyan", "-c, --clean")} Clean logs before start
|
|
47
|
+
${this.c("cyan", "-s, --no-build")} Skip initial build
|
|
48
|
+
${this.c("cyan", "-q, --quiet")} Minimal output
|
|
49
|
+
${this.c("cyan", "-V, --verbose")} Detailed output
|
|
50
|
+
${this.c("cyan", "-h, --help")} Show this help
|
|
51
|
+
${this.c("cyan", "-v, --version")} Show version
|
|
52
|
+
|
|
53
|
+
${this.c("yellow", "EXAMPLES")}
|
|
54
|
+
${this.c("dim", "# Development with hot reload and dashboard")}
|
|
55
|
+
xavva dev --tui --watch
|
|
56
|
+
|
|
57
|
+
${this.c("dim", "# Quick deploy to specific Tomcat")}
|
|
58
|
+
xavva deploy -p /opt/tomcat --port 8081
|
|
59
|
+
|
|
60
|
+
${this.c("dim", "# Run a class with debugging")}
|
|
61
|
+
xavva debug com.example.MyClass
|
|
62
|
+
|
|
63
|
+
${this.c("dim", "# Analyze dependencies for conflicts")}
|
|
64
|
+
xavva deps --verbose
|
|
65
|
+
|
|
66
|
+
${this.c("dim", "# Security audit with auto-fix suggestions")}
|
|
67
|
+
xavva audit --fix
|
|
68
|
+
|
|
69
|
+
${this.c("yellow", "CONFIGURATION")}
|
|
70
|
+
Settings are loaded from ${this.c("cyan", "xavva.json")} in the project root:
|
|
71
|
+
|
|
72
|
+
${this.c("dim", `{
|
|
73
|
+
"project": {
|
|
74
|
+
"appName": "my-app",
|
|
75
|
+
"buildTool": "maven",
|
|
76
|
+
"tui": true
|
|
77
|
+
},
|
|
78
|
+
"tomcat": {
|
|
79
|
+
"path": "C:/apache-tomcat",
|
|
80
|
+
"port": 8080
|
|
81
|
+
}
|
|
82
|
+
}`)}
|
|
83
|
+
|
|
84
|
+
${this.c("gray", "────────────────────────────────────────────────────────────")}
|
|
85
|
+
${this.c("gray", "Docs: github.com/leorsousa05/Xavva | License: MIT")}
|
|
86
|
+
`);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
private c(color: string, text: string): string {
|
|
90
|
+
const colors: Record<string, string> = {
|
|
91
|
+
reset: "\x1b[0m",
|
|
92
|
+
bold: "\x1b[1m",
|
|
93
|
+
dim: "\x1b[2m",
|
|
94
|
+
gray: "\x1b[90m",
|
|
95
|
+
red: "\x1b[31m",
|
|
96
|
+
green: "\x1b[32m",
|
|
97
|
+
yellow: "\x1b[33m",
|
|
98
|
+
blue: "\x1b[34m",
|
|
99
|
+
cyan: "\x1b[36m",
|
|
100
|
+
};
|
|
101
|
+
return `${colors[color] || ""}${text}\x1b[0m`;
|
|
48
102
|
}
|
|
49
103
|
}
|