@archznn/xavva 1.6.5 → 1.7.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 +12 -46
- package/package.json +1 -1
- package/src/commands/AuditCommand.ts +0 -4
- package/src/commands/DeployCommand.ts +84 -41
- package/src/commands/DocsCommand.ts +11 -11
- package/src/commands/DoctorCommand.ts +231 -68
- package/src/commands/RunCommand.ts +4 -3
- package/src/commands/StartCommand.ts +1 -1
- package/src/index.ts +29 -10
- package/src/services/AuditService.ts +0 -5
- package/src/services/BuildService.ts +7 -11
- package/src/services/EndpointService.ts +0 -3
- package/src/services/TomcatService.ts +138 -12
- package/src/utils/config.ts +1 -1
- package/src/utils/ui.ts +196 -102
|
@@ -5,85 +5,248 @@ import fs from "fs";
|
|
|
5
5
|
import path from "path";
|
|
6
6
|
|
|
7
7
|
export class DoctorCommand implements Command {
|
|
8
|
-
|
|
9
|
-
|
|
8
|
+
async execute(config: AppConfig, values: any = {}): Promise<void> {
|
|
9
|
+
Logger.section("Xavva Doctor - Ambiente");
|
|
10
10
|
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
if (tomcatOk) {
|
|
17
|
-
const binOk = fs.existsSync(path.join(config.tomcat.path, "bin", "catalina.bat"));
|
|
18
|
-
this.check("Tomcat Bin", binOk, binOk ? "OK" : "catalina.bat não encontrado");
|
|
19
|
-
}
|
|
11
|
+
this.check(
|
|
12
|
+
"JAVA_HOME",
|
|
13
|
+
!!process.env.JAVA_HOME,
|
|
14
|
+
process.env.JAVA_HOME || "Não definido",
|
|
15
|
+
);
|
|
20
16
|
|
|
21
|
-
|
|
22
|
-
|
|
17
|
+
const jvmInfo = this.checkJVM();
|
|
18
|
+
this.check(
|
|
19
|
+
"JVM Type",
|
|
20
|
+
jvmInfo.dcevm,
|
|
21
|
+
jvmInfo.name +
|
|
22
|
+
(jvmInfo.dcevm ? " (Advanced Hot Reload OK)" : " (Standard)"),
|
|
23
|
+
);
|
|
23
24
|
|
|
24
|
-
|
|
25
|
-
|
|
25
|
+
if (!jvmInfo.dcevm) {
|
|
26
|
+
Logger.log(
|
|
27
|
+
` ${Logger.C.yellow}💡 Dica: Sua JVM não suporta mudanças estruturais (novos métodos/campos).${Logger.C.reset}`,
|
|
28
|
+
);
|
|
29
|
+
if (values.fix) {
|
|
30
|
+
await this.installDCEVM();
|
|
31
|
+
} else {
|
|
32
|
+
Logger.log(
|
|
33
|
+
` ${Logger.C.cyan}Use 'xavva doctor --fix' para baixar uma JDK com DCEVM integrado.${Logger.C.reset}`,
|
|
34
|
+
);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
26
37
|
|
|
27
|
-
|
|
28
|
-
|
|
38
|
+
const tomcatOk = fs.existsSync(config.tomcat.path);
|
|
39
|
+
this.check("Tomcat Path", tomcatOk, config.tomcat.path);
|
|
29
40
|
|
|
30
|
-
|
|
31
|
-
|
|
41
|
+
if (tomcatOk) {
|
|
42
|
+
const binOk = fs.existsSync(
|
|
43
|
+
path.join(config.tomcat.path, "bin", "catalina.bat"),
|
|
44
|
+
);
|
|
45
|
+
this.check(
|
|
46
|
+
"Tomcat Bin",
|
|
47
|
+
binOk,
|
|
48
|
+
binOk ? "OK" : "catalina.bat não encontrado",
|
|
49
|
+
);
|
|
50
|
+
}
|
|
32
51
|
|
|
33
|
-
|
|
34
|
-
|
|
52
|
+
const mvnOk = this.checkBinary("mvn");
|
|
53
|
+
this.check("Maven", mvnOk, mvnOk ? "Disponível" : "Não encontrado no PATH");
|
|
35
54
|
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
const list = fs.readdirSync(dir, { withFileTypes: true });
|
|
43
|
-
for (const item of list) {
|
|
44
|
-
const res = path.resolve(dir, item.name);
|
|
45
|
-
if (item.isDirectory()) {
|
|
46
|
-
scan(res);
|
|
47
|
-
} else if (item.name.endsWith(".java")) {
|
|
48
|
-
const buffer = fs.readFileSync(res);
|
|
49
|
-
if (buffer[0] === 0xEF && buffer[1] === 0xBB && buffer[2] === 0xBF) {
|
|
50
|
-
filesWithBOM.push(res);
|
|
51
|
-
}
|
|
52
|
-
}
|
|
53
|
-
}
|
|
54
|
-
};
|
|
55
|
-
|
|
56
|
-
scan(srcPath);
|
|
57
|
-
|
|
58
|
-
if (filesWithBOM.length > 0) {
|
|
59
|
-
this.check("Encoding BOM", false, `${filesWithBOM.length} arquivos com BOM (UTF-8 com assinatura)`);
|
|
60
|
-
if (fix) {
|
|
61
|
-
for (const file of filesWithBOM) {
|
|
62
|
-
const buffer = fs.readFileSync(file);
|
|
63
|
-
const cleanBuffer = buffer.subarray(3);
|
|
64
|
-
fs.writeFileSync(file, cleanBuffer);
|
|
65
|
-
console.log(` \x1b[32m✔\x1b[0m Corrigido: ${path.basename(file)}`);
|
|
66
|
-
}
|
|
67
|
-
Logger.success("BOM removido de todos os arquivos!");
|
|
68
|
-
} else {
|
|
69
|
-
Logger.warn("Use 'xavva doctor --fix' para remover o BOM automaticamente.");
|
|
70
|
-
}
|
|
71
|
-
} else {
|
|
72
|
-
this.check("Encoding BOM", true, "Nenhum arquivo com BOM detectado.");
|
|
73
|
-
}
|
|
74
|
-
}
|
|
55
|
+
const gradleOk = this.checkBinary("gradle") || this.checkBinary("gradlew");
|
|
56
|
+
this.check(
|
|
57
|
+
"Gradle",
|
|
58
|
+
gradleOk,
|
|
59
|
+
gradleOk ? "Disponível" : "Não encontrado no PATH",
|
|
60
|
+
);
|
|
75
61
|
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
62
|
+
const gitOk = this.checkBinary("git");
|
|
63
|
+
this.check("Git", gitOk, gitOk ? "Disponível" : "Não encontrado no PATH");
|
|
64
|
+
|
|
65
|
+
Logger.section("Xavva Doctor - Integridade de Arquivos");
|
|
66
|
+
await this.checkBOM(values.fix);
|
|
67
|
+
|
|
68
|
+
console.log("");
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
private async checkBOM(fix: boolean) {
|
|
72
|
+
const srcPath = path.join(process.cwd(), "src");
|
|
73
|
+
if (!fs.existsSync(srcPath)) return;
|
|
74
|
+
|
|
75
|
+
const filesWithBOM: string[] = [];
|
|
76
|
+
const scan = (dir: string) => {
|
|
77
|
+
const list = fs.readdirSync(dir, { withFileTypes: true });
|
|
78
|
+
for (const item of list) {
|
|
79
|
+
const res = path.resolve(dir, item.name);
|
|
80
|
+
if (item.isDirectory()) {
|
|
81
|
+
scan(res);
|
|
82
|
+
} else if (item.name.endsWith(".java")) {
|
|
83
|
+
const buffer = fs.readFileSync(res);
|
|
84
|
+
if (buffer[0] === 0xef && buffer[1] === 0xbb && buffer[2] === 0xbf) {
|
|
85
|
+
filesWithBOM.push(res);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
};
|
|
80
90
|
|
|
81
|
-
|
|
91
|
+
scan(srcPath);
|
|
92
|
+
|
|
93
|
+
if (filesWithBOM.length > 0) {
|
|
94
|
+
this.check(
|
|
95
|
+
"Encoding BOM",
|
|
96
|
+
false,
|
|
97
|
+
`${filesWithBOM.length} arquivos com BOM (UTF-8 com assinatura)`,
|
|
98
|
+
);
|
|
99
|
+
if (fix) {
|
|
100
|
+
for (const file of filesWithBOM) {
|
|
101
|
+
const buffer = fs.readFileSync(file);
|
|
102
|
+
const cleanBuffer = buffer.subarray(3);
|
|
103
|
+
fs.writeFileSync(file, cleanBuffer);
|
|
104
|
+
console.log(
|
|
105
|
+
` \x1b[32m✔\x1b[0m Corrigido: ${path.basename(file)}`,
|
|
106
|
+
);
|
|
107
|
+
}
|
|
108
|
+
Logger.success("BOM removido de todos os arquivos!");
|
|
109
|
+
} else {
|
|
110
|
+
Logger.warn(
|
|
111
|
+
"Use 'xavva doctor --fix' para remover o BOM automaticamente.",
|
|
112
|
+
);
|
|
113
|
+
}
|
|
114
|
+
} else {
|
|
115
|
+
this.check("Encoding BOM", true, "Nenhum arquivo com BOM detectado.");
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
private check(label: string, ok: boolean, detail: string) {
|
|
120
|
+
const icon = ok ? "\x1b[32m✔\x1b[0m" : "\x1b[31m✘\x1b[0m";
|
|
121
|
+
console.log(` ${icon} ${label.padEnd(15)} ${detail}`);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
private checkBinary(name: string): boolean {
|
|
125
|
+
try {
|
|
126
|
+
const proc = Bun.spawnSync([
|
|
127
|
+
process.platform === "win32" ? "where" : "which",
|
|
128
|
+
name,
|
|
129
|
+
]);
|
|
130
|
+
return proc.exitCode === 0;
|
|
131
|
+
} catch {
|
|
132
|
+
return false;
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
private checkJVM(): { name: string, dcevm: boolean } {
|
|
82
137
|
try {
|
|
83
|
-
|
|
84
|
-
|
|
138
|
+
// Tentar primeiro o binário do JAVA_HOME para evitar cache do Path
|
|
139
|
+
let javaBin = "java";
|
|
140
|
+
if (process.env.JAVA_HOME) {
|
|
141
|
+
const homeBin = path.join(process.env.JAVA_HOME, "bin", "java.exe");
|
|
142
|
+
if (fs.existsSync(homeBin)) javaBin = homeBin;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
const proc = Bun.spawnSync([javaBin, "-version"]);
|
|
146
|
+
const output = (proc.stderr.toString() + proc.stdout.toString()).toLowerCase();
|
|
147
|
+
const isDcevm = output.includes("dcevm") ||
|
|
148
|
+
output.includes("jetbrains") ||
|
|
149
|
+
output.includes("trava") ||
|
|
150
|
+
output.includes("jbr");
|
|
151
|
+
|
|
152
|
+
const versionMatch = output.match(/version "(.*?)"/);
|
|
153
|
+
return {
|
|
154
|
+
name: versionMatch ? `Java ${versionMatch[1]}` : "Java Desconhecido",
|
|
155
|
+
dcevm: isDcevm
|
|
156
|
+
};
|
|
85
157
|
} catch {
|
|
86
|
-
return false;
|
|
158
|
+
return { name: "Não encontrada", dcevm: false };
|
|
87
159
|
}
|
|
88
160
|
}
|
|
161
|
+
|
|
162
|
+
private async installDCEVM() {
|
|
163
|
+
Logger.section("Instalação do JetBrains Runtime (JBR 21)");
|
|
164
|
+
Logger.log("Baixando JDK moderna com DCEVM nativo (JBR 21 SDK)...");
|
|
165
|
+
|
|
166
|
+
// URL para o JetBrains Runtime 21 SDK Windows x64
|
|
167
|
+
const url = "https://cache-redirector.jetbrains.com/intellij-jbr/jbrsdk-21.0.6-windows-x64-b895.97.tar.gz";
|
|
168
|
+
const installDir = path.join(require("os").homedir(), ".xavva", "jdk-dcevm");
|
|
169
|
+
|
|
170
|
+
// Limpar instalação anterior se existir
|
|
171
|
+
if (fs.existsSync(installDir)) {
|
|
172
|
+
try {
|
|
173
|
+
fs.rmSync(installDir, { recursive: true, force: true });
|
|
174
|
+
} catch (e) {}
|
|
175
|
+
}
|
|
176
|
+
fs.mkdirSync(installDir, { recursive: true });
|
|
177
|
+
|
|
178
|
+
try {
|
|
179
|
+
const response = await fetch(url);
|
|
180
|
+
if (!response.ok) throw new Error("Erro ao baixar JBR (verifique sua conexão)");
|
|
181
|
+
const buffer = await response.arrayBuffer();
|
|
182
|
+
const tarPath = path.join(installDir, "jbr.tar.gz");
|
|
183
|
+
fs.writeFileSync(tarPath, Buffer.from(buffer));
|
|
184
|
+
|
|
185
|
+
Logger.success("Download concluído. Extraindo binários...");
|
|
186
|
+
|
|
187
|
+
// Usar PowerShell para extrair .tar.gz (nativo no Windows 10/11)
|
|
188
|
+
const extractCmd = `powershell -command "tar -xzf '${tarPath}' -C '${installDir}'"`;
|
|
189
|
+
Bun.spawnSync(["powershell", "-command", extractCmd]);
|
|
190
|
+
|
|
191
|
+
fs.rmSync(tarPath);
|
|
192
|
+
|
|
193
|
+
// Busca recursiva para encontrar onde está o bin/java.exe
|
|
194
|
+
const findJdkRoot = (dir: string): string | null => {
|
|
195
|
+
if (fs.existsSync(path.join(dir, "bin", "java.exe"))) return dir;
|
|
196
|
+
const subdirs = fs.readdirSync(dir, { withFileTypes: true })
|
|
197
|
+
.filter(d => d.isDirectory())
|
|
198
|
+
.map(d => path.join(dir, d.name));
|
|
199
|
+
for (const subdir of subdirs) {
|
|
200
|
+
const found = findJdkRoot(subdir);
|
|
201
|
+
if (found) return found;
|
|
202
|
+
}
|
|
203
|
+
return null;
|
|
204
|
+
};
|
|
205
|
+
|
|
206
|
+
const jdkPath = findJdkRoot(installDir) || installDir;
|
|
207
|
+
|
|
208
|
+
Logger.process("Configurando variáveis de ambiente do SISTEMA...");
|
|
209
|
+
|
|
210
|
+
const setEnvCmd = `
|
|
211
|
+
$jdk = '${jdkPath}';
|
|
212
|
+
$bin = '${path.join(jdkPath, "bin")}';
|
|
213
|
+
try {
|
|
214
|
+
[Environment]::SetEnvironmentVariable('JAVA_HOME', $jdk, 'Machine');
|
|
215
|
+
$pathVar = [Environment]::GetEnvironmentVariable('Path', 'Machine');
|
|
216
|
+
$paths = $pathVar -split ';' | Where-Object { $_ -ne '' };
|
|
217
|
+
$normalizedBin = $bin.TrimEnd('\\').ToLower();
|
|
218
|
+
|
|
219
|
+
$exists = $false;
|
|
220
|
+
foreach ($p in $paths) {
|
|
221
|
+
if ($p.TrimEnd('\\').ToLower() -eq $normalizedBin) { $exists = $true; break; }
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
if (-not $exists) {
|
|
225
|
+
$newPath = "$bin;" + $pathVar;
|
|
226
|
+
[Environment]::SetEnvironmentVariable('Path', $newPath, 'Machine');
|
|
227
|
+
}
|
|
228
|
+
Write-Output "OK";
|
|
229
|
+
} catch {
|
|
230
|
+
Write-Error $_.Exception.Message;
|
|
231
|
+
}
|
|
232
|
+
`.replace(/\n/g, ' ');
|
|
233
|
+
const result = Bun.spawnSync(["powershell", "-command", setEnvCmd]);
|
|
234
|
+
const output = result.stdout.toString() + result.stderr.toString();
|
|
235
|
+
|
|
236
|
+
if (output.includes("ACCESS_DENIED")) {
|
|
237
|
+
Logger.error("Falha ao configurar variáveis do SISTEMA (Acesso Negado).");
|
|
238
|
+
Logger.warn("Dica: Execute o terminal como ADMINISTRADOR para permitir esta alteração.");
|
|
239
|
+
Logger.info("JAVA_HOME manual", jdkPath);
|
|
240
|
+
} else {
|
|
241
|
+
Logger.success(`DCEVM configurado no SISTEMA com sucesso!`);
|
|
242
|
+
Logger.info("JAVA_HOME", jdkPath);
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
Logger.newline();
|
|
246
|
+
Logger.warn("IMPORTANTE: Reinicie seu terminal (ou o VS Code) para as mudanças surtirem efeito.");
|
|
247
|
+
} catch (e: any) {
|
|
248
|
+
|
|
249
|
+
Logger.error(`Falha na instalação: ${e.message}`);
|
|
250
|
+
}
|
|
251
|
+
}
|
|
89
252
|
}
|
|
@@ -49,7 +49,8 @@ export class RunCommand implements Command {
|
|
|
49
49
|
|
|
50
50
|
if (this.debug) {
|
|
51
51
|
Logger.warn(`🚀 Aguardando debugger na porta 5005 para ${className}...`);
|
|
52
|
-
Logger.log(`${
|
|
52
|
+
Logger.log(`${Logger.C.cyan}Dica:${Logger.C.reset} No VS Code ou IntelliJ, use 'Attach to Remote JVM' na porta 5005.`);
|
|
53
|
+
Logger.newline();
|
|
53
54
|
} else {
|
|
54
55
|
Logger.warn(`🚀 Executando ${className}...`);
|
|
55
56
|
}
|
|
@@ -140,7 +141,7 @@ export class RunCommand implements Command {
|
|
|
140
141
|
});
|
|
141
142
|
|
|
142
143
|
return new Promise((resolve) => {
|
|
143
|
-
readline.question(
|
|
144
|
+
readline.question(` Escolha a classe (1-${uniqueClasses.length}) ou [C]ancelar: `, (answer: string) => {
|
|
144
145
|
readline.close();
|
|
145
146
|
const idx = parseInt(answer) - 1;
|
|
146
147
|
if (!isNaN(idx) && uniqueClasses[idx]) {
|
|
@@ -175,7 +176,7 @@ export class RunCommand implements Command {
|
|
|
175
176
|
});
|
|
176
177
|
|
|
177
178
|
return new Promise((resolve) => {
|
|
178
|
-
readline.question(
|
|
179
|
+
readline.question(` Escolha a classe (1-${Math.min(history.length, 5)}) ou [C]ancelar: `, (answer: string) => {
|
|
179
180
|
readline.close();
|
|
180
181
|
if (!answer.trim()) {
|
|
181
182
|
resolve(history[0]);
|
|
@@ -15,7 +15,7 @@ export class StartCommand implements Command {
|
|
|
15
15
|
Logger.step("Checking ports");
|
|
16
16
|
await tomcat.killConflict();
|
|
17
17
|
Logger.step("Starting Tomcat");
|
|
18
|
-
tomcat.start(config
|
|
18
|
+
tomcat.start(config, false);
|
|
19
19
|
|
|
20
20
|
await new Promise(() => {});
|
|
21
21
|
} catch (error: any) {
|
package/src/index.ts
CHANGED
|
@@ -20,7 +20,7 @@ async function main() {
|
|
|
20
20
|
const { config, positionals, values } = await ConfigManager.load();
|
|
21
21
|
|
|
22
22
|
if (values.version) {
|
|
23
|
-
|
|
23
|
+
Logger.log(`v${pkg.version}`);
|
|
24
24
|
process.exit(0);
|
|
25
25
|
}
|
|
26
26
|
|
|
@@ -66,7 +66,7 @@ async function main() {
|
|
|
66
66
|
await handleDeploy(config, values);
|
|
67
67
|
break;
|
|
68
68
|
default:
|
|
69
|
-
|
|
69
|
+
Logger.error(`Comando desconhecido: ${commandName}`);
|
|
70
70
|
new HelpCommand().execute(config);
|
|
71
71
|
process.exit(1);
|
|
72
72
|
}
|
|
@@ -93,9 +93,15 @@ async function handleDeploy(config: any, values: any) {
|
|
|
93
93
|
await run(false);
|
|
94
94
|
|
|
95
95
|
let debounceTimer: Timer;
|
|
96
|
+
const coolingFiles = new Set<string>();
|
|
97
|
+
|
|
96
98
|
watch(process.cwd(), { recursive: true }, async (event, filename) => {
|
|
97
99
|
if (!filename) return;
|
|
98
100
|
|
|
101
|
+
if (coolingFiles.has(filename)) return;
|
|
102
|
+
coolingFiles.add(filename);
|
|
103
|
+
setTimeout(() => coolingFiles.delete(filename), 500);
|
|
104
|
+
|
|
99
105
|
const isJava = filename.endsWith(".java") || filename === "pom.xml" || filename === "build.gradle";
|
|
100
106
|
const isResource = filename.endsWith(".jsp") || filename.endsWith(".html") ||
|
|
101
107
|
filename.endsWith(".css") || filename.endsWith(".js") ||
|
|
@@ -116,21 +122,34 @@ async function handleDeploy(config: any, values: any) {
|
|
|
116
122
|
if (isJsp) {
|
|
117
123
|
const parts = filename.split(/[/\\]/);
|
|
118
124
|
const webappIndex = parts.indexOf("webapp");
|
|
119
|
-
|
|
120
|
-
|
|
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("/");
|
|
121
130
|
isPrivate = relPath.startsWith("WEB-INF") || relPath.startsWith("META-INF");
|
|
122
|
-
|
|
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
|
+
|
|
123
142
|
jspUrl = `http://localhost:${config.tomcat.port}${contextPath ? "/" + contextPath : ""}/${relPath}`;
|
|
124
143
|
}
|
|
125
144
|
}
|
|
126
145
|
|
|
127
146
|
if (isJsp && isPrivate) {
|
|
128
|
-
|
|
129
|
-
|
|
147
|
+
Logger.watcher(`JSP Private (WEB-INF): ${filename}`, 'change');
|
|
148
|
+
Logger.dim(`Nota: Este arquivo não é acessível via URL direta.`);
|
|
130
149
|
} else if (isJsp && jspUrl) {
|
|
131
|
-
|
|
150
|
+
Logger.watcher(`JSP Updated: ${jspUrl}`, 'success');
|
|
132
151
|
} else {
|
|
133
|
-
|
|
152
|
+
Logger.watcher(`Resource altered: ${filename}`, 'change');
|
|
134
153
|
}
|
|
135
154
|
|
|
136
155
|
await deployCmd.syncResource(config, filename);
|
|
@@ -139,7 +158,7 @@ async function handleDeploy(config: any, values: any) {
|
|
|
139
158
|
|
|
140
159
|
if (!isJava) return;
|
|
141
160
|
|
|
142
|
-
|
|
161
|
+
Logger.watcher(filename, 'watch');
|
|
143
162
|
clearTimeout(debounceTimer);
|
|
144
163
|
|
|
145
164
|
debounceTimer = setTimeout(() => {
|
|
@@ -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,8 +68,6 @@ 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
|
|
@@ -137,7 +133,6 @@ export class AuditService {
|
|
|
137
133
|
private extractSeverity(vuln: any): string {
|
|
138
134
|
if (vuln.database_specific?.severity) return vuln.database_specific.severity;
|
|
139
135
|
if (vuln.advisories?.[0]?.url?.includes("github.com/advisories")) {
|
|
140
|
-
// Try to infer from details if common keywords exist
|
|
141
136
|
const d = (vuln.details || "").toLowerCase();
|
|
142
137
|
if (d.includes("critical")) return "CRITICAL";
|
|
143
138
|
if (d.includes("high")) return "HIGH";
|
|
@@ -51,7 +51,7 @@ export class BuildService {
|
|
|
51
51
|
if (proc.exitCode !== 0) {
|
|
52
52
|
if (!this.projectConfig.verbose) {
|
|
53
53
|
const err = await new Response(proc.stderr).text();
|
|
54
|
-
|
|
54
|
+
Logger.log(err);
|
|
55
55
|
}
|
|
56
56
|
Logger.error(`${this.projectConfig.buildTool.toUpperCase()} build failed!`);
|
|
57
57
|
throw new Error("Falha no build do Java!");
|
|
@@ -79,7 +79,7 @@ export class BuildService {
|
|
|
79
79
|
errorCount++;
|
|
80
80
|
if (errorCount > maxErrors && !this.projectConfig.verbose) {
|
|
81
81
|
if (errorCount === maxErrors + 1) {
|
|
82
|
-
|
|
82
|
+
Logger.warn("... e mais erros ocultos. Use -V para ver todos.");
|
|
83
83
|
}
|
|
84
84
|
continue;
|
|
85
85
|
}
|
|
@@ -92,7 +92,7 @@ export class BuildService {
|
|
|
92
92
|
}
|
|
93
93
|
|
|
94
94
|
const summarized = Logger.summarize(cleanLine);
|
|
95
|
-
if (summarized)
|
|
95
|
+
if (summarized) Logger.log(summarized);
|
|
96
96
|
}
|
|
97
97
|
}
|
|
98
98
|
}
|
|
@@ -105,7 +105,7 @@ export class BuildService {
|
|
|
105
105
|
|
|
106
106
|
if (!appFolder && fs.existsSync(webappsPath)) {
|
|
107
107
|
const folders = fs.readdirSync(webappsPath, { withFileTypes: true })
|
|
108
|
-
.filter((dirent: any) => dirent.isDirectory() && !["ROOT", "manager", "host-manager", "docs"].includes(dirent.name));
|
|
108
|
+
.filter((dirent: any) => dirent.isDirectory() && !["ROOT", "manager", "host-manager", "docs", "examples"].includes(dirent.name));
|
|
109
109
|
|
|
110
110
|
if (folders.length === 1) {
|
|
111
111
|
appFolder = folders[0].name;
|
|
@@ -124,7 +124,6 @@ export class BuildService {
|
|
|
124
124
|
if (!fs.existsSync(sourceDir)) return null;
|
|
125
125
|
|
|
126
126
|
if (!appFolder || !fs.existsSync(destDir)) {
|
|
127
|
-
Logger.warn("Pasta descompactada no Tomcat não encontrada. Hot Swap impossível.");
|
|
128
127
|
return null;
|
|
129
128
|
}
|
|
130
129
|
|
|
@@ -145,11 +144,10 @@ export class BuildService {
|
|
|
145
144
|
};
|
|
146
145
|
|
|
147
146
|
copyDir(sourceDir, destDir);
|
|
148
|
-
Logger.success("Classes swapped in running Tomcat");
|
|
149
147
|
return appFolder;
|
|
150
148
|
}
|
|
151
149
|
|
|
152
|
-
async deployToWebapps(): Promise<string> {
|
|
150
|
+
async deployToWebapps(): Promise<{ path: string, finalName: string }> {
|
|
153
151
|
const destDir = path.join(this.tomcatConfig.path, this.tomcatConfig.webapps);
|
|
154
152
|
|
|
155
153
|
Logger.step("Searching for generated artifacts");
|
|
@@ -187,12 +185,10 @@ export class BuildService {
|
|
|
187
185
|
Logger.info("Artifact", warFile.name);
|
|
188
186
|
if (this.projectConfig.appName) Logger.info("Deploy as", finalName);
|
|
189
187
|
} else {
|
|
190
|
-
|
|
191
|
-
process.stdout.write(` ${"\x1b[90m"}➜${"\x1b[0m"} Deploying ${"\x1b[1m"}${displayName}${"\x1b[0m"}...\n`);
|
|
188
|
+
Logger.process(`Deploying ${this.projectConfig.appName ? this.projectConfig.appName : warFile.name.replace(".war", "")}...`);
|
|
192
189
|
}
|
|
193
190
|
|
|
194
|
-
copyFileSync(warFile.path, path.join(destDir, finalName));
|
|
195
191
|
this.inferredAppName = finalName.replace(".war", "");
|
|
196
|
-
return finalName;
|
|
192
|
+
return { path: warFile.path, finalName };
|
|
197
193
|
}
|
|
198
194
|
}
|
|
@@ -33,11 +33,9 @@ export class EndpointService {
|
|
|
33
33
|
const endpoints: ApiEndpoint[] = [];
|
|
34
34
|
const className = fileName.replace(".java", "");
|
|
35
35
|
|
|
36
|
-
// Find class-level mapping
|
|
37
36
|
const classPathMatch = content.match(/@(Path|RequestMapping)\s*\(\s*["'](.*?)["']\s*\)/);
|
|
38
37
|
const basePath = classPathMatch ? this.normalizePath(classPathMatch[2]) : "";
|
|
39
38
|
|
|
40
|
-
// Common mapping annotations
|
|
41
39
|
const methodRegex = /@(GET|POST|PUT|DELETE|PATCH|Path|RequestMapping|GetMapping|PostMapping|PutMapping|DeleteMapping|PatchMapping)\s*(\(\s*["'](.*?)["']\s*\))?\s*([\s\S]*?)\s+([a-zA-Z0-9_]+)\s*\(([\s\S]*?)\)/g;
|
|
42
40
|
|
|
43
41
|
let match;
|
|
@@ -98,7 +96,6 @@ export class EndpointService {
|
|
|
98
96
|
for (const p of individualParams) {
|
|
99
97
|
const trimmed = p.trim();
|
|
100
98
|
|
|
101
|
-
// Check for annotations
|
|
102
99
|
const pathParam = trimmed.match(/@PathParam\s*\(\s*["'](.*?)["']\s*\)\s*(\w+)\s+(\w+)/);
|
|
103
100
|
const pathVariable = trimmed.match(/@PathVariable\s*\(\s*["'](.*?)["']\s*\)\s*(\w+)\s+(\w+)/);
|
|
104
101
|
const queryParam = trimmed.match(/@QueryParam\s*\(\s*["'](.*?)["']\s*\)\s*(\w+)\s+(\w+)/);
|