@archznn/xavva 2.6.0 → 2.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 +55 -0
- package/package.json +1 -1
- package/src/commands/DeployCommand.ts +2 -1
- package/src/commands/EncodingCommand.ts +351 -0
- package/src/commands/HelpCommand.ts +15 -0
- package/src/config/versions.ts +63 -0
- package/src/di/container.ts +226 -0
- package/src/errors/ErrorHandler.ts +249 -0
- package/src/errors/XavvaError.ts +273 -0
- package/src/index.ts +98 -96
- package/src/services/AuditService.ts +3 -2
- package/src/services/BrowserService.ts +127 -16
- package/src/services/DeployWatcher.ts +183 -0
- package/src/services/EmbeddedTomcatService.ts +5 -19
- package/src/services/EncodingService.ts +548 -0
- package/src/services/FileWatcher.ts +243 -0
- package/src/services/TomcatService.ts +9 -5
- package/src/types/args.ts +136 -0
- package/src/types/config.ts +6 -0
- package/src/types/index.ts +7 -0
- package/src/utils/PathUtils.ts +221 -0
- package/src/utils/config.ts +6 -0
- package/src/utils/parsers/JavaParser.ts +413 -0
- package/src/utils/platform.ts +2 -2
- package/src/services/WatcherService.ts +0 -117
|
@@ -0,0 +1,273 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Hierarquia de erros específicos do Xavva
|
|
3
|
+
* Permite tratamento granular de diferentes tipos de falhas
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
export class XavvaError extends Error {
|
|
7
|
+
public readonly code: string;
|
|
8
|
+
public readonly exitCode: number;
|
|
9
|
+
public readonly isOperational: boolean;
|
|
10
|
+
|
|
11
|
+
constructor(
|
|
12
|
+
message: string,
|
|
13
|
+
code: string = "XAVVA_ERROR",
|
|
14
|
+
exitCode: number = 1,
|
|
15
|
+
isOperational: boolean = true
|
|
16
|
+
) {
|
|
17
|
+
super(message);
|
|
18
|
+
this.name = this.constructor.name;
|
|
19
|
+
this.code = code;
|
|
20
|
+
this.exitCode = exitCode;
|
|
21
|
+
this.isOperational = isOperational;
|
|
22
|
+
|
|
23
|
+
// Mantém o stack trace correto
|
|
24
|
+
Error.captureStackTrace(this, this.constructor);
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// ===== Erros de Build =====
|
|
29
|
+
export class BuildError extends XavvaError {
|
|
30
|
+
constructor(message: string, details?: string) {
|
|
31
|
+
super(
|
|
32
|
+
details ? `${message}: ${details}` : message,
|
|
33
|
+
"BUILD_ERROR",
|
|
34
|
+
3,
|
|
35
|
+
true
|
|
36
|
+
);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export class MavenError extends BuildError {
|
|
41
|
+
constructor(message: string, exitCode?: number) {
|
|
42
|
+
super(
|
|
43
|
+
`Maven build failed${exitCode ? ` (exit ${exitCode})` : ""}: ${message}`,
|
|
44
|
+
);
|
|
45
|
+
this.code = "MAVEN_ERROR";
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export class GradleError extends BuildError {
|
|
50
|
+
constructor(message: string, exitCode?: number) {
|
|
51
|
+
super(
|
|
52
|
+
`Gradle build failed${exitCode ? ` (exit ${exitCode})` : ""}: ${message}`,
|
|
53
|
+
);
|
|
54
|
+
this.code = "GRADLE_ERROR";
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// ===== Erros de Deploy =====
|
|
59
|
+
export class DeployError extends XavvaError {
|
|
60
|
+
constructor(message: string, details?: string) {
|
|
61
|
+
super(
|
|
62
|
+
details ? `${message}: ${details}` : message,
|
|
63
|
+
"DEPLOY_ERROR",
|
|
64
|
+
4,
|
|
65
|
+
true
|
|
66
|
+
);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
export class ArtifactNotFoundError extends DeployError {
|
|
71
|
+
constructor(buildDir: string) {
|
|
72
|
+
super(
|
|
73
|
+
`Nenhum artefato (.war ou pasta exploded) encontrado em ${buildDir}`,
|
|
74
|
+
"Certifique-se de que o build foi executado com sucesso"
|
|
75
|
+
);
|
|
76
|
+
this.code = "ARTIFACT_NOT_FOUND";
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// ===== Erros de Tomcat =====
|
|
81
|
+
export class TomcatError extends XavvaError {
|
|
82
|
+
constructor(message: string, details?: string) {
|
|
83
|
+
super(
|
|
84
|
+
details ? `${message}: ${details}` : message,
|
|
85
|
+
"TOMCAT_ERROR",
|
|
86
|
+
5,
|
|
87
|
+
true
|
|
88
|
+
);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
export class TomcatNotFoundError extends TomcatError {
|
|
93
|
+
constructor(path: string) {
|
|
94
|
+
super(
|
|
95
|
+
`Tomcat não encontrado em ${path}`,
|
|
96
|
+
"Defina TOMCAT_HOME, CATALINA_HOME ou use --path"
|
|
97
|
+
);
|
|
98
|
+
this.code = "TOMCAT_NOT_FOUND";
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
export class PortInUseError extends TomcatError {
|
|
103
|
+
constructor(port: number) {
|
|
104
|
+
super(
|
|
105
|
+
`Porta ${port} já está em uso`,
|
|
106
|
+
"Pare o processo que está usando a porta ou especifique outra com --port"
|
|
107
|
+
);
|
|
108
|
+
this.code = "PORT_IN_USE";
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
export class EmbeddedTomcatError extends TomcatError {
|
|
113
|
+
constructor(message: string) {
|
|
114
|
+
super(
|
|
115
|
+
`Falha no Tomcat embutido: ${message}`,
|
|
116
|
+
"Tente instalar manualmente ou use outra versão com --tomcat-version"
|
|
117
|
+
);
|
|
118
|
+
this.code = "EMBEDDED_TOMCAT_ERROR";
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// ===== Erros de Configuração =====
|
|
123
|
+
export class ConfigError extends XavvaError {
|
|
124
|
+
constructor(message: string, details?: string) {
|
|
125
|
+
super(
|
|
126
|
+
details ? `${message}: ${details}` : message,
|
|
127
|
+
"CONFIG_ERROR",
|
|
128
|
+
2,
|
|
129
|
+
true
|
|
130
|
+
);
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
export class InvalidConfigError extends ConfigError {
|
|
135
|
+
constructor(field: string, value: string, expected?: string) {
|
|
136
|
+
super(
|
|
137
|
+
`Configuração inválida: ${field} = '${value}'`,
|
|
138
|
+
expected || "Verifique sua configuração"
|
|
139
|
+
);
|
|
140
|
+
this.code = "INVALID_CONFIG";
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
export class MissingConfigError extends ConfigError {
|
|
145
|
+
constructor(field: string) {
|
|
146
|
+
super(
|
|
147
|
+
`Configuração obrigatória ausente: ${field}`,
|
|
148
|
+
`Defina ${field} no xavva.json ou via linha de comando`
|
|
149
|
+
);
|
|
150
|
+
this.code = "MISSING_CONFIG";
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
// ===== Erros de Projeto =====
|
|
155
|
+
export class ProjectError extends XavvaError {
|
|
156
|
+
constructor(message: string, details?: string) {
|
|
157
|
+
super(
|
|
158
|
+
details ? `${message}: ${details}` : message,
|
|
159
|
+
"PROJECT_ERROR",
|
|
160
|
+
6,
|
|
161
|
+
true
|
|
162
|
+
);
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
export class BuildToolNotFoundError extends ProjectError {
|
|
167
|
+
constructor() {
|
|
168
|
+
super(
|
|
169
|
+
"Não foi possível detectar a ferramenta de build",
|
|
170
|
+
"Certifique-se de estar no diretório raiz do projeto (pom.xml ou build.gradle)"
|
|
171
|
+
);
|
|
172
|
+
this.code = "BUILD_TOOL_NOT_FOUND";
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
export class JavaNotFoundError extends ProjectError {
|
|
177
|
+
constructor() {
|
|
178
|
+
super(
|
|
179
|
+
"Java não encontrado",
|
|
180
|
+
"Defina JAVA_HOME ou certifique-se de que 'java' está no PATH"
|
|
181
|
+
);
|
|
182
|
+
this.code = "JAVA_NOT_FOUND";
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
// ===== Erros de Audit/Security =====
|
|
187
|
+
export class AuditError extends XavvaError {
|
|
188
|
+
constructor(message: string) {
|
|
189
|
+
super(
|
|
190
|
+
`Erro na auditoria: ${message}`,
|
|
191
|
+
"AUDIT_ERROR",
|
|
192
|
+
7,
|
|
193
|
+
true
|
|
194
|
+
);
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
export class NetworkError extends XavvaError {
|
|
199
|
+
public readonly url: string;
|
|
200
|
+
public readonly originalError?: Error;
|
|
201
|
+
|
|
202
|
+
constructor(url: string, originalError?: Error) {
|
|
203
|
+
super(
|
|
204
|
+
`Falha na conexão com ${url}${originalError ? `: ${originalError.message}` : ""}`,
|
|
205
|
+
"NETWORK_ERROR",
|
|
206
|
+
8,
|
|
207
|
+
true
|
|
208
|
+
);
|
|
209
|
+
this.url = url;
|
|
210
|
+
this.originalError = originalError;
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
// ===== Erros de File System =====
|
|
215
|
+
export class FileSystemError extends XavvaError {
|
|
216
|
+
constructor(message: string, path?: string) {
|
|
217
|
+
super(
|
|
218
|
+
path ? `${message}: ${path}` : message,
|
|
219
|
+
"FILESYSTEM_ERROR",
|
|
220
|
+
9,
|
|
221
|
+
true
|
|
222
|
+
);
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
export class FileNotFoundError extends FileSystemError {
|
|
227
|
+
constructor(path: string) {
|
|
228
|
+
super("Arquivo não encontrado", path);
|
|
229
|
+
this.code = "FILE_NOT_FOUND";
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
export class PermissionError extends FileSystemError {
|
|
234
|
+
constructor(path: string) {
|
|
235
|
+
super("Permissão negada", path);
|
|
236
|
+
this.code = "PERMISSION_DENIED";
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
// ===== Erros de Comando =====
|
|
241
|
+
export class CommandError extends XavvaError {
|
|
242
|
+
constructor(command: string, message: string) {
|
|
243
|
+
super(
|
|
244
|
+
`Erro no comando '${command}': ${message}`,
|
|
245
|
+
"COMMAND_ERROR",
|
|
246
|
+
10,
|
|
247
|
+
true
|
|
248
|
+
);
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
export class UnknownCommandError extends CommandError {
|
|
253
|
+
constructor(command: string) {
|
|
254
|
+
super(command, "Comando desconhecido");
|
|
255
|
+
this.code = "UNKNOWN_COMMAND";
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
// Helper para identificar se é erro operacional (vs programação)
|
|
260
|
+
export function isOperationalError(error: Error): boolean {
|
|
261
|
+
if (error instanceof XavvaError) {
|
|
262
|
+
return error.isOperational;
|
|
263
|
+
}
|
|
264
|
+
return false;
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
// Helper para obter exit code de qualquer erro
|
|
268
|
+
export function getExitCode(error: Error): number {
|
|
269
|
+
if (error instanceof XavvaError) {
|
|
270
|
+
return error.exitCode;
|
|
271
|
+
}
|
|
272
|
+
return 1; // Erro genérico
|
|
273
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -1,115 +1,117 @@
|
|
|
1
1
|
#!/usr/bin/env bun
|
|
2
2
|
import { ConfigManager } from "./utils/config";
|
|
3
3
|
import { CommandRegistry } from "./commands/CommandRegistry";
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
8
|
-
import { DoctorCommand } from "./commands/DoctorCommand";
|
|
9
|
-
import { RunCommand } from "./commands/RunCommand";
|
|
10
|
-
import { LogsCommand } from "./commands/LogsCommand";
|
|
11
|
-
import { DocsCommand } from "./commands/DocsCommand";
|
|
12
|
-
import { AuditCommand } from "./commands/AuditCommand";
|
|
13
|
-
import { ProfilesCommand } from "./commands/ProfilesCommand";
|
|
14
|
-
import { DepsCommand } from "./commands/DepsCommand";
|
|
15
|
-
import { TomcatCommand } from "./commands/TomcatCommand";
|
|
16
|
-
|
|
17
|
-
import { ProjectService } from "./services/ProjectService";
|
|
18
|
-
import { TomcatService } from "./services/TomcatService";
|
|
19
|
-
import { BuildService } from "./services/BuildService";
|
|
20
|
-
import { AuditService } from "./services/AuditService";
|
|
21
|
-
import { WatcherService } from "./services/WatcherService";
|
|
22
|
-
import { BuildCacheService } from "./services/BuildCacheService";
|
|
23
|
-
import { DashboardService } from "./services/DashboardService";
|
|
24
|
-
import { LogAnalyzer } from "./services/LogAnalyzer";
|
|
25
|
-
|
|
4
|
+
import { createContainer, type DIContainer } from "./di/container";
|
|
5
|
+
import { DeployWatcher } from "./services/DeployWatcher";
|
|
6
|
+
import { ErrorHandler } from "./errors/ErrorHandler";
|
|
7
|
+
import { ProcessManager } from "./utils/processManager";
|
|
26
8
|
import pkg from "../package.json";
|
|
27
9
|
import { Logger } from "./utils/ui";
|
|
28
|
-
import {
|
|
29
|
-
import type { AppConfig, CLIArguments } from "./types/config";
|
|
10
|
+
import type { CLIArguments } from "./types/args";
|
|
30
11
|
|
|
31
12
|
async function main() {
|
|
32
|
-
|
|
33
|
-
|
|
13
|
+
const processManager = ProcessManager.getInstance();
|
|
14
|
+
const { config, positionals, values } = await ConfigManager.load();
|
|
15
|
+
|
|
16
|
+
// Handler de versão
|
|
17
|
+
if (values.version) {
|
|
18
|
+
Logger.log(`v${pkg.version}`);
|
|
19
|
+
await processManager.shutdown(0);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
// Identifica comando
|
|
23
|
+
const commandNames = [
|
|
24
|
+
"deploy", "build", "start", "dev", "doctor", "run",
|
|
25
|
+
"debug", "logs", "docs", "audit", "profiles",
|
|
26
|
+
"deps", "tomcat", "encoding"
|
|
27
|
+
];
|
|
28
|
+
const commandName = positionals.find(p => commandNames.includes(p)) || "deploy";
|
|
34
29
|
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
30
|
+
// Mostra banner (exceto em help ou TUI)
|
|
31
|
+
if (!values.help && !values.tui) {
|
|
32
|
+
Logger.banner(commandName, config.project.profile, config.project.encoding);
|
|
33
|
+
if (config.project.encoding) {
|
|
34
|
+
Logger.config("Encoding", config.project.encoding);
|
|
35
|
+
}
|
|
36
|
+
if (config.tomcat.embedded) {
|
|
37
|
+
Logger.config("Tomcat", `Embutido ${config.tomcat.version}`);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
39
40
|
|
|
40
|
-
|
|
41
|
-
|
|
41
|
+
// Handler de help
|
|
42
|
+
if (values.help) {
|
|
43
|
+
const { HelpCommand } = await import("./commands/HelpCommand");
|
|
44
|
+
new HelpCommand().execute(config, values as CLIArguments);
|
|
45
|
+
await processManager.shutdown(0);
|
|
46
|
+
}
|
|
42
47
|
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
48
|
+
// Inicializa Container de DI
|
|
49
|
+
let container: DIContainer;
|
|
50
|
+
try {
|
|
51
|
+
container = createContainer(config);
|
|
52
|
+
container.initialize();
|
|
53
|
+
} catch (error) {
|
|
54
|
+
await ErrorHandler.getInstance().handle(error, { phase: "di-initialization" });
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
52
57
|
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
await processManager.shutdown(0);
|
|
56
|
-
}
|
|
58
|
+
const services = container.getAllServices();
|
|
59
|
+
const commands = container.getAllCommands();
|
|
57
60
|
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
// Xavva 2.0: Dashboard & LogAnalyzer
|
|
67
|
-
const logAnalyzer = new LogAnalyzer(config.project);
|
|
68
|
-
const dashboard = new DashboardService(config);
|
|
69
|
-
Logger.setDashboard(dashboard);
|
|
61
|
+
// Configura ações da TUI
|
|
62
|
+
if (values.tui) {
|
|
63
|
+
const deployCmd = commands.deploy;
|
|
64
|
+
services.dashboardService.onAction("r", () => {
|
|
65
|
+
services.dashboardService.log(Logger.C.warning + "Restart manual solicitado via TUI...");
|
|
66
|
+
deployCmd.execute(config, { watch: true, incremental: false });
|
|
67
|
+
});
|
|
68
|
+
}
|
|
70
69
|
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
70
|
+
// Caso especial: Watch Mode para Deploy/Dev
|
|
71
|
+
if ((commandName === "deploy" || commandName === "dev") && values.watch) {
|
|
72
|
+
const deployCmd = commands.deploy;
|
|
73
|
+
const watcher = new DeployWatcher(config, deployCmd);
|
|
74
|
+
|
|
75
|
+
try {
|
|
76
|
+
await watcher.start();
|
|
77
|
+
} catch (error) {
|
|
78
|
+
await ErrorHandler.getInstance().handle(error, { phase: "watch-mode", command: commandName });
|
|
79
|
+
}
|
|
80
|
+
} else {
|
|
81
|
+
// Executa comando do Registry
|
|
82
|
+
const registry = new CommandRegistry();
|
|
83
|
+
|
|
84
|
+
// Registra todos os comandos
|
|
85
|
+
registry.register("build", commands.build);
|
|
86
|
+
registry.register("start", commands.start);
|
|
87
|
+
registry.register("doctor", commands.doctor);
|
|
88
|
+
registry.register("run", commands.run);
|
|
89
|
+
registry.register("debug", commands.debug);
|
|
90
|
+
registry.register("logs", commands.logs);
|
|
91
|
+
registry.register("docs", commands.docs);
|
|
92
|
+
registry.register("audit", commands.audit);
|
|
93
|
+
registry.register("profiles", commands.profiles);
|
|
94
|
+
registry.register("deps", commands.deps);
|
|
95
|
+
registry.register("tomcat", commands.tomcat);
|
|
96
|
+
registry.register("encoding", commands.encoding);
|
|
97
|
+
registry.register("deploy", commands.deploy);
|
|
98
|
+
registry.register("dev", commands.dev);
|
|
90
99
|
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
if (dashboard.isTuiActive()) {
|
|
95
|
-
dashboard.onAction("r", () => {
|
|
96
|
-
dashboard.log(Logger.C.warning + "Restart manual solicitado via TUI...");
|
|
97
|
-
deployCmd.execute(config, false, true); // Executa deploy completo mas mantém o watch
|
|
98
|
-
});
|
|
99
|
-
}
|
|
100
|
+
// Configura flags específicas
|
|
101
|
+
if (commandName === "debug") values.debug = true;
|
|
102
|
+
if (commandName === "run") values.debug = false;
|
|
100
103
|
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
await registry.execute(commandName, config, values, positionals);
|
|
109
|
-
}
|
|
104
|
+
try {
|
|
105
|
+
await registry.execute(commandName, config, values as CLIArguments, positionals);
|
|
106
|
+
} catch (error) {
|
|
107
|
+
await ErrorHandler.getInstance().handle(error, { phase: "command-execution", command: commandName });
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
110
|
}
|
|
111
111
|
|
|
112
|
+
// Entry point com tratamento global de erros
|
|
112
113
|
main().catch(async (error) => {
|
|
113
|
-
|
|
114
|
-
|
|
114
|
+
// Erro não tratado - possível bug
|
|
115
|
+
console.error("Erro fatal não tratado:", error);
|
|
116
|
+
await ProcessManager.getInstance().shutdown(1);
|
|
115
117
|
});
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import fs from "fs";
|
|
2
2
|
import path from "path";
|
|
3
|
-
import type { TomcatConfig } from "../types
|
|
3
|
+
import type { TomcatConfig } from "../types";
|
|
4
4
|
import { Logger } from "../utils/ui";
|
|
5
|
+
import { AuditError } from "../errors/XavvaError";
|
|
5
6
|
|
|
6
7
|
export interface Vulnerability {
|
|
7
8
|
id: string;
|
|
@@ -125,7 +126,7 @@ export class AuditService {
|
|
|
125
126
|
|
|
126
127
|
return { groupId, artifactId, version };
|
|
127
128
|
} catch (e) {
|
|
128
|
-
|
|
129
|
+
throw new AuditError(`Falha ao extrair informações do JAR: ${(e as Error).message}`);
|
|
129
130
|
}
|
|
130
131
|
}
|
|
131
132
|
|
|
@@ -2,30 +2,83 @@ import { Logger } from "../utils/ui";
|
|
|
2
2
|
import { isWindows, getOpenBrowserArgs } from "../utils/platform";
|
|
3
3
|
|
|
4
4
|
export class BrowserService {
|
|
5
|
+
private static debugPort = 9222;
|
|
6
|
+
|
|
5
7
|
/**
|
|
6
|
-
* Recarrega a aba
|
|
7
|
-
*
|
|
8
|
-
*
|
|
8
|
+
* Recarrega a aba do browser que corresponde à URL da aplicação.
|
|
9
|
+
* Usa o Chrome DevTools Protocol (CDP) para encontrar e recarregar a aba correta.
|
|
10
|
+
* Nota: No Linux/Mac, esta funcionalidade é limitada.
|
|
9
11
|
*/
|
|
10
12
|
public static async reload(url: string) {
|
|
11
13
|
// Pequeno delay para garantir que o Tomcat processou o novo contexto
|
|
12
14
|
await new Promise(r => setTimeout(r, 800));
|
|
13
15
|
|
|
14
16
|
if (!isWindows()) {
|
|
15
|
-
// No Linux/Mac, tenta notificar via Browser Sync ou similar se disponível
|
|
16
|
-
// Por enquanto, apenas loga (o usuário pode usar extensões de Live Reload)
|
|
17
17
|
return;
|
|
18
18
|
}
|
|
19
19
|
|
|
20
|
+
// Usar heredoc para evitar problemas de escaping
|
|
20
21
|
const psCommand = `
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
22
|
+
$ErrorActionPreference = 'SilentlyContinue'
|
|
23
|
+
$debugPort = ${this.debugPort}
|
|
24
|
+
|
|
25
|
+
function Reload-TabByUrl {
|
|
26
|
+
param($targetUrl, $port)
|
|
27
|
+
|
|
28
|
+
try {
|
|
29
|
+
$pages = Invoke-RestMethod -Uri "http://localhost:$port/json/list" -TimeoutSec 2
|
|
30
|
+
$targetUri = [System.Uri]$targetUrl
|
|
31
|
+
$targetPath = $targetUri.AbsolutePath.TrimEnd('/')
|
|
32
|
+
|
|
33
|
+
foreach ($page in $pages) {
|
|
34
|
+
if ($page.type -ne "page") { continue }
|
|
35
|
+
|
|
36
|
+
try {
|
|
37
|
+
$pageUri = [System.Uri]$page.url
|
|
38
|
+
if ($pageUri.Host -eq $targetUri.Host -and
|
|
39
|
+
$pageUri.AbsolutePath.TrimEnd('/') -eq $targetPath) {
|
|
40
|
+
|
|
41
|
+
$body = @{ id = 1; method = "Page.reload"; params = @{ ignoreCache = $false } } | ConvertTo-Json -Compress
|
|
42
|
+
Invoke-RestMethod -Uri "http://localhost:$port/json/reload/$($page.id)" -Method Put -Body $body -ContentType "application/json" -TimeoutSec 2 | Out-Null
|
|
43
|
+
return $true
|
|
44
|
+
}
|
|
45
|
+
} catch { continue }
|
|
46
|
+
}
|
|
47
|
+
} catch { }
|
|
48
|
+
return $false
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
$reloaded = Reload-TabByUrl -targetUrl "${url}" -port $debugPort
|
|
52
|
+
|
|
53
|
+
if (-not $reloaded) {
|
|
54
|
+
$shell = New-Object -ComObject WScript.Shell
|
|
55
|
+
$urlObj = [System.Uri]"${url}"
|
|
56
|
+
$context = $urlObj.AbsolutePath.Trim('/').Split('/')[0]
|
|
57
|
+
|
|
58
|
+
$processes = Get-Process | Where-Object {
|
|
59
|
+
($_.Name -eq "chrome" -or $_.Name -eq "msedge") -and
|
|
60
|
+
$_.MainWindowTitle -and
|
|
61
|
+
($_.MainWindowTitle -match $context -or $_.MainWindowTitle -match "localhost")
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
if ($processes) {
|
|
65
|
+
$targetProcess = $processes | Select-Object -First 1
|
|
66
|
+
$shell.AppActivate($targetProcess.Id)
|
|
67
|
+
Start-Sleep -Milliseconds 100
|
|
68
|
+
$shell.SendKeys("{F5}")
|
|
69
|
+
} else {
|
|
70
|
+
$anyBrowser = Get-Process | Where-Object {
|
|
71
|
+
$_.Name -eq "chrome" -or $_.Name -eq "msedge"
|
|
72
|
+
} | Select-Object -First 1
|
|
73
|
+
|
|
74
|
+
if ($anyBrowser) {
|
|
75
|
+
$shell.AppActivate($anyBrowser.Id)
|
|
76
|
+
Start-Sleep -Milliseconds 100
|
|
77
|
+
$shell.SendKeys("{F5}")
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
`;
|
|
29
82
|
|
|
30
83
|
try {
|
|
31
84
|
Bun.spawn(["powershell", "-command", psCommand]);
|
|
@@ -35,10 +88,68 @@ export class BrowserService {
|
|
|
35
88
|
}
|
|
36
89
|
|
|
37
90
|
/**
|
|
38
|
-
* Abre a URL no browser
|
|
91
|
+
* Abre a URL no browser com remote debugging habilitado (se possível).
|
|
92
|
+
* Isso permite recarregar a aba específica posteriormente via CDP.
|
|
39
93
|
*/
|
|
40
94
|
public static open(url: string) {
|
|
41
|
-
|
|
42
|
-
|
|
95
|
+
if (!isWindows()) {
|
|
96
|
+
const args = getOpenBrowserArgs(url);
|
|
97
|
+
Bun.spawn(args);
|
|
98
|
+
return;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
const psCommand = `
|
|
102
|
+
$ErrorActionPreference = 'SilentlyContinue'
|
|
103
|
+
$debugPort = ${this.debugPort}
|
|
104
|
+
$targetUrl = "${url}"
|
|
105
|
+
|
|
106
|
+
$debuggingEnabled = $false
|
|
107
|
+
try {
|
|
108
|
+
$resp = Invoke-RestMethod -Uri "http://localhost:$debugPort/json/version" -TimeoutSec 1
|
|
109
|
+
$debuggingEnabled = $true
|
|
110
|
+
} catch { }
|
|
111
|
+
|
|
112
|
+
# Encontra o executável do Chrome ou Edge
|
|
113
|
+
$browserExe = $null
|
|
114
|
+
$chromePaths = @(
|
|
115
|
+
("$env:ProgramFiles" + "\\Google\\Chrome\\Application\\chrome.exe"),
|
|
116
|
+
("$env:LOCALAPPDATA" + "\\Google\\Chrome\\Application\\chrome.exe")
|
|
117
|
+
)
|
|
118
|
+
$edgePaths = @(
|
|
119
|
+
("$env:ProgramFiles" + "\\Microsoft\\Edge\\Application\\msedge.exe")
|
|
120
|
+
)
|
|
121
|
+
|
|
122
|
+
foreach ($path in $chromePaths) {
|
|
123
|
+
if (Test-Path $path) { $browserExe = $path; break }
|
|
124
|
+
}
|
|
125
|
+
if (-not $browserExe) {
|
|
126
|
+
foreach ($path in $edgePaths) {
|
|
127
|
+
if (Test-Path $path) { $browserExe = $path; break }
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
if ($browserExe) {
|
|
132
|
+
if ($debuggingEnabled) {
|
|
133
|
+
try {
|
|
134
|
+
$body = @{ url = $targetUrl } | ConvertTo-Json -Compress
|
|
135
|
+
Invoke-RestMethod -Uri "http://localhost:$debugPort/json/new" -Method Put -Body $body -ContentType "application/json" -TimeoutSec 3 | Out-Null
|
|
136
|
+
} catch {
|
|
137
|
+
Start-Process $browserExe -ArgumentList $targetUrl
|
|
138
|
+
}
|
|
139
|
+
} else {
|
|
140
|
+
Start-Process $browserExe -ArgumentList $targetUrl, "--remote-debugging-port=$debugPort", "--no-first-run"
|
|
141
|
+
}
|
|
142
|
+
} else {
|
|
143
|
+
Start-Process $targetUrl
|
|
144
|
+
}
|
|
145
|
+
`;
|
|
146
|
+
|
|
147
|
+
try {
|
|
148
|
+
Bun.spawn(["powershell", "-command", psCommand]);
|
|
149
|
+
} catch (e) {
|
|
150
|
+
// Fallback para método padrão
|
|
151
|
+
const args = getOpenBrowserArgs(url);
|
|
152
|
+
Bun.spawn(args);
|
|
153
|
+
}
|
|
43
154
|
}
|
|
44
155
|
}
|