@archznn/xavva 2.2.1 β†’ 2.3.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,3 +1,245 @@
1
- # XAVVA CLI πŸš€> Ultra-fast development toolkit for Java Enterprise (Tomcat) on Windows[![Version](https://img.shields.io/badge/version-2.2.1-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
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.3.0-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
+ - 🐱 **Embedded Tomcat** β€” Auto-install Tomcat, no manual setup needed
22
+ - πŸ“¦ **WAR Generation** β€” Build as .war file or exploded directory
23
+
24
+ ---
25
+
26
+ ## πŸ“¦ Installation
27
+
28
+ ```powershell
29
+ # Via NPM
30
+ npm install -g @archznn/xavva
31
+
32
+ # Or run directly with Bun
33
+ bunx @archznn/xavva dev
34
+ ```
35
+
36
+ ---
37
+
38
+ ## πŸš€ Quick Start
39
+
40
+ ```bash
41
+ # Start development mode with dashboard
42
+ xavva dev --tui
43
+
44
+ # Deploy to Tomcat
45
+ xavva deploy
46
+
47
+ # Build and deploy as .war file
48
+ xavva deploy --war
49
+
50
+ # Analyze dependencies for issues
51
+ xavva deps
52
+
2
53
  # Update safe dependencies (non-breaking)
3
- xavva deps --update-safe# 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>
54
+ xavva deps --update-safe
55
+
56
+ # Check for security vulnerabilities
57
+ xavva audit
58
+
59
+ # Use embedded Tomcat (auto-install)
60
+ xavva dev --yes
61
+ ```
62
+
63
+ ---
64
+
65
+ ## πŸ“– Commands
66
+
67
+ ### Core Development
68
+
69
+ | Command | Description |
70
+ | -------------- | ------------------------------------------------------ |
71
+ | `xavva dev` | Full development mode (build + deploy + watch + debug) |
72
+ | `xavva deploy` | Build and deploy application to Tomcat |
73
+ | `xavva build` | Compile project only |
74
+ | `xavva start` | Start Tomcat server only |
75
+
76
+ ### Code Execution
77
+
78
+ | Command | Description |
79
+ | --------------------- | --------------------------------------------- |
80
+ | `xavva run <class>` | Execute a Java class with automatic classpath |
81
+ | `xavva debug <class>` | Debug a Java class (port 5005) |
82
+
83
+ ### Analysis & Monitoring
84
+
85
+ | Command | Description |
86
+ | ---------------- | --------------------------------------------------------- |
87
+ | `xavva logs` | Stream and analyze Tomcat logs in real-time |
88
+ | `xavva deps` | **Analyze dependencies** β€” detect conflicts, find updates |
89
+ | `xavva audit` | Security audit of JAR files via OSV.dev |
90
+ | `xavva doctor` | Diagnose environment issues (JAVA_HOME, DCEVM) |
91
+ | `xavva profiles` | List available Maven/Gradle profiles |
92
+ | `xavva docs` | Generate endpoint documentation |
93
+ | `xavva tomcat` | Manage embedded Tomcat installations |
94
+
95
+ ---
96
+
97
+ ## 🐱 Embedded Tomcat
98
+
99
+ Xavva can automatically download and manage a Tomcat installation for you:
100
+
101
+ ```bash
102
+ # First time usage - auto-install Tomcat
103
+ xavva dev --yes
104
+
105
+ # Or install manually
106
+ xavva tomcat install
107
+
108
+ # Check Tomcat status
109
+ xavva tomcat status
110
+
111
+ # List available versions
112
+ xavva tomcat list
113
+
114
+ # Use specific version
115
+ xavva dev --tomcat-version 9.0.115
116
+ ```
117
+
118
+ ---
119
+
120
+ ## πŸ” Dependency Analysis
121
+
122
+ The `xavva deps` command provides comprehensive dependency analysis:
123
+
124
+ ```bash
125
+ # Basic analysis
126
+ xavva deps
127
+
128
+ # With verbose output for debugging
129
+ xavva deps --verbose
130
+
131
+ # Update safe dependencies only (non-breaking)
132
+ xavva deps --update-safe
133
+
134
+ # Show fix suggestions for conflicts
135
+ xavva deps --fix
136
+
137
+ # Export report as JSON
138
+ xavva deps --output report.json
139
+
140
+ # Fail on critical conflicts (useful in CI/CD)
141
+ xavva deps --strict
142
+ ```
143
+
144
+ ### What it detects:
145
+
146
+ - ⚠️ **Version Conflicts** β€” Same dependency with different versions
147
+ - ⬆️ **Available Updates** β€” Newer versions in Maven Central
148
+ - πŸ”΄ **Major Updates** β€” Breaking changes that need attention
149
+ - πŸ“Š **Statistics** β€” Direct vs transitive dependencies
150
+
151
+ ### Sample output:
152
+
153
+ ```
154
+ ══════════════════════════════════════════════════════════
155
+ πŸ“Š DEPENDENCY ANALYSIS
156
+ ══════════════════════════════════════════════════════════
157
+
158
+ Statistics:
159
+ Total: 183 dependencies
160
+ Direct: 45 | Transitivas: 138
161
+
162
+ ⚠️ VERSION CONFLICTS (2)
163
+ βœ– com.fasterxml.jackson.core:jackson-databind
164
+ Versions: 2.13.0, 2.12.6
165
+
166
+ ⬆️ UPDATES AVAILABLE (5)
167
+ ↑ org.postgresql:postgresql
168
+ 42.2.5 β†’ 42.7.1
169
+
170
+ ⚠️ MAJOR UPDATES (1)
171
+ ! org.springframework.boot:spring-boot-starter
172
+ 2.5.0 β†’ 3.1.0
173
+ ```
174
+
175
+ ---
176
+
177
+ ## βš™οΈ Configuration
178
+
179
+ Create `xavva.json` in your project root:
180
+
181
+ ```json
182
+ {
183
+ "project": {
184
+ "appName": "my-application",
185
+ "buildTool": "maven",
186
+ "profile": "dev",
187
+ "tui": false
188
+ },
189
+ "tomcat": {
190
+ "path": "C:/apache-tomcat",
191
+ "port": 8080
192
+ }
193
+ }
194
+ ```
195
+
196
+ ### CLI Options
197
+
198
+ | Option | Description |
199
+ | ---------------------- | ------------------------------- |
200
+ | `-p, --path <path>` | Tomcat installation path |
201
+ | `-t, --tool <tool>` | Build tool: `maven` or `gradle` |
202
+ | `-n, --name <name>` | Application name (WAR context) |
203
+ | `--port <port>` | Tomcat port (default: 8080) |
204
+ | `-P, --profile <prof>` | Maven/Gradle profile |
205
+ | `-e, --encoding <enc>` | Source encoding (utf8, cp1252) |
206
+ | `-w, --watch` | Enable file watching |
207
+ | `--tui` | Interactive dashboard mode |
208
+ | `-d, --debug` | Enable JPDA debugger |
209
+ | `-c, --clean` | Clean logs before start |
210
+ | `-s, --no-build` | Skip initial build |
211
+ | `-W, --war` | Generate .war file (vs exploded)|
212
+ | `--cache` | Use build cache (faster) |
213
+ | `-y, --yes` | Auto-install Tomcat (no prompt) |
214
+ | `-V, --verbose` | Detailed output |
215
+
216
+ ---
217
+
218
+ ## πŸ—οΈ Architecture
219
+
220
+ Xavva uses a modular service-oriented architecture:
221
+
222
+ - **DashboardService** β€” TUI management and interactivity
223
+ - **LogAnalyzer** β€” Intelligent log processing
224
+ - **DependencyAnalyzerService** β€” Dependency conflict detection
225
+ - **ProjectService** β€” Project structure discovery
226
+ - **BuildService** β€” Maven/Gradle integration
227
+ - **TomcatService** β€” Server lifecycle management
228
+
229
+ ---
230
+
231
+ ## 🀝 Contributing
232
+
233
+ Contributions are welcome! Please feel free to submit a Pull Request.
234
+
235
+ ---
236
+
237
+ ## πŸ“„ License
238
+
239
+ MIT License β€” see [LICENSE](LICENSE) for details.
240
+
241
+ ---
242
+
243
+ <p align="center">
244
+ <sub>Built with ❀️ for Java developers who miss modern tooling</sub>
245
+ </p>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@archznn/xavva",
3
- "version": "2.2.1",
3
+ "version": "2.3.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",
@@ -34,7 +34,7 @@ export class DepsCommand implements Command {
34
34
  Logger.log(" β€’ Arquivo pom.xml/build.gradle nΓ£o encontrado");
35
35
  Logger.log(" β€’ Erro de parsing no arquivo de configuraΓ§Γ£o");
36
36
  Logger.newline();
37
- Logger.log(`${Logger.C.cyan}Dica:${Logger.C.reset} Execute com --verbose para mais detalhes`);
37
+ Logger.log(`${Logger.C.primary}Dica:${Logger.C.reset} Execute com --verbose para mais detalhes`);
38
38
  return;
39
39
  }
