@archznn/xavva 1.8.1 → 2.0.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 +30 -63
- package/package.json +1 -1
- package/src/commands/LogsCommand.ts +23 -17
- package/src/index.ts +9 -3
- package/src/services/DashboardService.ts +117 -0
- package/src/services/LogAnalyzer.ts +140 -0
- package/src/types/config.ts +2 -0
- package/src/utils/config.ts +29 -15
package/README.md
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# XAVVA 🚀 (Windows Only) `
|
|
1
|
+
# XAVVA 🚀 (Windows Only) `v2.0.0`
|
|
2
2
|
|
|
3
3
|
Xavva é uma CLI de alto desempenho construída com **Bun** para automatizar o ciclo de desenvolvimento de aplicações Java (Maven/Gradle) rodando no Apache Tomcat. Ela foi desenhada especificamente para desenvolvedores que buscam a velocidade de ambientes modernos (como Node.js/Vite) dentro do ecossistema Java Enterprise.
|
|
4
4
|
|
|
@@ -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,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,23 @@ 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);
|
|
52
58
|
|
|
53
59
|
// 2. Registrar Comandos
|
|
54
60
|
const registry = new CommandRegistry();
|
|
55
61
|
|
|
56
62
|
const deployCmd = new DeployCommand(tomcatService, buildService);
|
|
63
|
+
const logsCmd = new LogsCommand(dashboard, logAnalyzer);
|
|
57
64
|
|
|
58
65
|
registry.register("build", new BuildCommand(buildService));
|
|
59
66
|
registry.register("start", new StartCommand(tomcatService));
|
|
60
67
|
registry.register("doctor", new DoctorCommand());
|
|
61
68
|
registry.register("run", new RunCommand());
|
|
62
69
|
registry.register("debug", new RunCommand());
|
|
63
|
-
registry.register("logs",
|
|
70
|
+
registry.register("logs", logsCmd);
|
|
64
71
|
registry.register("docs", new DocsCommand());
|
|
65
72
|
registry.register("audit", new AuditCommand(auditService));
|
|
66
73
|
registry.register("deploy", deployCmd);
|
|
@@ -72,7 +79,6 @@ async function main() {
|
|
|
72
79
|
await watcher.start();
|
|
73
80
|
} else {
|
|
74
81
|
// 3. Executar do Registro
|
|
75
|
-
// Ajusta flags baseadas no nome do comando para comandos compartilhados
|
|
76
82
|
if (commandName === "debug") values.debug = true;
|
|
77
83
|
if (commandName === "run") values.debug = false;
|
|
78
84
|
|
|
@@ -0,0 +1,117 @@
|
|
|
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
|
+
|
|
12
|
+
constructor(private config: AppConfig) {
|
|
13
|
+
this.isTui = config.project.tui;
|
|
14
|
+
if (this.isTui) {
|
|
15
|
+
this.maxLogLines = process.stdout.rows - 10;
|
|
16
|
+
this.setupTui();
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
private setupTui() {
|
|
21
|
+
process.stdout.write("\x1B[?1049h"); // Switch to alternate buffer
|
|
22
|
+
process.stdout.write("\x1B[?25l"); // Hide cursor
|
|
23
|
+
|
|
24
|
+
process.stdin.setRawMode(true);
|
|
25
|
+
process.stdin.resume();
|
|
26
|
+
process.stdin.setEncoding("utf8");
|
|
27
|
+
|
|
28
|
+
process.stdin.on("data", (key: string) => {
|
|
29
|
+
// Ctrl+C ou Q para sair
|
|
30
|
+
if (key === "\u0003" || key.toLowerCase() === "q") {
|
|
31
|
+
this.exit();
|
|
32
|
+
}
|
|
33
|
+
if (key.toLowerCase() === "l") {
|
|
34
|
+
this.logLines = [];
|
|
35
|
+
this.render();
|
|
36
|
+
}
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
process.on("SIGINT", () => this.exit());
|
|
40
|
+
process.on("exit", () => this.exit());
|
|
41
|
+
|
|
42
|
+
// Atualiza o dashboard periodicamente para memória/status
|
|
43
|
+
setInterval(() => this.render(), 1000);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
public onKey(key: string, callback: () => void) {
|
|
47
|
+
process.stdin.on("data", (input: string) => {
|
|
48
|
+
if (input.toLowerCase() === key.toLowerCase()) {
|
|
49
|
+
callback();
|
|
50
|
+
}
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
public setStatus(status: string, color: string = Logger.C.cyan) {
|
|
55
|
+
this.status = status;
|
|
56
|
+
this.statusColor = color;
|
|
57
|
+
this.render();
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
public log(message: string) {
|
|
61
|
+
if (!message) return;
|
|
62
|
+
|
|
63
|
+
if (this.isTui) {
|
|
64
|
+
const lines = message.split("\n");
|
|
65
|
+
this.logLines.push(...lines);
|
|
66
|
+
if (this.logLines.length > 1000) { // Limite de scrollbuffer
|
|
67
|
+
this.logLines = this.logLines.slice(-1000);
|
|
68
|
+
}
|
|
69
|
+
this.render();
|
|
70
|
+
} else {
|
|
71
|
+
console.log(message);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
private render() {
|
|
76
|
+
if (!this.isTui) return;
|
|
77
|
+
|
|
78
|
+
this.maxLogLines = process.stdout.rows - 8;
|
|
79
|
+
|
|
80
|
+
let output = "\x1B[H"; // Move to 0,0
|
|
81
|
+
|
|
82
|
+
// Header
|
|
83
|
+
const name = (process.cwd().split(/[/\\]/).pop() || "PROJECT").toUpperCase();
|
|
84
|
+
const git = Logger.getGitContext();
|
|
85
|
+
const mem = Math.round((os.totalmem() - os.freemem()) / 1024 / 1024 / 1024 * 10) / 10;
|
|
86
|
+
const totalMem = Math.round(os.totalmem() / 1024 / 1024 / 1024);
|
|
87
|
+
|
|
88
|
+
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}\n`;
|
|
89
|
+
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}${git.branch || "unknown"}${Logger.C.reset}\n`;
|
|
90
|
+
output += `${Logger.C.dim}──────────────────────────────────────────────────────────────────────────${Logger.C.reset}\n`;
|
|
91
|
+
|
|
92
|
+
// Logs
|
|
93
|
+
const visibleLogs = this.logLines.slice(-this.maxLogLines);
|
|
94
|
+
for (let i = 0; i < this.maxLogLines; i++) {
|
|
95
|
+
const line = visibleLogs[i] || "";
|
|
96
|
+
// Limpa a linha antes de escrever (ANSI escape EL)
|
|
97
|
+
output += line.substring(0, process.stdout.columns) + "\x1B[K\n";
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// Footer
|
|
101
|
+
output += `\x1B[${process.stdout.rows - 1};1H`; // Move to last rows
|
|
102
|
+
output += `${Logger.C.dim}──────────────────────────────────────────────────────────────────────────${Logger.C.reset}\n`;
|
|
103
|
+
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}`;
|
|
104
|
+
|
|
105
|
+
process.stdout.write(output);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
private exit() {
|
|
109
|
+
if (this.isTui) {
|
|
110
|
+
process.stdout.write("\x1B[?1049l"); // Restore buffer
|
|
111
|
+
process.stdout.write("\x1B[?25h"); // Show cursor
|
|
112
|
+
process.stdin.setRawMode(false);
|
|
113
|
+
process.stdin.pause();
|
|
114
|
+
}
|
|
115
|
+
process.exit(0);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
@@ -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
|
|