@archznn/xavva 1.6.5

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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Donuts
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,75 @@
1
+ # XAVVA 🚀 (Windows Only) `v1.6.5`
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
+ > [!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
+ ## 🛠️ Funcionalidades de Elite
9
+
10
+ - **⚡ 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.
11
+ - **🛠️ Modo Dev Inteligente**: O comando `xavva dev` ativa hot-reload, logs limpos, debugger (JPDA) e monitoramento de memória em um único fluxo.
12
+ - **🌐 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**.
18
+
19
+ ## 🚀 Instalação e Uso
20
+
21
+ Você pode instalar o Xavva globalmente usando o NPM (requer [Bun](https://bun.sh/) instalado no sistema):
22
+
23
+ ```bash
24
+ # Instalação global
25
+ npm install -g @archznn/xavva
26
+
27
+ # Ou rodar sem instalar via npx
28
+ npx @archznn/xavva dev
29
+ ```
30
+
31
+ ## ⚙️ Zero Config & Auto-Detection
32
+
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.
38
+
39
+ ### Comandos Principais
40
+
41
+ ```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
56
+ ```
57
+
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
+ ---
75
+ *Desenvolvido para transformar a experiência de desenvolvimento Java Legacy em algo ágil e produtivo.*
package/package.json ADDED
@@ -0,0 +1,44 @@
1
+ {
2
+ "name": "@archznn/xavva",
3
+ "version": "1.6.5",
4
+ "description": "Ultra-fast CLI tool for Java/Tomcat development on Windows with Hot-Reload and Zero Config.",
5
+ "module": "src/index.ts",
6
+ "type": "module",
7
+ "bin": {
8
+ "xavva": "src/index.ts"
9
+ },
10
+ "files": [
11
+ "src",
12
+ "README.md",
13
+ "LICENSE"
14
+ ],
15
+ "keywords": [
16
+ "java",
17
+ "tomcat",
18
+ "maven",
19
+ "gradle",
20
+ "hot-reload",
21
+ "windows",
22
+ "cli",
23
+ "bun"
24
+ ],
25
+ "author": "Leonardo Sousa",
26
+ "license": "MIT",
27
+ "repository": {
28
+ "type": "git",
29
+ "url": "https://github.com/leorsousa05/Xavva.git"
30
+ },
31
+ "scripts": {
32
+ "build": "bun build --compile --minify --sourcemap ./src/index.ts --outfile xavva",
33
+ "compile": "bun build --compile --minify ./src/index.ts --target bun-windows-x64 --outfile bin/xavva-windows.exe && bun build --compile --minify ./src/index.ts --target bun-linux-x64 --outfile bin/xavva-linux && bun build --compile --minify ./src/index.ts --target bun-macos-x64 --outfile bin/xavva-macos"
34
+ },
35
+ "devDependencies": {
36
+ "@types/bun": "latest"
37
+ },
38
+ "peerDependencies": {
39
+ "typescript": "^5"
40
+ },
41
+ "dependencies": {
42
+ "glob": "^13.0.6"
43
+ }
44
+ }
@@ -0,0 +1,118 @@
1
+ import path from "path";
2
+ import fs from "fs";
3
+ import type { Command } from "./Command";
4
+ import type { AppConfig } from "../types/config";
5
+ import { AuditService, type JarAuditResult } from "../services/AuditService";
6
+ import { Logger } from "../utils/ui";
7
+
8
+ export class AuditCommand implements Command {
9
+ async execute(config: AppConfig): Promise<void> {
10
+ Logger.section("Vulnerability & JAR Audit");
11
+
12
+ let appName = config.project.appName;
13
+
14
+ // 1. Tentar inferir do diretório atual se não foi passado via config
15
+ if (!appName) {
16
+ appName = this.inferFromArtifacts();
17
+ }
18
+
19
+ // 2. Se ainda não tem nome, tenta inferir do Tomcat
20
+ if (!appName) {
21
+ const webappsPath = path.join(config.tomcat.path, "webapps");
22
+ if (fs.existsSync(webappsPath)) {
23
+ const folders = fs.readdirSync(webappsPath, { withFileTypes: true })
24
+ .filter(dirent => dirent.isDirectory() && !["ROOT", "manager", "host-manager", "docs", "examples"].includes(dirent.name));
25
+
26
+ if (folders.length === 1) {
27
+ appName = folders[0].name;
28
+ } else if (folders.length > 1) {
29
+ Logger.error("Vários apps encontrados no Tomcat:");
30
+ folders.forEach(f => console.log(` ${"\x1b[90m"}➜${"\x1b[0m"} ${f.name}`));
31
+ console.log(`\n Use ${"\x1b[33m"}xavva audit -n <nome>${"\x1b[0m"} para especificar.`);
32
+ return;
33
+ }
34
+ }
35
+ }
36
+
37
+ if (!appName) {
38
+ Logger.error("Não foi possível identificar o app automaticamente.");
39
+ Logger.warn("Certifique-se de que o projeto foi buildado ou use -n <nome>.");
40
+ return;
41
+ }
42
+
43
+ const auditService = new AuditService(config.tomcat);
44
+
45
+ try {
46
+ const results = await auditService.runAudit(appName);
47
+ const vulnerable = results.filter(r => r.vulnerabilities.length > 0);
48
+
49
+ if (vulnerable.length === 0) {
50
+ Logger.success(`Nenhuma vulnerabilidade conhecida encontrada em ${results.length} JARs.`);
51
+ return;
52
+ }
53
+
54
+ Logger.warn(`Encontradas vulnerabilidades em ${vulnerable.length} de ${results.length} dependências.`);
55
+ console.log("");
56
+
57
+ for (const res of vulnerable) {
58
+ this.renderResult(res);
59
+ }
60
+
61
+ const totalVulns = vulnerable.reduce((acc, r) => acc + r.vulnerabilities.length, 0);
62
+ Logger.info("Total de Falhas", totalVulns);
63
+ Logger.info("Relatório gerado via", "OSV.dev (Open Source Vulnerability Database)");
64
+
65
+ } catch (e: any) {
66
+ Logger.error(e.message);
67
+ }
68
+ }
69
+
70
+ private inferFromArtifacts(): string | undefined {
71
+ // Busca .war no target (Maven) ou build/libs (Gradle)
72
+ const paths = ["target", "build/libs"];
73
+ for (const p of paths) {
74
+ const fullPath = path.join(process.cwd(), p);
75
+ if (fs.existsSync(fullPath)) {
76
+ const wars = fs.readdirSync(fullPath).filter(f => f.endsWith(".war"));
77
+ if (wars.length > 0) {
78
+ // Retorna o nome do .war mais recente sem a extensão
79
+ const latest = wars.map(name => ({
80
+ name,
81
+ time: fs.statSync(path.join(fullPath, name)).mtimeMs
82
+ })).sort((a, b) => b.time - a.time)[0];
83
+
84
+ return latest.name.replace(".war", "");
85
+ }
86
+ }
87
+ }
88
+ return undefined;
89
+ }
90
+
91
+ private renderResult(res: JarAuditResult) {
92
+ const C = {
93
+ reset: "\x1b[0m",
94
+ bold: "\x1b[1m",
95
+ dim: "\x1b[90m",
96
+ red: "\x1b[31m",
97
+ yellow: "\x1b[33m",
98
+ cyan: "\x1b[36m",
99
+ blue: "\x1b[34m"
100
+ };
101
+
102
+ const depName = res.groupId ? `${res.groupId}:${res.artifactId}` : res.artifactId;
103
+ console.log(` ${C.bold}${C.cyan}${depName}${C.reset} ${C.dim}@ ${res.version}${C.reset}`);
104
+ console.log(` ${C.dim}➜ ${res.jarName}${C.reset}`);
105
+
106
+ for (const v of res.vulnerabilities) {
107
+ let sevColor = C.reset;
108
+ if (v.severity === "CRITICAL" || v.severity === "HIGH") sevColor = C.red;
109
+ else if (v.severity === "MEDIUM") sevColor = C.yellow;
110
+
111
+ console.log(` ${sevColor}[${v.severity}]${C.reset} ${C.bold}${v.id}${C.reset}: ${v.summary}`);
112
+ if (v.fixedIn) {
113
+ console.log(` ${C.blue}💡 Fixed in:${C.reset} ${C.bold}${v.fixedIn}${C.reset}`);
114
+ }
115
+ }
116
+ console.log("");
117
+ }
118
+ }
@@ -0,0 +1,22 @@
1
+ import type { Command } from "./Command";
2
+ import type { AppConfig } from "../types/config";
3
+ import { BuildService } from "../services/BuildService";
4
+ import { Logger } from "../utils/ui";
5
+
6
+ export class BuildCommand implements Command {
7
+ async execute(config: AppConfig): Promise<void> {
8
+ const builder = new BuildService(config.project, config.tomcat);
9
+
10
+ Logger.section("Build Only");
11
+ Logger.info("Tool", config.project.buildTool.toUpperCase());
12
+ if (config.project.profile) Logger.info("Profile", config.project.profile);
13
+
14
+ try {
15
+ await builder.runBuild();
16
+ Logger.success("Build completed successfully!");
17
+ } catch (error: any) {
18
+ Logger.error(error.message);
19
+ process.exit(1);
20
+ }
21
+ }
22
+ }
@@ -0,0 +1,5 @@
1
+ import type { AppConfig } from "../types/config";
2
+
3
+ export interface Command {
4
+ execute(config: AppConfig): Promise<void>;
5
+ }
@@ -0,0 +1,162 @@
1
+ import path from "path";
2
+ import fs from "fs";
3
+ import type { Command } from "./Command";
4
+ import type { AppConfig } from "../types/config";
5
+ import { BuildService } from "../services/BuildService";
6
+ import { TomcatService } from "../services/TomcatService";
7
+ import { Logger } from "../utils/ui";
8
+ import { EndpointService } from "../services/EndpointService";
9
+
10
+ export class DeployCommand implements Command {
11
+ constructor(private tomcat?: TomcatService, private builder?: BuildService) {}
12
+
13
+ private async reloadBrowser(url: string) {
14
+ if (process.platform !== 'win32') return;
15
+
16
+ await new Promise(r => setTimeout(r, 800));
17
+
18
+ const psCommand = `
19
+ $shell = New-Object -ComObject WScript.Shell
20
+ $process = Get-Process | Where-Object { $_.MainWindowTitle -match "Chrome" -or $_.MainWindowTitle -match "Edge" } | Select-Object -First 1
21
+ if ($process) {
22
+ $shell.AppActivate($process.Id)
23
+ Sleep -m 100
24
+ $shell.SendKeys("{F5}")
25
+ }
26
+ `;
27
+ Bun.spawn(["powershell", "-command", psCommand]);
28
+ }
29
+
30
+ async execute(config: AppConfig, incremental = false, isWatching = false): Promise<void> {
31
+ const tomcat = this.tomcat || new TomcatService(config.tomcat);
32
+ const builder = this.builder || new BuildService(config.project, config.tomcat);
33
+
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");
45
+ }
46
+
47
+ const srcPath = path.join(process.cwd(), "src");
48
+ if (fs.existsSync(srcPath)) {
49
+ const contextPath = (config.project.appName || "").replace(".war", "");
50
+ const endpoints = EndpointService.scan(srcPath, contextPath);
51
+ if (endpoints.length > 0) {
52
+ Logger.info("Endpoints", endpoints.length);
53
+ }
54
+ }
55
+ } else {
56
+ console.log("");
57
+ Logger.warn("Re-deploying detected changes...");
58
+ }
59
+
60
+ try {
61
+ const contextPath = (config.project.appName || "").replace(".war", "");
62
+
63
+ if (!incremental) {
64
+ await tomcat.killConflict();
65
+ }
66
+
67
+ if (!config.project.skipBuild) {
68
+ await builder.runBuild(incremental);
69
+ }
70
+
71
+ 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
+ }
78
+ return;
79
+ }
80
+
81
+ Logger.step("Cleaning webapps and cache");
82
+ tomcat.clearWebapps();
83
+
84
+ Logger.step("Moving artifacts to webapps");
85
+ const artifact = await builder.deployToWebapps();
86
+
87
+ const finalContextPath = contextPath || artifact.replace(".war", "");
88
+ const finalAppUrl = `http://localhost:${config.tomcat.port}/${finalContextPath}`;
89
+
90
+ tomcat.onReady = async () => {
91
+ Logger.step(`Checking health at ${finalAppUrl}`);
92
+
93
+ try {
94
+ await new Promise(r => setTimeout(r, 1500));
95
+
96
+ const response = await fetch(finalAppUrl);
97
+ if (response.status < 500) {
98
+ const memory = await tomcat.getMemoryUsage();
99
+ Logger.success(`App is UP! (Status: ${response.status} | RAM: ${memory})`);
100
+
101
+ if (!config.project.quiet) {
102
+ const endpoints = EndpointService.scan(path.join(process.cwd(), "src"), finalContextPath);
103
+ 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("");
107
+ }
108
+ }
109
+
110
+ if (incremental) {
111
+ await this.reloadBrowser(finalAppUrl);
112
+ } else {
113
+ if (process.platform === 'win32') {
114
+ Bun.spawn(["cmd", "/c", "start", finalAppUrl]);
115
+ } else {
116
+ const start = process.platform === 'darwin' ? 'open' : 'xdg-open';
117
+ Bun.spawn([start, finalAppUrl]);
118
+ }
119
+ }
120
+ } else {
121
+ Logger.warn(`App is starting, but returned status ${response.status}. Check your logs.`);
122
+ }
123
+ } catch (e) {
124
+ Logger.error(`Health check failed: Could not connect to ${finalAppUrl}`);
125
+ }
126
+ };
127
+
128
+ tomcat.start(config.project.cleanLogs, config.project.debug, config.project.skipScan, config.project.quiet);
129
+ } catch (error: any) {
130
+ Logger.error(error.message);
131
+ throw error;
132
+ }
133
+ }
134
+
135
+ async syncResource(config: AppConfig, filename: string): Promise<void> {
136
+ const appName = config.project.appName || "";
137
+ const explodedPath = path.join(config.tomcat.path, "webapps", appName);
138
+
139
+ if (!fs.existsSync(explodedPath)) {
140
+ return;
141
+ }
142
+
143
+ const parts = filename.split(/[/\\]/);
144
+ const webappIndex = parts.indexOf("webapp");
145
+
146
+ if (webappIndex !== -1) {
147
+ const relPath = parts.slice(webappIndex + 1).join(path.sep);
148
+ const targetPath = path.join(explodedPath, relPath);
149
+
150
+ try {
151
+ fs.copyFileSync(filename, targetPath);
152
+ if (!config.project.quiet) Logger.success(`Synced ${path.basename(filename)} directly to Tomcat!`);
153
+
154
+ const contextPath = config.project.appName || "";
155
+ const appUrl = `http://localhost:${config.tomcat.port}/${contextPath}`;
156
+ await this.reloadBrowser(appUrl);
157
+ } catch (e) {
158
+ Logger.error(`Failed to sync resource: ${filename}`);
159
+ }
160
+ }
161
+ }
162
+ }
@@ -0,0 +1,90 @@
1
+ import path from "path";
2
+ import fs from "fs";
3
+ import type { Command } from "./Command";
4
+ import type { AppConfig } from "../types/config";
5
+ import { EndpointService } from "../services/EndpointService";
6
+ import { Logger } from "../utils/ui";
7
+ import type { ApiEndpoint, ApiParam } from "../types/endpoint";
8
+
9
+ export class DocsCommand implements Command {
10
+ async execute(config: AppConfig): Promise<void> {
11
+ const srcPath = path.join(process.cwd(), "src");
12
+ if (!fs.existsSync(srcPath)) {
13
+ Logger.error("Pasta 'src' não encontrada. Certifique-se de estar na raiz do projeto Java.");
14
+ return;
15
+ }
16
+
17
+ const contextPath = (config.project.appName || "").replace(".war", "");
18
+ const endpoints = EndpointService.scan(srcPath, contextPath);
19
+
20
+ if (endpoints.length === 0) {
21
+ Logger.warn("Nenhum endpoint encontrado.");
22
+ return;
23
+ }
24
+
25
+ Logger.section("API Documentation (Swagger-like)");
26
+ console.log("");
27
+
28
+ const grouped = this.groupByController(endpoints);
29
+
30
+ for (const [controller, controllerEndpoints] of Object.entries(grouped)) {
31
+ console.log(` ${"\x1b[36m"}${controller}${"\x1b[0m"}`);
32
+ console.log(` ${"\x1b[90m"}──────────────────────────────────────────────────${"\x1b[0m"}`);
33
+
34
+ for (const ep of controllerEndpoints) {
35
+ this.renderEndpoint(ep, config.tomcat.port);
36
+ }
37
+ console.log("");
38
+ }
39
+ }
40
+
41
+ private groupByController(endpoints: ApiEndpoint[]): Record<string, ApiEndpoint[]> {
42
+ return endpoints.reduce((acc, ep) => {
43
+ const controller = ep.className;
44
+ if (!acc[controller]) acc[controller] = [];
45
+ acc[controller].push(ep);
46
+ return acc;
47
+ }, {} as Record<string, ApiEndpoint[]>);
48
+ }
49
+
50
+ private renderEndpoint(ep: ApiEndpoint, port: number) {
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
58
+ };
59
+
60
+ const color = methodColors[ep.method] || "\x1b[37m";
61
+ const methodLabel = ep.method.padEnd(7);
62
+ const fullUrl = `http://localhost:${port}${ep.fullPath}`;
63
+
64
+ console.log(` ${color}${methodLabel}${"\x1b[0m"} ${"\x1b[1m"}${ep.fullPath}${"\x1b[0m"}`);
65
+ console.log(` ${"\x1b[90m"}${ep.methodName}()${"\x1b[0m"}`);
66
+
67
+ if (ep.parameters.length > 0) {
68
+ console.log(` ${"\x1b[90m"}Parameters:${"\x1b[0m"}`);
69
+ for (const param of ep.parameters) {
70
+ this.renderParameter(param);
71
+ }
72
+ }
73
+ console.log("");
74
+ }
75
+
76
+ private renderParameter(param: ApiParam) {
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
83
+ };
84
+
85
+ const color = sourceColors[param.source] || "\x1b[37m";
86
+ const required = param.required ? "\x1b[31m*\x1b[0m" : "";
87
+
88
+ console.log(` ${color}[${param.source}]${"\x1b[0m"} ${param.name}${required} : ${"\x1b[90m"}${param.type}${"\x1b[0m"}`);
89
+ }
90
+ }
@@ -0,0 +1,89 @@
1
+ import type { Command } from "./Command";
2
+ import type { AppConfig } from "../types/config";
3
+ import { Logger } from "../utils/ui";
4
+ import fs from "fs";
5
+ import path from "path";
6
+
7
+ export class DoctorCommand implements Command {
8
+ async execute(config: AppConfig, values: any = {}): Promise<void> {
9
+ Logger.section("Xavva Doctor - Ambiente");
10
+
11
+ this.check("JAVA_HOME", !!process.env.JAVA_HOME, process.env.JAVA_HOME || "Não definido");
12
+
13
+ const tomcatOk = fs.existsSync(config.tomcat.path);
14
+ this.check("Tomcat Path", tomcatOk, config.tomcat.path);
15
+
16
+ if (tomcatOk) {
17
+ const binOk = fs.existsSync(path.join(config.tomcat.path, "bin", "catalina.bat"));
18
+ this.check("Tomcat Bin", binOk, binOk ? "OK" : "catalina.bat não encontrado");
19
+ }
20
+
21
+ const mvnOk = this.checkBinary("mvn");
22
+ this.check("Maven", mvnOk, mvnOk ? "Disponível" : "Não encontrado no PATH");
23
+
24
+ const gradleOk = this.checkBinary("gradle") || this.checkBinary("gradlew");
25
+ this.check("Gradle", gradleOk, gradleOk ? "Disponível" : "Não encontrado no PATH");
26
+
27
+ const gitOk = this.checkBinary("git");
28
+ this.check("Git", gitOk, gitOk ? "Disponível" : "Não encontrado no PATH");
29
+
30
+ Logger.section("Xavva Doctor - Integridade de Arquivos");
31
+ await this.checkBOM(values.fix);
32
+
33
+ console.log("");
34
+ }
35
+
36
+ private async checkBOM(fix: boolean) {
37
+ const srcPath = path.join(process.cwd(), "src");
38
+ if (!fs.existsSync(srcPath)) return;
39
+
40
+ const filesWithBOM: string[] = [];
41
+ const scan = (dir: string) => {
42
+ const list = fs.readdirSync(dir, { withFileTypes: true });
43
+ for (const item of list) {
44
+ const res = path.resolve(dir, item.name);
45
+ if (item.isDirectory()) {
46
+ scan(res);
47
+ } else if (item.name.endsWith(".java")) {
48
+ const buffer = fs.readFileSync(res);
49
+ if (buffer[0] === 0xEF && buffer[1] === 0xBB && buffer[2] === 0xBF) {
50
+ filesWithBOM.push(res);
51
+ }
52
+ }
53
+ }
54
+ };
55
+
56
+ scan(srcPath);
57
+
58
+ if (filesWithBOM.length > 0) {
59
+ this.check("Encoding BOM", false, `${filesWithBOM.length} arquivos com BOM (UTF-8 com assinatura)`);
60
+ if (fix) {
61
+ for (const file of filesWithBOM) {
62
+ const buffer = fs.readFileSync(file);
63
+ const cleanBuffer = buffer.subarray(3);
64
+ fs.writeFileSync(file, cleanBuffer);
65
+ console.log(` \x1b[32m✔\x1b[0m Corrigido: ${path.basename(file)}`);
66
+ }
67
+ Logger.success("BOM removido de todos os arquivos!");
68
+ } else {
69
+ Logger.warn("Use 'xavva doctor --fix' para remover o BOM automaticamente.");
70
+ }
71
+ } else {
72
+ this.check("Encoding BOM", true, "Nenhum arquivo com BOM detectado.");
73
+ }
74
+ }
75
+
76
+ private check(label: string, ok: boolean, detail: string) {
77
+ const icon = ok ? "\x1b[32m✔\x1b[0m" : "\x1b[31m✘\x1b[0m";
78
+ console.log(` ${icon} ${label.padEnd(15)} ${detail}`);
79
+ }
80
+
81
+ private checkBinary(name: string): boolean {
82
+ try {
83
+ const proc = Bun.spawnSync([process.platform === 'win32' ? "where" : "which", name]);
84
+ return proc.exitCode === 0;
85
+ } catch {
86
+ return false;
87
+ }
88
+ }
89
+ }