@archznn/xavva 2.1.0 β†’ 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 CHANGED
@@ -1,204 +1 @@
1
- # XAVVA CLI πŸš€
2
-
3
- > Ultra-fast development toolkit for Java Enterprise (Tomcat) on Windows
4
-
5
- [![Version](https://img.shields.io/badge/version-2.0.3-blue.svg)](https://github.com/leorsousa05/Xavva)
6
- [![License](https://img.shields.io/badge/license-MIT-green.svg)](LICENSE)
7
-
8
- 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.
9
-
10
- ---
11
-
12
- ## ✨ Features
13
-
14
- - ⚑ **Hot Reload** β€” Incremental compilation and class injection without server restart
15
- - πŸ“Š **Interactive Dashboard** β€” Real-time TUI with system metrics and shortcuts
16
- - 🧠 **Smart Log Analyzer** β€” Stack trace folding and root cause highlighting
17
- - πŸ”’ **Security Audit** β€” Automated vulnerability scanning via OSV.dev
18
- - πŸ“¦ **Dependency Analysis** β€” Detect conflicts and outdated dependencies
19
- - 🎯 **Maven & Gradle** β€” Native support for both build tools
20
- - πŸ”§ **Auto-Healing** β€” Automatic diagnosis and repair of common issues
21
-
22
- ---
23
-
24
- ## πŸ“¦ Installation
25
-
26
- ```powershell
27
- # Via NPM
28
- npm install -g @archznn/xavva
29
-
30
- # Or run directly with Bun
31
- bunx @archznn/xavva dev
32
- ```
33
-
34
- ---
35
-
36
- ## πŸš€ Quick Start
37
-
38
- ```bash
39
- # Start development mode with dashboard
40
- xavva dev --tui
41
-
42
- # Deploy to Tomcat
43
- xavva deploy
44
-
45
- # Analyze dependencies for issues
46
- xavva deps
47
-
48
- # Check for security vulnerabilities
49
- xavva audit
50
- ```
51
-
52
- ---
53
-
54
- ## πŸ“– Commands
55
-
56
- ### Core Development
57
-
58
- | Command | Description |
59
- |---------|-------------|
60
- | `xavva dev` | Full development mode (build + deploy + watch + debug) |
61
- | `xavva deploy` | Build and deploy application to Tomcat |
62
- | `xavva build` | Compile project only |
63
- | `xavva start` | Start Tomcat server only |
64
-
65
- ### Code Execution
66
-
67
- | Command | Description |
68
- |---------|-------------|
69
- | `xavva run <class>` | Execute a Java class with automatic classpath |
70
- | `xavva debug <class>` | Debug a Java class (port 5005) |
71
-
72
- ### Analysis & Monitoring
73
-
74
- | Command | Description |
75
- |---------|-------------|
76
- | `xavva logs` | Stream and analyze Tomcat logs in real-time |
77
- | `xavva deps` | **Analyze dependencies** β€” detect conflicts, find updates |
78
- | `xavva audit` | Security audit of JAR files via OSV.dev |
79
- | `xavva doctor` | Diagnose environment issues (JAVA_HOME, DCEVM) |
80
- | `xavva profiles` | List available Maven/Gradle profiles |
81
- | `xavva docs` | Generate endpoint documentation |
82
-
83
- ---
84
-
85
- ## πŸ” Dependency Analysis
86
-
87
- The `xavva deps` command provides comprehensive dependency analysis:
88
-
89
- ```bash
90
- # Basic analysis
91
- xavva deps
92
-
93
- # With verbose output for debugging
94
- xavva deps --verbose
95
-
96
- # Show fix suggestions for conflicts
97
- xavva deps --fix
98
-
99
- # Export report as JSON
100
- xavva deps --output report.json
101
-
102
- # Fail on critical conflicts (useful in CI/CD)
103
- xavva deps --strict
104
- ```
105
-
106
- ### What it detects:
107
-
108
- - ⚠️ **Version Conflicts** β€” Same dependency with different versions
109
- - ⬆️ **Available Updates** β€” Newer versions in Maven Central
110
- - πŸ”΄ **Major Updates** β€” Breaking changes that need attention
111
- - πŸ“Š **Statistics** β€” Direct vs transitive dependencies
112
-
113
- ### Sample output:
114
-
115
- ```
116
- ══════════════════════════════════════════════════════════
117
- πŸ“Š DEPENDENCY ANALYSIS
118
- ══════════════════════════════════════════════════════════
119
-
120
- Statistics:
121
- Total: 183 dependencies
122
- Direct: 45 | Transitive: 138
123
-
124
- ⚠️ VERSION CONFLICTS (2)
125
- βœ– com.fasterxml.jackson.core:jackson-databind
126
- Versions: 2.13.0, 2.12.6
127
-
128
- ⬆️ UPDATES AVAILABLE (5)
129
- ↑ org.postgresql:postgresql
130
- 42.2.5 β†’ 42.7.1
131
-
132
- ⚠️ MAJOR UPDATES (1)
133
- ! org.springframework.boot:spring-boot-starter
134
- 2.5.0 β†’ 3.1.0
135
- ```
136
-
137
- ---
138
-
139
- ## βš™οΈ Configuration
140
-
141
- Create `xavva.json` in your project root:
142
-
143
- ```json
144
- {
145
- "project": {
146
- "appName": "my-application",
147
- "buildTool": "maven",
148
- "profile": "dev",
149
- "tui": false
150
- },
151
- "tomcat": {
152
- "path": "C:/apache-tomcat",
153
- "port": 8080
154
- }
155
- }
156
- ```
157
-
158
- ### CLI Options
159
-
160
- | Option | Description |
161
- |--------|-------------|
162
- | `-p, --path <path>` | Tomcat installation path |
163
- | `-t, --tool <tool>` | Build tool: `maven` or `gradle` |
164
- | `-n, --name <name>` | Application name (WAR context) |
165
- | `--port <port>` | Tomcat port (default: 8080) |
166
- | `-P, --profile <prof>` | Maven/Gradle profile |
167
- | `-e, --encoding <enc>` | Source encoding (utf8, cp1252) |
168
- | `-w, --watch` | Enable file watching |
169
- | `--tui` | Interactive dashboard mode |
170
- | `-d, --debug` | Enable JPDA debugger |
171
- | `-c, --clean` | Clean logs before start |
172
- | `-s, --no-build` | Skip initial build |
173
- | `-V, --verbose` | Detailed output |
174
-
175
- ---
176
-
177
- ## πŸ—οΈ Architecture
178
-
179
- Xavva uses a modular service-oriented architecture:
180
-
181
- - **DashboardService** β€” TUI management and interactivity
182
- - **LogAnalyzer** β€” Intelligent log processing
183
- - **DependencyAnalyzerService** β€” Dependency conflict detection
184
- - **ProjectService** β€” Project structure discovery
185
- - **BuildService** β€” Maven/Gradle integration
186
- - **TomcatService** β€” Server lifecycle management
187
-
188
- ---
189
-
190
- ## 🀝 Contributing
191
-
192
- Contributions are welcome! Please feel free to submit a Pull Request.
193
-
194
- ---
195
-
196
- ## πŸ“„ License
197
-
198
- MIT License β€” see [LICENSE](LICENSE) for details.
199
-
200
- ---
201
-
202
- <p align="center">
203
- <sub>Built with ❀️ for Java developers who miss modern tooling</sub>
204
- </p>
1
+ # XAVVA CLI πŸš€> Ultra-fast development toolkit for Java Enterprise (Tomcat) on Windows[![Version](https://img.shields.io/badge/version-2.2.0-blue.svg)](https://github.com/leorsousa05/Xavva)[![License](https://img.shields.io/badge/license-MIT-green.svg)](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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@archznn/xavva",
3
- "version": "2.1.0",
3
+ "version": "2.2.0",
4
4
  "description": "Ultra-fast CLI tool for Java/Tomcat development on Windows with Hot-Reload and Zero Config.",
5
5
  "module": "src/index.ts",
6
6
  "type": "module",
@@ -20,7 +20,7 @@ export class DeployCommand implements Command {
20
20
  if (!incremental) {
21
21
  this.logConfiguration(config, isWatching);
22
22
  } else {
23
- Logger.watcher("Change detected", "change");
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.watcher("Building project", "start");
34
+ Logger.build("compiling...");
35
35
  await builder.runBuild(incremental);
36
36
  }
37
37
 
38
38
  if (!config.project.skipBuild) {
39
- Logger.build("Full project build and environment ready");
39
+ Logger.build("completed");
40
40
  }
41
41
  } else {
42
42
  if (!config.project.skipBuild) {
43
- Logger.watcher("Incremental compilation", "start");
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.watcher("Redeploy completed", "success");
53
+ Logger.success("redeploy completed");
54
54
  return;
55
55
  }
56
56
 
57
- Logger.build("Webapps cleaned");
57
+ Logger.server("cleaning webapps...");
58
58
  const artifactInfo = await builder.deployToWebapps();
59
- Logger.build("Artifacts generated");
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.build("Exploded directory synced to webapps");
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.build("Artifacts deployed");
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.build("Artifacts deployed (legacy mode)");
88
+ Logger.server("extracted WAR (legacy)");
89
89
  }
90
90
  } else {
91
- Logger.build("Webapp already up to date, skipping extraction");
91
+ Logger.server("webapp up to date");
92
92
  }
93
93
  }
94
94
 
@@ -110,10 +110,11 @@ export class DeployCommand implements Command {
110
110
  }
111
111
 
112
112
  private logConfiguration(config: AppConfig, isWatching: boolean) {
113
- Logger.config("Runtime", config.project.buildTool.toUpperCase());
114
- if (config.project.profile) Logger.config("Profile", config.project.profile.toUpperCase());
115
- Logger.config("Watch Mode", isWatching ? "ON" : "OFF");
116
- Logger.config("Debug", config.project.debug ? `ON (Port ${config.project.debugPort})` : "OFF");
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);
117
118
 
118
119
  let javaBin = "java";
119
120
  if (process.env.JAVA_HOME) {
@@ -126,9 +127,9 @@ export class DeployCommand implements Command {
126
127
  const hasDcevm = ["dcevm", "jetbrains", "trava", "jbr"].some(v => output.includes(v));
127
128
 
128
129
  if (!hasDcevm && isWatching) {
129
- Logger.config("Hot Reload", "Standard (No structural changes)");
130
+ Logger.config("hotswap", "standard");
130
131
  } else if (hasDcevm) {
131
- Logger.config("Hot Reload", "Advanced (DCEVM Active)");
132
+ Logger.config("hotswap", "dcevm");
132
133
  }
133
134
 
134
135
  const srcPath = path.join(process.cwd(), "src");
@@ -136,9 +137,10 @@ export class DeployCommand implements Command {
136
137
  const contextPath = (config.project.appName || "").replace(".war", "");
137
138
  const endpoints = EndpointService.scan(srcPath, contextPath);
138
139
  if (endpoints.length > 0) {
139
- Logger.config("Endpoints", endpoints.length);
140
+ Logger.config("endpoints", endpoints.length);
140
141
  }
141
142
  }
143
+ Logger.endSection();
142
144
  }
143
145
 
144
146
  private injectContextConfiguration(appPath: string) {
@@ -171,9 +173,12 @@ export class DeployCommand implements Command {
171
173
  const response = await fetch(url);
172
174
  if (response.status < 500) {
173
175
  const memory = await tomcat.getMemoryUsage();
174
- Logger.health(url, "success");
175
- Logger.health(`Status ${response.status}`, "success");
176
- Logger.health(`Memory ${memory}`, "success");
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();
177
182
 
178
183
  if (!config.project.quiet) {
179
184
  this.showEndpointMap(config.tomcat.port, context);
@@ -185,32 +190,32 @@ export class DeployCommand implements Command {
185
190
  BrowserService.open(url);
186
191
  }
187
192
  } else {
188
- Logger.health(`App returned status ${response.status}`, "warn");
193
+ Logger.warn(`App returned status ${response.status}`);
189
194
  }
190
195
  } catch (e) {
191
- Logger.health(`Could not connect to ${url}`, "error");
196
+ Logger.error(`Could not connect to ${url}`);
192
197
  }
193
198
  }
194
199
 
195
200
  private showEndpointMap(port: number, context: string) {
196
201
  const endpoints = EndpointService.scan(path.join(process.cwd(), "src"), context);
197
202
  if (endpoints.length > 0) {
198
- Logger.newline();
199
- Logger.log(`${Logger.C.cyan}β—ˆ ENDPOINT MAP:${Logger.C.reset}`);
203
+ Logger.section("Endpoints");
200
204
 
201
205
  const apis = endpoints.filter(e => e.className !== "JSP");
202
206
  const jsps = endpoints.filter(e => e.className === "JSP");
203
207
 
204
208
  if (apis.length > 0) {
205
209
  const uniqueApiUrls = [...new Set(apis.map(e => `http://localhost:${port}${e.fullPath}`))];
206
- uniqueApiUrls.forEach(url => Logger.log(`${Logger.C.dim}➜ ${Logger.C.reset}${url}`));
210
+ uniqueApiUrls.forEach(url => Logger.info("", url));
207
211
  }
208
212
 
209
213
  if (jsps.length > 0) {
210
- Logger.log(`${Logger.C.dim}--- JSPs ---${Logger.C.reset}`);
214
+ Logger.info("JSPs", "");
211
215
  const uniqueJspUrls = [...new Set(jsps.map(e => `http://localhost:${port}${e.fullPath}`))];
212
- uniqueJspUrls.forEach(url => Logger.log(`${Logger.C.dim}πŸ“„ ${Logger.C.reset}${url}`));
216
+ uniqueJspUrls.forEach(url => Logger.info("", ` ${url}`));
213
217
  }
218
+ Logger.endSection();
214
219
  }
215
220
  }
216
221
 
@@ -1,142 +1,187 @@
1
1
  import { Logger } from "../utils/ui";
2
2
  import { ProcessManager } from "../utils/processManager";
3
3
  import {
4
- MAX_LOG_SCROLLBUFFER,
5
- DASHBOARD_REFRESH_INTERVAL_MS,
6
- DASHBOARD_LOG_SLICE_LINES
4
+ MAX_LOG_SCROLLBUFFER,
5
+ DASHBOARD_REFRESH_INTERVAL_MS,
6
+ DASHBOARD_LOG_SLICE_LINES
7
7
  } from "../utils/constants";
8
8
  import type { AppConfig } from "../types/config";
9
9
  import os from "os";
10
10
 
11
+ const C = Logger.C;
12
+
11
13
  export class DashboardService {
12
- private isTui: boolean;
13
- private logLines: string[] = [];
14
- private maxLogLines: number = 0;
15
- private status: string = "IDLE";
16
- private statusColor: string = Logger.C.dim;
17
- private gitContext: { branch: string; commit: string } | null = null;
18
- private actions: Map<string, () => void> = new Map();
19
-
20
- constructor(private config: AppConfig) {
21
- this.isTui = config.project.tui;
22
- if (this.isTui) {
23
- this.gitContext = Logger.getGitContext();
24
- this.maxLogLines = process.stdout.rows - 6;
25
- this.setupTui();
26
- this.registerShutdownHandlers();
27
- }
28
- }
29
-
30
- private registerShutdownHandlers() {
31
- const processManager = ProcessManager.getInstance();
32
- processManager.onShutdown(() => {
33
- this.restoreTerminal();
34
- });
35
- }
36
-
37
- private restoreTerminal() {
38
- if (this.isTui) {
39
- process.stdout.write("\x1B[?1049l"); // Restore buffer
40
- process.stdout.write("\x1B[?25h"); // Show cursor
41
- process.stdin.setRawMode(false);
42
- process.stdin.pause();
43
- }
44
- }
45
-
46
- public isTuiActive(): boolean {
47
- return this.isTui;
48
- }
49
-
50
- public onAction(key: string, callback: () => void) {
51
- this.actions.set(key.toLowerCase(), callback);
52
- }
53
-
54
- private setupTui() {
55
- process.stdout.write("\x1B[?1049h"); // Switch to alternate buffer
56
- process.stdout.write("\x1B[2J"); // Clear screen
57
- process.stdout.write("\x1B[?25l"); // Hide cursor
58
-
59
- process.stdin.setRawMode(true);
60
- process.stdin.resume();
61
- process.stdin.setEncoding("utf8");
62
-
63
- process.stdin.on("data", (key: string) => {
64
- const input = key.toLowerCase();
65
- // Ctrl+C ou Q para sair
66
- if (key === "\u0003" || input === "q") {
67
- this.exit();
68
- }
69
- if (input === "l") {
70
- this.logLines = [];
71
- this.render();
72
- return;
73
- }
74
-
75
- const action = this.actions.get(input);
76
- if (action) action();
77
- });
78
-
79
- process.on("SIGINT", () => this.exit());
80
- process.on("exit", () => this.exit());
81
-
82
- // Atualiza o dashboard periodicamente para memΓ³ria/status
83
- setInterval(() => this.render(), 1000);
84
- }
85
-
86
- public setStatus(status: string, color: string = Logger.C.cyan) {
87
- this.status = status;
88
- this.statusColor = color;
89
- this.render();
90
- }
91
-
92
- public log(message: string) {
93
- if (!message) return;
94
-
95
- if (this.isTui) {
96
- const lines = message.split("\n");
97
- this.logLines.push(...lines);
98
- if (this.logLines.length > MAX_LOG_SCROLLBUFFER) {
99
- this.logLines = this.logLines.slice(-DASHBOARD_LOG_SLICE_LINES);
100
- }
101
- this.render();
102
- } else {
103
- console.log(message);
104
- }
105
- }
106
-
107
- private render() {
108
- if (!this.isTui) return;
109
-
110
- this.maxLogLines = process.stdout.rows - 6;
111
-
112
- let output = "\x1B[H"; // Move to 0,0
113
-
114
- // Header
115
- const name = (process.cwd().split(/[/\\]/).pop() || "PROJECT").toUpperCase();
116
- const mem = Math.round((os.totalmem() - os.freemem()) / 1024 / 1024 / 1024 * 10) / 10;
117
- const totalMem = Math.round(os.totalmem() / 1024 / 1024 / 1024);
118
- const profile = this.config.project.profile ? ` ${Logger.C.dim}β€’${Logger.C.reset} ${Logger.C.yellow}♦ ${this.config.project.profile.toUpperCase()}${Logger.C.reset}` : "";
119
-
120
- 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}${profile}\x1B[K\n`;
121
- 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`;
122
- output += `${Logger.C.dim}──────────────────────────────────────────────────────────────────────────${Logger.C.reset}\x1B[K\n`;
123
-
124
- // Logs
125
- const visibleLogs = this.logLines.slice(-this.maxLogLines);
126
- for (let i = 0; i < this.maxLogLines; i++) {
127
- const line = visibleLogs[i] || "";
128
- output += line.substring(0, process.stdout.columns) + "\x1B[K\n";
129
- }
130
-
131
- // Footer
132
- output += `\x1B[${process.stdout.rows};1H`; // Move to last row
133
- 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`;
134
-
135
- process.stdout.write(output);
136
- }
137
-
138
- private async exit() {
139
- this.restoreTerminal();
140
- await ProcessManager.getInstance().shutdown(0);
141
- }
14
+ private isTui: boolean;
15
+ private logLines: string[] = [];
16
+ private maxLogLines: number = 0;
17
+ private status: string = "idle";
18
+ private statusColor: string = C.gray;
19
+ private gitContext: { branch: string; hash: string } | null = null;
20
+ private actions: Map<string, () => void> = new Map();
21
+ private startTime = Date.now();
22
+
23
+ constructor(private config: AppConfig) {
24
+ this.isTui = config.project.tui;
25
+ if (this.isTui) {
26
+ this.gitContext = Logger.getGitContext();
27
+ this.maxLogLines = process.stdout.rows - 8;
28
+ this.setupTui();
29
+ this.registerShutdownHandlers();
30
+ }
31
+ }
32
+
33
+ private registerShutdownHandlers() {
34
+ const processManager = ProcessManager.getInstance();
35
+ processManager.onShutdown(() => {
36
+ this.restoreTerminal();
37
+ });
38
+ }
39
+
40
+ private restoreTerminal() {
41
+ if (this.isTui) {
42
+ process.stdout.write("\x1B[?1049l");
43
+ process.stdout.write("\x1B[?25h");
44
+ process.stdin.setRawMode(false);
45
+ process.stdin.pause();
46
+ }
47
+ }
48
+
49
+ public isTuiActive(): boolean {
50
+ return this.isTui;
51
+ }
52
+
53
+ public onAction(key: string, callback: () => void) {
54
+ this.actions.set(key.toLowerCase(), callback);
55
+ }
56
+
57
+ private setupTui() {
58
+ process.stdout.write("\x1B[?1049h");
59
+ process.stdout.write("\x1B[2J");
60
+ process.stdout.write("\x1B[?25l");
61
+
62
+ process.stdin.setRawMode(true);
63
+ process.stdin.resume();
64
+ process.stdin.setEncoding("utf8");
65
+
66
+ process.stdin.on("data", (key: string) => {
67
+ const input = key.toLowerCase();
68
+ if (key === "\u0003" || input === "q") {
69
+ this.exit();
70
+ }
71
+ if (input === "l") {
72
+ this.logLines = [];
73
+ this.render();
74
+ }
75
+
76
+ const action = this.actions.get(input);
77
+ if (action) action();
78
+ });
79
+
80
+ process.on("SIGINT", () => this.exit());
81
+ process.on("exit", () => this.exit());
82
+
83
+ setInterval(() => this.render(), DASHBOARD_REFRESH_INTERVAL_MS);
84
+ }
85
+
86
+ public setStatus(status: string, type: 'idle' | 'building' | 'ready' | 'error' = 'idle') {
87
+ this.status = status;
88
+ switch (type) {
89
+ case 'building':
90
+ this.statusColor = C.primary;
91
+ break;
92
+ case 'ready':
93
+ this.statusColor = C.success;
94
+ break;
95
+ case 'error':
96
+ this.statusColor = C.error;
97
+ break;
98
+ default:
99
+ this.statusColor = C.gray;
100
+ }
101
+ this.render();
102
+ }
103
+
104
+ public spinner(msg: string) {
105
+ const frames = ["β ‹", "β ™", "β Ή", "β Έ", "β Ό", "β ΄", "β ¦", "β §", "β ‡", "⠏"];
106
+ let i = 0;
107
+
108
+ this.setStatus(msg, 'building');
109
+
110
+ const timer = setInterval(() => {
111
+ this.status = `${frames[i]} ${msg}`;
112
+ i = (i + 1) % frames.length;
113
+ this.render();
114
+ }, 80);
115
+
116
+ return (success = true) => {
117
+ clearInterval(timer);
118
+ if (success) {
119
+ this.setStatus('ready', 'ready');
120
+ } else {
121
+ this.setStatus('error', 'error');
122
+ }
123
+ };
124
+ }
125
+
126
+ public log(message: string) {
127
+ if (!message) return;
128
+
129
+ if (this.isTui) {
130
+ const lines = message.split("\n");
131
+ this.logLines.push(...lines);
132
+ if (this.logLines.length > MAX_LOG_SCROLLBUFFER) {
133
+ this.logLines = this.logLines.slice(-DASHBOARD_LOG_SLICE_LINES);
134
+ }
135
+ this.render();
136
+ } else {
137
+ console.log(message);
138
+ }
139
+ }
140
+
141
+ private render() {
142
+ if (!this.isTui) return;
143
+
144
+ this.maxLogLines = process.stdout.rows - 8;
145
+
146
+ const projectName = (process.cwd().split(/[/\\]/).pop() || "project").toLowerCase();
147
+ const uptime = Math.floor((Date.now() - this.startTime) / 1000);
148
+ const uptimeStr = uptime < 60 ? `${uptime}s` : `${Math.floor(uptime / 60)}m ${uptime % 60}s`;
149
+
150
+ const mem = Math.round((os.totalmem() - os.freemem()) / 1024 / 1024 / 1024 * 10) / 10;
151
+ const totalMem = Math.round(os.totalmem() / 1024 / 1024 / 1024);
152
+
153
+ // Header minimalista
154
+ let output = "\x1B[H";
155
+ output += `${C.gray}β”Œβ”€ ${C.primary}${C.bold}XAVVA${C.reset}${C.gray}.${C.reset}${projectName}${C.reset}`;
156
+ output += ` ${C.gray}β”‚${C.reset} ${this.statusColor}${this.status}${C.reset}\x1B[K\n`;
157
+
158
+ // Info bar
159
+ const infos: string[] = [];
160
+ if (this.config.project.profile) infos.push(`${C.warning}${this.config.project.profile}${C.reset}`);
161
+ if (this.gitContext?.branch) infos.push(`${C.secondary}git:${this.gitContext.branch}${C.reset}`);
162
+ infos.push(`${C.gray}mem:${mem}/${totalMem}GB${C.reset}`);
163
+ infos.push(`${C.gray}up:${uptimeStr}${C.reset}`);
164
+
165
+ output += `${C.gray}β”‚${C.reset} ${infos.join(` ${C.gray}β€’${C.reset} `)}\x1B[K\n`;
166
+ output += `${C.gray}β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€${C.reset}\x1B[K\n`;
167
+
168
+ // Logs
169
+ const visibleLogs = this.logLines.slice(-this.maxLogLines);
170
+ for (let i = 0; i < this.maxLogLines; i++) {
171
+ const line = visibleLogs[i] || "";
172
+ output += `${C.gray}β”‚${C.reset} ${line.substring(0, process.stdout.columns - 3)}\x1B[K\n`;
173
+ }
174
+
175
+ // Footer minimalista
176
+ output += `${C.gray}β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€${C.reset}\x1B[K\n`;
177
+ output += `${C.gray}β”‚${C.reset} ${C.white}[r]${C.reset}${C.gray}estart ${C.white}[l]${C.reset}${C.gray}og clear ${C.white}[q]${C.reset}${C.gray}uit${C.reset}\x1B[K\n`;
178
+ output += `${C.gray}β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜${C.reset}\x1B[K`;
179
+
180
+ process.stdout.write(output);
181
+ }
182
+
183
+ private async exit() {
184
+ this.restoreTerminal();
185
+ await ProcessManager.getInstance().shutdown(0);
186
+ }
142
187
  }
package/src/utils/ui.ts CHANGED
@@ -1,317 +1,241 @@
1
1
  import pkg from "../../package.json";
2
2
  import type { DashboardService } from "../services/DashboardService";
3
3
 
4
+ // Paleta de cores moderna e minimalista
5
+ const C = {
6
+ reset: "\x1b[0m",
7
+ bold: "\x1b[1m",
8
+ dim: "\x1b[2m",
9
+ italic: "\x1b[3m",
10
+
11
+ // Cores principais
12
+ primary: "\x1b[36m", // Cyan
13
+ primaryBright: "\x1b[96m", // Bright Cyan
14
+ secondary: "\x1b[35m", // Magenta
15
+
16
+ // Estados
17
+ success: "\x1b[32m", // Green
18
+ successBright: "\x1b[92m", // Bright Green
19
+ warning: "\x1b[33m", // Yellow
20
+ warningBright: "\x1b[93m", // Bright Yellow
21
+ error: "\x1b[31m", // Red
22
+ errorBright: "\x1b[91m", // Bright Red
23
+ info: "\x1b[34m", // Blue
24
+
25
+ // Neutros
26
+ white: "\x1b[37m",
27
+ gray: "\x1b[90m",
28
+ lightGray: "\x1b[37m",
29
+ darkGray: "\x1b[38;5;240m",
30
+ };
31
+
4
32
  export class Logger {
5
- public static readonly C = {
6
- reset: "\x1b[0m",
7
- cyan: "\x1b[36m",
8
- green: "\x1b[32m",
9
- yellow: "\x1b[33m",
10
- red: "\x1b[31m",
11
- dim: "\x1b[90m",
12
- bold: "\x1b[1m",
13
- blue: "\x1b[34m",
14
- magenta: "\x1b[35m",
15
- bgRed: "\x1b[41m",
16
- white: "\x1b[37m",
17
- gray: "\x1b[38;5;240m",
18
- lightGray: "\x1b[38;5;248m",
19
- darkGray: "\x1b[38;5;238m"
20
- };
21
-
22
- private static hotswapPluginsCount = 0;
23
- private static lastDomain = "";
24
- private static lastHotswapMsg = "";
25
- private static activeSpinnerMsg = "";
26
- private static dashboard: DashboardService | null = null;
27
-
28
- static setDashboard(dashboard: DashboardService) {
29
- this.dashboard = dashboard;
30
- }
31
-
32
- private static write(message: string, isError: boolean = false) {
33
- if (this.dashboard && this.dashboard.isTuiActive()) {
34
- this.dashboard.log(message);
35
- return;
36
- }
37
-
38
- if (this.activeSpinnerMsg) {
39
- process.stdout.write("\r\x1B[K"); // Limpa a linha do spinner
40
- }
41
-
42
- if (isError) {
43
- console.error(message + this.C.reset);
44
- } else {
45
- console.log(message + this.C.reset);
46
- }
47
-
48
- if (this.activeSpinnerMsg) {
49
- // Re-imprime o inΓ­cio da linha do spinner para o prΓ³ximo frame
50
- process.stdout.write(` ${this.C.cyan}β ‹${this.C.reset} ${this.activeSpinnerMsg}...`);
51
- }
52
- }
53
-
54
- static getGitContext(): { branch: string, author: string, hash: string } {
55
- try {
56
- const branch = Bun.spawnSync(["git", "rev-parse", "--abbrev-ref", "HEAD"]).stdout.toString().trim();
57
- const author = Bun.spawnSync(["git", "log", "-1", "--format=%an"]).stdout.toString().trim();
58
- const hash = Bun.spawnSync(["git", "rev-parse", "--short", "HEAD"]).stdout.toString().trim();
59
- return { branch, author, hash };
60
- } catch (e) {
61
- return { branch: "", author: "", hash: "" };
62
- }
63
- }
64
-
65
- static banner(command?: string, profile?: string, encoding?: string) {
66
- console.clear();
67
- const git = this.getGitContext();
68
- const name = (process.cwd().split(/[/\\]/).pop() || "PROJECT").toUpperCase();
69
- const version = `v${pkg.version}`;
70
-
71
- const mode = command?.toUpperCase() || "DEPLOY";
72
- const modeColor = mode === "DEV" ? this.C.green : this.C.blue;
73
- const modeIcon = mode === "DEV" ? "⚑" : "πŸš€";
74
-
75
- console.log("");
76
- console.log(` ${this.C.bold}${this.C.cyan}X A V V A${this.C.reset} ${this.C.dim}─${this.C.reset} ${this.C.bold}${this.C.white}${name}${this.C.reset}`);
77
-
78
- const profileInfo = profile ? ` ${this.C.dim}β€’${this.C.reset} ${this.C.yellow}♦ ${profile.toUpperCase()}${this.C.reset}` : "";
79
- const encodingInfo = encoding ? ` ${this.C.dim}β€’${this.C.reset} ${this.C.cyan}βš“ ${encoding.toUpperCase()}${this.C.reset}` : "";
80
- const gitInfo = git.branch ? `${this.C.magenta}🌿 ${git.branch}${this.C.reset} ${this.C.dim}β€’${this.C.reset} ${this.C.yellow}${git.hash}${this.C.reset}` : "";
81
- console.log(` ${this.C.dim}πŸ“¦ ${version}${profileInfo}${encodingInfo}${gitInfo ? ` ${this.C.dim}β€’${this.C.reset} ${gitInfo}` : ""}${this.C.reset}`);
82
-
83
- console.log(` ${modeColor}${this.C.bold}β¬’ ${modeIcon} ${mode} MODE${this.C.reset}`);
84
- console.log(` ${this.C.dim}─────────────────────────────────────────────────${this.C.reset}`);
85
- }
86
-
87
- static section(title: string) {
88
- this.write(`\n${this.C.bold}${this.C.blue}[${title.toUpperCase()}]${this.C.reset}`);
89
- }
90
-
91
- private static domain(name: string) {
92
- if (this.lastDomain !== name) {
93
- this.write(`\n${this.C.bold}${this.C.blue}[${name.toUpperCase()}]${this.C.reset}`);
94
- this.lastDomain = name;
95
- }
96
- }
97
-
98
- static config(label: string, value: string | number | boolean) {
99
- this.domain("config");
100
- this.info(label, value);
101
- }
102
-
103
- static info(label: string, value: string | number | boolean) {
104
- this.write(` ${this.C.lightGray}${label.padEnd(12)}${this.C.reset} : ${this.C.bold}${value}${this.C.reset}`);
105
- }
106
-
107
- static build(msg: string, status: 'start' | 'success' | 'error' | 'info' = 'success') {
108
- this.domain("build");
109
- const symbol = status === 'start' ? `${this.C.blue}β–Ά` : status === 'success' ? `${this.C.green}βœ”` : status === 'error' ? `${this.C.red}βœ–` : `${this.C.dim}β„Ή`;
110
- this.write(` ${symbol} ${this.C.reset}${msg}`);
111
- }
112
-
113
- static server(msg: string, status: 'start' | 'success' | 'error' | 'info' = 'info') {
114
- this.domain("server");
115
- const symbol = status === 'start' ? `${this.C.blue}β–Ά` : status === 'success' ? `${this.C.green}βœ”` : status === 'error' ? `${this.C.red}βœ–` : `${this.C.dim}β„Ή`;
116
- this.write(` ${symbol} ${this.C.reset}${msg}`);
117
- }
118
-
119
- static health(msg: string, status: 'success' | 'error' | 'warn' = 'success') {
120
- this.domain("health");
121
- const symbol = status === 'success' ? `${this.C.green}βœ”` : status === 'error' ? `${this.C.red}βœ–` : `${this.C.yellow}⚠`;
122
- this.write(` ${symbol} ${this.C.reset}${msg}`);
123
- }
124
-
125
- static watcher(msg: string, status: 'watch' | 'change' | 'start' | 'success' = 'success') {
126
- this.domain("watcher");
127
- const symbol = status === 'watch' ? `${this.C.magenta}πŸ‘€` : status === 'change' ? `${this.C.yellow}β–²` : status === 'start' ? `${this.C.blue}β–Ά` : `${this.C.green}βœ”`;
128
- this.write(` ${symbol} ${this.C.reset}${msg}`);
129
- }
130
-
131
- static success(msg: string) { this.write(` ${this.C.green}βœ” ${msg}`); }
132
- static error(msg: string) { this.write(` ${this.C.red}βœ– ${msg}`, true); }
133
- static warn(msg: string) { this.write(` ${this.C.yellow}⚠ ${msg}`); }
134
- static log(msg: string) { this.write(` ${msg}`); }
135
- static step(msg: string) { this.write(` ${this.C.dim}Β» ${msg}`); }
136
- static debug(msg: string) { this.write(` ${this.C.magenta}πŸ› ${msg}`); }
137
- static process(msg: string) { this.write(` ${this.C.blue}β–Ά ${msg}`); }
138
- static newline() { this.write(""); }
139
- static dim(msg: string) { this.write(` ${this.C.dim}${msg}${this.C.reset}`); }
140
-
141
- static spinner(msg: string) {
142
- if (this.dashboard && this.dashboard.isTuiActive()) {
143
- this.dashboard.log(`${this.C.cyan}β ‹${this.C.reset} ${msg}...`);
144
- return (success = true) => {
145
- if (success) {
146
- this.dashboard.log(`${this.C.green}βœ”${this.C.reset} ${msg}`);
147
- } else {
148
- this.dashboard.log(`${this.C.red}βœ–${this.C.reset} Falha em ${msg}`);
149
- }
150
- };
151
- }
152
-
153
- this.activeSpinnerMsg = msg;
154
- const frames = ["β ‹", "β ™", "β Ή", "β Έ", "β Ό", "β ΄", "β ¦", "β §", "β ‡", "⠏"];
155
- let i = 0;
156
- process.stdout.write("\x1B[?25l");
157
-
158
- const timer = setInterval(() => {
159
- process.stdout.write(`\r ${this.C.cyan}${frames[i]}${this.C.reset} ${msg}...`);
160
- i = (i + 1) % frames.length;
161
- }, 80);
162
-
163
- return (success = true) => {
164
- clearInterval(timer);
165
- this.activeSpinnerMsg = "";
166
- process.stdout.write("\r\x1B[K");
167
- process.stdout.write("\x1B[?25h");
168
- if (success) {
169
- this.write(` ${this.C.green}βœ”${this.C.reset} ${msg}`);
170
- } else {
171
- this.error(`Falha em ${msg}`);
172
- }
173
- };
174
- }
175
-
176
- static isSystemNoise(line: string): boolean {
177
- const noise = [
178
- "Using CATALINA_", "Using JRE_HOME", "Using CLASSPATH", "NOTE: Picked up JDK_JAVA_OPTIONS",
179
- "Command line argument", "VersionLoggerListener", "Scanning for projects...",
180
- "Building ", "--- ", "+++ ", "DEBUG: ", "org.apache.catalina.startup.VersionLoggerListener",
181
- "org.apache.catalina.core.AprLifecycleListener", "org.apache.coyote.AbstractProtocol.init",
182
- "org.apache.catalina.startup.Catalina.load", "Arquivos processados em",
183
- "org.apache.jasper.servlet.TldScanner.scanJars", "Listening for transport dt_socket",
184
- "org.apache.catalina.startup.ExpandWar.expand", "org.apache.catalina.startup.ContextConfig.configureStart",
185
- "SLF4J: ", "org.glassfish.jersey.internal.Errors.logErrors", "contains empty path annotation",
186
- "org.apache.catalina.core.StandardContext.setPath", "milliseconds",
187
- "org.apache.catalina.startup.HostConfig.deployWAR", "org.apache.catalina.startup.HostConfig.deployDirectory",
188
- "Deployment of web application", "Deploying web application archive", "at org.apache",
189
- "Registering directory", "initialized in ClassLoader", "Discovered plugins:",
190
- "enhanced with plugin initialization", "registerJerseyContainer", "JasperLoader@",
191
- "Hotswap ready (Plugins:", "autoHotswap.delay", "watchResources=false",
192
- "org.apache.catalina.webresources.Cache.getResource", "insufficient free space available"
193
- ];
194
- return noise.some(n => line.includes(n));
195
- }
196
-
197
- static isEssential(line: string): boolean {
198
- return line.includes("SEVERE") || line.includes("ERROR") || line.includes("Exception") ||
199
- line.includes("Caused by") || line.includes("at ") || line.includes("... ") ||
200
- line.includes("Server startup in") || line.includes("HOTSWAP AGENT:");
201
- }
202
-
203
- static summarize(line: string): string {
204
- if (this.isSystemNoise(line)) return "";
205
-
206
- const startupMatch = line.match(/Server startup in (\[?)(.*?)(\]?)\s*ms/);
207
- if (startupMatch) {
208
- const time = (parseInt(startupMatch[2]) / 1000).toFixed(1);
209
- this.domain("server");
210
- return `${this.C.green}βœ” ${this.C.bold}Server started in ${time}s`;
211
- }
212
-
213
- const deployMatch = line.match(/Deployment of web application archive \[(.*?)\] has finished in \[(.*?)\] ms/);
214
- if (deployMatch) {
215
- this.domain("build");
216
- return `${this.C.green}βœ” Artifacts deployed`;
217
- }
218
-
219
- const hotswapPattern = /HOTSWAP AGENT:.*? (INFO|WARN|ERROR|RELOAD) (.*?) - (.*)/;
220
- const hotswapMatch = line.match(hotswapPattern);
221
- if (hotswapMatch) {
222
- const level = hotswapMatch[1];
223
- let msg = hotswapMatch[3];
224
-
225
- if (msg.includes("plugin initialized")) {
226
- this.hotswapPluginsCount++;
227
- return "";
228
- }
229
-
230
- if (msg.includes("redefinition") || msg.includes("reloaded") || level === 'RELOAD') {
231
- if (msg.includes("Reloading classes [")) {
232
- const classes = msg.match(/\[(.*?)\]/)?.[1] || "";
233
- const classCount = classes.split(",").length;
234
- if (classCount > 3) msg = `Reloading ${classCount} classes...`;
235
- }
236
-
237
- if (msg === this.lastHotswapMsg) return "";
238
- this.lastHotswapMsg = msg;
239
-
240
- this.watcher(`Hotswap: ${msg.replace(/Class '.*?'/, (m) => this.C.bold + m + this.C.reset)}`, 'success');
241
- return "";
242
- }
243
-
244
- if (msg.includes("Loading Hotswap agent")) {
245
- this.domain("server");
246
- return `${this.C.blue}β–Ά ${this.C.reset}Initializing Hotswap Agent ${msg.match(/\d+\.\d+\.\d+/)?.[0] || ""}`;
247
- }
248
-
249
- if (this.hotswapPluginsCount > 0) {
250
- const count = this.hotswapPluginsCount;
251
- this.hotswapPluginsCount = 0;
252
- this.domain("server");
253
- this.write(` ${this.C.green}βœ” ${this.C.reset}Hotswap ready (Plugins: ${count} loaded)`);
254
- }
255
-
256
- let color = this.C.cyan;
257
- let symbol = "●";
258
- if (level === "WARN") { color = this.C.yellow; symbol = "β–²"; }
259
- else if (level === "ERROR") { color = this.C.red; symbol = "βœ–"; }
260
-
261
- this.domain("server");
262
- return `${color}${symbol} ${this.C.bold}Hotswap:${this.C.reset} ${msg}`;
263
- }
264
-
265
- if (line.includes("java.lang.UnsupportedOperationException") && (line.includes("add a method") || line.includes("change the schema"))) {
266
- this.domain("watcher");
267
- this.write(` ${this.C.red}βœ– ${this.C.bold}Hotswap Falhou:${this.C.reset} MudanΓ§a estrutural detectada (novo mΓ©todo/campo).`);
268
- this.write(` ${this.C.yellow}πŸ’‘ Dica: Sua JVM atual nΓ£o suporta mudar a estrutura da classe. Reinicie o servidor para aplicar.`);
269
- return "";
270
- }
271
-
272
- const tomcatPattern = /^(\d{2}-\w{3}-\d{4} \d{2}:\d{2}:\d{2}\.\d{3})\s+(INFO|WARNING|SEVERE|ERROR)\s+\[(.*?)\]\s+(.*)$/;
273
- const tMatch = line.match(tomcatPattern);
274
- if (tMatch) {
275
- const label = tMatch[2];
276
- let msg = tMatch[4].trim();
277
- if (this.isSystemNoise(msg)) return "";
278
- let color = this.C.dim;
279
- let symbol = "β„Ή";
280
- if (label === "WARNING") { color = this.C.yellow; symbol = "β–²"; }
281
- else if (label === "SEVERE" || label === "ERROR") { color = this.C.red; symbol = "βœ–"; }
282
- msg = msg.replace(/^(org\.apache|com\.sun|java\..*?|org\.glassfish)\.[a-zA-Z0-9.]+\s/, "").trim();
283
- if (!msg) return "";
284
- return `${color}${symbol} ${msg}`;
285
- }
286
-
287
- const compilationErrorMatch = line.match(/^\[ERROR\]\s+(.*\.java):\[(\d+),(\d+)\]\s+(.*)$/);
288
- if (compilationErrorMatch) {
289
- const [_, filePath, row, col, msg] = compilationErrorMatch;
290
- const fileName = filePath.split(/[/\\]/).pop();
291
- return `${this.C.red}βœ– ERROR ${this.C.reset}${this.C.dim}em ${this.C.reset}${this.C.bold}${fileName}${this.C.reset}${this.C.dim}:${row}${this.C.reset} ${this.C.red}➜ ${this.C.reset}${msg}`;
292
- }
293
-
294
- const logPattern = /^\[(INFO|WARNING|WARN|SEVERE|ERROR)\]\s+(.*)$/;
295
- const match = line.match(logPattern);
296
- if (match) {
297
- const label = match[1];
298
- let msg = match[2].trim();
299
- if (msg.includes("Total time:") || msg.includes("Finished at:") || msg.includes("Final Memory:") || msg.includes("-----------------------")) return "";
300
- let color = this.C.dim;
301
- let symbol = "β„Ή";
302
- if (label === "WARNING") { color = this.C.yellow; symbol = "β–²"; }
303
- else if (label === "SEVERE" || label === "ERROR") { color = this.C.red; symbol = "βœ–"; }
304
- msg = msg.replace(/^(org\.apache|com\.sun|java\..*?)\.[a-zA-Z0-9.]+\s/, "").trim();
305
- if (!msg || msg === "]" || msg.includes("Compilation failure")) return "";
306
- return `${color}${symbol} ${msg}`;
307
- }
308
-
309
- if (line.includes("Exception") || line.includes("Caused by") || line.includes("at ")) {
310
- const trimmed = line.trim();
311
- const color = (trimmed.includes("org.apache") || trimmed.includes("java.base") || trimmed.includes("sun.reflect")) ? this.C.dim : this.C.yellow;
312
- return ` ${color}${trimmed}`;
313
- }
314
-
315
- return "";
316
- }
33
+ public static readonly C = C;
34
+ private static dashboard: DashboardService | null = null;
35
+ private static activeSpinner: { stop: (success?: boolean) => void } | null = null;
36
+ private static lastDomain = "";
37
+
38
+ static setDashboard(dashboard: DashboardService) {
39
+ this.dashboard = dashboard;
40
+ }
41
+
42
+ // Banner moderno e clean
43
+ static banner(command?: string, profile?: string, encoding?: string) {
44
+ console.clear();
45
+ const git = this.getGitContext();
46
+ const name = process.cwd().split(/[/\\]/).pop() || "project";
47
+
48
+ // Linha superior decorativa
49
+ console.log(`${C.gray}β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”${C.reset}`);
50
+
51
+ // Logo e projeto
52
+ console.log(`${C.gray}β”‚${C.reset} ${C.primary}${C.bold}XAVVA${C.reset}${C.gray}.${C.reset}${C.dim}v${pkg.version}${C.reset} ${C.gray}β”‚${C.reset} ${C.white}${C.bold}${name}${C.reset}`);
53
+
54
+ // Info adicional em uma linha
55
+ const parts: string[] = [];
56
+ if (command) parts.push(`${C.primary}${command}${C.reset}`);
57
+ if (profile) parts.push(`${C.warning}profile:${profile}${C.reset}`);
58
+ if (encoding) parts.push(`${C.info}${encoding}${C.reset}`);
59
+ if (git.branch) parts.push(`${C.secondary}git:${git.branch}${C.reset}`);
60
+
61
+ if (parts.length > 0) {
62
+ console.log(`${C.gray}β”‚${C.reset} ${C.dim}mode${C.reset} ${C.gray}β”‚${C.reset} ${parts.join(` ${C.gray}β€’${C.reset} `)}`);
63
+ }
64
+
65
+ // Linha inferior
66
+ console.log(`${C.gray}β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜${C.reset}`);
67
+ console.log();
68
+ }
69
+
70
+ // SeΓ§Γ΅es com divisΓ³rias clean
71
+ static section(title: string) {
72
+ console.log(`${C.gray}β”Œβ”€ ${C.white}${C.bold}${title}${C.reset}`);
73
+ }
74
+
75
+ static endSection() {
76
+ console.log(`${C.gray}β””${C.reset}`);
77
+ }
78
+
79
+ // ConfiguraΓ§Γ΅es em formato chave: valor alinhado
80
+ static config(label: string, value: string | number | boolean) {
81
+ const valueStr = String(value);
82
+ const isBool = typeof value === 'boolean';
83
+ const displayValue = isBool
84
+ ? (value ? `${C.successBright}βœ“${C.reset} ${C.success}enabled${C.reset}` : `${C.gray}β—‹${C.reset} ${C.gray}disabled${C.reset}`)
85
+ : `${C.white}${valueStr}${C.reset}`;
86
+
87
+ console.log(`${C.gray}β”‚${C.reset} ${C.dim}${label.padEnd(12)}${C.reset} ${C.gray}:${C.reset} ${displayValue}`);
88
+ }
89
+
90
+ // Status com Γ­cones minimalistas
91
+ static ready(msg: string) {
92
+ console.log(`${C.gray}β”‚${C.reset} ${C.success}●${C.reset} ${msg}`);
93
+ }
94
+
95
+ static info(label: string, value?: string) {
96
+ if (value) {
97
+ console.log(`${C.gray}β”‚${C.reset} ${C.dim}${label}${C.reset} ${C.gray}:${C.reset} ${C.white}${value}${C.reset}`);
98
+ } else {
99
+ console.log(`${C.gray}β”‚${C.reset} ${C.info}β„Ή${C.reset} ${label}`);
100
+ }
101
+ }
102
+
103
+ static success(msg: string) {
104
+ console.log(`${C.gray}β”‚${C.reset} ${C.success}βœ“${C.reset} ${msg}`);
105
+ }
106
+
107
+ static error(msg: string) {
108
+ console.log(`${C.gray}β”‚${C.reset} ${C.error}βœ—${C.reset} ${C.error}${msg}${C.reset}`);
109
+ }
110
+
111
+ static warn(msg: string) {
112
+ console.log(`${C.gray}β”‚${C.reset} ${C.warning}⚠${C.reset} ${msg}`);
113
+ }
114
+
115
+ static build(msg: string) {
116
+ console.log(`${C.gray}β”‚${C.reset} ${C.primary}β–Έ${C.reset} ${C.dim}build${C.reset} ${C.gray}:${C.reset} ${msg}`);
117
+ }
118
+
119
+ static server(msg: string) {
120
+ console.log(`${C.gray}β”‚${C.reset} ${C.primary}β–Έ${C.reset} ${C.dim}server${C.reset} ${C.gray}:${C.reset} ${msg}`);
121
+ }
122
+
123
+ static watch(msg: string) {
124
+ console.log(`${C.gray}β”‚${C.reset} ${C.secondary}β—‰${C.reset} ${C.dim}watch${C.reset} ${C.gray}:${C.reset} ${msg}`);
125
+ }
126
+
127
+ static hotswap(msg: string) {
128
+ console.log(`${C.gray}β”‚${C.reset} ${C.secondary}↻${C.reset} ${C.dim}hotswap${C.reset} ${C.gray}:${C.reset} ${msg}`);
129
+ }
130
+
131
+ // URL formatada de forma destacada
132
+ static url(label: string, url: string) {
133
+ console.log(`${C.gray}β”‚${C.reset} ${C.dim}${label}${C.reset} ${C.gray}:${C.reset} ${C.primaryBright}${C.bold}${url}${C.reset}`);
134
+ }
135
+
136
+ // Spinner moderno
137
+ static spinner(msg: string) {
138
+ if (this.dashboard?.isTuiActive()) {
139
+ return this.dashboard.spinner(msg);
140
+ }
141
+
142
+ const frames = ["β ‹", "β ™", "β Ή", "β Έ", "β Ό", "β ΄", "β ¦", "β §", "β ‡", "⠏"];
143
+ let i = 0;
144
+
145
+ process.stdout.write(`${C.gray}β”‚${C.reset} `);
146
+ process.stdout.write("\x1B[?25l");
147
+
148
+ const timer = setInterval(() => {
149
+ process.stdout.write(`\r${C.gray}β”‚${C.reset} ${C.primary}${frames[i]}${C.reset} ${C.dim}${msg}${C.reset}`);
150
+ i = (i + 1) % frames.length;
151
+ }, 80);
152
+
153
+ return (success = true) => {
154
+ clearInterval(timer);
155
+ process.stdout.write("\x1B[?25h");
156
+ if (success) {
157
+ console.log(`\r${C.gray}β”‚${C.reset} ${C.success}βœ“${C.reset} ${msg}`);
158
+ } else {
159
+ console.log(`\r${C.gray}β”‚${C.reset} ${C.error}βœ—${C.reset} ${C.error}${msg}${C.reset}`);
160
+ }
161
+ };
162
+ }
163
+
164
+ // Linha em branco
165
+ static newline() {
166
+ console.log();
167
+ }
168
+
169
+ // DivisΓ³ria simples
170
+ static divider() {
171
+ console.log(`${C.gray}β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€${C.reset}`);
172
+ }
173
+
174
+ // FinalizaΓ§Γ£o
175
+ static done() {
176
+ console.log(`${C.gray}β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜${C.reset}`);
177
+ console.log();
178
+ }
179
+
180
+ // Helper para contexto git
181
+ static getGitContext(): { branch: string; author: string; hash: string } {
182
+ try {
183
+ const branch = Bun.spawnSync(["git", "rev-parse", "abbrev-ref", "HEAD"]).stdout.toString().trim();
184
+ const author = Bun.spawnSync(["git", "log", "-1", "format=%an"]).stdout.toString().trim();
185
+ const hash = Bun.spawnSync(["git", "rev-parse", "--short", "HEAD"]).stdout.toString().trim();
186
+ return { branch, author, hash };
187
+ } catch {
188
+ return { branch: "", author: "", hash: "" };
189
+ }
190
+ }
191
+
192
+ // Filtros de noise (mantidos)
193
+ static isSystemNoise(line: string): boolean {
194
+ const noise = [
195
+ "Using CATALINA_", "Using JRE_HOME", "Using CLASSPATH",
196
+ "Scanning for projects...", "Building ", "--- ", "+++ ",
197
+ "Arquivos processados em", "milliseconds",
198
+ "SLF4J: ", "Discovered plugins:",
199
+ "enhanced with plugin initialization", "Hotswap ready",
200
+ "autoHotswap.delay", "watchResources=false",
201
+ ];
202
+ return noise.some(n => line.includes(n));
203
+ }
204
+
205
+ // SumarizaΓ§Γ£o de logs do Tomcat (simplificada)
206
+ static summarize(line: string): string {
207
+ if (this.isSystemNoise(line)) return "";
208
+
209
+ // Server startup
210
+ const startupMatch = line.match(/Server startup in.*?([\d,]+)\s*ms/);
211
+ if (startupMatch) {
212
+ const time = startupMatch[1].replace(",", "");
213
+ const seconds = (parseInt(time) / 1000).toFixed(1);
214
+ return `${C.success}ready ${C.gray}in ${C.white}${seconds}s${C.reset}`;
215
+ }
216
+
217
+ // Hotswap
218
+ if (line.includes("HOTSWAP AGENT") && line.includes("RELOAD")) {
219
+ return `${C.secondary}↻ hotswap ${C.gray}detected${C.reset}`;
220
+ }
221
+
222
+ // Erros de compilaΓ§Γ£o
223
+ const compilationError = line.match(/\[ERROR\].*?(\w+\.java):\[(\d+).*?\]\s*(.+)/);
224
+ if (compilationError) {
225
+ const [, file, lineNum, msg] = compilationError;
226
+ return `${C.error}βœ— ${C.white}${file}${C.gray}:${lineNum}${C.reset} ${C.gray}${msg.slice(0, 50)}${C.reset}`;
227
+ }
228
+
229
+ // Erros SEVERE
230
+ if (line.includes("SEVERE") || line.includes("Exception")) {
231
+ return `${C.error}βœ— ${C.gray}${line.slice(0, 80)}${C.reset}`;
232
+ }
233
+
234
+ // Warnings
235
+ if (line.includes("WARNING")) {
236
+ return `${C.warning}⚠ ${C.gray}${line.slice(0, 80)}${C.reset}`;
237
+ }
238
+
239
+ return "";
240
+ }
317
241
  }