40
40
 
@@ -87,7 +87,7 @@ export class DepsCommand implements Command {
87
87
  Logger.section("SugestΓ΅es de CorreΓ§Γ£o");
88
88
 
89
89
  for (const conflict of result.conflicts) {
90
- Logger.log(`\n${Logger.C.cyan}${conflict.groupId}:${conflict.artifactId}${Logger.C.reset}`);
90
+ Logger.log(`\n${Logger.C.primary}${conflict.groupId}:${conflict.artifactId}${Logger.C.reset}`);
91
91
 
92
92
  if (buildTool === "maven") {
93
93
  Logger.log(" Adicione ao pom.xml:");
@@ -142,7 +142,8 @@ export class DepsCommand implements Command {
142
142
  }
143
143
 
144
144
  Logger.newline();
145
- Logger.log(`${Logger.C.warning}⚠️ Execute 'mvn compile' ou 'gradle build' para aplicar as mudanças${Logger.C.reset}`);
145
+ Logger.log(`${Logger.C.warning}⚠️ Execute 'xavva build' para compilar e aplicar as mudanças${Logger.C.reset}`);
146
+ Logger.log(`${Logger.C.primary}πŸ’‘ Dica:${Logger.C.reset} Execute 'xavva audit' para verificar vulnerabilidades nas novas versΓ΅es`);
146
147
  } else {
147
148
  Logger.warn("Nenhuma dependΓͺncia foi atualizada");
148
149
  }
@@ -31,7 +31,7 @@ export class DoctorCommand implements Command {
31
31
  await this.installDCEVM();
32
32
  } else {
33
33
  Logger.log(
34
- ` ${Logger.C.cyan}Use 'xavva doctor --fix' para baixar uma JDK com DCEVM integrado.${Logger.C.reset}`,
34
+ ` ${Logger.C.primary}Use 'xavva doctor --fix' para baixar uma JDK com DCEVM integrado.${Logger.C.reset}`,
35
35
  );
36
36
  }
37
37
  }
@@ -28,9 +28,10 @@ export class HelpCommand implements Command {
28
28
  ${this.c("green", "audit")} Security audit of JAR files
29
29
  ${this.c("green", "doctor")} Diagnose and fix environment issues
30
30
  ${this.c("green", "profiles")} List available Maven/Gradle profiles
31
+ ${this.c("green", "tomcat")} Manage embedded Tomcat (install, list, status)
31
32
  ${this.c("green", "docs")} Generate endpoint documentation
32
33
 
33
- ${this.c("yellow", "OPTIONS")}
34
+ ${this.c("yellow", "GENERAL OPTIONS")}
34
35
  ${this.c("cyan", "-p, --path")} <path> Tomcat installation path
35
36
  ${this.c("cyan", "-t, --tool")} <tool> Build tool: maven | gradle
36
37
  ${this.c("cyan", "-n, --name")} <name> Application name (WAR context)
@@ -43,32 +44,56 @@ export class HelpCommand implements Command {
43
44
  ${this.c("cyan", "-d, --debug")} Enable JPDA debugger
44
45
  ${this.c("cyan", "--dp")} <port> Debugger port (default: 5005)
45
46
 
46
- ${this.c("cyan", "-c, --clean")} Clean logs before start
47
+ ${this.c("cyan", "-c, --clean")} Clean before build
47
48
  ${this.c("cyan", "-s, --no-build")} Skip initial build
48
49
  ${this.c("cyan", "-q, --quiet")} Minimal output
49
50
  ${this.c("cyan", "-V, --verbose")} Detailed output
50
51
  ${this.c("cyan", "-h, --help")} Show this help
51
52
  ${this.c("cyan", "-v, --version")} Show version
52
53
 
54
+ ${this.c("yellow", "BUILD OPTIONS")} ${this.c("dim", "(for deploy, dev, build)")}
55
+ ${this.c("cyan", "-W, --war")} Generate .war file instead of exploded directory
56
+ ${this.c("cyan", "--cache")} Use build cache (skip if no changes)
57
+
58
+ ${this.c("yellow", "TOMCAT OPTIONS")} ${this.c("dim", "(for embedded Tomcat)")}
59
+ ${this.c("cyan", "--tomcat-version")} <v> Tomcat version to install (default: 10.1.52)
60
+ ${this.c("cyan", "-y, --yes")} Auto-install without confirmation
61
+
62
+ ${this.c("yellow", "DEPS OPTIONS")} ${this.c("dim", "(for xavva deps)")}
63
+ ${this.c("cyan", "--update-safe")} Update only non-breaking dependencies
64
+ ${this.c("cyan", "--fix")} Show fix suggestions for conflicts
65
+ ${this.c("cyan", "--strict")} Fail on critical conflicts (for CI/CD)
66
+ ${this.c("cyan", "-o, --output")} <file> Export report as JSON
67
+
53
68
  ${this.c("yellow", "EXAMPLES")}
54
69
  ${this.c("dim", "# Development with hot reload and dashboard")}
55
70
  xavva dev --tui --watch
56
71
 
57
- ${this.c("dim", "# Quick deploy to specific Tomcat")}
72
+ ${this.c("dim", "# Deploy to specific Tomcat installation")}
58
73
  xavva deploy -p /opt/tomcat --port 8081
59
74
 
75
+ ${this.c("dim", "# Build and deploy as .war file")}
76
+ xavva deploy --war
77
+
60
78
  ${this.c("dim", "# Run a class with debugging")}
61
79
  xavva debug com.example.MyClass
62
80
 
63
- ${this.c("dim", "# Analyze dependencies for conflicts")}
64
- xavva deps --verbose
81
+ ${this.c("dim", "# Use embedded Tomcat (auto-install)")}
82
+ xavva dev --yes
83
+ xavva dev --tomcat-version 9.0.115
65
84
 
66
- ${this.c("dim", "# Update safe dependencies (non-breaking only)")}
85
+ ${this.c("dim", "# Analyze and update dependencies")}
86
+ xavva deps --verbose
67
87
  xavva deps --update-safe
68
88
 
69
- ${this.c("dim", "# Security audit with auto-fix suggestions")}
89
+ ${this.c("dim", "# Security audit with auto-fix")}
70
90
  xavva audit --fix
71
91
 
92
+ ${this.c("dim", "# Manage embedded Tomcat")}
93
+ xavva tomcat install
94
+ xavva tomcat status
95
+ xavva tomcat list
96
+
72
97
  ${this.c("yellow", "CONFIGURATION")}
73
98
  Settings are loaded from ${this.c("cyan", "xavva.json")} in the project root:
74
99
 
@@ -20,7 +20,7 @@ export class ProfilesCommand implements Command {
20
20
  }
21
21
 
22
22
  Logger.log(`
23
- ${Logger.C.cyan}Perfis detectados:${Logger.C.reset}`);
23
+ ${Logger.C.primary}Perfis detectados:${Logger.C.reset}`);
24
24
  profiles.forEach(p => {
25
25
  const active = config.project.profile === p ? ` ${Logger.C.green}(Ativo)${Logger.C.reset}` : "";
26
26
  Logger.log(` ${Logger.C.bold}➜${Logger.C.reset} ${p}${active}`);
@@ -54,7 +54,7 @@ export class RunCommand implements Command {
54
54
 
55
55
  if (isDebug) {
56
56
  Logger.warn(`πŸš€ Aguardando debugger na porta 5005 para ${className}...`);
57
- Logger.log(`${Logger.C.cyan}Dica:${Logger.C.reset} No VS Code ou IntelliJ, use 'Attach to Remote JVM' na porta 5005.`);
57
+ Logger.log(`${Logger.C.primary}Dica:${Logger.C.reset} No VS Code ou IntelliJ, use 'Attach to Remote JVM' na porta 5005.`);
58
58
  Logger.newline();
59
59
  } else {
60
60
  Logger.warn(`πŸš€ Executando ${className}...`);
@@ -0,0 +1,114 @@
1
+ import type { Command } from "./Command";
2
+ import type { AppConfig, CLIArguments } from "../types/config";
3
+ import { EmbeddedTomcatService } from "../services/EmbeddedTomcatService";
4
+ import { Logger } from "../utils/ui";
5
+ import path from "path";
6
+
7
+ export class TomcatCommand implements Command {
8
+ async execute(config: AppConfig, args?: CLIArguments): Promise<void> {
9
+ const action = args?.["tomcat-action"] || "status";
10
+
11
+ switch (action) {
12
+ case "install":
13
+ await this.handleInstall(config, args);
14
+ break;
15
+ case "list":
16
+ this.handleList();
17
+ break;
18
+ case "uninstall":
19
+ await this.handleUninstall(config, args);
20
+ break;
21
+ case "status":
22
+ await this.handleStatus(config);
23
+ break;
24
+ default:
25
+ Logger.error(`AΓ§Γ£o desconhecida: ${action}`);
26
+ Logger.info("AΓ§Γ΅es disponΓ­veis", "install, list, uninstall, status");
27
+ }
28
+ }
29
+
30
+ private async handleInstall(config: AppConfig, args?: CLIArguments): Promise<void> {
31
+ const version = args?.["tomcat-version"] || config.tomcat.version || "10.1.52";
32
+
33
+ // Detectar webapp path
34
+ const webappPath = config.project.buildTool === "maven"
35
+ ? path.join(process.cwd(), "src", "main", "webapp")
36
+ : path.join(process.cwd(), "src", "main", "webapp");
37
+
38
+ const service = new EmbeddedTomcatService({
39
+ version,
40
+ port: config.tomcat.port,
41
+ webappPath
42
+ });
43
+
44
+ if (service.checkInstallation()) {
45
+ Logger.info("Tomcat", `VersΓ£o ${version} jΓ‘ estΓ‘ instalada`);
46
+ const info = service.getInfo();
47
+ Logger.config("Local", info.home);
48
+ return;
49
+ }
50
+
51
+ const installed = await service.install();
52
+ if (installed) {
53
+ Logger.success(`Tomcat ${version} instalado com sucesso!`);
54
+ } else {
55
+ Logger.error("Falha na instalaΓ§Γ£o");
56
+ }
57
+ }
58
+
59
+ private handleList(): void {
60
+ Logger.section("VersΓ΅es DisponΓ­veis");
61
+ const versions = EmbeddedTomcatService.getAvailableVersions();
62
+
63
+ for (const version of versions) {
64
+ Logger.log(` ${Logger.C.primary}β€’${Logger.C.reset} ${version}`);
65
+ }
66
+
67
+ Logger.newline();
68
+ Logger.info("VersΓ£o padrΓ£o", "10.1.52");
69
+ }
70
+
71
+ private async handleUninstall(config: AppConfig, args?: CLIArguments): Promise<void> {
72
+ const version = args?.["tomcat-version"] || config.tomcat.version || "10.1.52";
73
+
74
+ const service = new EmbeddedTomcatService({
75
+ version,
76
+ port: config.tomcat.port,
77
+ webappPath: "."
78
+ });
79
+
80
+ if (!service.checkInstallation()) {
81
+ Logger.warn(`Tomcat ${version} nΓ£o estΓ‘ instalado`);
82
+ return;
83
+ }
84
+
85
+ Logger.step(`Removendo Tomcat ${version}...`);
86
+ await service.uninstall();
87
+ Logger.success("Removido com sucesso!");
88
+ }
89
+
90
+ private async handleStatus(config: AppConfig): Promise<void> {
91
+ Logger.section("Status do Tomcat");
92
+
93
+ if (config.tomcat.embedded) {
94
+ Logger.config("Modo", "Embutido");
95
+ Logger.config("VersΓ£o", config.tomcat.version || "10.1.52");
96
+ Logger.config("Porta", String(config.tomcat.port));
97
+ Logger.config("Home", config.tomcat.path);
98
+ } else {
99
+ Logger.config("Modo", "Externo");
100
+ Logger.config("CATALINA_HOME", config.tomcat.path);
101
+ Logger.config("Porta", String(config.tomcat.port));
102
+ }
103
+
104
+ // Verificar se estΓ‘ rodando
105
+ const netstat = Bun.spawnSync(["cmd", "/c", `netstat -ano | findstr :${config.tomcat.port}`]);
106
+ const output = await new Response(netstat.stdout).text();
107
+
108
+ if (output.trim()) {
109
+ Logger.config("Status", `${Logger.C.success}Rodando${Logger.C.reset}`);
110
+ } else {
111
+ Logger.config("Status", `${Logger.C.warning}Parado${Logger.C.reset}`);
112
+ }
113
+ }
114
+ }
package/src/index.ts CHANGED
@@ -12,6 +12,7 @@ import { DocsCommand } from "./commands/DocsCommand";
12
12
  import { AuditCommand } from "./commands/AuditCommand";
13
13
  import { ProfilesCommand } from "./commands/ProfilesCommand";
14
14
  import { DepsCommand } from "./commands/DepsCommand";
15
+ import { TomcatCommand } from "./commands/TomcatCommand";
15
16
 
16
17
  import { ProjectService } from "./services/ProjectService";
17
18
  import { TomcatService } from "./services/TomcatService";
@@ -36,7 +37,7 @@ async function main() {
36
37
  await processManager.shutdown(0);
37
38
  }
38
39
 
39
- const commandNames = ["deploy", "build", "start", "dev", "doctor", "run", "debug", "logs", "docs", "audit", "profiles", "deps"];
40
+ const commandNames = ["deploy", "build", "start", "dev", "doctor", "run", "debug", "logs", "docs", "audit", "profiles", "deps", "tomcat"];
40
41
  const commandName = positionals.find(p => commandNames.includes(p)) || "deploy";
41
42
 
42
43
  if (!values.help && !values.tui) {
@@ -44,6 +45,9 @@ async function main() {
44
45
  if (config.project.encoding) {
45
46
  Logger.config("Encoding", config.project.encoding);
46
47
  }
48
+ if (config.tomcat.embedded) {
49
+ Logger.config("Tomcat", `Embutido ${config.tomcat.version}`);
50
+ }
47
51
  }
48
52
 
49
53
  if (values.help) {
@@ -80,6 +84,7 @@ async function main() {
80
84
  registry.register("audit", new AuditCommand(auditService));
81
85
  registry.register("profiles", new ProfilesCommand(projectService));
82
86
  registry.register("deps", new DepsCommand());
87
+ registry.register("tomcat", new TomcatCommand());
83
88
  registry.register("deploy", deployCmd);
84
89
  registry.register("dev", deployCmd);
85
90
 
@@ -30,7 +30,10 @@ export class BuildService {
30
30
  this.cache.clearCache();
31
31
  }
32
32
 
33
- if (!incremental && !this.projectConfig.skipBuild) {
33
+ // Cache sΓ³ Γ© usado se --cache for passado ou em modo incremental
34
+ const useCache = this.projectConfig.cache || incremental;
35
+
36
+ if (useCache && !incremental && !this.projectConfig.skipBuild) {
34
37
  if (!this.projectConfig.clean && !this.cache.shouldRebuild(this.projectConfig.buildTool, this.projectService)) {
35
38
  Logger.success("Build cache hit! Skipping full build.");
36
39
  return;
@@ -43,8 +46,8 @@ export class BuildService {
43
46
  if (this.projectConfig.buildTool === 'maven') {
44
47
  command.push(process.platform === "win32" ? "mvn.cmd" : "mvn");
45
48
 
46
- // Smart Offline: Se o pom.xml nΓ£o mudou e Γ© incremental ou rebuild forΓ§ado (mas cache existe), usa -o
47
- if (!this.cache.shouldRebuild('maven', this.projectService)) {
49
+ // Smart Offline: sΓ³ usa -o se cache estiver habilitado e pom nΓ£o mudou
50
+ if (useCache && !this.cache.shouldRebuild('maven', this.projectService)) {
48
51
  command.push("-o");
49
52
  }
50
53
 
@@ -52,7 +55,12 @@ export class BuildService {
52
55
  command.push("compile");
53
56
  } else {
54
57
  if (this.projectConfig.clean) command.push("clean");
55
- command.push("compile", "war:exploded");
58
+ // Use 'package' para gerar .war ou 'war:exploded' para pasta
59
+ if (this.projectConfig.war) {
60
+ command.push("package");
61
+ } else {
62
+ command.push("compile", "war:exploded");
63
+ }
56
64
  command.push("-T", "1C");
57
65
  }
58
66
  command.push("-Dmaven.test.skip=true", "-Dmaven.javadoc.skip=true");
@@ -417,6 +417,14 @@ export class DependencyAnalyzerService {
417
417
 
418
418
  private async checkSingleUpdate(dep: Dependency): Promise<DependencyUpdate | null> {
419
419
  try {
420
+ // Primeiro verifica se a versΓ£o atual existe no Maven Central
421
+ // Se nΓ£o existir, Γ© uma dependΓͺncia local - nΓ£o sugerir atualizaΓ§Γ£o
422
+ const currentExists = await this.checkVersionExists(dep.groupId, dep.artifactId, dep.version);
423
+ if (!currentExists) {
424
+ Logger.debug(`DependΓͺncia local detectada (nΓ£o no Maven Central): ${dep.groupId}:${dep.artifactId}:${dep.version}`);
425
+ return null;
426
+ }
427
+
420
428
  const latest = await this.fetchLatestVersion(dep.groupId, dep.artifactId);
421
429
  if (latest && this.isNewer(latest, dep.version)) {
422
430
  return {
@@ -433,6 +441,18 @@ export class DependencyAnalyzerService {
433
441
  return null;
434
442
  }
435
443
 
444
+ private async checkVersionExists(groupId: string, artifactId: string, version: string): Promise<boolean> {
445
+ try {
446
+ const url = `https://search.maven.org/solrsearch/select?q=g:"${groupId}"+AND+a:"${artifactId}"+AND+v:"${version}"&rows=1&wt=json`;
447
+ const response = await fetch(url, { signal: AbortSignal.timeout(5000) });
448
+ const data = await response.json();
449
+ return data.response?.numFound > 0;
450
+ } catch (e) {
451
+ // Se nΓ£o conseguir verificar, assume que existe para nΓ£o bloquear
452
+ return true;
453
+ }
454
+ }
455
+
436
456
  private async fetchLatestVersion(groupId: string, artifactId: string): Promise<string | null> {
437
457
  // Usar Maven Central API
438
458
  try {
@@ -505,13 +525,17 @@ export class DependencyAnalyzerService {
505
525
  const currentVersion = update.currentVersion.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
506
526
 
507
527
  // Regex para encontrar <version> dentro do bloco da dependΓͺncia
528
+ // Captura: grupo 1 = tudo antes da versΓ£o incluindo <version>
529
+ // grupo 2 = a versΓ£o atual
530
+ // grupo 3 = </version> e resto
508
531
  const depPattern = new RegExp(
509
- `(<dependency>\\s*<groupId>${groupId}</groupId>\\s*<artifactId>${artifactId}</artifactId>(?:\\s*<version>)${currentVersion}(</version>))`,
532
+ `(<dependency>\\s*<groupId>${groupId}</groupId>\\s*<artifactId>${artifactId}</artifactId>\\s*<version>)(${currentVersion})(</version>)`,
510
533
  'g'
511
534
  );
512
535
 
513
536
  if (depPattern.test(content)) {
514
- content = content.replace(depPattern, `$1${update.latestVersion}$2`);
537
+ // Substitui apenas o grupo 2 (a versΓ£o), mantendo as tags
538
+ content = content.replace(depPattern, `$1${update.latestVersion}$3`);
515
539
  result.updated++;
516
540
  modified = true;
517
541
  } else {
@@ -0,0 +1,391 @@
1
+ import { Logger } from "../utils/ui";
2
+ import { existsSync, mkdirSync, createWriteStream, writeFileSync, promises as fs } from "fs";
3
+ import path from "path";
4
+ import os from "os";
5
+ import { spawn } from "child_process";
6
+
7
+ export interface EmbeddedTomcatOptions {
8
+ version?: string;
9
+ port?: number;
10
+ webappPath: string;
11
+ contextPath?: string;
12
+ }
13
+
14
+ interface DownloadProgress {
15
+ downloaded: number;
16
+ total: number;
17
+ percent: number;
18
+ }
19
+
20
+ export class EmbeddedTomcatService {
21
+ private readonly baseDir: string;
22
+ private readonly version: string;
23
+ private port: number;
24
+ private webappPath: string;
25
+ private contextPath: string;
26
+ private tomcatHome: string;
27
+ private downloadUrl: string;
28
+ private isInstalled: boolean = false;
29
+
30
+ // VersΓ΅es estΓ‘veis do Tomcat (atualizadas: 2026-03-04)
31
+ private static readonly VERSIONS: Record<string, { url: string; sha512: string }> = {
32
+ "10.1.52": {
33
+ url: "https://dlcdn.apache.org/tomcat/tomcat-10/v10.1.52/bin/apache-tomcat-10.1.52-windows-x64.zip",
34
+ sha512: ""
35
+ },
36
+ "9.0.115": {
37
+ url: "https://dlcdn.apache.org/tomcat/tomcat-9/v9.0.115/bin/apache-tomcat-9.0.115-windows-x64.zip",
38
+ sha512: ""
39
+ },
40
+ "11.0.18": {
41
+ url: "https://dlcdn.apache.org/tomcat/tomcat-11/v11.0.18/bin/apache-tomcat-11.0.18-windows-x64.zip",
42
+ sha512: ""
43
+ }
44
+ };
45
+
46
+ constructor(options: EmbeddedTomcatOptions) {
47
+ this.version = options.version || "10.1.52";
48
+ this.port = options.port || 8080;
49
+ this.webappPath = path.resolve(options.webappPath);
50
+ this.contextPath = options.contextPath || "/";
51
+ this.baseDir = path.join(os.homedir(), ".xavva", "tomcat");
52
+ this.tomcatHome = path.join(this.baseDir, this.version);
53
+
54
+ // Se a versΓ£o nΓ£o estΓ‘ na lista, usa URL padrΓ£o
55
+ const versionInfo = EmbeddedTomcatService.VERSIONS[this.version];
56
+ if (versionInfo) {
57
+ this.downloadUrl = versionInfo.url;
58
+ } else {
59
+ // Tenta inferir URL baseado no padrΓ£o Apache
60
+ const majorVersion = this.version.split(".")[0];
61
+ this.downloadUrl = `https://archive.apache.org/dist/tomcat/tomcat-${majorVersion}/v${this.version}/bin/apache-tomcat-${this.version}-windows-x64.zip`;
62
+ }
63
+ }
64
+
65
+ /**
66
+ * Verifica se o Tomcat jΓ‘ estΓ‘ instalado
67
+ */
68
+ checkInstallation(): boolean {
69
+ const catalinaBat = path.join(this.tomcatHome, "bin", "catalina.bat");
70
+ this.isInstalled = existsSync(catalinaBat);
71
+ return this.isInstalled;
72
+ }
73
+
74
+ /**
75
+ * Retorna o caminho do Tomcat (instalado ou para instalar)
76
+ */
77
+ getTomcatHome(): string {
78
+ return this.tomcatHome;
79
+ }
80
+
81
+ /**
82
+ * Baixa e instala o Tomcat
83
+ */
84
+ async install(): Promise<boolean> {
85
+ if (this.checkInstallation()) {
86
+ Logger.info("Tomcat", `VersΓ£o ${this.version} jΓ‘ instalada`);
87
+ return true;
88
+ }
89
+
90
+ Logger.section("Instalando Tomcat Embutido");
91
+ Logger.info("VersΓ£o", this.version);
92
+ Logger.info("Destino", this.tomcatHome);
93
+
94
+ // Cria diretΓ³rio base
95
+ if (!existsSync(this.baseDir)) {
96
+ mkdirSync(this.baseDir, { recursive: true });
97
+ }
98
+
99
+ const zipPath = path.join(this.baseDir, `apache-tomcat-${this.version}.zip`);
100
+
101
+ try {
102
+ // Download
103
+ await this.downloadFile(this.downloadUrl, zipPath);
104
+
105
+ // ExtraΓ§Γ£o
106
+ await this.extractZip(zipPath, this.baseDir);
107
+
108
+ // Renomeia diretΓ³rio extraΓ­do para versΓ£o padronizada
109
+ const extractedDir = path.join(this.baseDir, `apache-tomcat-${this.version}`);
110
+ if (existsSync(extractedDir) && extractedDir !== this.tomcatHome) {
111
+ await fs.rename(extractedDir, this.tomcatHome);
112
+ }
113
+
114
+ // Limpa arquivo zip
115
+ await fs.unlink(zipPath).catch(() => {});
116
+
117
+ // Configura server.xml
118
+ await this.configureServerXml();
119
+
120
+ // Configura context.xml para hot-reload
121
+ await this.configureContextXml();
122
+
123
+ this.isInstalled = true;
124
+ Logger.success(`Tomcat ${this.version} instalado com sucesso!`);
125
+ return true;
126
+
127
+ } catch (error) {
128
+ Logger.error(`Falha ao instalar Tomcat: ${error}`);
129
+ // Limpa arquivos parciais
130
+ if (existsSync(this.tomcatHome)) {
131
+ await fs.rm(this.tomcatHome, { recursive: true, force: true });
132
+ }
133
+ return false;
134
+ }
135
+ }
136
+
137
+ /**
138
+ * Configura server.xml com porta personalizada
139
+ */
140
+ private async configureServerXml(): Promise<void> {
141
+ const serverXmlPath = path.join(this.tomcatHome, "conf", "server.xml");
142
+
143
+ if (!existsSync(serverXmlPath)) {
144
+ throw new Error("server.xml nΓ£o encontrado apΓ³s extraΓ§Γ£o");
145
+ }
146
+
147
+ let content = await fs.readFile(serverXmlPath, "utf-8");
148
+
149
+ // Atualiza porta HTTP
150
+ content = content.replace(
151
+ /<Connector port="8080"/,
152
+ `<Connector port="${this.port}"`
153
+ );
154
+
155
+ // Atualiza porta de shutdown
156
+ const shutdownPort = this.port + 1000;
157
+ content = content.replace(
158
+ /<Server port="8005"/,
159
+ `<Server port="${shutdownPort}"`
160
+ );
161
+
162
+ // Atualiza porta AJP (se existir)
163
+ content = content.replace(
164
+ /<Connector port="8009"/,
165
+ `<Connector port="${this.port + 1001}"`
166
+ );
167
+
168
+ // Desabilita manager e host-manager em embedded (opcional)
169
+ // Remove context do manager para seguranΓ§a
170
+ content = content.replace(
171
+ /<Context docBase="manager"[^>]*\/>/g,
172
+ "<!-- <Context docBase=\"manager\" ... /> -->"
173
+ );
174
+
175
+ await fs.writeFile(serverXmlPath, content, "utf-8");
176
+ Logger.debug(`server.xml configurado na porta ${this.port}`);
177
+ }
178
+
179
+ /**
180
+ * Configura context.xml para hot-reload
181
+ */
182
+ private async configureContextXml(): Promise<void> {
183
+ const contextXmlPath = path.join(this.tomcatHome, "conf", "context.xml");
184
+
185
+ if (!existsSync(contextXmlPath)) return;
186
+
187
+ let content = await fs.readFile(contextXmlPath, "utf-8");
188
+
189
+ // Adiciona atributos para hot-reload se nΓ£o existirem
190
+ if (!content.includes("reloadable")) {
191
+ content = content.replace(
192
+ /<Context>/,
193
+ '<Context reloadable="true" autoDeploy="true" deployOnStartup="true">'
194
+ );
195
+ }
196
+
197
+ await fs.writeFile(contextXmlPath, content, "utf-8");
198
+ }
199
+
200
+ /**
201
+ * Cria contexto para a aplicaΓ§Γ£o
202
+ */
203
+ async createAppContext(): Promise<void> {
204
+ const webappsDir = path.join(this.tomcatHome, "webapps");
205
+
206
+ // Limpa webapps padrΓ£o
207
+ const defaultApps = ["docs", "examples", "host-manager", "manager", "ROOT"];
208
+ for (const app of defaultApps) {
209
+ const appPath = path.join(webappsDir, app);
210
+ if (existsSync(appPath)) {
211
+ await fs.rm(appPath, { recursive: true, force: true });
212
+ }
213
+ }
214
+
215
+ // Cria diretΓ³rio para a aplicaΓ§Γ£o
216
+ const appName = this.contextPath === "/" ? "ROOT" : this.contextPath.replace(/^\//, "");
217
+ const appDir = path.join(webappsDir, appName);
218
+
219
+ if (existsSync(appDir)) {
220
+ await fs.rm(appDir, { recursive: true, force: true });
221
+ }
222
+
223
+ // Se webappPath Γ© um diretΓ³rio, cria link/simula deploy
224
+ if (existsSync(this.webappPath)) {
225
+ // Em Windows, vamos copiar inicialmente (symlink requer privilΓ©gios)
226
+ // Ou criar um context XML apontando para o diretΓ³rio
227
+ await this.createContextXml(appName);
228
+ }
229
+ }
230
+
231
+ /**
232
+ * Cria arquivo context XML para apontar para diretΓ³rio externo
233
+ */
234
+ private async createContextXml(appName: string): Promise<void> {
235
+ const confDir = path.join(this.tomcatHome, "conf", "Catalina", "localhost");
236
+
237
+ if (!existsSync(confDir)) {
238
+ mkdirSync(confDir, { recursive: true });
239
+ }
240
+
241
+ const contextFile = path.join(confDir, `${appName}.xml`);
242
+ const content = `<?xml version="1.0" encoding="UTF-8"?>
243
+ <Context
244
+ docBase="${this.webappPath.replace(/\\/g, "/")}"
245
+ reloadable="true"
246
+ crossContext="true"
247
+ antiResourceLocking="false"
248
+ antiJARLocking="false">
249
+ </Context>`;
250
+
251
+ writeFileSync(contextFile, content);
252
+ Logger.debug(`Context criado: ${contextFile}`);
253
+ }
254
+
255
+ /**
256
+ * Verifica se porta estΓ‘ disponΓ­vel
257
+ */
258
+ async isPortAvailable(): Promise<boolean> {
259
+ return new Promise((resolve) => {
260
+ const netstat = spawn("cmd", ["/c", `netstat -ano | findstr :${this.port}`]);
261
+ let output = "";
262
+
263
+ netstat.stdout?.on("data", (data) => {
264
+ output += data.toString();
265
+ });
266
+
267
+ netstat.on("close", () => {
268
+ resolve(output.trim().length === 0);
269
+ });
270
+
271
+ netstat.on("error", () => {
272
+ resolve(true); // Assume disponΓ­vel se nΓ£o conseguir verificar
273
+ });
274
+ });
275
+ }
276
+
277
+ /**
278
+ * Encontra prΓ³xima porta disponΓ­vel
279
+ */
280
+ async findAvailablePort(startPort: number = 8080): Promise<number> {
281
+ let port = startPort;
282
+ while (!(await this.isPortAvailable())) {
283
+ port++;
284
+ if (port > 65535) {
285
+ throw new Error("Nenhuma porta disponΓ­vel encontrada");
286
+ }
287
+ }
288
+ this.port = port;
289
+ return port;
290
+ }
291
+
292
+ /**
293
+ * Retorna variΓ‘veis de ambiente para o Tomcat
294
+ */
295
+ getEnvironment(): Record<string, string> {
296
+ return {
297
+ CATALINA_HOME: this.tomcatHome,
298
+ CATALINA_BASE: this.tomcatHome,
299
+ CATALINA_OPTS: process.env.CATALINA_OPTS || ""
300
+ };
301
+ }
302
+
303
+ /**
304
+ * Lista versΓ΅es disponΓ­veis
305
+ */
306
+ static getAvailableVersions(): string[] {
307
+ return Object.keys(EmbeddedTomcatService.VERSIONS);
308
+ }
309
+
310
+ /**
311
+ * Download com progresso
312
+ */
313
+ private async downloadFile(url: string, destPath: string): Promise<void> {
314
+ const spinner = Logger.spinner(`Baixando Tomcat ${this.version}...`);
315
+
316
+ try {
317
+ const response = await fetch(url);
318
+
319
+ if (!response.ok) {
320
+ throw new Error(`HTTP ${response.status}: ${response.statusText}`);
321
+ }
322
+
323
+ const totalSize = parseInt(response.headers.get("content-length") || "0");
324
+ const buffer = await response.arrayBuffer();
325
+
326
+ writeFileSync(destPath, Buffer.from(buffer));
327
+
328
+ spinner(true);
329
+
330
+ const sizeMB = (buffer.byteLength / 1024 / 1024).toFixed(1);
331
+ Logger.info("Download", `${sizeMB} MB baixados`);
332
+
333
+ } catch (error) {
334
+ spinner(false);
335
+ throw error;
336
+ }
337
+ }
338
+
339
+ /**
340
+ * Extrai arquivo ZIP usando PowerShell
341
+ */
342
+ private async extractZip(zipPath: string, destDir: string): Promise<void> {
343
+ const spinner = Logger.spinner("Extraindo arquivos...");
344
+
345
+ return new Promise((resolve, reject) => {
346
+ const ps = spawn("powershell", [
347
+ "-command",
348
+ `Expand-Archive -Path '${zipPath}' -DestinationPath '${destDir}' -Force`
349
+ ]);
350
+
351
+ ps.on("close", (code) => {
352
+ if (code === 0) {
353
+ spinner(true);
354
+ resolve();
355
+ } else {
356
+ spinner(false);
357
+ reject(new Error(`Falha ao extrair (cΓ³digo ${code})`));
358
+ }
359
+ });
360
+
361
+ ps.on("error", (err) => {
362
+ spinner(false);
363
+ reject(err);
364
+ });
365
+ });
366
+ }
367
+
368
+ /**
369
+ * Remove instalaΓ§Γ£o
370
+ */
371
+ async uninstall(): Promise<void> {
372
+ if (existsSync(this.tomcatHome)) {
373
+ await fs.rm(this.tomcatHome, { recursive: true, force: true });
374
+ Logger.info("Tomcat", `VersΓ£o ${this.version} removida`);
375
+ }
376
+ }
377
+
378
+ /**
379
+ * Retorna informaΓ§Γ΅es da instalaΓ§Γ£o
380
+ */
381
+ getInfo(): Record<string, string> {
382
+ return {
383
+ version: this.version,
384
+ home: this.tomcatHome,
385
+ port: String(this.port),
386
+ installed: this.isInstalled ? "sim" : "nΓ£o",
387
+ webapp: this.webappPath,
388
+ context: this.contextPath
389
+ };
390
+ }
391
+ }
@@ -98,7 +98,7 @@ export class LogAnalyzer {
98
98
  return `${Logger.C.magenta}πŸ‘€ ${Logger.C.bold}Hotswap:${Logger.C.reset} ${msg.replace(/Class '.*?'/, (m) => Logger.C.bold + m + Logger.C.reset)}`;
99
99
  }
100
100
 
101
- let color = Logger.C.cyan;
101
+ let color = Logger.C.primary;
102
102
  let symbol = "●";
103
103
  if (level === "WARN") { color = Logger.C.yellow; symbol = "β–²"; }
104
104
  else if (level === "ERROR") { color = Logger.C.red; symbol = "βœ–"; }
@@ -28,7 +28,12 @@ export class ProjectService {
28
28
  const artifacts = this.searchArtifacts(buildDir).sort((a, b) => b.time - a.time);
29
29
 
30
30
  if (artifacts.length === 0) {
31
- throw new Error(`Nenhum artefato (.war ou pasta exploded) encontrado em ${buildDir}!`);
31
+ // Debug: listar o que existe no diretΓ³rio target
32
+ let debugInfo = `\nDiretΓ³rio ${buildDir} existe: ${existsSync(buildDir)}`;
33
+ if (existsSync(buildDir)) {
34
+ debugInfo += `\nConteΓΊdo: ${readdirSync(buildDir).join(', ')}`;
35
+ }
36
+ throw new Error(`Nenhum artefato (.war ou pasta exploded) encontrado em ${buildDir}!${debugInfo}`);
32
37
  }
33
38
 
34
39
  const artifact = artifacts[0];
@@ -3,6 +3,8 @@ export interface TomcatConfig {
3
3
  port: number;
4
4
  webapps: string;
5
5
  grep?: string;
6
+ embedded?: boolean;
7
+ version?: string;
6
8
  }
7
9
 
8
10
  export interface ProjectConfig {
@@ -20,6 +22,8 @@ export interface ProjectConfig {
20
22
  grep?: string;
21
23
  tui: boolean;
22
24
  encoding?: string;
25
+ war?: boolean;
26
+ cache?: boolean;
23
27
  }
24
28
 
25
29
  export interface AppConfig {
@@ -50,6 +54,13 @@ export interface CLIArguments {
50
54
  tui?: boolean;
51
55
  output?: string;
52
56
  strict?: boolean;
57
+ "tomcat-version"?: string;
58
+ "tomcat-action"?: string;
59
+ "update-safe"?: boolean;
60
+ "updateSafe"?: boolean;
61
+ yes?: boolean;
62
+ war?: boolean;
63
+ cache?: boolean;
53
64
  }
54
65
 
55
66
  export interface CommandContext {
@@ -3,6 +3,8 @@ import path from "path";
3
3
  import fs from "fs";
4
4
  import { DEFAULT_TOMCAT_PORT, DEFAULT_DEBUG_PORT } from "./constants";
5
5
  import type { AppConfig, CLIArguments, CommandContext } from "../types/config";
6
+ import { EmbeddedTomcatService } from "../services/EmbeddedTomcatService";
7
+ import { Logger } from "./ui";
6
8
 
7
9
  export class ConfigManager {
8
10
  static async load(): Promise<CommandContext> {
@@ -32,6 +34,10 @@ export class ConfigManager {
32
34
  tui: { type: "boolean" },
33
35
  output: { type: "string", short: "o" },
34
36
  strict: { type: "boolean" },
37
+ "tomcat-version": { type: "string" },
38
+ yes: { type: "boolean", short: "y" },
39
+ war: { type: "boolean", short: "W" },
40
+ cache: { type: "boolean" },
35
41
  },
36
42
  strict: false,
37
43
  allowPositionals: true,
@@ -52,8 +58,9 @@ export class ConfigManager {
52
58
 
53
59
  const isDev = positionals.includes("dev");
54
60
  const isRun = positionals.includes("run") || positionals.includes("debug");
61
+ const isStart = positionals.includes("start") || positionals.includes("deploy") || isDev;
55
62
 
56
- const envTomcatPath = process.env.TOMCAT_HOME || process.env.CATALINA_HOME || "C:\\apache-tomcat";
63
+ const envTomcatPath = process.env.TOMCAT_HOME || process.env.CATALINA_HOME;
57
64
  const detectedTool = this.detectBuildTool();
58
65
 
59
66
  let runClass = "";
@@ -64,12 +71,77 @@ export class ConfigManager {
64
71
  runClass = positionals[idx + 1] || "";
65
72
  }
66
73
 
74
+ // Detectar webapp path baseado no build tool
75
+ const webappPath = detectedTool === "maven"
76
+ ? path.join(process.cwd(), "src", "main", "webapp")
77
+ : path.join(process.cwd(), "src", "main", "webapp");
78
+
79
+ // Verificar se usar Tomcat embutido
80
+ let tomcatPath = String(cliValues.path || xavvaJson.path || envTomcatPath || "");
81
+ let useEmbedded = false;
82
+ let embeddedVersion = String(cliValues["tomcat-version"] || xavvaJson.version || "10.1.52");
83
+
84
+ // Se nΓ£o hΓ‘ Tomcat configurado ou nΓ£o existe no path, usar embutido
85
+ if (!tomcatPath || (!fs.existsSync(path.join(tomcatPath, "bin", "catalina.bat")) && isStart)) {
86
+ useEmbedded = true;
87
+ const embeddedService = new EmbeddedTomcatService({
88
+ version: embeddedVersion,
89
+ port: parseInt(String(cliValues.port || xavvaJson.port || String(DEFAULT_TOMCAT_PORT))),
90
+ webappPath: webappPath
91
+ });
92
+
93
+ // Instala se necessΓ‘rio
94
+ if (!embeddedService.checkInstallation()) {
95
+ Logger.newline();
96
+ Logger.warn("Tomcat nΓ£o encontrado!");
97
+ Logger.info("VersΓ£o solicitada", embeddedVersion);
98
+ Logger.newline();
99
+ Logger.log(`${Logger.C.primary}?${Logger.C.reset} Deseja instalar o Tomcat ${embeddedVersion} automaticamente?`);
100
+ Logger.log(`${Logger.C.dim} O download Γ© de ~16MB e serΓ‘ salvo em:~/.xavva/tomcat/${embeddedVersion}${Logger.C.reset}`);
101
+ Logger.newline();
102
+
103
+ // Garante que nΓ£o hΓ‘ output pendente antes da pergunta
104
+ await new Promise(resolve => setTimeout(resolve, 50));
105
+ process.stdout.write('\r\x1b[K'); // Limpa linha atual
106
+
107
+ const autoYes = !!cliValues.yes;
108
+ const shouldInstall = autoYes || await this.askYesNo("Instalar");
109
+
110
+ if (!shouldInstall) {
111
+ Logger.newline();
112
+ Logger.info("OpΓ§Γ΅es disponΓ­veis", "");
113
+ Logger.log(` ${Logger.C.primary}1.${Logger.C.reset} Defina TOMCAT_HOME ou CATALINA_HOME`);
114
+ Logger.log(` ${Logger.C.primary}2.${Logger.C.reset} Use --path para especificar o Tomcat`);
115
+ Logger.log(` ${Logger.C.primary}3.${Logger.C.reset} Use --tomcat-version para outra versΓ£o`);
116
+ Logger.newline();
117
+ process.exit(0);
118
+ }
119
+
120
+ Logger.newline();
121
+ const installed = await embeddedService.install();
122
+ if (!installed) {
123
+ Logger.error("Falha ao instalar Tomcat embutido.");
124
+ Logger.info("Dica", "Instale o Tomcat manualmente ou defina TOMCAT_HOME");
125
+ process.exit(1);
126
+ }
127
+ } else {
128
+ Logger.info("Tomcat", `Usando versΓ£o embutida ${embeddedVersion}`);
129
+ }
130
+
131
+ // Configura contexto da aplicaΓ§Γ£o
132
+ await embeddedService.createAppContext();
133
+
134
+ tomcatPath = embeddedService.getTomcatHome();
135
+ }
136
+
67
137
  const config: AppConfig = {
68
138
  tomcat: {
69
- path: String(cliValues.path || xavvaJson.path || envTomcatPath),
139
+ path: tomcatPath,
70
140
  port: parseInt(String(cliValues.port || xavvaJson.port || String(DEFAULT_TOMCAT_PORT))),
71
141
  webapps: "webapps",
72
142
  grep: cliValues.grep || xavvaJson.grep ? String(cliValues.grep || xavvaJson.grep) : "",
143
+ embedded: useEmbedded,
144
+ version: embeddedVersion,
73
145
  },
74
146
  project: {
75
147
  appName: cliValues.name || xavvaJson.name ? String(cliValues.name || xavvaJson.name) : "",
@@ -86,6 +158,8 @@ export class ConfigManager {
86
158
  grep: runClass || (cliValues.grep || xavvaJson.grep ? String(cliValues.grep || xavvaJson.grep) : ""),
87
159
  tui: !!(cliValues.tui ?? xavvaJson.tui),
88
160
  encoding: cliValues.encoding || xavvaJson.encoding || "",
161
+ war: !!(cliValues.war ?? xavvaJson.war),
162
+ cache: !!(cliValues.cache ?? xavvaJson.cache),
89
163
  }
90
164
  };
91
165
 
@@ -106,6 +180,50 @@ export class ConfigManager {
106
180
  return "maven";
107
181
  }
108
182
 
183
+ private static async askYesNo(question: string): Promise<boolean> {
184
+ // Pequeno delay para garantir que o output anterior foi processado
185
+ await new Promise(resolve => setTimeout(resolve, 100));
186
+
187
+ // Limpa qualquer coisa pendente no stdout
188
+ process.stdout.write('\x1b[0m');
189
+
190
+ return new Promise((resolve) => {
191
+ const chunks: Buffer[] = [];
192
+
193
+ const cleanup = () => {
194
+ process.stdin.removeListener('data', onData);
195
+ process.stdin.removeListener('end', onEnd);
196
+ process.stdin.pause();
197
+ };
198
+
199
+ const onData = (data: Buffer) => {
200
+ chunks.push(data);
201
+ const str = Buffer.concat(chunks).toString();
202
+
203
+ // Procura por enter no input
204
+ if (str.includes('\n') || str.includes('\r')) {
205
+ cleanup();
206
+ const answer = str.replace(/\r?\n/g, '').trim().toLowerCase();
207
+ process.stdout.write('\n');
208
+ resolve(answer === '' || answer === 'y' || answer === 'yes');
209
+ }
210
+ };
211
+
212
+ const onEnd = () => {
213
+ cleanup();
214
+ const answer = Buffer.concat(chunks).toString().trim().toLowerCase();
215
+ resolve(answer === '' || answer === 'y' || answer === 'yes');
216
+ };
217
+
218
+ // Mostra a pergunta
219
+ process.stdout.write(`${question} [Y/n]: `);
220
+
221
+ process.stdin.resume();
222
+ process.stdin.on('data', onData);
223
+ process.stdin.on('end', onEnd);
224
+ });
225
+ }
226
+
109
227
  private static ensureGitIgnore() {
110
228
  const gitignorePath = path.join(process.cwd(), ".gitignore");
111
229