@archznn/xavva 2.2.2 → 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 +36 -1
- package/package.json +1 -1
- package/src/commands/DepsCommand.ts +3 -3
- package/src/commands/DoctorCommand.ts +1 -1
- package/src/commands/HelpCommand.ts +32 -7
- package/src/commands/ProfilesCommand.ts +1 -1
- package/src/commands/RunCommand.ts +1 -1
- package/src/commands/TomcatCommand.ts +114 -0
- package/src/index.ts +6 -1
- package/src/services/BuildService.ts +12 -4
- package/src/services/DependencyAnalyzerService.ts +26 -2
- package/src/services/EmbeddedTomcatService.ts +391 -0
- package/src/services/LogAnalyzer.ts +1 -1
- package/src/services/ProjectService.ts +6 -1
- package/src/types/config.ts +11 -0
- package/src/utils/config.ts +120 -2
package/README.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
> Ultra-fast development toolkit for Java Enterprise (Tomcat) on Windows
|
|
4
4
|
|
|
5
|
-
[](https://github.com/leorsousa05/Xavva)
|
|
6
6
|
[](LICENSE)
|
|
7
7
|
|
|
8
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.
|
|
@@ -18,6 +18,8 @@ Xavva is a high-performance CLI built with **Bun** that transforms the Java/Tomc
|
|
|
18
18
|
- 📦 **Dependency Analysis** — Detect conflicts and outdated dependencies
|
|
19
19
|
- 🎯 **Maven & Gradle** — Native support for both build tools
|
|
20
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
|
|
21
23
|
|
|
22
24
|
---
|
|
23
25
|
|
|
@@ -42,6 +44,9 @@ xavva dev --tui
|
|
|
42
44
|
# Deploy to Tomcat
|
|
43
45
|
xavva deploy
|
|
44
46
|
|
|
47
|
+
# Build and deploy as .war file
|
|
48
|
+
xavva deploy --war
|
|
49
|
+
|
|
45
50
|
# Analyze dependencies for issues
|
|
46
51
|
xavva deps
|
|
47
52
|
|
|
@@ -50,6 +55,9 @@ xavva deps --update-safe
|
|
|
50
55
|
|
|
51
56
|
# Check for security vulnerabilities
|
|
52
57
|
xavva audit
|
|
58
|
+
|
|
59
|
+
# Use embedded Tomcat (auto-install)
|
|
60
|
+
xavva dev --yes
|
|
53
61
|
```
|
|
54
62
|
|
|
55
63
|
---
|
|
@@ -82,6 +90,30 @@ xavva audit
|
|
|
82
90
|
| `xavva doctor` | Diagnose environment issues (JAVA_HOME, DCEVM) |
|
|
83
91
|
| `xavva profiles` | List available Maven/Gradle profiles |
|
|
84
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
|
+
```
|
|
85
117
|
|
|
86
118
|
---
|
|
87
119
|
|
|
@@ -176,6 +208,9 @@ Create `xavva.json` in your project root:
|
|
|
176
208
|
| `-d, --debug` | Enable JPDA debugger |
|
|
177
209
|
| `-c, --clean` | Clean logs before start |
|
|
178
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) |
|
|
179
214
|
| `-V, --verbose` | Detailed output |
|
|
180
215
|
|
|
181
216
|
---
|
package/package.json
CHANGED
|
@@ -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.
|
|
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.
|
|
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:");
|
|
@@ -143,7 +143,7 @@ export class DepsCommand implements Command {
|
|
|
143
143
|
|
|
144
144
|
Logger.newline();
|
|
145
145
|
Logger.log(`${Logger.C.warning}⚠️ Execute 'xavva build' para compilar e aplicar as mudanças${Logger.C.reset}`);
|
|
146
|
-
Logger.log(`${Logger.C.
|
|
146
|
+
Logger.log(`${Logger.C.primary}💡 Dica:${Logger.C.reset} Execute 'xavva audit' para verificar vulnerabilidades nas novas versões`);
|
|
147
147
|
} else {
|
|
148
148
|
Logger.warn("Nenhuma dependência foi atualizada");
|
|
149
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.
|
|
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
|
|
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", "#
|
|
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", "#
|
|
64
|
-
xavva
|
|
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", "#
|
|
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
|
|
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.
|
|
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.
|
|
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
|
-
|
|
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:
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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.
|
|
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
|
-
|
|
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];
|
package/src/types/config.ts
CHANGED
|
@@ -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 {
|
package/src/utils/config.ts
CHANGED
|
@@ -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
|
|
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:
|
|
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
|
|