@archznn/xavva 1.6.5 → 1.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/README.md +84 -48
- package/package.json +1 -1
- package/src/commands/AuditCommand.ts +6 -10
- 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 +181 -105
- package/src/commands/DocsCommand.ts +11 -11
- package/src/commands/DoctorCommand.ts +331 -68
- package/src/commands/HelpCommand.ts +2 -1
- package/src/commands/RunCommand.ts +108 -31
- package/src/commands/StartCommand.ts +4 -2
- package/src/index.ts +43 -115
- package/src/services/AuditService.ts +7 -7
- package/src/services/BrowserService.ts +41 -0
- package/src/services/BuildCacheService.ts +75 -0
- package/src/services/BuildService.ts +96 -88
- package/src/services/EndpointService.ts +17 -3
- package/src/services/ProjectService.ts +126 -0
- package/src/services/TomcatService.ts +125 -18
- package/src/services/WatcherService.ts +78 -0
- package/src/types/config.ts +30 -1
- package/src/utils/config.ts +22 -18
- package/src/utils/ui.ts +198 -102
|
@@ -1,19 +1,18 @@
|
|
|
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
4
|
import { spawn } from "child_process";
|
|
5
5
|
import path from "path";
|
|
6
6
|
|
|
7
7
|
export class RunCommand implements Command {
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
async execute(config: AppConfig): Promise<void> {
|
|
8
|
+
async execute(config: AppConfig, args?: CLIArguments): Promise<void> {
|
|
9
|
+
const isDebug = args?.debug !== false; // Default to true if not specified, matching previous behavior
|
|
11
10
|
let className = config.project.grep;
|
|
12
11
|
|
|
13
12
|
if (!className) {
|
|
14
13
|
className = await this.loadFromHistory();
|
|
15
14
|
if (!className) {
|
|
16
|
-
Logger.error(`Uso: xavva ${
|
|
15
|
+
Logger.error(`Uso: xavva ${isDebug ? "debug" : "run"} NomeDaClasse`);
|
|
17
16
|
return;
|
|
18
17
|
}
|
|
19
18
|
}
|
|
@@ -26,7 +25,7 @@ export class RunCommand implements Command {
|
|
|
26
25
|
|
|
27
26
|
this.saveToHistory(className);
|
|
28
27
|
|
|
29
|
-
if (
|
|
28
|
+
if (isDebug) {
|
|
30
29
|
Logger.section(`Interactive Debug: ${className}`);
|
|
31
30
|
} else {
|
|
32
31
|
Logger.section(`Running: ${className}`);
|
|
@@ -37,33 +36,34 @@ export class RunCommand implements Command {
|
|
|
37
36
|
|
|
38
37
|
const finalCp = `${localCp};${pathingJar}`;
|
|
39
38
|
|
|
40
|
-
const
|
|
39
|
+
const javaArgs = [
|
|
41
40
|
"-classpath", finalCp,
|
|
42
41
|
];
|
|
43
42
|
|
|
44
|
-
if (
|
|
45
|
-
|
|
43
|
+
if (isDebug) {
|
|
44
|
+
javaArgs.push("-agentlib:jdwp=transport=dt_socket,server=y,suspend=y,address=5005");
|
|
46
45
|
}
|
|
47
46
|
|
|
48
|
-
|
|
47
|
+
javaArgs.push(className);
|
|
49
48
|
|
|
50
|
-
if (
|
|
49
|
+
if (isDebug) {
|
|
51
50
|
Logger.warn(`🚀 Aguardando debugger na porta 5005 para ${className}...`);
|
|
52
|
-
Logger.log(`${
|
|
51
|
+
Logger.log(`${Logger.C.cyan}Dica:${Logger.C.reset} No VS Code ou IntelliJ, use 'Attach to Remote JVM' na porta 5005.`);
|
|
52
|
+
Logger.newline();
|
|
53
53
|
} else {
|
|
54
54
|
Logger.warn(`🚀 Executando ${className}...`);
|
|
55
55
|
}
|
|
56
56
|
|
|
57
|
-
const bin = "java";
|
|
57
|
+
const bin = process.env.JAVA_HOME ? path.join(process.env.JAVA_HOME, "bin", "java") : "java";
|
|
58
58
|
|
|
59
59
|
return new Promise((resolve) => {
|
|
60
|
-
const child = spawn(bin,
|
|
60
|
+
const child = spawn(bin, javaArgs, {
|
|
61
61
|
stdio: "inherit",
|
|
62
|
-
shell:
|
|
62
|
+
shell: false
|
|
63
63
|
});
|
|
64
64
|
|
|
65
65
|
child.on("exit", () => {
|
|
66
|
-
Logger.log(`Sessão de ${
|
|
66
|
+
Logger.log(`Sessão de ${isDebug ? "debug" : "execução"} encerrada.`);
|
|
67
67
|
resolve();
|
|
68
68
|
});
|
|
69
69
|
});
|
|
@@ -140,7 +140,7 @@ export class RunCommand implements Command {
|
|
|
140
140
|
});
|
|
141
141
|
|
|
142
142
|
return new Promise((resolve) => {
|
|
143
|
-
readline.question(
|
|
143
|
+
readline.question(` Escolha a classe (1-${uniqueClasses.length}) ou [C]ancelar: `, (answer: string) => {
|
|
144
144
|
readline.close();
|
|
145
145
|
const idx = parseInt(answer) - 1;
|
|
146
146
|
if (!isNaN(idx) && uniqueClasses[idx]) {
|
|
@@ -175,7 +175,7 @@ export class RunCommand implements Command {
|
|
|
175
175
|
});
|
|
176
176
|
|
|
177
177
|
return new Promise((resolve) => {
|
|
178
|
-
readline.question(
|
|
178
|
+
readline.question(` Escolha a classe (1-${Math.min(history.length, 5)}) ou [C]ancelar: `, (answer: string) => {
|
|
179
179
|
readline.close();
|
|
180
180
|
if (!answer.trim()) {
|
|
181
181
|
resolve(history[0]);
|
|
@@ -221,22 +221,62 @@ export class RunCommand implements Command {
|
|
|
221
221
|
const paths = dependencyCp.split(";").filter(p => p.trim());
|
|
222
222
|
const relativePaths = paths.map(p => {
|
|
223
223
|
let rel = path.relative(xavvaDir, p).replace(/\\/g, "/");
|
|
224
|
-
if (fs.statSync(p).isDirectory() && !rel.endsWith("/")) rel += "/";
|
|
225
|
-
|
|
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, ']');
|
|
226
231
|
}).join(" ");
|
|
227
232
|
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
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
|
+
}
|
|
234
274
|
} else {
|
|
235
|
-
|
|
275
|
+
currentLine += part;
|
|
236
276
|
}
|
|
237
277
|
}
|
|
278
|
+
manifestContent += currentLine + "\r\n\r\n";
|
|
238
279
|
|
|
239
|
-
const manifestContent = `Manifest-Version: 1.0\r\nClass-Path: ${wrappedCp}\r\n\r\n`;
|
|
240
280
|
const manifestPath = path.join(xavvaDir, "MANIFEST.MF");
|
|
241
281
|
fs.writeFileSync(manifestPath, manifestContent);
|
|
242
282
|
|
|
@@ -254,22 +294,59 @@ export class RunCommand implements Command {
|
|
|
254
294
|
if (!fs.existsSync(cpFile)) {
|
|
255
295
|
const stopSpinner = Logger.spinner("Generating project classpath");
|
|
256
296
|
try {
|
|
257
|
-
if (config.project.buildTool ===
|
|
297
|
+
if (config.project.buildTool === "maven") {
|
|
258
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);
|
|
259
324
|
} else {
|
|
260
325
|
fs.writeFileSync(cpFile, ".");
|
|
261
326
|
}
|
|
262
|
-
} catch (e) {
|
|
327
|
+
} catch (e) {
|
|
328
|
+
Logger.error(`Falha ao gerar classpath: ${e}`);
|
|
329
|
+
}
|
|
263
330
|
stopSpinner();
|
|
264
331
|
}
|
|
265
332
|
|
|
266
|
-
|
|
333
|
+
let dependencyCp = fs.existsSync(cpFile) ? fs.readFileSync(cpFile, "utf8").trim() : "";
|
|
267
334
|
|
|
335
|
+
// Normalize platform specific separators to semicolon for consistency
|
|
336
|
+
if (path.delimiter !== ";") {
|
|
337
|
+
dependencyCp = dependencyCp.split(path.delimiter).join(";");
|
|
338
|
+
}
|
|
339
|
+
|
|
268
340
|
const localFolders = [
|
|
269
341
|
"target/classes",
|
|
270
342
|
"target/test-classes",
|
|
271
343
|
"build/classes/java/main",
|
|
272
344
|
"build/classes/java/test",
|
|
345
|
+
"build/classes/kotlin/main",
|
|
346
|
+
"build/resources/main",
|
|
347
|
+
"build/resources/test",
|
|
348
|
+
"bin/main",
|
|
349
|
+
"bin/test",
|
|
273
350
|
"."
|
|
274
351
|
];
|
|
275
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);
|
|
@@ -15,7 +17,7 @@ export class StartCommand implements Command {
|
|
|
15
17
|
Logger.step("Checking ports");
|
|
16
18
|
await tomcat.killConflict();
|
|
17
19
|
Logger.step("Starting Tomcat");
|
|
18
|
-
tomcat.start(config
|
|
20
|
+
tomcat.start(config, false);
|
|
19
21
|
|
|
20
22
|
await new Promise(() => {});
|
|
21
23
|
} catch (error: any) {
|
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,17 +10,23 @@ 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();
|
|
21
27
|
|
|
22
28
|
if (values.version) {
|
|
23
|
-
|
|
29
|
+
Logger.log(`v${pkg.version}`);
|
|
24
30
|
process.exit(0);
|
|
25
31
|
}
|
|
26
32
|
|
|
@@ -32,123 +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
|
-
console.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
|
-
watch(process.cwd(), { recursive: true }, async (event, filename) => {
|
|
97
|
-
if (!filename) return;
|
|
98
|
-
|
|
99
|
-
const isJava = filename.endsWith(".java") || filename === "pom.xml" || filename === "build.gradle";
|
|
100
|
-
const isResource = filename.endsWith(".jsp") || filename.endsWith(".html") ||
|
|
101
|
-
filename.endsWith(".css") || filename.endsWith(".js") ||
|
|
102
|
-
filename.endsWith(".xml") || filename.endsWith(".properties");
|
|
103
|
-
|
|
104
|
-
const isIgnored = filename.includes("target") ||
|
|
105
|
-
filename.includes("build") ||
|
|
106
|
-
filename.includes("node_modules") ||
|
|
107
|
-
filename.split(/[/\\]/).some(part => part.startsWith("."));
|
|
108
|
-
|
|
109
|
-
if (isIgnored) return;
|
|
110
|
-
|
|
111
|
-
if (isResource && !isJava) {
|
|
112
|
-
const isJsp = filename.endsWith(".jsp");
|
|
113
|
-
let jspUrl = "";
|
|
114
|
-
let isPrivate = false;
|
|
115
|
-
|
|
116
|
-
if (isJsp) {
|
|
117
|
-
const parts = filename.split(/[/\\]/);
|
|
118
|
-
const webappIndex = parts.indexOf("webapp");
|
|
119
|
-
if (webappIndex !== -1) {
|
|
120
|
-
const relPath = parts.slice(webappIndex + 1).join("/");
|
|
121
|
-
isPrivate = relPath.startsWith("WEB-INF") || relPath.startsWith("META-INF");
|
|
122
|
-
const contextPath = (config.project.appName || "").replace(".war", "");
|
|
123
|
-
jspUrl = `http://localhost:${config.tomcat.port}${contextPath ? "/" + contextPath : ""}/${relPath}`;
|
|
124
|
-
}
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
if (isJsp && isPrivate) {
|
|
128
|
-
console.log(`\n ${"\x1b[33m"}🔒${"\x1b[0m"} JSP Privado alterado (WEB-INF): ${filename}`);
|
|
129
|
-
console.log(` ${"\x1b[90m"}Nota: Este arquivo não é acessível via URL direta.${"\x1b[0m"}`);
|
|
130
|
-
} else if (isJsp && jspUrl) {
|
|
131
|
-
console.log(`\n ${"\x1b[32m"}📄${"\x1b[0m"} JSP Atualizado: ${"\x1b[4m"}${jspUrl}${"\x1b[0m"}`);
|
|
132
|
-
} else {
|
|
133
|
-
console.log(`\n ${"\x1b[35m"}⚡${"\x1b[0m"} Recurso alterado: ${filename}`);
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
await deployCmd.syncResource(config, filename);
|
|
137
|
-
return;
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
if (!isJava) return;
|
|
141
|
-
|
|
142
|
-
console.log(`\n ${"\x1b[33m"}👀${"\x1b[0m"} Alteração detectada em: ${filename}`);
|
|
143
|
-
clearTimeout(debounceTimer);
|
|
144
|
-
|
|
145
|
-
debounceTimer = setTimeout(() => {
|
|
146
|
-
run(true);
|
|
147
|
-
}, 1000);
|
|
148
|
-
});
|
|
149
|
-
|
|
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();
|
|
150
73
|
} else {
|
|
151
|
-
|
|
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);
|
|
152
80
|
}
|
|
153
81
|
}
|
|
154
82
|
|
|
@@ -34,7 +34,6 @@ export class AuditService {
|
|
|
34
34
|
|
|
35
35
|
const stopSpinner = Logger.spinner(`Auditando ${jars.length} dependências`);
|
|
36
36
|
|
|
37
|
-
// Process in chunks to avoid overwhelming the API
|
|
38
37
|
const chunkSize = 10;
|
|
39
38
|
for (let i = 0; i < jars.length; i += chunkSize) {
|
|
40
39
|
const chunk = jars.slice(i, i + chunkSize);
|
|
@@ -52,7 +51,6 @@ export class AuditService {
|
|
|
52
51
|
const info = await this.extractJarInfo(jarPath);
|
|
53
52
|
|
|
54
53
|
if (!info.artifactId || !info.version) {
|
|
55
|
-
// Fallback to filename parsing if pom.properties is missing
|
|
56
54
|
const match = jarName.match(/(.+)-([\d\.]+.*)\.jar/);
|
|
57
55
|
if (match) {
|
|
58
56
|
info.artifactId = info.artifactId || match[1];
|
|
@@ -70,12 +68,10 @@ export class AuditService {
|
|
|
70
68
|
}
|
|
71
69
|
|
|
72
70
|
private async extractJarInfo(jarPath: string): Promise<{ groupId?: string, artifactId?: string, version?: string }> {
|
|
73
|
-
// We use PowerShell to quickly peek inside the JAR for pom.properties
|
|
74
|
-
// This is faster than extracting the whole JAR
|
|
75
71
|
const normalizedPath = jarPath.split(path.sep).join("/");
|
|
76
72
|
const psCommand = `
|
|
77
73
|
Add-Type -AssemblyName System.IO.Compression.FileSystem
|
|
78
|
-
$zip = [System.IO.Compression.ZipFile]::OpenRead(
|
|
74
|
+
$zip = [System.IO.Compression.ZipFile]::OpenRead($env:JAR_PATH)
|
|
79
75
|
$entry = $zip.Entries | Where-Object { $_.FullName -match "pom.properties$" } | Select-Object -First 1
|
|
80
76
|
if ($entry) {
|
|
81
77
|
$stream = $entry.Open()
|
|
@@ -89,7 +85,12 @@ export class AuditService {
|
|
|
89
85
|
`;
|
|
90
86
|
|
|
91
87
|
try {
|
|
92
|
-
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
|
+
});
|
|
93
94
|
const output = await new Response(proc.stdout).text();
|
|
94
95
|
|
|
95
96
|
const groupId = output.match(/groupId=(.*)/)?.[1]?.trim();
|
|
@@ -137,7 +138,6 @@ export class AuditService {
|
|
|
137
138
|
private extractSeverity(vuln: any): string {
|
|
138
139
|
if (vuln.database_specific?.severity) return vuln.database_specific.severity;
|
|
139
140
|
if (vuln.advisories?.[0]?.url?.includes("github.com/advisories")) {
|
|
140
|
-
// Try to infer from details if common keywords exist
|
|
141
141
|
const d = (vuln.details || "").toLowerCase();
|
|
142
142
|
if (d.includes("critical")) return "CRITICAL";
|
|
143
143
|
if (d.includes("high")) return "HIGH";
|
|
@@ -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
|
+
}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import fs from "fs";
|
|
2
|
+
import path from "path";
|
|
3
|
+
import crypto from "crypto";
|
|
4
|
+
|
|
5
|
+
export interface CacheData {
|
|
6
|
+
lastConfigHash: string;
|
|
7
|
+
lastBuildTime: number;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export class BuildCacheService {
|
|
11
|
+
private cacheDir: string;
|
|
12
|
+
private cacheFile: string;
|
|
13
|
+
|
|
14
|
+
constructor() {
|
|
15
|
+
this.cacheDir = path.join(process.cwd(), ".xavva");
|
|
16
|
+
this.cacheFile = path.join(this.cacheDir, "build-cache.json");
|
|
17
|
+
if (!fs.existsSync(this.cacheDir)) {
|
|
18
|
+
fs.mkdirSync(this.cacheDir, { recursive: true });
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
getHash(filePath: string): string {
|
|
23
|
+
if (!fs.existsSync(filePath)) return "";
|
|
24
|
+
const content = fs.readFileSync(filePath);
|
|
25
|
+
return crypto.createHash("md5").update(content).digest("hex");
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
getConfigHash(tool: "maven" | "gradle"): string {
|
|
29
|
+
const file = tool === "maven" ? "pom.xml" : "build.gradle";
|
|
30
|
+
const configPath = path.join(process.cwd(), file);
|
|
31
|
+
let hash = this.getHash(configPath);
|
|
32
|
+
|
|
33
|
+
// Se for gradle, também checar build.gradle.kts e settings
|
|
34
|
+
if (tool === "gradle") {
|
|
35
|
+
const kts = path.join(process.cwd(), "build.gradle.kts");
|
|
36
|
+
const settings = path.join(process.cwd(), "settings.gradle");
|
|
37
|
+
if (fs.existsSync(kts)) hash += this.getHash(kts);
|
|
38
|
+
if (fs.existsSync(settings)) hash += this.getHash(settings);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
return crypto.createHash("md5").update(hash).digest("hex");
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
shouldRebuild(tool: "maven" | "gradle"): boolean {
|
|
45
|
+
if (!fs.existsSync(this.cacheFile)) return true;
|
|
46
|
+
|
|
47
|
+
try {
|
|
48
|
+
const currentHash = this.getConfigHash(tool);
|
|
49
|
+
const cache: CacheData = JSON.parse(fs.readFileSync(this.cacheFile, "utf-8"));
|
|
50
|
+
|
|
51
|
+
// Se o pom/gradle mudou, precisa de rebuild completo
|
|
52
|
+
if (currentHash !== cache.lastConfigHash) return true;
|
|
53
|
+
|
|
54
|
+
// Verificar se o artefato (.war) ainda existe
|
|
55
|
+
// (Esta parte será integrada ao BuildService)
|
|
56
|
+
return false;
|
|
57
|
+
} catch (e) {
|
|
58
|
+
return true;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
saveCache(tool: "maven" | "gradle") {
|
|
63
|
+
const data: CacheData = {
|
|
64
|
+
lastConfigHash: this.getConfigHash(tool),
|
|
65
|
+
lastBuildTime: Date.now()
|
|
66
|
+
};
|
|
67
|
+
fs.writeFileSync(this.cacheFile, JSON.stringify(data, null, 2));
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
clearCache() {
|
|
71
|
+
if (fs.existsSync(this.cacheFile)) {
|
|
72
|
+
fs.unlinkSync(this.cacheFile);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
}
|