@archznn/xavva 1.8.1 → 2.0.1
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 +30 -63
- package/package.json +1 -1
- package/src/commands/HelpCommand.ts +35 -37
- package/src/commands/LogsCommand.ts +23 -17
- package/src/index.ts +18 -3
- package/src/services/DashboardService.ts +123 -0
- package/src/services/LogAnalyzer.ts +140 -0
- package/src/types/config.ts +2 -0
- package/src/utils/config.ts +29 -15
- package/src/utils/ui.ts +21 -0
package/README.md
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# XAVVA 🚀 (Windows Only) `
|
|
1
|
+
# XAVVA 🚀 (Windows Only) `v2.0.1`
|
|
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
|
|
|
@@ -10,102 +10,69 @@ Desenvolver para Java/Tomcat tradicionalmente envolve ciclos lentos de `clean in
|
|
|
10
10
|
|
|
11
11
|
### ⚡ Funcionalidades de Elite
|
|
12
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.
|
|
13
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.
|
|
14
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`).
|
|
15
|
-
- **DCEVM Integration**: O Xavva pode baixar e configurar automaticamente uma JDK com DCEVM (JetBrains Runtime), permitindo mudanças estruturais em classes (novos métodos/campos) em tempo real.
|
|
16
|
-
- **API Documentation (Swagger-like)**: Mapeamento estático de endpoints, métodos HTTP e parâmetros diretamente no terminal via `xavva docs`.
|
|
17
|
-
- **Live Reload Automático**: Sincronização inteligente que atualiza o browser (Chrome/Edge) após mudanças em JSPs ou recursos estáticos.
|
|
18
17
|
- **Segurança & Robustez**: Auditoria de dependências (`.jar`) e execução protegida contra *Command Injection* no PowerShell.
|
|
19
|
-
- **Pathing JAR (Windows)**: Contorna limites de caracteres do Windows em classpaths gigantes
|
|
20
|
-
- **Auto-Healing**: Diagnóstico e reparo automático de problemas comuns de ambiente
|
|
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.
|
|
21
20
|
|
|
22
21
|
---
|
|
23
22
|
|
|
24
23
|
## 🚀 Começo Rápido
|
|
25
24
|
|
|
26
|
-
### Pré-requisitos
|
|
27
|
-
- **Windows** (Otimizado para PowerShell Core e Windows PowerShell)
|
|
28
|
-
- **Bun** instalado (`powershell -c "irm bun.sh/install.ps1 | iex"`)
|
|
29
|
-
- **Tomcat** configurado via variável de ambiente `TOMCAT_HOME` ou `CATALINA_HOME`.
|
|
30
|
-
|
|
31
25
|
### Instalação
|
|
32
26
|
```powershell
|
|
33
27
|
# Instalação global via NPM
|
|
34
28
|
npm install -g @archznn/xavva
|
|
35
29
|
|
|
36
|
-
#
|
|
37
|
-
|
|
30
|
+
# Iniciar em modo Dashboard (TUI)
|
|
31
|
+
xavva dev --tui
|
|
38
32
|
```
|
|
39
33
|
|
|
40
34
|
---
|
|
41
35
|
|
|
42
36
|
## 📖 Referência de Comandos
|
|
43
37
|
|
|
44
|
-
O Xavva utiliza uma arquitetura modular de comandos e serviços
|
|
38
|
+
O Xavva 2.0 utiliza uma arquitetura modular de comandos e serviços.
|
|
45
39
|
|
|
46
40
|
### 1. Modo Desenvolvimento (`xavva dev`)
|
|
47
41
|
O comando principal para o dia a dia. Ativa o monitoramento de arquivos e o Hot-Reload.
|
|
48
|
-
- **O que faz**: Compila Java, sincroniza recursos, limpa logs, inicia o Tomcat e monitora mudanças.
|
|
49
42
|
- **Flags úteis**:
|
|
43
|
+
- `--tui`: Ativa o Dashboard interativo no terminal.
|
|
50
44
|
- `--no-build`: Pula o build inicial.
|
|
51
45
|
- `--watch`: Ativa o modo de observação de arquivos (padrão em `dev`).
|
|
52
46
|
- `--port 8081`: Define uma porta específica para o Tomcat.
|
|
53
|
-
- `--dp 9000`: Altera a porta do Debugger (JPDA) (padrão 5005).
|
|
54
|
-
|
|
55
|
-
### 2. Execução de Classes (`xavva run` / `xavva debug`)
|
|
56
|
-
Executa classes Java standalone (`public static void main`) com resolução automática de dependências Maven ou Gradle.
|
|
57
|
-
- **Inteligência de Classpath**: Gera automaticamente um `classpath.jar` temporário (Pathing JAR) para evitar o erro de "Command line too long" no Windows.
|
|
58
|
-
- **Busca por Grep**: Se você fornecer apenas parte do nome da classe, o Xavva a encontrará recursivamente no projeto.
|
|
59
|
-
|
|
60
|
-
### 3. Documentação de API (`xavva docs`)
|
|
61
|
-
Gera uma documentação instantânea dos seus controladores Jersey/Spring no terminal.
|
|
62
|
-
- Mostra a URL completa, método HTTP e parâmetros (Path, Query, Body).
|
|
63
|
-
|
|
64
|
-
### 4. Diagnóstico e Reparo (`xavva doctor`)
|
|
65
|
-
Verifica se o seu ambiente está saudável.
|
|
66
|
-
- **`xavva doctor --fix`**:
|
|
67
|
-
- Instala o **JetBrains Runtime (DCEVM)** se necessário.
|
|
68
|
-
- Remove automaticamente o **BOM (Byte Order Mark)** de arquivos Java que causam erros de compilação.
|
|
69
|
-
- Configura o `JAVA_HOME` do sistema.
|
|
70
47
|
|
|
71
|
-
###
|
|
72
|
-
|
|
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
|
+
```
|
|
73
62
|
|
|
74
|
-
###
|
|
75
|
-
|
|
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.
|
|
76
65
|
|
|
77
66
|
---
|
|
78
67
|
|
|
79
|
-
## 🏗️ Arquitetura
|
|
68
|
+
## 🏗️ Arquitetura Xavva 2.0
|
|
80
69
|
|
|
81
70
|
O Xavva foi refatorado para uma arquitetura de **Injeção de Dependências** e **Serviços Centralizados**:
|
|
82
71
|
|
|
83
|
-
- **
|
|
84
|
-
- **
|
|
85
|
-
- **
|
|
86
|
-
- **
|
|
87
|
-
|
|
88
|
-
---
|
|
89
|
-
|
|
90
|
-
## ⚙️ Configuração (Zero Config)
|
|
91
|
-
|
|
92
|
-
O Xavva funciona sem arquivos de configuração externos, baseando-se no ambiente:
|
|
93
|
-
|
|
94
|
-
| Variável | Descrição |
|
|
95
|
-
|----------|-----------|
|
|
96
|
-
| `TOMCAT_HOME` | Caminho raiz do seu Apache Tomcat. |
|
|
97
|
-
| `JAVA_HOME` | JDK utilizada para compilação e execução. |
|
|
98
|
-
|
|
99
|
-
**Dica**: O Xavva cria automaticamente uma pasta `.xavva` no seu projeto para cache e artefatos temporários, e a adiciona ao seu `.gitignore`.
|
|
100
|
-
|
|
101
|
-
---
|
|
102
|
-
|
|
103
|
-
## 🧩 Sincronização de Recursos
|
|
104
|
-
|
|
105
|
-
Ao editar um arquivo, o Xavva decide a melhor estratégia:
|
|
106
|
-
- **`.java`**: Compila apenas a classe e injeta o bytecode via `fastSync`.
|
|
107
|
-
- **`.jsp` / `.html` / `.css`**: Sincroniza o arquivo diretamente na pasta de deploy do Tomcat.
|
|
108
|
-
- **`pom.xml` / `build.gradle`**: Identifica mudanças estruturais e sugere um rebuild completo com invalidação de cache inteligente.
|
|
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.
|
|
109
76
|
|
|
110
77
|
---
|
|
111
78
|
*Desenvolvido para transformar o legado em produtivo. 🚀*
|
package/package.json
CHANGED
|
@@ -1,50 +1,48 @@
|
|
|
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 pkg from "../../package.json";
|
|
4
4
|
|
|
5
5
|
export class HelpCommand implements Command {
|
|
6
|
-
async execute(_config: AppConfig): Promise<void> {
|
|
6
|
+
async execute(_config: AppConfig, _args?: CLIArguments): Promise<void> {
|
|
7
7
|
console.log(`
|
|
8
|
-
🛠️
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
8
|
+
🛠️ XAVVA CLI v${pkg.version} 🚀
|
|
9
|
+
---------------------------------------
|
|
10
|
+
Automatização de alta performance para Java Enterprise (Tomcat) no Windows.
|
|
11
|
+
Detecta automaticamente Maven/Gradle e otimiza o ciclo de desenvolvimento.
|
|
12
12
|
|
|
13
13
|
Comandos principais:
|
|
14
|
-
dev 🚀 MODO COMPLETO: Deploy + Watch + Debug
|
|
15
|
-
deploy (Padrão)
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
doctor 🩺
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
debug 🐞 Debuga uma classe main (Uso: xavva debug NomeDaClasse).
|
|
23
|
-
logs 📋 Monitora o catalina.out do Tomcat em tempo real.
|
|
14
|
+
dev 🚀 MODO COMPLETO: Build + Deploy + Watch + Debug.
|
|
15
|
+
deploy (Padrão) Compila, sincroniza e inicia o servidor.
|
|
16
|
+
logs 📋 Tail inteligente (catalina.out) com Smart Folding.
|
|
17
|
+
run / debug 🚀 Executa classes standalone com Pathing JAR (Windows).
|
|
18
|
+
doctor 🩺 Diagnóstico e reparo de ambiente (DCEVM, JAVA_HOME).
|
|
19
|
+
audit 🛡️ Auditoria de segurança em JARs via OSV.dev.
|
|
20
|
+
docs 📖 Mapeamento estático de Endpoints e JSPs.
|
|
21
|
+
build / start Comandos granulares de compilação ou startup.
|
|
24
22
|
|
|
25
|
-
Opções:
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
--
|
|
29
|
-
-c, --clean 🧹 Logs coloridos e simplificados (
|
|
30
|
-
-
|
|
31
|
-
-V, --verbose 📣 Mostra logs completos do Maven/Gradle.
|
|
32
|
-
-s, --no-build Pula o build (usa o que já estiver na pasta target/build).
|
|
33
|
-
-P, --profile Define o profile (ex: -P prod).
|
|
34
|
-
--fix 🔧 Corrige problemas automaticamente no 'doctor'.
|
|
23
|
+
Opções de Interface:
|
|
24
|
+
--tui 🖥️ Ativa o Dashboard Interativo (Interface TUI).
|
|
25
|
+
No modo TUI use: [R] Restart, [L] Clear, [Q] Quit.
|
|
26
|
+
-w, --watch 👀 Hot Reload: Redeploy incremental de classes e recursos.
|
|
27
|
+
-c, --clean 🧹 Logs coloridos e simplificados (Recomendado).
|
|
28
|
+
-V, --verbose 📣 Exibe logs completos do Maven/Gradle.
|
|
35
29
|
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
xavva build -P dev Apenas compila usando o profile 'dev'.
|
|
30
|
+
Opções de Runtime:
|
|
31
|
+
-d, --debug 🐞 Habilita JPDA Debugging (Porta 5005).
|
|
32
|
+
-P, --profile Define o profile de build (ex: -P prod).
|
|
33
|
+
-s, --no-build Pula o build inicial.
|
|
34
|
+
--fix 🔧 Corrige problemas automaticamente (Doctor).
|
|
42
35
|
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
-t,
|
|
46
|
-
|
|
47
|
-
|
|
36
|
+
Configuração:
|
|
37
|
+
O Xavva prioriza o arquivo 'xavva.json' na raiz do projeto.
|
|
38
|
+
Flags de linha de comando: -p (Tomcat Path), -t (Build Tool), -n (WAR Name).
|
|
39
|
+
|
|
40
|
+
Exemplos:
|
|
41
|
+
xavva dev --tui Experiência completa com Dashboard.
|
|
42
|
+
xavva run MyClass Execução standalone com classpath automático.
|
|
43
|
+
xavva audit Verifica vulnerabilidades conhecidas.
|
|
44
|
+
|
|
45
|
+
*Transformando o legado em produtivo.*
|
|
48
46
|
`);
|
|
49
47
|
}
|
|
50
48
|
}
|
|
@@ -1,34 +1,36 @@
|
|
|
1
1
|
import type { Command } from "./Command";
|
|
2
|
-
import type { AppConfig } from "../types/config";
|
|
2
|
+
import type { AppConfig, CLIArguments } from "../types/config";
|
|
3
|
+
import { DashboardService } from "../services/DashboardService";
|
|
4
|
+
import { LogAnalyzer } from "../services/LogAnalyzer";
|
|
3
5
|
import { Logger } from "../utils/ui";
|
|
4
6
|
import path from "path";
|
|
5
7
|
import fs from "fs";
|
|
6
8
|
|
|
7
9
|
export class LogsCommand implements Command {
|
|
8
|
-
|
|
10
|
+
constructor(private dashboard?: DashboardService, private logAnalyzer?: LogAnalyzer) {}
|
|
11
|
+
|
|
12
|
+
async execute(config: AppConfig, args?: CLIArguments): Promise<void> {
|
|
9
13
|
const logPath = path.join(config.tomcat.path, "logs", "catalina.out");
|
|
10
14
|
|
|
11
15
|
if (!fs.existsSync(logPath)) {
|
|
12
|
-
|
|
16
|
+
const errorMsg = `Arquivo de log não encontrado: ${logPath}`;
|
|
17
|
+
if (this.dashboard) this.dashboard.log(Logger.C.red + errorMsg);
|
|
18
|
+
else Logger.error(errorMsg);
|
|
13
19
|
return;
|
|
14
20
|
}
|
|
15
21
|
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
22
|
+
const analyzer = this.logAnalyzer || new LogAnalyzer(config.project);
|
|
23
|
+
const dashboard = this.dashboard || new DashboardService(config);
|
|
24
|
+
|
|
25
|
+
dashboard.setStatus("LOGGING", Logger.C.green);
|
|
26
|
+
|
|
27
|
+
if (args?.grep) {
|
|
28
|
+
dashboard.log(`${Logger.C.dim}Filter:${Logger.C.reset} ${Logger.C.bold}${args.grep}${Logger.C.reset}`);
|
|
19
29
|
}
|
|
20
30
|
|
|
21
31
|
const stats = fs.statSync(logPath);
|
|
22
32
|
let currentSize = stats.size;
|
|
23
33
|
|
|
24
|
-
const colorize = (line: string): string => {
|
|
25
|
-
if (line.match(/SEVERE|ERROR|Exception|Error/i)) return `\x1b[31m${line}\x1b[0m`;
|
|
26
|
-
if (line.match(/WARNING|WARN/i)) return `\x1b[33m${line}\x1b[0m`;
|
|
27
|
-
if (line.match(/INFO/i)) return `\x1b[36m${line}\x1b[0m`;
|
|
28
|
-
if (line.match(/DEBUG/i)) return `\x1b[90m${line}\x1b[0m`;
|
|
29
|
-
return line;
|
|
30
|
-
};
|
|
31
|
-
|
|
32
34
|
fs.watch(logPath, (event) => {
|
|
33
35
|
if (event === "change") {
|
|
34
36
|
const newStats = fs.statSync(logPath);
|
|
@@ -43,18 +45,22 @@ export class LogsCommand implements Command {
|
|
|
43
45
|
lines.forEach(line => {
|
|
44
46
|
if (!line.trim()) return;
|
|
45
47
|
|
|
46
|
-
|
|
48
|
+
const grep = args?.grep || config.project.grep;
|
|
49
|
+
if (grep && !line.toLowerCase().includes(grep.toLowerCase())) {
|
|
47
50
|
return;
|
|
48
51
|
}
|
|
49
52
|
|
|
50
|
-
|
|
53
|
+
const formatted = analyzer.summarize(line);
|
|
54
|
+
if (formatted) {
|
|
55
|
+
dashboard.log(formatted);
|
|
56
|
+
}
|
|
51
57
|
});
|
|
52
58
|
});
|
|
53
59
|
|
|
54
60
|
currentSize = newStats.size;
|
|
55
61
|
} else if (newStats.size < currentSize) {
|
|
56
62
|
currentSize = newStats.size;
|
|
57
|
-
Logger.
|
|
63
|
+
dashboard.log(Logger.C.yellow + "Arquivo de log foi resetado/rotacionado.");
|
|
58
64
|
}
|
|
59
65
|
}
|
|
60
66
|
});
|
package/src/index.ts
CHANGED
|
@@ -17,6 +17,8 @@ import { BuildService } from "./services/BuildService";
|
|
|
17
17
|
import { AuditService } from "./services/AuditService";
|
|
18
18
|
import { WatcherService } from "./services/WatcherService";
|
|
19
19
|
import { BuildCacheService } from "./services/BuildCacheService";
|
|
20
|
+
import { DashboardService } from "./services/DashboardService";
|
|
21
|
+
import { LogAnalyzer } from "./services/LogAnalyzer";
|
|
20
22
|
|
|
21
23
|
import pkg from "../package.json";
|
|
22
24
|
import { Logger } from "./utils/ui";
|
|
@@ -33,7 +35,7 @@ async function main() {
|
|
|
33
35
|
const commandNames = ["deploy", "build", "start", "dev", "doctor", "run", "debug", "logs", "docs", "audit"];
|
|
34
36
|
const commandName = positionals.find(p => commandNames.includes(p)) || "deploy";
|
|
35
37
|
|
|
36
|
-
if (!values.help) {
|
|
38
|
+
if (!values.help && !values.tui) {
|
|
37
39
|
Logger.banner(commandName);
|
|
38
40
|
}
|
|
39
41
|
|
|
@@ -49,18 +51,24 @@ async function main() {
|
|
|
49
51
|
const tomcatService = new TomcatService(config.tomcat);
|
|
50
52
|
tomcatService.setProjectService(projectService);
|
|
51
53
|
const auditService = new AuditService(config.tomcat);
|
|
54
|
+
|
|
55
|
+
// Xavva 2.0: Dashboard & LogAnalyzer
|
|
56
|
+
const logAnalyzer = new LogAnalyzer(config.project);
|
|
57
|
+
const dashboard = new DashboardService(config);
|
|
58
|
+
Logger.setDashboard(dashboard);
|
|
52
59
|
|
|
53
60
|
// 2. Registrar Comandos
|
|
54
61
|
const registry = new CommandRegistry();
|
|
55
62
|
|
|
56
63
|
const deployCmd = new DeployCommand(tomcatService, buildService);
|
|
64
|
+
const logsCmd = new LogsCommand(dashboard, logAnalyzer);
|
|
57
65
|
|
|
58
66
|
registry.register("build", new BuildCommand(buildService));
|
|
59
67
|
registry.register("start", new StartCommand(tomcatService));
|
|
60
68
|
registry.register("doctor", new DoctorCommand());
|
|
61
69
|
registry.register("run", new RunCommand());
|
|
62
70
|
registry.register("debug", new RunCommand());
|
|
63
|
-
registry.register("logs",
|
|
71
|
+
registry.register("logs", logsCmd);
|
|
64
72
|
registry.register("docs", new DocsCommand());
|
|
65
73
|
registry.register("audit", new AuditCommand(auditService));
|
|
66
74
|
registry.register("deploy", deployCmd);
|
|
@@ -68,11 +76,18 @@ async function main() {
|
|
|
68
76
|
|
|
69
77
|
// Caso especial: Watch Mode para Deploy/Dev
|
|
70
78
|
if ((commandName === "deploy" || commandName === "dev") && values.watch) {
|
|
79
|
+
// Registrar ação de restart manual na TUI
|
|
80
|
+
if (dashboard.isTuiActive()) {
|
|
81
|
+
dashboard.onAction("r", () => {
|
|
82
|
+
dashboard.log(Logger.C.yellow + "Restart manual solicitado via TUI...");
|
|
83
|
+
deployCmd.execute(config, false, true); // Executa deploy completo mas mantém o watch
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
|
|
71
87
|
const watcher = new WatcherService(config, deployCmd);
|
|
72
88
|
await watcher.start();
|
|
73
89
|
} else {
|
|
74
90
|
// 3. Executar do Registro
|
|
75
|
-
// Ajusta flags baseadas no nome do comando para comandos compartilhados
|
|
76
91
|
if (commandName === "debug") values.debug = true;
|
|
77
92
|
if (commandName === "run") values.debug = false;
|
|
78
93
|
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
import { Logger } from "../utils/ui";
|
|
2
|
+
import type { AppConfig } from "../types/config";
|
|
3
|
+
import os from "os";
|
|
4
|
+
|
|
5
|
+
export class DashboardService {
|
|
6
|
+
private isTui: boolean;
|
|
7
|
+
private logLines: string[] = [];
|
|
8
|
+
private maxLogLines: number = 0;
|
|
9
|
+
private status: string = "IDLE";
|
|
10
|
+
private statusColor: string = Logger.C.dim;
|
|
11
|
+
private gitContext: any = null;
|
|
12
|
+
private actions: Map<string, () => void> = new Map();
|
|
13
|
+
|
|
14
|
+
constructor(private config: AppConfig) {
|
|
15
|
+
this.isTui = config.project.tui;
|
|
16
|
+
if (this.isTui) {
|
|
17
|
+
this.gitContext = Logger.getGitContext();
|
|
18
|
+
this.maxLogLines = process.stdout.rows - 6;
|
|
19
|
+
this.setupTui();
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
public isTuiActive(): boolean {
|
|
24
|
+
return this.isTui;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
public onAction(key: string, callback: () => void) {
|
|
28
|
+
this.actions.set(key.toLowerCase(), callback);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
private setupTui() {
|
|
32
|
+
process.stdout.write("\x1B[?1049h"); // Switch to alternate buffer
|
|
33
|
+
process.stdout.write("\x1B[2J"); // Clear screen
|
|
34
|
+
process.stdout.write("\x1B[?25l"); // Hide cursor
|
|
35
|
+
|
|
36
|
+
process.stdin.setRawMode(true);
|
|
37
|
+
process.stdin.resume();
|
|
38
|
+
process.stdin.setEncoding("utf8");
|
|
39
|
+
|
|
40
|
+
process.stdin.on("data", (key: string) => {
|
|
41
|
+
const input = key.toLowerCase();
|
|
42
|
+
// Ctrl+C ou Q para sair
|
|
43
|
+
if (key === "\u0003" || input === "q") {
|
|
44
|
+
this.exit();
|
|
45
|
+
}
|
|
46
|
+
if (input === "l") {
|
|
47
|
+
this.logLines = [];
|
|
48
|
+
this.render();
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const action = this.actions.get(input);
|
|
53
|
+
if (action) action();
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
process.on("SIGINT", () => this.exit());
|
|
57
|
+
process.on("exit", () => this.exit());
|
|
58
|
+
|
|
59
|
+
// Atualiza o dashboard periodicamente para memória/status
|
|
60
|
+
setInterval(() => this.render(), 1000);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
public setStatus(status: string, color: string = Logger.C.cyan) {
|
|
64
|
+
this.status = status;
|
|
65
|
+
this.statusColor = color;
|
|
66
|
+
this.render();
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
public log(message: string) {
|
|
70
|
+
if (!message) return;
|
|
71
|
+
|
|
72
|
+
if (this.isTui) {
|
|
73
|
+
const lines = message.split("\n");
|
|
74
|
+
this.logLines.push(...lines);
|
|
75
|
+
if (this.logLines.length > 1000) { // Limite de scrollbuffer
|
|
76
|
+
this.logLines = this.logLines.slice(-1000);
|
|
77
|
+
}
|
|
78
|
+
this.render();
|
|
79
|
+
} else {
|
|
80
|
+
console.log(message);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
private render() {
|
|
85
|
+
if (!this.isTui) return;
|
|
86
|
+
|
|
87
|
+
this.maxLogLines = process.stdout.rows - 6;
|
|
88
|
+
|
|
89
|
+
let output = "\x1B[H"; // Move to 0,0
|
|
90
|
+
|
|
91
|
+
// Header
|
|
92
|
+
const name = (process.cwd().split(/[/\\]/).pop() || "PROJECT").toUpperCase();
|
|
93
|
+
const mem = Math.round((os.totalmem() - os.freemem()) / 1024 / 1024 / 1024 * 10) / 10;
|
|
94
|
+
const totalMem = Math.round(os.totalmem() / 1024 / 1024 / 1024);
|
|
95
|
+
|
|
96
|
+
output += `${Logger.C.bold}${Logger.C.cyan} X A V V A 2.0 ${Logger.C.reset} ${Logger.C.dim}│${Logger.C.reset} ${Logger.C.white}${Logger.C.bold}${name}${Logger.C.reset}\x1B[K\n`;
|
|
97
|
+
output += `${Logger.C.dim} STATUS: ${this.statusColor}${this.status.padEnd(10)}${Logger.C.reset} ${Logger.C.dim}│ MEM: ${Logger.C.yellow}${mem}G/${totalMem}G${Logger.C.reset} ${Logger.C.dim}│ BRANCH: ${Logger.C.magenta}${this.gitContext?.branch || "unknown"}${Logger.C.reset}\x1B[K\n`;
|
|
98
|
+
output += `${Logger.C.dim}──────────────────────────────────────────────────────────────────────────${Logger.C.reset}\x1B[K\n`;
|
|
99
|
+
|
|
100
|
+
// Logs
|
|
101
|
+
const visibleLogs = this.logLines.slice(-this.maxLogLines);
|
|
102
|
+
for (let i = 0; i < this.maxLogLines; i++) {
|
|
103
|
+
const line = visibleLogs[i] || "";
|
|
104
|
+
output += line.substring(0, process.stdout.columns) + "\x1B[K\n";
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// Footer
|
|
108
|
+
output += `\x1B[${process.stdout.rows};1H`; // Move to last row
|
|
109
|
+
output += ` ${Logger.C.bold}${Logger.C.white}R${Logger.C.reset} Restart ${Logger.C.bold}${Logger.C.white}L${Logger.C.reset} Clear ${Logger.C.bold}${Logger.C.white}Q${Logger.C.reset} Quit ${Logger.C.dim} (Xavva 2.0 TUI Mode)${Logger.C.reset}\x1B[K`;
|
|
110
|
+
|
|
111
|
+
process.stdout.write(output);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
private exit() {
|
|
115
|
+
if (this.isTui) {
|
|
116
|
+
process.stdout.write("\x1B[?1049l"); // Restore buffer
|
|
117
|
+
process.stdout.write("\x1B[?25h"); // Show cursor
|
|
118
|
+
process.stdin.setRawMode(false);
|
|
119
|
+
process.stdin.pause();
|
|
120
|
+
}
|
|
121
|
+
process.exit(0);
|
|
122
|
+
}
|
|
123
|
+
}
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
import { Logger } from "../utils/ui";
|
|
2
|
+
import type { ProjectConfig } from "../types/config";
|
|
3
|
+
|
|
4
|
+
export class LogAnalyzer {
|
|
5
|
+
private projectPrefixes: string[] = [];
|
|
6
|
+
|
|
7
|
+
constructor(private config: ProjectConfig) {
|
|
8
|
+
// Tentamos inferir prefixos comuns do projeto.
|
|
9
|
+
// Em Java/Maven/Gradle geralmente começam com com.empresa, br.com.etc
|
|
10
|
+
// Podemos extrair isso do ProjectService se necessário, ou usar o appName como base.
|
|
11
|
+
if (config.appName) {
|
|
12
|
+
this.projectPrefixes.push(config.appName.split('-')[0].toLowerCase());
|
|
13
|
+
}
|
|
14
|
+
// Prefixos padrões que queremos destacar se encontrarmos
|
|
15
|
+
this.projectPrefixes.push("com.xavva");
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
setProjectPrefixes(prefixes: string[]) {
|
|
19
|
+
this.projectPrefixes = [...new Set([...this.projectPrefixes, ...prefixes])];
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
public summarize(line: string): string {
|
|
23
|
+
if (Logger.isSystemNoise(line)) return "";
|
|
24
|
+
|
|
25
|
+
// Reuso da lógica existente no Logger.summarize, mas aprimorada
|
|
26
|
+
const startupMatch = line.match(/Server startup in (\[?)(.*?)(\]?)\s*ms/);
|
|
27
|
+
if (startupMatch) {
|
|
28
|
+
const time = (parseInt(startupMatch[2]) / 1000).toFixed(1);
|
|
29
|
+
return `${Logger.C.green}✔ ${Logger.C.bold}Server started in ${time}s`;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const deployMatch = line.match(/Deployment of web application archive \[(.*?)\] has finished in \[(.*?)\] ms/);
|
|
33
|
+
if (deployMatch) {
|
|
34
|
+
return `${Logger.C.green}✔ Artifacts deployed`;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// Smart Folding para Stack Traces
|
|
38
|
+
if (line.trim().startsWith("at ")) {
|
|
39
|
+
return this.formatStackTraceLine(line);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
if (line.includes("Caused by:")) {
|
|
43
|
+
return `${Logger.C.bgRed}${Logger.C.white}${Logger.C.bold} ROOT CAUSE ${Logger.C.reset} ${Logger.C.red}${line.trim()}${Logger.C.reset}`;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// Hotswap
|
|
47
|
+
const hotswapPattern = /HOTSWAP AGENT:.*? (INFO|WARN|ERROR|RELOAD) (.*?) - (.*)/;
|
|
48
|
+
const hotswapMatch = line.match(hotswapPattern);
|
|
49
|
+
if (hotswapMatch) {
|
|
50
|
+
return this.formatHotswapLine(hotswapMatch);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// Tomcat standard logs
|
|
54
|
+
const tomcatPattern = /^(\d{2}-\w{3}-\d{4} \d{2}:\d{2}:\d{2}\.\d{3})\s+(INFO|WARNING|SEVERE|ERROR)\s+\[(.*?)\]\s+(.*)$/;
|
|
55
|
+
const tMatch = line.match(tomcatPattern);
|
|
56
|
+
if (tMatch) {
|
|
57
|
+
return this.formatTomcatLine(tMatch);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// Generic [LEVEL] logs
|
|
61
|
+
const logPattern = /^\[(INFO|WARNING|WARN|SEVERE|ERROR)\]\s+(.*)$/;
|
|
62
|
+
const match = line.match(logPattern);
|
|
63
|
+
if (match) {
|
|
64
|
+
return this.formatGenericLog(match);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// Se a linha contém Exception mas não é o 'at ', destaca
|
|
68
|
+
if (line.includes("Exception:")) {
|
|
69
|
+
return `${Logger.C.red}${Logger.C.bold}${line.trim()}${Logger.C.reset}`;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
return "";
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
private formatStackTraceLine(line: string): string {
|
|
76
|
+
const trimmed = line.trim();
|
|
77
|
+
const isProject = this.projectPrefixes.some(p => trimmed.includes(p));
|
|
78
|
+
|
|
79
|
+
if (isProject) {
|
|
80
|
+
return ` ${Logger.C.bold}${Logger.C.yellow}${trimmed}${Logger.C.reset}`;
|
|
81
|
+
} else {
|
|
82
|
+
return ` ${Logger.C.dim}${trimmed}${Logger.C.reset}`;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
private formatHotswapLine(match: RegExpMatchArray): string {
|
|
87
|
+
const level = match[1];
|
|
88
|
+
let msg = match[3];
|
|
89
|
+
|
|
90
|
+
if (msg.includes("plugin initialized")) return "";
|
|
91
|
+
|
|
92
|
+
if (msg.includes("redefinition") || msg.includes("reloaded") || level === 'RELOAD') {
|
|
93
|
+
if (msg.includes("Reloading classes [")) {
|
|
94
|
+
const classes = msg.match(/\[(.*?)\]/)?.[1] || "";
|
|
95
|
+
const classCount = classes.split(",").length;
|
|
96
|
+
if (classCount > 3) msg = `Reloading ${classCount} classes...`;
|
|
97
|
+
}
|
|
98
|
+
return `${Logger.C.magenta}👀 ${Logger.C.bold}Hotswap:${Logger.C.reset} ${msg.replace(/Class '.*?'/, (m) => Logger.C.bold + m + Logger.C.reset)}`;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
let color = Logger.C.cyan;
|
|
102
|
+
let symbol = "●";
|
|
103
|
+
if (level === "WARN") { color = Logger.C.yellow; symbol = "▲"; }
|
|
104
|
+
else if (level === "ERROR") { color = Logger.C.red; symbol = "✖"; }
|
|
105
|
+
|
|
106
|
+
return `${color}${symbol} ${Logger.C.bold}Hotswap:${Logger.C.reset} ${msg}`;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
private formatTomcatLine(match: RegExpMatchArray): string {
|
|
110
|
+
const label = match[2];
|
|
111
|
+
let msg = match[4].trim();
|
|
112
|
+
if (Logger.isSystemNoise(msg)) return "";
|
|
113
|
+
|
|
114
|
+
let color = Logger.C.dim;
|
|
115
|
+
let symbol = "ℹ";
|
|
116
|
+
if (label === "WARNING") { color = Logger.C.yellow; symbol = "▲"; }
|
|
117
|
+
else if (label === "SEVERE" || label === "ERROR") { color = Logger.C.red; symbol = "✖"; }
|
|
118
|
+
|
|
119
|
+
msg = msg.replace(/^(org\.apache|com\.sun|java\..*?|org\.glassfish)\.[a-zA-Z0-9.]+\s/, "").trim();
|
|
120
|
+
if (!msg) return "";
|
|
121
|
+
|
|
122
|
+
return `${color}${symbol} ${msg}`;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
private formatGenericLog(match: RegExpMatchArray): string {
|
|
126
|
+
const label = match[1];
|
|
127
|
+
let msg = match[2].trim();
|
|
128
|
+
if (msg.includes("Total time:") || msg.includes("Finished at:") || msg.includes("Final Memory:") || msg.includes("-----------------------")) return "";
|
|
129
|
+
|
|
130
|
+
let color = Logger.C.dim;
|
|
131
|
+
let symbol = "ℹ";
|
|
132
|
+
if (label === "WARNING" || label === "WARN") { color = Logger.C.yellow; symbol = "▲"; }
|
|
133
|
+
else if (label === "SEVERE" || label === "ERROR") { color = Logger.C.red; symbol = "✖"; }
|
|
134
|
+
|
|
135
|
+
msg = msg.replace(/^(org\.apache|com\.sun|java\..*?)\.[a-zA-Z0-9.]+\s/, "").trim();
|
|
136
|
+
if (!msg || msg === "]" || msg.includes("Compilation failure")) return "";
|
|
137
|
+
|
|
138
|
+
return `${color}${symbol} ${msg}`;
|
|
139
|
+
}
|
|
140
|
+
}
|
package/src/types/config.ts
CHANGED
|
@@ -18,6 +18,7 @@ export interface ProjectConfig {
|
|
|
18
18
|
debugPort: number;
|
|
19
19
|
cleanLogs: boolean;
|
|
20
20
|
grep?: string;
|
|
21
|
+
tui: boolean;
|
|
21
22
|
}
|
|
22
23
|
|
|
23
24
|
export interface AppConfig {
|
|
@@ -44,6 +45,7 @@ export interface CLIArguments {
|
|
|
44
45
|
dp?: string;
|
|
45
46
|
fix?: boolean;
|
|
46
47
|
incremental?: boolean;
|
|
48
|
+
tui?: boolean;
|
|
47
49
|
}
|
|
48
50
|
|
|
49
51
|
export interface CommandContext {
|
package/src/utils/config.ts
CHANGED
|
@@ -27,12 +27,25 @@ export class ConfigManager {
|
|
|
27
27
|
verbose: { type: "boolean", short: "V" },
|
|
28
28
|
dp: { type: "string" },
|
|
29
29
|
fix: { type: "boolean" },
|
|
30
|
+
tui: { type: "boolean" },
|
|
30
31
|
},
|
|
31
32
|
strict: false,
|
|
32
33
|
allowPositionals: true,
|
|
33
34
|
});
|
|
34
35
|
|
|
35
36
|
const cliValues = values as CLIArguments;
|
|
37
|
+
|
|
38
|
+
// Load xavva.json
|
|
39
|
+
const xavvaJsonPath = path.join(process.cwd(), "xavva.json");
|
|
40
|
+
let xavvaJson: Partial<CLIArguments> = {};
|
|
41
|
+
if (fs.existsSync(xavvaJsonPath)) {
|
|
42
|
+
try {
|
|
43
|
+
xavvaJson = JSON.parse(fs.readFileSync(xavvaJsonPath, "utf8"));
|
|
44
|
+
} catch (e) {
|
|
45
|
+
console.error("Error reading xavva.json:", (e as Error).message);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
36
49
|
const isDev = positionals.includes("dev");
|
|
37
50
|
const isRun = positionals.includes("run") || positionals.includes("debug");
|
|
38
51
|
|
|
@@ -49,24 +62,25 @@ export class ConfigManager {
|
|
|
49
62
|
|
|
50
63
|
const config: AppConfig = {
|
|
51
64
|
tomcat: {
|
|
52
|
-
path: String(cliValues.path || envTomcatPath),
|
|
53
|
-
port: parseInt(String(cliValues.port || "8080")),
|
|
65
|
+
path: String(cliValues.path || xavvaJson.path || envTomcatPath),
|
|
66
|
+
port: parseInt(String(cliValues.port || xavvaJson.port || "8080")),
|
|
54
67
|
webapps: "webapps",
|
|
55
|
-
grep: cliValues.grep ? String(cliValues.grep) : "",
|
|
68
|
+
grep: cliValues.grep || xavvaJson.grep ? String(cliValues.grep || xavvaJson.grep) : "",
|
|
56
69
|
},
|
|
57
70
|
project: {
|
|
58
|
-
appName: cliValues.name ? String(cliValues.name) : "",
|
|
59
|
-
buildTool: (cliValues.tool as
|
|
60
|
-
profile: String(cliValues.profile || ""),
|
|
61
|
-
skipBuild: !!cliValues["no-build"],
|
|
62
|
-
skipScan: cliValues.scan !== undefined ? !cliValues.scan : true,
|
|
63
|
-
clean: !!cliValues.clean,
|
|
64
|
-
cleanLogs: cliValues.verbose ? false : true,
|
|
65
|
-
quiet: cliValues.verbose ? false : true,
|
|
66
|
-
verbose: !!cliValues.verbose,
|
|
67
|
-
debug: !!(cliValues.debug
|
|
68
|
-
debugPort: parseInt(String(cliValues.dp || "5005")),
|
|
69
|
-
grep: runClass || (cliValues.grep ? String(cliValues.grep) : ""),
|
|
71
|
+
appName: cliValues.name || xavvaJson.name ? String(cliValues.name || xavvaJson.name) : "",
|
|
72
|
+
buildTool: (cliValues.tool as any) || (xavvaJson.tool as any) || detectedTool,
|
|
73
|
+
profile: String(cliValues.profile || xavvaJson.profile || ""),
|
|
74
|
+
skipBuild: !!(cliValues["no-build"] ?? xavvaJson["no-build"]),
|
|
75
|
+
skipScan: cliValues.scan !== undefined ? !cliValues.scan : (xavvaJson.scan !== undefined ? !xavvaJson.scan : true),
|
|
76
|
+
clean: !!(cliValues.clean ?? xavvaJson.clean),
|
|
77
|
+
cleanLogs: (cliValues.verbose ?? xavvaJson.verbose) ? false : true,
|
|
78
|
+
quiet: (cliValues.verbose ?? xavvaJson.verbose) ? false : true,
|
|
79
|
+
verbose: !!(cliValues.verbose ?? xavvaJson.verbose),
|
|
80
|
+
debug: !!(cliValues.debug ?? xavvaJson.debug ?? isDev ?? isRun),
|
|
81
|
+
debugPort: parseInt(String(cliValues.dp || xavvaJson.dp || "5005")),
|
|
82
|
+
grep: runClass || (cliValues.grep || xavvaJson.grep ? String(cliValues.grep || xavvaJson.grep) : ""),
|
|
83
|
+
tui: !!(cliValues.tui ?? xavvaJson.tui),
|
|
70
84
|
}
|
|
71
85
|
};
|
|
72
86
|
|
package/src/utils/ui.ts
CHANGED
|
@@ -22,8 +22,18 @@ export class Logger {
|
|
|
22
22
|
private static lastDomain = "";
|
|
23
23
|
private static lastHotswapMsg = "";
|
|
24
24
|
private static activeSpinnerMsg = "";
|
|
25
|
+
private static dashboard: any = null;
|
|
26
|
+
|
|
27
|
+
static setDashboard(dashboard: any) {
|
|
28
|
+
this.dashboard = dashboard;
|
|
29
|
+
}
|
|
25
30
|
|
|
26
31
|
private static write(message: string, isError: boolean = false) {
|
|
32
|
+
if (this.dashboard && this.dashboard.isTuiActive()) {
|
|
33
|
+
this.dashboard.log(message);
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
|
|
27
37
|
if (this.activeSpinnerMsg) {
|
|
28
38
|
process.stdout.write("\r\x1B[K"); // Limpa a linha do spinner
|
|
29
39
|
}
|
|
@@ -126,6 +136,17 @@ export class Logger {
|
|
|
126
136
|
static dim(msg: string) { this.write(` ${this.C.dim}${msg}${this.C.reset}`); }
|
|
127
137
|
|
|
128
138
|
static spinner(msg: string) {
|
|
139
|
+
if (this.dashboard && this.dashboard.isTuiActive()) {
|
|
140
|
+
this.dashboard.log(`${this.C.cyan}⠋${this.C.reset} ${msg}...`);
|
|
141
|
+
return (success = true) => {
|
|
142
|
+
if (success) {
|
|
143
|
+
this.dashboard.log(`${this.C.green}✔${this.C.reset} ${msg}`);
|
|
144
|
+
} else {
|
|
145
|
+
this.dashboard.log(`${this.C.red}✖${this.C.reset} Falha em ${msg}`);
|
|
146
|
+
}
|
|
147
|
+
};
|
|
148
|
+
}
|
|
149
|
+
|
|
129
150
|
this.activeSpinnerMsg = msg;
|
|
130
151
|
const frames = ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"];
|
|
131
152
|
let i = 0;
|