@archznn/xavva 1.7.0 → 1.8.1
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 +95 -25
- package/package.json +1 -1
- package/src/commands/AuditCommand.ts +6 -6
- package/src/commands/BuildCommand.ts +3 -3
- package/src/commands/Command.ts +2 -2
- package/src/commands/CommandRegistry.ts +36 -0
- package/src/commands/DeployCommand.ts +146 -116
- package/src/commands/DoctorCommand.ts +105 -5
- package/src/commands/HelpCommand.ts +2 -1
- package/src/commands/RunCommand.ts +112 -36
- package/src/commands/StartCommand.ts +3 -1
- package/src/index.ts +42 -133
- package/src/services/AuditService.ts +7 -2
- package/src/services/BrowserService.ts +41 -0
- package/src/services/BuildCacheService.ts +83 -0
- package/src/services/BuildService.ts +105 -82
- package/src/services/EndpointService.ts +17 -0
- package/src/services/ProjectService.ts +126 -0
- package/src/services/TomcatService.ts +65 -67
- package/src/services/WatcherService.ts +78 -0
- package/src/types/config.ts +30 -1
- package/src/utils/config.ts +21 -17
- package/src/utils/ui.ts +15 -13
|
@@ -63,11 +63,98 @@ export class DoctorCommand implements Command {
|
|
|
63
63
|
this.check("Git", gitOk, gitOk ? "Disponível" : "Não encontrado no PATH");
|
|
64
64
|
|
|
65
65
|
Logger.section("Xavva Doctor - Integridade de Arquivos");
|
|
66
|
+
await this.checkJarIntegrity(values.fix, config);
|
|
66
67
|
await this.checkBOM(values.fix);
|
|
67
68
|
|
|
68
69
|
console.log("");
|
|
69
70
|
}
|
|
70
71
|
|
|
72
|
+
private async checkJarIntegrity(fix: boolean, config: AppConfig) {
|
|
73
|
+
const searchPaths = [
|
|
74
|
+
path.join(process.cwd(), "target"),
|
|
75
|
+
path.join(process.cwd(), "build"),
|
|
76
|
+
path.join(config.tomcat.path, "webapps")
|
|
77
|
+
].filter(p => fs.existsSync(p));
|
|
78
|
+
|
|
79
|
+
const corruptedJars: string[] = [];
|
|
80
|
+
const zipEndSignature = Buffer.from([0x50, 0x4b, 0x05, 0x06]);
|
|
81
|
+
|
|
82
|
+
const scan = (dir: string) => {
|
|
83
|
+
try {
|
|
84
|
+
const list = fs.readdirSync(dir, { withFileTypes: true });
|
|
85
|
+
for (const item of list) {
|
|
86
|
+
const res = path.resolve(dir, item.name);
|
|
87
|
+
if (item.isDirectory()) {
|
|
88
|
+
if (item.name === "node_modules" || item.name === ".git") continue;
|
|
89
|
+
scan(res);
|
|
90
|
+
} else if (item.name.endsWith(".jar")) {
|
|
91
|
+
try {
|
|
92
|
+
const stats = fs.statSync(res);
|
|
93
|
+
if (stats.size < 22) {
|
|
94
|
+
corruptedJars.push(res);
|
|
95
|
+
continue;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// Lê os últimos 1024 bytes para encontrar a assinatura EOCD do ZIP
|
|
99
|
+
const readSize = Math.min(stats.size, 1024);
|
|
100
|
+
const buffer = Buffer.alloc(readSize);
|
|
101
|
+
const fd = fs.openSync(res, "r");
|
|
102
|
+
fs.readSync(fd, buffer, 0, readSize, stats.size - readSize);
|
|
103
|
+
fs.closeSync(fd);
|
|
104
|
+
|
|
105
|
+
if (!buffer.includes(zipEndSignature)) {
|
|
106
|
+
corruptedJars.push(res);
|
|
107
|
+
}
|
|
108
|
+
} catch (e) {
|
|
109
|
+
corruptedJars.push(res);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
} catch (e) {}
|
|
114
|
+
};
|
|
115
|
+
|
|
116
|
+
for (const p of searchPaths) {
|
|
117
|
+
scan(p);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
if (corruptedJars.length > 0) {
|
|
121
|
+
this.check(
|
|
122
|
+
"Integridade JAR",
|
|
123
|
+
false,
|
|
124
|
+
`${corruptedJars.length} arquivos corrompidos detectados.`,
|
|
125
|
+
);
|
|
126
|
+
if (fix) {
|
|
127
|
+
for (const file of corruptedJars) {
|
|
128
|
+
try {
|
|
129
|
+
fs.unlinkSync(file);
|
|
130
|
+
console.log(` \x1b[32m✔\x1b[0m Removido: ${path.basename(file)}`);
|
|
131
|
+
} catch (e) {}
|
|
132
|
+
}
|
|
133
|
+
Logger.success("JARs corrompidos removidos! Eles serão reconstruídos no próximo build.");
|
|
134
|
+
|
|
135
|
+
// Limpar cache do Tomcat
|
|
136
|
+
Logger.process("Limpando cache do Tomcat (work/temp)...");
|
|
137
|
+
const tomcatWork = path.join(config.tomcat.path, "work");
|
|
138
|
+
const tomcatTemp = path.join(config.tomcat.path, "temp");
|
|
139
|
+
[tomcatWork, tomcatTemp].forEach(p => {
|
|
140
|
+
try {
|
|
141
|
+
if (fs.existsSync(p)) {
|
|
142
|
+
fs.rmSync(p, { recursive: true, force: true });
|
|
143
|
+
fs.mkdirSync(p);
|
|
144
|
+
}
|
|
145
|
+
} catch (e) {}
|
|
146
|
+
});
|
|
147
|
+
Logger.success("Cache do Tomcat limpo com sucesso.");
|
|
148
|
+
} else {
|
|
149
|
+
Logger.warn(
|
|
150
|
+
"Use 'xavva doctor --fix' para remover os JARs corrompidos e limpar o cache.",
|
|
151
|
+
);
|
|
152
|
+
}
|
|
153
|
+
} else {
|
|
154
|
+
this.check("Integridade JAR", true, "Todos os arquivos JAR parecem íntegros.");
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
|
|
71
158
|
private async checkBOM(fix: boolean) {
|
|
72
159
|
const srcPath = path.join(process.cwd(), "src");
|
|
73
160
|
if (!fs.existsSync(srcPath)) return;
|
|
@@ -185,8 +272,14 @@ export class DoctorCommand implements Command {
|
|
|
185
272
|
Logger.success("Download concluído. Extraindo binários...");
|
|
186
273
|
|
|
187
274
|
// Usar PowerShell para extrair .tar.gz (nativo no Windows 10/11)
|
|
188
|
-
const extractCmd = `
|
|
189
|
-
Bun.spawnSync(["powershell", "-command", extractCmd]
|
|
275
|
+
const extractCmd = `tar -xzf $env:TAR_PATH -C $env:INSTALL_DIR`;
|
|
276
|
+
Bun.spawnSync(["powershell", "-command", extractCmd], {
|
|
277
|
+
env: {
|
|
278
|
+
...process.env,
|
|
279
|
+
TAR_PATH: tarPath,
|
|
280
|
+
INSTALL_DIR: installDir
|
|
281
|
+
}
|
|
282
|
+
});
|
|
190
283
|
|
|
191
284
|
fs.rmSync(tarPath);
|
|
192
285
|
|
|
@@ -204,12 +297,13 @@ export class DoctorCommand implements Command {
|
|
|
204
297
|
};
|
|
205
298
|
|
|
206
299
|
const jdkPath = findJdkRoot(installDir) || installDir;
|
|
300
|
+
const binPath = path.join(jdkPath, "bin");
|
|
207
301
|
|
|
208
302
|
Logger.process("Configurando variáveis de ambiente do SISTEMA...");
|
|
209
303
|
|
|
210
304
|
const setEnvCmd = `
|
|
211
|
-
$jdk =
|
|
212
|
-
$bin =
|
|
305
|
+
$jdk = $env:JDK_PATH;
|
|
306
|
+
$bin = $env:BIN_PATH;
|
|
213
307
|
try {
|
|
214
308
|
[Environment]::SetEnvironmentVariable('JAVA_HOME', $jdk, 'Machine');
|
|
215
309
|
$pathVar = [Environment]::GetEnvironmentVariable('Path', 'Machine');
|
|
@@ -230,7 +324,13 @@ export class DoctorCommand implements Command {
|
|
|
230
324
|
Write-Error $_.Exception.Message;
|
|
231
325
|
}
|
|
232
326
|
`.replace(/\n/g, ' ');
|
|
233
|
-
const result = Bun.spawnSync(["powershell", "-command", setEnvCmd]
|
|
327
|
+
const result = Bun.spawnSync(["powershell", "-command", setEnvCmd], {
|
|
328
|
+
env: {
|
|
329
|
+
...process.env,
|
|
330
|
+
JDK_PATH: jdkPath,
|
|
331
|
+
BIN_PATH: binPath
|
|
332
|
+
}
|
|
333
|
+
});
|
|
234
334
|
const output = result.stdout.toString() + result.stderr.toString();
|
|
235
335
|
|
|
236
336
|
if (output.includes("ACCESS_DENIED")) {
|
|
@@ -24,7 +24,8 @@ Comandos principais:
|
|
|
24
24
|
|
|
25
25
|
Opções:
|
|
26
26
|
-w, --watch 👀 Hot Reload: monitora arquivos e redeploya automaticamente.
|
|
27
|
-
-d, --debug 🐞 Habilita debug Java (JPDA)
|
|
27
|
+
-d, --debug 🐞 Habilita debug Java (JPDA).
|
|
28
|
+
--dp [porta] 🔌 Porta do Debugger (padrão: 5005).
|
|
28
29
|
-c, --clean 🧹 Logs coloridos e simplificados (recomendado).
|
|
29
30
|
-q, --quiet 🤫 Mostra apenas mensagens essenciais nos logs.
|
|
30
31
|
-V, --verbose 📣 Mostra logs completos do Maven/Gradle.
|
|
@@ -1,19 +1,17 @@
|
|
|
1
1
|
import type { Command } from "./Command";
|
|
2
|
-
import type { AppConfig } from "../types/config";
|
|
2
|
+
import type { AppConfig, CLIArguments } from "../types/config";
|
|
3
3
|
import { Logger } from "../utils/ui";
|
|
4
|
-
import { spawn } from "child_process";
|
|
5
4
|
import path from "path";
|
|
6
5
|
|
|
7
6
|
export class RunCommand implements Command {
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
async execute(config: AppConfig): Promise<void> {
|
|
7
|
+
async execute(config: AppConfig, args?: CLIArguments): Promise<void> {
|
|
8
|
+
const isDebug = args?.debug !== false; // Default to true if not specified, matching previous behavior
|
|
11
9
|
let className = config.project.grep;
|
|
12
10
|
|
|
13
11
|
if (!className) {
|
|
14
12
|
className = await this.loadFromHistory();
|
|
15
13
|
if (!className) {
|
|
16
|
-
Logger.error(`Uso: xavva ${
|
|
14
|
+
Logger.error(`Uso: xavva ${isDebug ? "debug" : "run"} NomeDaClasse`);
|
|
17
15
|
return;
|
|
18
16
|
}
|
|
19
17
|
}
|
|
@@ -26,7 +24,7 @@ export class RunCommand implements Command {
|
|
|
26
24
|
|
|
27
25
|
this.saveToHistory(className);
|
|
28
26
|
|
|
29
|
-
if (
|
|
27
|
+
if (isDebug) {
|
|
30
28
|
Logger.section(`Interactive Debug: ${className}`);
|
|
31
29
|
} else {
|
|
32
30
|
Logger.section(`Running: ${className}`);
|
|
@@ -37,17 +35,17 @@ export class RunCommand implements Command {
|
|
|
37
35
|
|
|
38
36
|
const finalCp = `${localCp};${pathingJar}`;
|
|
39
37
|
|
|
40
|
-
const
|
|
38
|
+
const javaArgs = [
|
|
41
39
|
"-classpath", finalCp,
|
|
42
40
|
];
|
|
43
41
|
|
|
44
|
-
if (
|
|
45
|
-
|
|
42
|
+
if (isDebug) {
|
|
43
|
+
javaArgs.push("-agentlib:jdwp=transport=dt_socket,server=y,suspend=y,address=5005");
|
|
46
44
|
}
|
|
47
45
|
|
|
48
|
-
|
|
46
|
+
javaArgs.push(className);
|
|
49
47
|
|
|
50
|
-
if (
|
|
48
|
+
if (isDebug) {
|
|
51
49
|
Logger.warn(`🚀 Aguardando debugger na porta 5005 para ${className}...`);
|
|
52
50
|
Logger.log(`${Logger.C.cyan}Dica:${Logger.C.reset} No VS Code ou IntelliJ, use 'Attach to Remote JVM' na porta 5005.`);
|
|
53
51
|
Logger.newline();
|
|
@@ -55,19 +53,20 @@ export class RunCommand implements Command {
|
|
|
55
53
|
Logger.warn(`🚀 Executando ${className}...`);
|
|
56
54
|
}
|
|
57
55
|
|
|
58
|
-
const bin = "java";
|
|
56
|
+
const bin = process.env.JAVA_HOME ? path.join(process.env.JAVA_HOME, "bin", "java.exe") : "java";
|
|
59
57
|
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
resolve();
|
|
69
|
-
});
|
|
58
|
+
const proc = Bun.spawn([bin, ...javaArgs], {
|
|
59
|
+
stdout: "inherit",
|
|
60
|
+
stderr: "inherit",
|
|
61
|
+
stdin: "inherit",
|
|
62
|
+
env: {
|
|
63
|
+
...process.env,
|
|
64
|
+
JAVA_OPTS: "-Xms256m -Xmx1024m"
|
|
65
|
+
} as any
|
|
70
66
|
});
|
|
67
|
+
|
|
68
|
+
await proc.exited;
|
|
69
|
+
Logger.log(`Sessão de ${isDebug ? "debug" : "execução"} encerrada.`);
|
|
71
70
|
}
|
|
72
71
|
|
|
73
72
|
private async discoverClass(simpleName: string): Promise<string | null> {
|
|
@@ -222,22 +221,62 @@ export class RunCommand implements Command {
|
|
|
222
221
|
const paths = dependencyCp.split(";").filter(p => p.trim());
|
|
223
222
|
const relativePaths = paths.map(p => {
|
|
224
223
|
let rel = path.relative(xavvaDir, p).replace(/\\/g, "/");
|
|
225
|
-
if (fs.statSync(p).isDirectory() && !rel.endsWith("/")) rel += "/";
|
|
226
|
-
|
|
224
|
+
if (fs.existsSync(p) && fs.statSync(p).isDirectory() && !rel.endsWith("/")) rel += "/";
|
|
225
|
+
// Robust URL encoding for Class-Path as per Java Spec
|
|
226
|
+
return encodeURI(rel)
|
|
227
|
+
.replace(/#/g, '%23')
|
|
228
|
+
.replace(/\?/g, '%3F')
|
|
229
|
+
.replace(/%5B/g, '[')
|
|
230
|
+
.replace(/%5D/g, ']');
|
|
227
231
|
}).join(" ");
|
|
228
232
|
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
233
|
+
const header = "Class-Path: ";
|
|
234
|
+
let manifestContent = "Manifest-Version: 1.0\r\n";
|
|
235
|
+
|
|
236
|
+
let currentLine = header;
|
|
237
|
+
const parts = relativePaths.split(" ");
|
|
238
|
+
|
|
239
|
+
for (let i = 0; i < parts.length; i++) {
|
|
240
|
+
const part = parts[i] + (i < parts.length - 1 ? " " : "");
|
|
241
|
+
|
|
242
|
+
// Se adicionar o próximo 'part' exceder 70 bytes (margem de segurança antes do CRLF)
|
|
243
|
+
if (Buffer.from(currentLine + part).length > 70) {
|
|
244
|
+
// Se a parte em si for muito longa, precisamos quebrá-la
|
|
245
|
+
if (Buffer.from(" " + part).length > 70) {
|
|
246
|
+
let remainingPart = part;
|
|
247
|
+
while (remainingPart.length > 0) {
|
|
248
|
+
const spaceLeft = 70 - Buffer.from(currentLine).length;
|
|
249
|
+
|
|
250
|
+
// Encontra quantos caracteres de 'remainingPart' cabem no espaço restante
|
|
251
|
+
let fitCount = 0;
|
|
252
|
+
let fitBytes = 0;
|
|
253
|
+
for (let j = 0; j < remainingPart.length; j++) {
|
|
254
|
+
const charBytes = Buffer.from(remainingPart[j]).length;
|
|
255
|
+
if (fitBytes + charBytes > spaceLeft) break;
|
|
256
|
+
fitBytes += charBytes;
|
|
257
|
+
fitCount++;
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
if (fitCount > 0) {
|
|
261
|
+
currentLine += remainingPart.substring(0, fitCount);
|
|
262
|
+
remainingPart = remainingPart.substring(fitCount);
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
if (remainingPart.length > 0) {
|
|
266
|
+
manifestContent += currentLine + "\r\n";
|
|
267
|
+
currentLine = " ";
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
} else {
|
|
271
|
+
manifestContent += currentLine + "\r\n";
|
|
272
|
+
currentLine = " " + part;
|
|
273
|
+
}
|
|
235
274
|
} else {
|
|
236
|
-
|
|
275
|
+
currentLine += part;
|
|
237
276
|
}
|
|
238
277
|
}
|
|
278
|
+
manifestContent += currentLine + "\r\n\r\n";
|
|
239
279
|
|
|
240
|
-
const manifestContent = `Manifest-Version: 1.0\r\nClass-Path: ${wrappedCp}\r\n\r\n`;
|
|
241
280
|
const manifestPath = path.join(xavvaDir, "MANIFEST.MF");
|
|
242
281
|
fs.writeFileSync(manifestPath, manifestContent);
|
|
243
282
|
|
|
@@ -255,22 +294,59 @@ export class RunCommand implements Command {
|
|
|
255
294
|
if (!fs.existsSync(cpFile)) {
|
|
256
295
|
const stopSpinner = Logger.spinner("Generating project classpath");
|
|
257
296
|
try {
|
|
258
|
-
if (config.project.buildTool ===
|
|
297
|
+
if (config.project.buildTool === "maven") {
|
|
259
298
|
Bun.spawnSync(["mvn", "dependency:build-classpath", `-Dmdep.outputFile=${cpFile}`]);
|
|
299
|
+
} else if (config.project.buildTool === "gradle") {
|
|
300
|
+
const initScriptPath = path.join(xavvaDir, "init-cp.gradle");
|
|
301
|
+
const normalizedCpFile = cpFile.replace(/\\/g, "/");
|
|
302
|
+
const initScriptContent = `
|
|
303
|
+
allprojects {
|
|
304
|
+
afterEvaluate { project ->
|
|
305
|
+
if (project.plugins.hasPlugin('java')) {
|
|
306
|
+
tasks.register('printClasspath') {
|
|
307
|
+
doLast {
|
|
308
|
+
def cp = project.sourceSets.main.runtimeClasspath.asPath
|
|
309
|
+
def file = new File("${normalizedCpFile}")
|
|
310
|
+
if (!file.exists()) {
|
|
311
|
+
file.text = cp
|
|
312
|
+
} else {
|
|
313
|
+
file.text = file.text + File.pathSeparator + cp
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
`.trim().replace(/^ {24}/gm, ""); // Remove excess indentation
|
|
321
|
+
fs.writeFileSync(initScriptPath, initScriptContent);
|
|
322
|
+
Bun.spawnSync(["gradle", "-q", "printClasspath", "-I", initScriptPath]);
|
|
323
|
+
if (fs.existsSync(initScriptPath)) fs.unlinkSync(initScriptPath);
|
|
260
324
|
} else {
|
|
261
325
|
fs.writeFileSync(cpFile, ".");
|
|
262
326
|
}
|
|
263
|
-
} catch (e) {
|
|
327
|
+
} catch (e) {
|
|
328
|
+
Logger.error(`Falha ao gerar classpath: ${e}`);
|
|
329
|
+
}
|
|
264
330
|
stopSpinner();
|
|
265
331
|
}
|
|
266
332
|
|
|
267
|
-
|
|
333
|
+
let dependencyCp = fs.existsSync(cpFile) ? fs.readFileSync(cpFile, "utf8").trim() : "";
|
|
268
334
|
|
|
335
|
+
// Normalize platform specific separators to semicolon for consistency
|
|
336
|
+
if (path.delimiter !== ";") {
|
|
337
|
+
dependencyCp = dependencyCp.split(path.delimiter).join(";");
|
|
338
|
+
}
|
|
339
|
+
|
|
269
340
|
const localFolders = [
|
|
270
341
|
"target/classes",
|
|
271
342
|
"target/test-classes",
|
|
272
343
|
"build/classes/java/main",
|
|
273
344
|
"build/classes/java/test",
|
|
345
|
+
"build/classes/kotlin/main",
|
|
346
|
+
"build/resources/main",
|
|
347
|
+
"build/resources/test",
|
|
348
|
+
"bin/main",
|
|
349
|
+
"bin/test",
|
|
274
350
|
"."
|
|
275
351
|
];
|
|
276
352
|
|
|
@@ -4,8 +4,10 @@ import { TomcatService } from "../services/TomcatService";
|
|
|
4
4
|
import { Logger } from "../utils/ui";
|
|
5
5
|
|
|
6
6
|
export class StartCommand implements Command {
|
|
7
|
+
constructor(private tomcat: TomcatService) {}
|
|
8
|
+
|
|
7
9
|
async execute(config: AppConfig): Promise<void> {
|
|
8
|
-
const tomcat =
|
|
10
|
+
const tomcat = this.tomcat;
|
|
9
11
|
|
|
10
12
|
Logger.section("Start Only");
|
|
11
13
|
Logger.info("Port", config.tomcat.port);
|
package/src/index.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env bun
|
|
2
|
-
import { watch } from "fs";
|
|
3
2
|
import { ConfigManager } from "./utils/config";
|
|
3
|
+
import { CommandRegistry } from "./commands/CommandRegistry";
|
|
4
4
|
import { BuildCommand } from "./commands/BuildCommand";
|
|
5
5
|
import { DeployCommand } from "./commands/DeployCommand";
|
|
6
6
|
import { StartCommand } from "./commands/StartCommand";
|
|
@@ -10,11 +10,17 @@ import { RunCommand } from "./commands/RunCommand";
|
|
|
10
10
|
import { LogsCommand } from "./commands/LogsCommand";
|
|
11
11
|
import { DocsCommand } from "./commands/DocsCommand";
|
|
12
12
|
import { AuditCommand } from "./commands/AuditCommand";
|
|
13
|
+
|
|
14
|
+
import { ProjectService } from "./services/ProjectService";
|
|
13
15
|
import { TomcatService } from "./services/TomcatService";
|
|
14
|
-
import {
|
|
16
|
+
import { BuildService } from "./services/BuildService";
|
|
17
|
+
import { AuditService } from "./services/AuditService";
|
|
18
|
+
import { WatcherService } from "./services/WatcherService";
|
|
19
|
+
import { BuildCacheService } from "./services/BuildCacheService";
|
|
20
|
+
|
|
15
21
|
import pkg from "../package.json";
|
|
16
22
|
import { Logger } from "./utils/ui";
|
|
17
|
-
import
|
|
23
|
+
import type { AppConfig, CLIArguments } from "./types/config";
|
|
18
24
|
|
|
19
25
|
async function main() {
|
|
20
26
|
const { config, positionals, values } = await ConfigManager.load();
|
|
@@ -32,142 +38,45 @@ async function main() {
|
|
|
32
38
|
}
|
|
33
39
|
|
|
34
40
|
if (values.help) {
|
|
35
|
-
new HelpCommand().execute(config);
|
|
41
|
+
new HelpCommand().execute(config, values);
|
|
36
42
|
process.exit(0);
|
|
37
43
|
}
|
|
38
44
|
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
case "doctor":
|
|
47
|
-
await new DoctorCommand().execute(config, values);
|
|
48
|
-
break;
|
|
49
|
-
case "run":
|
|
50
|
-
await new RunCommand(false).execute(config);
|
|
51
|
-
break;
|
|
52
|
-
case "debug":
|
|
53
|
-
await new RunCommand(true).execute(config);
|
|
54
|
-
break;
|
|
55
|
-
case "logs":
|
|
56
|
-
await new LogsCommand().execute(config);
|
|
57
|
-
break;
|
|
58
|
-
case "docs":
|
|
59
|
-
await new DocsCommand().execute(config);
|
|
60
|
-
break;
|
|
61
|
-
case "audit":
|
|
62
|
-
await new AuditCommand().execute(config);
|
|
63
|
-
break;
|
|
64
|
-
case "dev":
|
|
65
|
-
case "deploy":
|
|
66
|
-
await handleDeploy(config, values);
|
|
67
|
-
break;
|
|
68
|
-
default:
|
|
69
|
-
Logger.error(`Comando desconhecido: ${commandName}`);
|
|
70
|
-
new HelpCommand().execute(config);
|
|
71
|
-
process.exit(1);
|
|
72
|
-
}
|
|
73
|
-
}
|
|
45
|
+
// 1. Instanciar Serviços (Injeção de Dependência)
|
|
46
|
+
const projectService = new ProjectService(config.project);
|
|
47
|
+
const buildCacheService = new BuildCacheService();
|
|
48
|
+
const buildService = new BuildService(config.project, config.tomcat, projectService, buildCacheService);
|
|
49
|
+
const tomcatService = new TomcatService(config.tomcat);
|
|
50
|
+
tomcatService.setProjectService(projectService);
|
|
51
|
+
const auditService = new AuditService(config.tomcat);
|
|
74
52
|
|
|
75
|
-
|
|
76
|
-
const
|
|
77
|
-
const deployCmd = new DeployCommand(tomcat);
|
|
53
|
+
// 2. Registrar Comandos
|
|
54
|
+
const registry = new CommandRegistry();
|
|
78
55
|
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
const coolingFiles = new Set<string>();
|
|
97
|
-
|
|
98
|
-
watch(process.cwd(), { recursive: true }, async (event, filename) => {
|
|
99
|
-
if (!filename) return;
|
|
100
|
-
|
|
101
|
-
if (coolingFiles.has(filename)) return;
|
|
102
|
-
coolingFiles.add(filename);
|
|
103
|
-
setTimeout(() => coolingFiles.delete(filename), 500);
|
|
104
|
-
|
|
105
|
-
const isJava = filename.endsWith(".java") || filename === "pom.xml" || filename === "build.gradle";
|
|
106
|
-
const isResource = filename.endsWith(".jsp") || filename.endsWith(".html") ||
|
|
107
|
-
filename.endsWith(".css") || filename.endsWith(".js") ||
|
|
108
|
-
filename.endsWith(".xml") || filename.endsWith(".properties");
|
|
109
|
-
|
|
110
|
-
const isIgnored = filename.includes("target") ||
|
|
111
|
-
filename.includes("build") ||
|
|
112
|
-
filename.includes("node_modules") ||
|
|
113
|
-
filename.split(/[/\\]/).some(part => part.startsWith("."));
|
|
114
|
-
|
|
115
|
-
if (isIgnored) return;
|
|
116
|
-
|
|
117
|
-
if (isResource && !isJava) {
|
|
118
|
-
const isJsp = filename.endsWith(".jsp");
|
|
119
|
-
let jspUrl = "";
|
|
120
|
-
let isPrivate = false;
|
|
121
|
-
|
|
122
|
-
if (isJsp) {
|
|
123
|
-
const parts = filename.split(/[/\\]/);
|
|
124
|
-
const webappIndex = parts.indexOf("webapp");
|
|
125
|
-
const webContentIndex = parts.indexOf("WebContent");
|
|
126
|
-
const rootIndex = webappIndex !== -1 ? webappIndex : webContentIndex;
|
|
127
|
-
|
|
128
|
-
if (rootIndex !== -1) {
|
|
129
|
-
const relPath = parts.slice(rootIndex + 1).join("/");
|
|
130
|
-
isPrivate = relPath.startsWith("WEB-INF") || relPath.startsWith("META-INF");
|
|
131
|
-
|
|
132
|
-
let contextPath = (config.project.appName || "").replace(".war", "");
|
|
133
|
-
if (!contextPath) {
|
|
134
|
-
const webappsPath = require("path").join(config.tomcat.path, "webapps");
|
|
135
|
-
if (require("fs").existsSync(webappsPath)) {
|
|
136
|
-
const folders = require("fs").readdirSync(webappsPath, { withFileTypes: true })
|
|
137
|
-
.filter((dirent: any) => dirent.isDirectory() && !["ROOT", "manager", "host-manager", "docs"].includes(dirent.name));
|
|
138
|
-
if (folders.length === 1) contextPath = folders[0].name;
|
|
139
|
-
}
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
jspUrl = `http://localhost:${config.tomcat.port}${contextPath ? "/" + contextPath : ""}/${relPath}`;
|
|
143
|
-
}
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
if (isJsp && isPrivate) {
|
|
147
|
-
Logger.watcher(`JSP Private (WEB-INF): ${filename}`, 'change');
|
|
148
|
-
Logger.dim(`Nota: Este arquivo não é acessível via URL direta.`);
|
|
149
|
-
} else if (isJsp && jspUrl) {
|
|
150
|
-
Logger.watcher(`JSP Updated: ${jspUrl}`, 'success');
|
|
151
|
-
} else {
|
|
152
|
-
Logger.watcher(`Resource altered: ${filename}`, 'change');
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
await deployCmd.syncResource(config, filename);
|
|
156
|
-
return;
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
if (!isJava) return;
|
|
160
|
-
|
|
161
|
-
Logger.watcher(filename, 'watch');
|
|
162
|
-
clearTimeout(debounceTimer);
|
|
163
|
-
|
|
164
|
-
debounceTimer = setTimeout(() => {
|
|
165
|
-
run(true);
|
|
166
|
-
}, 1000);
|
|
167
|
-
});
|
|
168
|
-
|
|
56
|
+
const deployCmd = new DeployCommand(tomcatService, buildService);
|
|
57
|
+
|
|
58
|
+
registry.register("build", new BuildCommand(buildService));
|
|
59
|
+
registry.register("start", new StartCommand(tomcatService));
|
|
60
|
+
registry.register("doctor", new DoctorCommand());
|
|
61
|
+
registry.register("run", new RunCommand());
|
|
62
|
+
registry.register("debug", new RunCommand());
|
|
63
|
+
registry.register("logs", new LogsCommand());
|
|
64
|
+
registry.register("docs", new DocsCommand());
|
|
65
|
+
registry.register("audit", new AuditCommand(auditService));
|
|
66
|
+
registry.register("deploy", deployCmd);
|
|
67
|
+
registry.register("dev", deployCmd);
|
|
68
|
+
|
|
69
|
+
// Caso especial: Watch Mode para Deploy/Dev
|
|
70
|
+
if ((commandName === "deploy" || commandName === "dev") && values.watch) {
|
|
71
|
+
const watcher = new WatcherService(config, deployCmd);
|
|
72
|
+
await watcher.start();
|
|
169
73
|
} else {
|
|
170
|
-
|
|
74
|
+
// 3. Executar do Registro
|
|
75
|
+
// Ajusta flags baseadas no nome do comando para comandos compartilhados
|
|
76
|
+
if (commandName === "debug") values.debug = true;
|
|
77
|
+
if (commandName === "run") values.debug = false;
|
|
78
|
+
|
|
79
|
+
await registry.execute(commandName, config, values);
|
|
171
80
|
}
|
|
172
81
|
}
|
|
173
82
|
|
|
@@ -71,7 +71,7 @@ export class AuditService {
|
|
|
71
71
|
const normalizedPath = jarPath.split(path.sep).join("/");
|
|
72
72
|
const psCommand = `
|
|
73
73
|
Add-Type -AssemblyName System.IO.Compression.FileSystem
|
|
74
|
-
$zip = [System.IO.Compression.ZipFile]::OpenRead(
|
|
74
|
+
$zip = [System.IO.Compression.ZipFile]::OpenRead($env:JAR_PATH)
|
|
75
75
|
$entry = $zip.Entries | Where-Object { $_.FullName -match "pom.properties$" } | Select-Object -First 1
|
|
76
76
|
if ($entry) {
|
|
77
77
|
$stream = $entry.Open()
|
|
@@ -85,7 +85,12 @@ export class AuditService {
|
|
|
85
85
|
`;
|
|
86
86
|
|
|
87
87
|
try {
|
|
88
|
-
const proc = Bun.spawn(["powershell", "-command", psCommand]
|
|
88
|
+
const proc = Bun.spawn(["powershell", "-command", psCommand], {
|
|
89
|
+
env: {
|
|
90
|
+
...process.env,
|
|
91
|
+
JAR_PATH: normalizedPath
|
|
92
|
+
}
|
|
93
|
+
});
|
|
89
94
|
const output = await new Response(proc.stdout).text();
|
|
90
95
|
|
|
91
96
|
const groupId = output.match(/groupId=(.*)/)?.[1]?.trim();
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { Logger } from "../utils/ui";
|
|
2
|
+
|
|
3
|
+
export class BrowserService {
|
|
4
|
+
/**
|
|
5
|
+
* Recarrega a aba ativa do browser (Chrome ou Edge) no Windows.
|
|
6
|
+
*/
|
|
7
|
+
public static async reload(url: string) {
|
|
8
|
+
if (process.platform !== 'win32') return;
|
|
9
|
+
|
|
10
|
+
// Pequeno delay para garantir que o Tomcat processou o novo contexto
|
|
11
|
+
await new Promise(r => setTimeout(r, 800));
|
|
12
|
+
|
|
13
|
+
const psCommand = `
|
|
14
|
+
$shell = New-Object -ComObject WScript.Shell
|
|
15
|
+
$process = Get-Process | Where-Object { $_.MainWindowTitle -match "Chrome" -or $_.MainWindowTitle -match "Edge" } | Select-Object -First 1
|
|
16
|
+
if ($process) {
|
|
17
|
+
$shell.AppActivate($process.Id)
|
|
18
|
+
Sleep -m 100
|
|
19
|
+
$shell.SendKeys("{F5}")
|
|
20
|
+
}
|
|
21
|
+
`;
|
|
22
|
+
|
|
23
|
+
try {
|
|
24
|
+
Bun.spawn(["powershell", "-command", psCommand]);
|
|
25
|
+
} catch (e) {
|
|
26
|
+
Logger.warn("Não foi possível recarregar o browser automaticamente.");
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Abre a URL no browser padrão do sistema.
|
|
32
|
+
*/
|
|
33
|
+
public static open(url: string) {
|
|
34
|
+
if (process.platform === 'win32') {
|
|
35
|
+
Bun.spawn(["cmd", "/c", "start", url]);
|
|
36
|
+
} else {
|
|
37
|
+
const start = process.platform === 'darwin' ? 'open' : 'xdg-open';
|
|
38
|
+
Bun.spawn([start, url]);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
}
|