@archznn/xavva 2.5.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.
@@ -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 { BuildCommand } from "./commands/BuildCommand";
5
- import { DeployCommand } from "./commands/DeployCommand";
6
- import { StartCommand } from "./commands/StartCommand";
7
- import { HelpCommand } from "./commands/HelpCommand";
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 { ProcessManager } from "./utils/processManager";
29
- import type { AppConfig, CLIArguments } from "./types/config";
10
+ import type { CLIArguments } from "./types/args";
30
11
 
31
12
  async function main() {
32
- const processManager = ProcessManager.getInstance();
33
- const { config, positionals, values } = await ConfigManager.load();
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
- if (values.version) {
36
- Logger.log(`v${pkg.version}`);
37
- await processManager.shutdown(0);
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
- const commandNames = ["deploy", "build", "start", "dev", "doctor", "run", "debug", "logs", "docs", "audit", "profiles", "deps", "tomcat"];
41
- const commandName = positionals.find(p => commandNames.includes(p)) || "deploy";
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
- if (!values.help && !values.tui) {
44
- Logger.banner(commandName, config.project.profile, config.project.encoding);
45
- if (config.project.encoding) {
46
- Logger.config("Encoding", config.project.encoding);
47
- }
48
- if (config.tomcat.embedded) {
49
- Logger.config("Tomcat", `Embutido ${config.tomcat.version}`);
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
- if (values.help) {
54
- new HelpCommand().execute(config, values);
55
- await processManager.shutdown(0);
56
- }
58
+ const services = container.getAllServices();
59
+ const commands = container.getAllCommands();
57
60
 
58
- // 1. Instanciar Serviços (Injeção de Dependência)
59
- const projectService = new ProjectService(config.project);
60
- const buildCacheService = new BuildCacheService();
61
- const buildService = new BuildService(config.project, config.tomcat, projectService, buildCacheService);
62
- const tomcatService = new TomcatService(config.tomcat);
63
- tomcatService.setProjectService(projectService);
64
- const auditService = new AuditService(config.tomcat);
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
- // 2. Registrar Comandos
72
- const registry = new CommandRegistry();
73
-
74
- const deployCmd = new DeployCommand(tomcatService, buildService);
75
- const logsCmd = new LogsCommand(dashboard, logAnalyzer);
76
-
77
- registry.register("build", new BuildCommand(buildService));
78
- registry.register("start", new StartCommand(tomcatService));
79
- registry.register("doctor", new DoctorCommand());
80
- registry.register("run", new RunCommand());
81
- registry.register("debug", new RunCommand());
82
- registry.register("logs", logsCmd);
83
- registry.register("docs", new DocsCommand());
84
- registry.register("audit", new AuditCommand(auditService));
85
- registry.register("profiles", new ProfilesCommand(projectService));
86
- registry.register("deps", new DepsCommand());
87
- registry.register("tomcat", new TomcatCommand());
88
- registry.register("deploy", deployCmd);
89
- registry.register("dev", deployCmd);
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
- // Caso especial: Watch Mode para Deploy/Dev
92
- if ((commandName === "deploy" || commandName === "dev") && values.watch) {
93
- // Registrar ação de restart manual na TUI
94
- if (dashboard.isTuiActive()) {
95
- dashboard.onAction("r", () => {
96
- dashboard.log(Logger.C.yellow + "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
- const watcher = new WatcherService(config, deployCmd);
102
- await watcher.start();
103
- } else {
104
- // 3. Executar do Registro
105
- if (commandName === "debug") values.debug = true;
106
- if (commandName === "run") values.debug = false;
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
- console.error(error);
114
- await ProcessManager.getInstance().shutdown(1);
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/config";
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
- return {};
129
+ throw new AuditError(`Falha ao extrair informações do JAR: ${(e as Error).message}`);
129
130
  }
130
131
  }
131
132
 
@@ -1,24 +1,84 @@
1
1
  import { Logger } from "../utils/ui";
2
+ import { isWindows, getOpenBrowserArgs } from "../utils/platform";
2
3
 
3
4
  export class BrowserService {
5
+ private static debugPort = 9222;
6
+
4
7
  /**
5
- * Recarrega a aba ativa do browser (Chrome ou Edge) no Windows.
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.
6
11
  */
7
12
  public static async reload(url: string) {
8
- if (process.platform !== 'win32') return;
9
-
10
13
  // Pequeno delay para garantir que o Tomcat processou o novo contexto
11
14
  await new Promise(r => setTimeout(r, 800));
12
15
 
16
+ if (!isWindows()) {
17
+ return;
18
+ }
19
+
20
+ // Usar heredoc para evitar problemas de escaping
13
21
  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
+ $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
+ `;
22
82
 
23
83
  try {
24
84
  Bun.spawn(["powershell", "-command", psCommand]);
@@ -28,14 +88,68 @@ export class BrowserService {
28
88
  }
29
89
 
30
90
  /**
31
- * Abre a URL no browser padrão do sistema.
91
+ * Abre a URL no browser com remote debugging habilitado (se possível).
92
+ * Isso permite recarregar a aba específica posteriormente via CDP.
32
93
  */
33
94
  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]);
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);
39
153
  }
40
154
  }
41
155
  }