@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 +1 -204
- package/package.json +1 -1
- package/src/commands/DeployCommand.ts +33 -28
- package/src/services/DashboardService.ts +178 -133
- package/src/utils/ui.ts +236 -312
package/README.md
CHANGED
|
@@ -1,204 +1 @@
|
|
|
1
|
-
# XAVVA CLI π
|
|
2
|
-
|
|
3
|
-
> Ultra-fast development toolkit for Java Enterprise (Tomcat) on Windows
|
|
4
|
-
|
|
5
|
-
[](https://github.com/leorsousa05/Xavva)
|
|
6
|
-
[](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[](https://github.com/leorsousa05/Xavva)[](LICENSE)Xavva is a high-performance CLI built with **Bun** that transforms the Java/Tomcat development experience. It brings modern development workflows (like Node.js/Vite) to the Java Enterprise ecosystem with hot-reload, smart logging, and automated deployment.---## β¨ Features- β‘ **Hot Reload** β Incremental compilation and class injection without server restart- π **Interactive Dashboard** β Real-time TUI with system metrics and shortcuts- π§ **Smart Log Analyzer** β Stack trace folding and root cause highlighting- π **Security Audit** β Automated vulnerability scanning via OSV.dev- π¦ **Dependency Analysis** β Detect conflicts and outdated dependencies- π― **Maven & Gradle** β Native support for both build tools- π§ **Auto-Healing** β Automatic diagnosis and repair of common issues---## π¦ Installation```powershell# Via NPMnpm install -g @archznn/xavva# Or run directly with Bunbunx @archznn/xavva dev```---## π Quick Start```bash# Start development mode with dashboardxavva dev --tui# Deploy to Tomcatxavva deploy# Analyze dependencies for issuesxavva deps# Check for security vulnerabilitiesxavva audit```---## π Commands### Core Development| Command | Description ||---------|-------------|| `xavva dev` | Full development mode (build + deploy + watch + debug) || `xavva deploy` | Build and deploy application to Tomcat || `xavva build` | Compile project only || `xavva start` | Start Tomcat server only |### Code Execution| Command | Description ||---------|-------------|| `xavva run <class>` | Execute a Java class with automatic classpath || `xavva debug <class>` | Debug a Java class (port 5005) |### Analysis & Monitoring| Command | Description ||---------|-------------|| `xavva logs` | Stream and analyze Tomcat logs in real-time || `xavva deps` | **Analyze dependencies** β detect conflicts, find updates || `xavva audit` | Security audit of JAR files via OSV.dev || `xavva doctor` | Diagnose environment issues (JAVA_HOME, DCEVM) || `xavva profiles` | List available Maven/Gradle profiles || `xavva docs` | Generate endpoint documentation |---## π Dependency AnalysisThe `xavva deps` command provides comprehensive dependency analysis:```bash# Basic analysisxavva deps# With verbose output for debuggingxavva deps --verbose# Show fix suggestions for conflictsxavva deps --fix# Export report as JSONxavva deps --output report.json# Fail on critical conflicts (useful in CI/CD)xavva deps --strict```### What it detects:- β οΈ **Version Conflicts** β Same dependency with different versions- β¬οΈ **Available Updates** β Newer versions in Maven Central- π΄ **Major Updates** β Breaking changes that need attention- π **Statistics** β Direct vs transitive dependencies### Sample output:```ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββπ DEPENDENCY ANALYSISββββββββββββββββββββββββββββββββββββββββββββββββββββββββββStatistics: Total: 183 dependencies Direct: 45 | Transitive: 138β οΈ VERSION CONFLICTS (2) β com.fasterxml.jackson.core:jackson-databind Versions: 2.13.0, 2.12.6β¬οΈ UPDATES AVAILABLE (5) β org.postgresql:postgresql 42.2.5 β 42.7.1β οΈ MAJOR UPDATES (1) ! org.springframework.boot:spring-boot-starter 2.5.0 β 3.1.0```---## βοΈ ConfigurationCreate `xavva.json` in your project root:```json{ "project": { "appName": "my-application", "buildTool": "maven", "profile": "dev", "tui": false }, "tomcat": { "path": "C:/apache-tomcat", "port": 8080 }}```### CLI Options| Option | Description ||--------|-------------|| `-p, --path <path>` | Tomcat installation path || `-t, --tool <tool>` | Build tool: `maven` or `gradle` || `-n, --name <name>` | Application name (WAR context) || `--port <port>` | Tomcat port (default: 8080) || `-P, --profile <prof>` | Maven/Gradle profile || `-e, --encoding <enc>` | Source encoding (utf8, cp1252) || `-w, --watch` | Enable file watching || `--tui` | Interactive dashboard mode || `-d, --debug` | Enable JPDA debugger || `-c, --clean` | Clean logs before start || `-s, --no-build` | Skip initial build || `-V, --verbose` | Detailed output |---## ποΈ ArchitectureXavva uses a modular service-oriented architecture:- **DashboardService** β TUI management and interactivity- **LogAnalyzer** β Intelligent log processing- **DependencyAnalyzerService** β Dependency conflict detection- **ProjectService** β Project structure discovery- **BuildService** β Maven/Gradle integration- **TomcatService** β Server lifecycle management---## π€ ContributingContributions are welcome! Please feel free to submit a Pull Request.---## π LicenseMIT License β see [LICENSE](LICENSE) for details.---<p align="center"> <sub>Built with β€οΈ for Java developers who miss modern tooling</sub></p>
|
package/package.json
CHANGED
|
@@ -20,7 +20,7 @@ export class DeployCommand implements Command {
|
|
|
20
20
|
if (!incremental) {
|
|
21
21
|
this.logConfiguration(config, isWatching);
|
|
22
22
|
} else {
|
|
23
|
-
Logger.
|
|
23
|
+
Logger.watch("Change detected");
|
|
24
24
|
}
|
|
25
25
|
|
|
26
26
|
try {
|
|
@@ -31,16 +31,16 @@ export class DeployCommand implements Command {
|
|
|
31
31
|
await tomcat.clearWebapps();
|
|
32
32
|
|
|
33
33
|
if (!config.project.skipBuild) {
|
|
34
|
-
Logger.
|
|
34
|
+
Logger.build("compiling...");
|
|
35
35
|
await builder.runBuild(incremental);
|
|
36
36
|
}
|
|
37
37
|
|
|
38
38
|
if (!config.project.skipBuild) {
|
|
39
|
-
Logger.build("
|
|
39
|
+
Logger.build("completed");
|
|
40
40
|
}
|
|
41
41
|
} else {
|
|
42
42
|
if (!config.project.skipBuild) {
|
|
43
|
-
Logger.
|
|
43
|
+
Logger.build("incremental compile...");
|
|
44
44
|
await builder.runBuild(incremental);
|
|
45
45
|
}
|
|
46
46
|
}
|
|
@@ -50,13 +50,13 @@ export class DeployCommand implements Command {
|
|
|
50
50
|
const actualContextPath = contextPath || actualAppFolder || "";
|
|
51
51
|
const actualAppUrl = `http://localhost:${config.tomcat.port}/${actualContextPath}`;
|
|
52
52
|
await BrowserService.reload(actualAppUrl);
|
|
53
|
-
Logger.
|
|
53
|
+
Logger.success("redeploy completed");
|
|
54
54
|
return;
|
|
55
55
|
}
|
|
56
56
|
|
|
57
|
-
Logger.
|
|
57
|
+
Logger.server("cleaning webapps...");
|
|
58
58
|
const artifactInfo = await builder.deployToWebapps();
|
|
59
|
-
Logger.
|
|
59
|
+
Logger.server("artifacts ready");
|
|
60
60
|
|
|
61
61
|
const finalContextPath = contextPath || artifactInfo.finalName.replace(".war", "");
|
|
62
62
|
const appWebappPath = path.join(config.tomcat.path, "webapps", finalContextPath);
|
|
@@ -65,7 +65,7 @@ export class DeployCommand implements Command {
|
|
|
65
65
|
// Se Γ© um diretΓ³rio (exploded), sincronizamos o conteΓΊdo total para a pasta do webapps
|
|
66
66
|
if (!fs.existsSync(appWebappPath)) fs.mkdirSync(appWebappPath, { recursive: true });
|
|
67
67
|
await builder.syncExploded(artifactInfo.path, appWebappPath);
|
|
68
|
-
Logger.
|
|
68
|
+
Logger.server("synced exploded directory");
|
|
69
69
|
} else {
|
|
70
70
|
if (!fs.existsSync(appWebappPath)) fs.mkdirSync(appWebappPath, { recursive: true });
|
|
71
71
|
|
|
@@ -75,7 +75,7 @@ export class DeployCommand implements Command {
|
|
|
75
75
|
if (!webappStat || artifactStat.mtimeMs > webappStat.mtimeMs) {
|
|
76
76
|
try {
|
|
77
77
|
Bun.spawnSync(["jar", "xf", artifactInfo.path], { cwd: appWebappPath });
|
|
78
|
-
Logger.
|
|
78
|
+
Logger.server("extracted WAR");
|
|
79
79
|
} catch (e) {
|
|
80
80
|
const extractCmd = `Expand-Archive -Path $env:ARTIFACT_PATH -DestinationPath $env:DEST_PATH -Force`;
|
|
81
81
|
Bun.spawnSync(["powershell", "-command", extractCmd], {
|
|
@@ -85,10 +85,10 @@ export class DeployCommand implements Command {
|
|
|
85
85
|
DEST_PATH: appWebappPath
|
|
86
86
|
}
|
|
87
87
|
});
|
|
88
|
-
Logger.
|
|
88
|
+
Logger.server("extracted WAR (legacy)");
|
|
89
89
|
}
|
|
90
90
|
} else {
|
|
91
|
-
Logger.
|
|
91
|
+
Logger.server("webapp up to date");
|
|
92
92
|
}
|
|
93
93
|
}
|
|
94
94
|
|
|
@@ -110,10 +110,11 @@ export class DeployCommand implements Command {
|
|
|
110
110
|
}
|
|
111
111
|
|
|
112
112
|
private logConfiguration(config: AppConfig, isWatching: boolean) {
|
|
113
|
-
Logger.
|
|
114
|
-
|
|
115
|
-
Logger.config("
|
|
116
|
-
Logger.config("
|
|
113
|
+
Logger.section("Configuration");
|
|
114
|
+
Logger.config("runtime", config.project.buildTool.toLowerCase());
|
|
115
|
+
if (config.project.profile) Logger.config("profile", config.project.profile);
|
|
116
|
+
Logger.config("watch", isWatching);
|
|
117
|
+
Logger.config("debug", config.project.debug ? `port ${config.project.debugPort}` : false);
|
|
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("
|
|
130
|
+
Logger.config("hotswap", "standard");
|
|
130
131
|
} else if (hasDcevm) {
|
|
131
|
-
Logger.config("
|
|
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("
|
|
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.
|
|
175
|
-
Logger.
|
|
176
|
-
Logger.
|
|
176
|
+
Logger.divider();
|
|
177
|
+
Logger.ready("Server ready");
|
|
178
|
+
Logger.url("Local", url);
|
|
179
|
+
Logger.info("Status", `${response.status}`);
|
|
180
|
+
Logger.info("Memory", memory);
|
|
181
|
+
Logger.done();
|
|
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.
|
|
193
|
+
Logger.warn(`App returned status ${response.status}`);
|
|
189
194
|
}
|
|
190
195
|
} catch (e) {
|
|
191
|
-
Logger.
|
|
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.
|
|
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.
|
|
210
|
+
uniqueApiUrls.forEach(url => Logger.info("", url));
|
|
207
211
|
}
|
|
208
212
|
|
|
209
213
|
if (jsps.length > 0) {
|
|
210
|
-
Logger.
|
|
214
|
+
Logger.info("JSPs", "");
|
|
211
215
|
const uniqueJspUrls = [...new Set(jsps.map(e => `http://localhost:${port}${e.fullPath}`))];
|
|
212
|
-
uniqueJspUrls.forEach(url => Logger.
|
|
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
|
-
|
|
5
|
-
|
|
6
|
-
|
|
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
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
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
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
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
|
}
|