@archznn/xavva 2.7.0 → 2.8.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/package.json +4 -2
- package/src/commands/CompletionCommand.ts +212 -0
- package/src/commands/ConfigCommand.ts +184 -0
- package/src/commands/HealthCommand.ts +302 -0
- package/src/commands/HelpCommand.ts +27 -0
- package/src/commands/HistoryCommand.ts +49 -0
- package/src/commands/InitCommand.ts +148 -0
- package/src/commands/RedoCommand.ts +36 -0
- package/src/di/container.ts +23 -0
- package/src/index.ts +39 -1
- package/src/services/EmbeddedTomcatService.ts +62 -18
- package/src/services/HistoryService.ts +73 -0
- package/src/services/NotificationService.ts +145 -0
- package/src/services/TomcatService.ts +52 -23
- package/src/types/args.ts +15 -0
- package/src/utils/ProgressBar.ts +182 -0
|
@@ -0,0 +1,302 @@
|
|
|
1
|
+
import { exec } from "child_process";
|
|
2
|
+
import { promisify } from "util";
|
|
3
|
+
import { platform, totalmem, freemem, arch } from "os";
|
|
4
|
+
import { existsSync } from "fs";
|
|
5
|
+
import type { Command } from "./Command";
|
|
6
|
+
import type { AppConfig, CLIArguments } from "../types/config";
|
|
7
|
+
import { Logger } from "../utils/ui";
|
|
8
|
+
|
|
9
|
+
const execAsync = promisify(exec);
|
|
10
|
+
|
|
11
|
+
interface HealthCheck {
|
|
12
|
+
name: string;
|
|
13
|
+
status: "ok" | "warning" | "error";
|
|
14
|
+
message: string;
|
|
15
|
+
details?: string;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export class HealthCommand implements Command {
|
|
19
|
+
async execute(config: AppConfig, _args?: CLIArguments): Promise<void> {
|
|
20
|
+
Logger.banner("health");
|
|
21
|
+
Logger.section("Verificando saúde do ambiente");
|
|
22
|
+
|
|
23
|
+
const checks: HealthCheck[] = [];
|
|
24
|
+
|
|
25
|
+
// Java
|
|
26
|
+
checks.push(await this.checkJava());
|
|
27
|
+
|
|
28
|
+
// Maven/Gradle
|
|
29
|
+
checks.push(await this.checkBuildTool(config.project.buildTool));
|
|
30
|
+
|
|
31
|
+
// Tomcat
|
|
32
|
+
checks.push(await this.checkTomcat(config));
|
|
33
|
+
|
|
34
|
+
// Portas
|
|
35
|
+
checks.push(await this.checkPorts(config.tomcat.port));
|
|
36
|
+
|
|
37
|
+
// Memória
|
|
38
|
+
checks.push(this.checkMemory());
|
|
39
|
+
|
|
40
|
+
// Disco
|
|
41
|
+
checks.push(await this.checkDisk());
|
|
42
|
+
|
|
43
|
+
// Git
|
|
44
|
+
checks.push(this.checkGit());
|
|
45
|
+
|
|
46
|
+
// Exibir resultados
|
|
47
|
+
Logger.newline();
|
|
48
|
+
let errors = 0;
|
|
49
|
+
let warnings = 0;
|
|
50
|
+
|
|
51
|
+
for (const check of checks) {
|
|
52
|
+
const icon = check.status === "ok"
|
|
53
|
+
? `${Logger.C.success}✓${Logger.C.reset}`
|
|
54
|
+
: check.status === "warning"
|
|
55
|
+
? `${Logger.C.warning}⚠${Logger.C.reset}`
|
|
56
|
+
: `${Logger.C.error}✗${Logger.C.reset}`;
|
|
57
|
+
|
|
58
|
+
Logger.log(`${Logger.C.gray}│${Logger.C.reset} ${icon} ${Logger.C.bold}${check.name}${Logger.C.reset}`);
|
|
59
|
+
Logger.log(`${Logger.C.gray}│${Logger.C.reset} ${check.message}`);
|
|
60
|
+
|
|
61
|
+
if (check.details) {
|
|
62
|
+
Logger.log(`${Logger.C.gray}│${Logger.C.reset} ${Logger.C.dim}${check.details}${Logger.C.reset}`);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
if (check.status === "error") errors++;
|
|
66
|
+
if (check.status === "warning") warnings++;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
Logger.endSection();
|
|
70
|
+
|
|
71
|
+
// Summary
|
|
72
|
+
if (errors === 0 && warnings === 0) {
|
|
73
|
+
Logger.ready("Ambiente saudável! ✓");
|
|
74
|
+
} else if (errors === 0) {
|
|
75
|
+
Logger.warn(`${warnings} aviso(s) encontrado(s)`);
|
|
76
|
+
} else {
|
|
77
|
+
Logger.error(`${errors} erro(s) encontrado(s)`);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
private async checkJava(): Promise<HealthCheck> {
|
|
82
|
+
try {
|
|
83
|
+
const { stdout, stderr } = await execAsync("java -version");
|
|
84
|
+
const output = stderr || stdout;
|
|
85
|
+
const versionMatch = output.match(/version "?(\d+\.?\d*)/);
|
|
86
|
+
const version = versionMatch ? versionMatch[1] : "unknown";
|
|
87
|
+
const isDCEVM = output.toLowerCase().includes("dcevm") || output.toLowerCase().includes("jbr");
|
|
88
|
+
|
|
89
|
+
const majorVersion = parseInt(version.split(".")[0]);
|
|
90
|
+
const status = majorVersion >= 11 ? "ok" : "warning";
|
|
91
|
+
|
|
92
|
+
return {
|
|
93
|
+
name: "Java",
|
|
94
|
+
status,
|
|
95
|
+
message: `v${version}${isDCEVM ? " + DCEVM" : ""}`,
|
|
96
|
+
details: isDCEVM ? "Hot-reload disponível" : "Considere instalar DCEVM para hot-reload"
|
|
97
|
+
};
|
|
98
|
+
} catch {
|
|
99
|
+
return {
|
|
100
|
+
name: "Java",
|
|
101
|
+
status: "error",
|
|
102
|
+
message: "Java não encontrado",
|
|
103
|
+
details: "Instale o JDK 11+ e configure JAVA_HOME"
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
private async checkBuildTool(tool: string): Promise<HealthCheck> {
|
|
109
|
+
try {
|
|
110
|
+
if (tool === "maven") {
|
|
111
|
+
const { stdout } = await execAsync("mvn -version");
|
|
112
|
+
const version = stdout.match(/Apache Maven (\d+\.\d+\.\d+)/)?.[1] || "unknown";
|
|
113
|
+
return {
|
|
114
|
+
name: "Maven",
|
|
115
|
+
status: "ok",
|
|
116
|
+
message: `v${version}`
|
|
117
|
+
};
|
|
118
|
+
} else {
|
|
119
|
+
const { stdout } = await execAsync("gradle --version");
|
|
120
|
+
const version = stdout.match(/Gradle (\d+\.\d+\.\d+)/)?.[1] || "unknown";
|
|
121
|
+
return {
|
|
122
|
+
name: "Gradle",
|
|
123
|
+
status: "ok",
|
|
124
|
+
message: `v${version}`
|
|
125
|
+
};
|
|
126
|
+
}
|
|
127
|
+
} catch {
|
|
128
|
+
return {
|
|
129
|
+
name: tool === "maven" ? "Maven" : "Gradle",
|
|
130
|
+
status: "error",
|
|
131
|
+
message: `${tool === "maven" ? "mvn" : "gradle"} não encontrado`,
|
|
132
|
+
details: `Instale ${tool === "maven" ? "Maven" : "Gradle"} e adicione ao PATH`
|
|
133
|
+
};
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
private async checkTomcat(config: AppConfig): Promise<HealthCheck> {
|
|
138
|
+
if (config.tomcat.embedded) {
|
|
139
|
+
return {
|
|
140
|
+
name: "Tomcat",
|
|
141
|
+
status: "ok",
|
|
142
|
+
message: `Embutido v${config.tomcat.version || "10.1.52"}`,
|
|
143
|
+
details: "Auto-download habilitado"
|
|
144
|
+
};
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
if (existsSync(config.tomcat.path)) {
|
|
148
|
+
const versionFile = `${config.tomcat.path}/bin/version.sh`;
|
|
149
|
+
const versionBat = `${config.tomcat.path}/bin/version.bat`;
|
|
150
|
+
|
|
151
|
+
try {
|
|
152
|
+
const cmd = existsSync(versionBat) ? versionBat : versionFile;
|
|
153
|
+
const { stdout } = await execAsync(cmd);
|
|
154
|
+
const version = stdout.match(/Server version: Apache Tomcat\/(\d+\.\d+\.\d+)/)?.[1] || "unknown";
|
|
155
|
+
return {
|
|
156
|
+
name: "Tomcat",
|
|
157
|
+
status: "ok",
|
|
158
|
+
message: `v${version}`,
|
|
159
|
+
details: config.tomcat.path
|
|
160
|
+
};
|
|
161
|
+
} catch {
|
|
162
|
+
return {
|
|
163
|
+
name: "Tomcat",
|
|
164
|
+
status: "warning",
|
|
165
|
+
message: "Caminho existe mas não foi possível verificar versão",
|
|
166
|
+
details: config.tomcat.path
|
|
167
|
+
};
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
return {
|
|
172
|
+
name: "Tomcat",
|
|
173
|
+
status: "error",
|
|
174
|
+
message: "Caminho não encontrado",
|
|
175
|
+
details: `Configure CATALINA_HOME ou use Tomcat embutido`
|
|
176
|
+
};
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
private async checkPorts(port: number): Promise<HealthCheck> {
|
|
180
|
+
try {
|
|
181
|
+
let cmd: string;
|
|
182
|
+
if (platform() === "win32") {
|
|
183
|
+
cmd = `netstat -an | findstr :${port}`;
|
|
184
|
+
} else if (platform() === "darwin") {
|
|
185
|
+
cmd = `lsof -i :${port}`;
|
|
186
|
+
} else {
|
|
187
|
+
cmd = `ss -tuln | grep :${port}`;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
const { stdout } = await execAsync(cmd);
|
|
191
|
+
const isInUse = stdout.trim().length > 0;
|
|
192
|
+
|
|
193
|
+
if (isInUse) {
|
|
194
|
+
return {
|
|
195
|
+
name: "Portas",
|
|
196
|
+
status: "warning",
|
|
197
|
+
message: `Porta ${port} em uso`,
|
|
198
|
+
details: "Outro processo pode estar usando a porta"
|
|
199
|
+
};
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
return {
|
|
203
|
+
name: "Portas",
|
|
204
|
+
status: "ok",
|
|
205
|
+
message: `Porta ${port} disponível`
|
|
206
|
+
};
|
|
207
|
+
} catch {
|
|
208
|
+
// Comando falhou, assume que porta está livre
|
|
209
|
+
return {
|
|
210
|
+
name: "Portas",
|
|
211
|
+
status: "ok",
|
|
212
|
+
message: `Porta ${port} parece disponível`
|
|
213
|
+
};
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
private checkMemory(): HealthCheck {
|
|
218
|
+
const total = totalmem();
|
|
219
|
+
const free = freemem();
|
|
220
|
+
const used = total - free;
|
|
221
|
+
const percentUsed = Math.round((used / total) * 100);
|
|
222
|
+
const freeGB = (free / 1024 / 1024 / 1024).toFixed(1);
|
|
223
|
+
const totalGB = (total / 1024 / 1024 / 1024).toFixed(1);
|
|
224
|
+
|
|
225
|
+
const status = percentUsed > 90 ? "warning" : "ok";
|
|
226
|
+
|
|
227
|
+
return {
|
|
228
|
+
name: "Memória",
|
|
229
|
+
status,
|
|
230
|
+
message: `${freeGB}GB livre de ${totalGB}GB`,
|
|
231
|
+
details: `${percentUsed}% em uso`
|
|
232
|
+
};
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
private async checkDisk(): Promise<HealthCheck> {
|
|
236
|
+
try {
|
|
237
|
+
let cmd: string;
|
|
238
|
+
if (platform() === "win32") {
|
|
239
|
+
cmd = "wmic logicaldisk get size,freespace,caption";
|
|
240
|
+
} else {
|
|
241
|
+
cmd = "df -h .";
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
const { stdout } = await execAsync(cmd);
|
|
245
|
+
|
|
246
|
+
if (platform() === "win32") {
|
|
247
|
+
const lines = stdout.trim().split("\n").slice(1);
|
|
248
|
+
const mainDisk = lines.find(l => l.includes(":")) || "";
|
|
249
|
+
const parts = mainDisk.trim().split(/\s+/);
|
|
250
|
+
if (parts.length >= 3) {
|
|
251
|
+
const free = parseInt(parts[0]) / 1024 / 1024 / 1024;
|
|
252
|
+
const total = parseInt(parts[1]) / 1024 / 1024 / 1024;
|
|
253
|
+
const percentFree = Math.round((free / total) * 100);
|
|
254
|
+
|
|
255
|
+
return {
|
|
256
|
+
name: "Disco",
|
|
257
|
+
status: percentFree < 10 ? "warning" : "ok",
|
|
258
|
+
message: `${free.toFixed(1)}GB livre`,
|
|
259
|
+
details: `${percentFree}% disponível`
|
|
260
|
+
};
|
|
261
|
+
}
|
|
262
|
+
} else {
|
|
263
|
+
const match = stdout.match(/(\d+)%/);
|
|
264
|
+
if (match) {
|
|
265
|
+
const used = parseInt(match[1]);
|
|
266
|
+
return {
|
|
267
|
+
name: "Disco",
|
|
268
|
+
status: used > 90 ? "warning" : "ok",
|
|
269
|
+
message: `${100 - used}% disponível`,
|
|
270
|
+
details: stdout.split("\n")[1]?.split(/\s+/).pop() || ""
|
|
271
|
+
};
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
throw new Error("Could not parse disk info");
|
|
276
|
+
} catch {
|
|
277
|
+
return {
|
|
278
|
+
name: "Disco",
|
|
279
|
+
status: "warning",
|
|
280
|
+
message: "Não foi possível verificar",
|
|
281
|
+
details: "Verifique manualmente"
|
|
282
|
+
};
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
private checkGit(): HealthCheck {
|
|
287
|
+
if (existsSync(".git")) {
|
|
288
|
+
return {
|
|
289
|
+
name: "Git",
|
|
290
|
+
status: "ok",
|
|
291
|
+
message: "Repositório Git detectado"
|
|
292
|
+
};
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
return {
|
|
296
|
+
name: "Git",
|
|
297
|
+
status: "warning",
|
|
298
|
+
message: "Sem repositório Git",
|
|
299
|
+
details: "Execute 'git init' para versionamento"
|
|
300
|
+
};
|
|
301
|
+
}
|
|
302
|
+
}
|
|
@@ -31,6 +31,13 @@ export class HelpCommand implements Command {
|
|
|
31
31
|
${this.c("green", "tomcat")} Manage embedded Tomcat (install, list, installed, use, status)
|
|
32
32
|
${this.c("green", "docs")} Generate endpoint documentation
|
|
33
33
|
${this.c("green", "encoding")} Convert file encoding (detect, convert, fix, list)
|
|
34
|
+
|
|
35
|
+
${this.c("cyan", "init")} Initialize project configuration (wizard)
|
|
36
|
+
${this.c("cyan", "config")} View/edit configuration (--interactive)
|
|
37
|
+
${this.c("cyan", "history")} Show command history
|
|
38
|
+
${this.c("cyan", "redo")} Repeat last command
|
|
39
|
+
${this.c("cyan", "health")} Check environment health
|
|
40
|
+
${this.c("cyan", "completion")} Generate shell completions (bash/zsh/fish)
|
|
34
41
|
|
|
35
42
|
${this.c("yellow", "GENERAL OPTIONS")}
|
|
36
43
|
${this.c("cyan", "-p, --path")} <path> Tomcat installation path
|
|
@@ -112,6 +119,26 @@ export class HelpCommand implements Command {
|
|
|
112
119
|
xavva encoding fix src/main/java/MinhaClasse.java # Fix mojibake
|
|
113
120
|
xavva encoding list # List all file encodings
|
|
114
121
|
|
|
122
|
+
${this.c("dim", "# Initialize new project")}
|
|
123
|
+
xavva init # Interactive wizard
|
|
124
|
+
|
|
125
|
+
${this.c("dim", "# Manage configuration")}
|
|
126
|
+
xavva config # View current config
|
|
127
|
+
xavva config --interactive # Edit config interactively
|
|
128
|
+
|
|
129
|
+
${this.c("dim", "# Command history")}
|
|
130
|
+
xavva history # Show recent commands
|
|
131
|
+
xavva history --clear # Clear history
|
|
132
|
+
xavva redo # Repeat last command
|
|
133
|
+
|
|
134
|
+
${this.c("dim", "# Health check")}
|
|
135
|
+
xavva health # Check environment health
|
|
136
|
+
|
|
137
|
+
${this.c("dim", "# Shell completions")}
|
|
138
|
+
xavva completion bash # Generate bash completions
|
|
139
|
+
xavva completion zsh # Generate zsh completions
|
|
140
|
+
eval "$(xavva completion bash)" # Enable in current shell
|
|
141
|
+
|
|
115
142
|
${this.c("yellow", "CONFIGURATION")}
|
|
116
143
|
Settings are loaded from ${this.c("cyan", "xavva.json")} in the project root:
|
|
117
144
|
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import type { Command } from "./Command";
|
|
2
|
+
import type { AppConfig, CLIArguments } from "../types/config";
|
|
3
|
+
import { HistoryService } from "../services/HistoryService";
|
|
4
|
+
import { Logger } from "../utils/ui";
|
|
5
|
+
|
|
6
|
+
export class HistoryCommand implements Command {
|
|
7
|
+
private historyService = new HistoryService();
|
|
8
|
+
|
|
9
|
+
async execute(_config: AppConfig, args?: CLIArguments): Promise<void> {
|
|
10
|
+
const clear = args?.["clear"] || false;
|
|
11
|
+
const limit = parseInt(String(args?.["limit"] || "10"));
|
|
12
|
+
|
|
13
|
+
if (clear) {
|
|
14
|
+
await this.historyService.clear();
|
|
15
|
+
Logger.success("Histórico limpo!");
|
|
16
|
+
return;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const entries = await this.historyService.getRecent(limit);
|
|
20
|
+
const stats = await this.historyService.getStats();
|
|
21
|
+
|
|
22
|
+
Logger.banner("history");
|
|
23
|
+
Logger.section(`Últimos ${entries.length} comandos`);
|
|
24
|
+
|
|
25
|
+
if (entries.length === 0) {
|
|
26
|
+
Logger.dim("Nenhum comando no histórico");
|
|
27
|
+
Logger.endSection();
|
|
28
|
+
return;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
for (let i = 0; i < entries.length; i++) {
|
|
32
|
+
const entry = entries[i];
|
|
33
|
+
const date = new Date(entry.timestamp);
|
|
34
|
+
const time = date.toLocaleTimeString("pt-BR", { hour: "2-digit", minute: "2-digit" });
|
|
35
|
+
const icon = entry.success
|
|
36
|
+
? `${Logger.C.success}✓${Logger.C.reset}`
|
|
37
|
+
: `${Logger.C.error}✗${Logger.C.reset}`;
|
|
38
|
+
|
|
39
|
+
const args = entry.args.length > 0 ? entry.args.join(" ") : "";
|
|
40
|
+
const duration = entry.duration ? `${Logger.C.gray}(${entry.duration.toFixed(1)}s)${Logger.C.reset}` : "";
|
|
41
|
+
|
|
42
|
+
Logger.log(`${Logger.C.gray}│${Logger.C.reset} ${Logger.C.dim}${time}${Logger.C.reset} ${icon} ${Logger.C.white}xavva ${entry.command}${Logger.C.reset} ${Logger.C.gray}${args}${Logger.C.reset} ${duration}`);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
Logger.endSection();
|
|
46
|
+
Logger.info(`Total: ${stats.total} | Sucesso: ${stats.successful} | Falha: ${stats.failed}`);
|
|
47
|
+
Logger.dim("Use 'xavva redo' para repetir o último comando");
|
|
48
|
+
}
|
|
49
|
+
}
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
import { input, select, confirm, number } from "@inquirer/prompts";
|
|
2
|
+
import { writeFile, access } from "fs/promises";
|
|
3
|
+
import { join } from "path";
|
|
4
|
+
import { constants } from "fs";
|
|
5
|
+
import type { Command } from "./Command";
|
|
6
|
+
import type { AppConfig, CLIArguments } from "../types/config";
|
|
7
|
+
import { Logger } from "../utils/ui";
|
|
8
|
+
|
|
9
|
+
export class InitCommand implements Command {
|
|
10
|
+
async execute(_config: AppConfig, _args?: CLIArguments): Promise<void> {
|
|
11
|
+
Logger.banner("init");
|
|
12
|
+
Logger.section("Wizard de Configuração");
|
|
13
|
+
Logger.info("Vamos configurar seu projeto Xavva");
|
|
14
|
+
Logger.newline();
|
|
15
|
+
|
|
16
|
+
// Detectar build tool
|
|
17
|
+
const buildTool = await this.detectBuildTool();
|
|
18
|
+
|
|
19
|
+
// Nome da aplicação
|
|
20
|
+
const appName = await input({
|
|
21
|
+
message: "Nome da aplicação:",
|
|
22
|
+
default: process.cwd().split(/[/\\]/).pop() || "my-app",
|
|
23
|
+
validate: (value) => value.length > 0 || "Nome é obrigatório"
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
// Profile
|
|
27
|
+
const profile = await select({
|
|
28
|
+
message: "Profile padrão:",
|
|
29
|
+
choices: [
|
|
30
|
+
{ name: "desenvolvimento", value: "dev" },
|
|
31
|
+
{ name: "teste", value: "test" },
|
|
32
|
+
{ name: "produção", value: "prod" },
|
|
33
|
+
{ name: "customizado", value: "custom" }
|
|
34
|
+
],
|
|
35
|
+
default: "dev"
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
const customProfile = profile === "custom" ? await input({
|
|
39
|
+
message: "Nome do profile:",
|
|
40
|
+
default: "local"
|
|
41
|
+
}) : profile;
|
|
42
|
+
|
|
43
|
+
// Porta do Tomcat
|
|
44
|
+
const port = await number({
|
|
45
|
+
message: "Porta do Tomcat:",
|
|
46
|
+
default: 8080,
|
|
47
|
+
validate: (value) => (value && value > 0 && value < 65536) || "Porta inválida"
|
|
48
|
+
}) || 8080;
|
|
49
|
+
|
|
50
|
+
// Configurações opcionais
|
|
51
|
+
Logger.newline();
|
|
52
|
+
Logger.dim("Configurações avançadas:");
|
|
53
|
+
|
|
54
|
+
const useEmbedded = await confirm({
|
|
55
|
+
message: "Usar Tomcat embutido (auto-download)?",
|
|
56
|
+
default: true
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
const enableCache = await confirm({
|
|
60
|
+
message: "Habilitar cache de build?",
|
|
61
|
+
default: true
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
const enableTui = await confirm({
|
|
65
|
+
message: "Habilitar dashboard TUI?",
|
|
66
|
+
default: true
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
const encoding = await select({
|
|
70
|
+
message: "Encoding:",
|
|
71
|
+
choices: [
|
|
72
|
+
{ name: "UTF-8", value: "UTF-8" },
|
|
73
|
+
{ name: "ISO-8859-1", value: "ISO-8859-1" },
|
|
74
|
+
{ name: "Windows-1252", value: "Windows-1252" }
|
|
75
|
+
],
|
|
76
|
+
default: "UTF-8"
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
// Montar configuração
|
|
80
|
+
const config: Record<string, unknown> = {
|
|
81
|
+
appName,
|
|
82
|
+
buildTool,
|
|
83
|
+
profile: customProfile,
|
|
84
|
+
port,
|
|
85
|
+
cache: enableCache,
|
|
86
|
+
tui: enableTui,
|
|
87
|
+
encoding
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
if (useEmbedded) {
|
|
91
|
+
config.embedded = true;
|
|
92
|
+
config.tomcatVersion = "10.1.52";
|
|
93
|
+
} else {
|
|
94
|
+
const tomcatPath = await input({
|
|
95
|
+
message: "Caminho do Tomcat (CATALINA_HOME):",
|
|
96
|
+
validate: async (value) => {
|
|
97
|
+
if (!value) return "Caminho é obrigatório";
|
|
98
|
+
try {
|
|
99
|
+
await access(value, constants.R_OK);
|
|
100
|
+
return true;
|
|
101
|
+
} catch {
|
|
102
|
+
return "Caminho não acessível";
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
});
|
|
106
|
+
config.tomcatPath = tomcatPath;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// Salvar arquivo
|
|
110
|
+
Logger.newline();
|
|
111
|
+
Logger.step("Salvando configuração...");
|
|
112
|
+
|
|
113
|
+
const configPath = join(process.cwd(), "xavva.json");
|
|
114
|
+
await writeFile(configPath, JSON.stringify(config, null, 2));
|
|
115
|
+
|
|
116
|
+
Logger.success(`Configuração salva em ${configPath}`);
|
|
117
|
+
Logger.newline();
|
|
118
|
+
Logger.ready("Projeto configurado!");
|
|
119
|
+
Logger.info("Próximos passos:");
|
|
120
|
+
Logger.log(` ${Logger.C.gray}│${Logger.C.reset} ${Logger.C.primary}xavva build${Logger.C.reset} ${Logger.C.gray}- Compilar projeto${Logger.C.reset}`);
|
|
121
|
+
Logger.log(` ${Logger.C.gray}│${Logger.C.reset} ${Logger.C.primary}xavva deploy${Logger.C.reset} ${Logger.C.gray}- Build + deploy${Logger.C.reset}`);
|
|
122
|
+
Logger.log(` ${Logger.C.gray}│${Logger.C.reset} ${Logger.C.primary}xavva doctor${Logger.C.reset} ${Logger.C.gray}- Verificar ambiente${Logger.C.reset}`);
|
|
123
|
+
Logger.done();
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
private async detectBuildTool(): Promise<"maven" | "gradle"> {
|
|
127
|
+
try {
|
|
128
|
+
await access(join(process.cwd(), "pom.xml"), constants.R_OK);
|
|
129
|
+
Logger.info("Detectado: Projeto Maven");
|
|
130
|
+
return "maven";
|
|
131
|
+
} catch {
|
|
132
|
+
try {
|
|
133
|
+
await access(join(process.cwd(), "build.gradle"), constants.R_OK);
|
|
134
|
+
Logger.info("Detectado: Projeto Gradle");
|
|
135
|
+
return "gradle";
|
|
136
|
+
} catch {
|
|
137
|
+
const choice = await select({
|
|
138
|
+
message: "Build tool:",
|
|
139
|
+
choices: [
|
|
140
|
+
{ name: "Maven", value: "maven" },
|
|
141
|
+
{ name: "Gradle", value: "gradle" }
|
|
142
|
+
]
|
|
143
|
+
});
|
|
144
|
+
return choice;
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import type { Command } from "./Command";
|
|
2
|
+
import type { AppConfig, CLIArguments } from "../types/config";
|
|
3
|
+
import { HistoryService } from "../services/HistoryService";
|
|
4
|
+
import { Logger } from "../utils/ui";
|
|
5
|
+
import { ProcessManager } from "../utils/processManager";
|
|
6
|
+
|
|
7
|
+
export class RedoCommand implements Command {
|
|
8
|
+
private historyService = new HistoryService();
|
|
9
|
+
|
|
10
|
+
async execute(_config: AppConfig, _args?: CLIArguments): Promise<void> {
|
|
11
|
+
const lastEntry = await this.historyService.getLast();
|
|
12
|
+
|
|
13
|
+
if (!lastEntry) {
|
|
14
|
+
Logger.error("Nenhum comando no histórico");
|
|
15
|
+
return;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const args = lastEntry.args.length > 0 ? lastEntry.args.join(" ") : "";
|
|
19
|
+
Logger.banner("redo");
|
|
20
|
+
Logger.info(`Repetindo: ${Logger.C.white}xavva ${lastEntry.command}${Logger.C.reset} ${Logger.C.gray}${args}${Logger.C.reset}`);
|
|
21
|
+
Logger.newline();
|
|
22
|
+
|
|
23
|
+
// Re-executar o comando
|
|
24
|
+
const proc = Bun.spawn([
|
|
25
|
+
"bun", "run", "src/index.ts",
|
|
26
|
+
lastEntry.command,
|
|
27
|
+
...lastEntry.args
|
|
28
|
+
], {
|
|
29
|
+
stdio: "inherit",
|
|
30
|
+
cwd: process.cwd()
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
await proc.exited;
|
|
34
|
+
ProcessManager.getInstance().shutdown(proc.exitCode || 0);
|
|
35
|
+
}
|
|
36
|
+
}
|
package/src/di/container.ts
CHANGED
|
@@ -24,6 +24,14 @@ import { DepsCommand } from "../commands/DepsCommand";
|
|
|
24
24
|
import { TomcatCommand } from "../commands/TomcatCommand";
|
|
25
25
|
import { EncodingCommand } from "../commands/EncodingCommand";
|
|
26
26
|
import { DocsCommand } from "../commands/DocsCommand";
|
|
27
|
+
import { InitCommand } from "../commands/InitCommand";
|
|
28
|
+
import { ConfigCommand } from "../commands/ConfigCommand";
|
|
29
|
+
import { HistoryCommand } from "../commands/HistoryCommand";
|
|
30
|
+
import { RedoCommand } from "../commands/RedoCommand";
|
|
31
|
+
import { HealthCommand } from "../commands/HealthCommand";
|
|
32
|
+
import { CompletionCommand } from "../commands/CompletionCommand";
|
|
33
|
+
import { HistoryService } from "../services/HistoryService";
|
|
34
|
+
import { NotificationService } from "../services/NotificationService";
|
|
27
35
|
import type { Command } from "../commands/Command";
|
|
28
36
|
import { Logger } from "../utils/ui";
|
|
29
37
|
|
|
@@ -35,6 +43,7 @@ export interface Services {
|
|
|
35
43
|
auditService: AuditService;
|
|
36
44
|
dashboardService: DashboardService;
|
|
37
45
|
logAnalyzer: LogAnalyzer;
|
|
46
|
+
historyService: HistoryService;
|
|
38
47
|
}
|
|
39
48
|
|
|
40
49
|
export interface Commands {
|
|
@@ -53,6 +62,12 @@ export interface Commands {
|
|
|
53
62
|
tomcat: TomcatCommand;
|
|
54
63
|
encoding: EncodingCommand;
|
|
55
64
|
docs: DocsCommand;
|
|
65
|
+
init: InitCommand;
|
|
66
|
+
config: ConfigCommand;
|
|
67
|
+
history: HistoryCommand;
|
|
68
|
+
redo: RedoCommand;
|
|
69
|
+
health: HealthCommand;
|
|
70
|
+
completion: CompletionCommand;
|
|
56
71
|
}
|
|
57
72
|
|
|
58
73
|
export class DIContainer {
|
|
@@ -101,6 +116,7 @@ export class DIContainer {
|
|
|
101
116
|
tomcatService.setProjectService(projectService);
|
|
102
117
|
|
|
103
118
|
const auditService = new AuditService(this.config.tomcat);
|
|
119
|
+
const historyService = new HistoryService();
|
|
104
120
|
|
|
105
121
|
this.services = {
|
|
106
122
|
projectService,
|
|
@@ -110,6 +126,7 @@ export class DIContainer {
|
|
|
110
126
|
auditService,
|
|
111
127
|
dashboardService,
|
|
112
128
|
logAnalyzer,
|
|
129
|
+
historyService,
|
|
113
130
|
};
|
|
114
131
|
}
|
|
115
132
|
|
|
@@ -140,6 +157,12 @@ export class DIContainer {
|
|
|
140
157
|
tomcat: new TomcatCommand(),
|
|
141
158
|
encoding: new EncodingCommand(),
|
|
142
159
|
docs: new DocsCommand(),
|
|
160
|
+
init: new InitCommand(),
|
|
161
|
+
config: new ConfigCommand(),
|
|
162
|
+
history: new HistoryCommand(),
|
|
163
|
+
redo: new RedoCommand(),
|
|
164
|
+
health: new HealthCommand(),
|
|
165
|
+
completion: new CompletionCommand(),
|
|
143
166
|
};
|
|
144
167
|
}
|
|
145
168
|
|