@archznn/xavva 2.6.0 → 2.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 +55 -0
- package/package.json +4 -2
- package/src/commands/CompletionCommand.ts +212 -0
- package/src/commands/ConfigCommand.ts +184 -0
- package/src/commands/DeployCommand.ts +2 -1
- package/src/commands/EncodingCommand.ts +351 -0
- package/src/commands/HealthCommand.ts +302 -0
- package/src/commands/HelpCommand.ts +42 -0
- package/src/commands/HistoryCommand.ts +49 -0
- package/src/commands/InitCommand.ts +148 -0
- package/src/commands/RedoCommand.ts +36 -0
- package/src/config/versions.ts +63 -0
- package/src/di/container.ts +249 -0
- package/src/errors/ErrorHandler.ts +249 -0
- package/src/errors/XavvaError.ts +273 -0
- package/src/index.ts +136 -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 +67 -37
- package/src/services/EncodingService.ts +548 -0
- package/src/services/FileWatcher.ts +243 -0
- package/src/services/HistoryService.ts +73 -0
- package/src/services/NotificationService.ts +145 -0
- package/src/services/TomcatService.ts +59 -26
- package/src/types/args.ts +151 -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/ProgressBar.ts +182 -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
package/src/index.ts
CHANGED
|
@@ -1,115 +1,155 @@
|
|
|
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", "init", "config",
|
|
27
|
+
"history", "redo", "health", "completion", "help"
|
|
28
|
+
];
|
|
29
|
+
const commandName = positionals.find(p => commandNames.includes(p)) || "deploy";
|
|
30
|
+
|
|
31
|
+
// Mostra banner (exceto em help ou TUI)
|
|
32
|
+
if (!values.help && !values.tui) {
|
|
33
|
+
Logger.banner(commandName, config.project.profile, config.project.encoding);
|
|
34
|
+
if (config.project.encoding) {
|
|
35
|
+
Logger.config("Encoding", config.project.encoding);
|
|
36
|
+
}
|
|
37
|
+
if (config.tomcat.embedded) {
|
|
38
|
+
Logger.config("Tomcat", `Embutido ${config.tomcat.version}`);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// Handler de help
|
|
43
|
+
if (values.help) {
|
|
44
|
+
const { HelpCommand } = await import("./commands/HelpCommand");
|
|
45
|
+
new HelpCommand().execute(config, values as CLIArguments);
|
|
46
|
+
await processManager.shutdown(0);
|
|
47
|
+
}
|
|
34
48
|
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
49
|
+
// Inicializa Container de DI
|
|
50
|
+
let container: DIContainer;
|
|
51
|
+
try {
|
|
52
|
+
container = createContainer(config);
|
|
53
|
+
container.initialize();
|
|
54
|
+
} catch (error) {
|
|
55
|
+
await ErrorHandler.getInstance().handle(error, { phase: "di-initialization" });
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
39
58
|
|
|
40
|
-
|
|
41
|
-
|
|
59
|
+
const services = container.getAllServices();
|
|
60
|
+
const commands = container.getAllCommands();
|
|
42
61
|
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
}
|
|
62
|
+
// Configura ações da TUI
|
|
63
|
+
if (values.tui) {
|
|
64
|
+
const deployCmd = commands.deploy;
|
|
65
|
+
services.dashboardService.onAction("r", () => {
|
|
66
|
+
services.dashboardService.log(Logger.C.warning + "Restart manual solicitado via TUI...");
|
|
67
|
+
deployCmd.execute(config, { watch: true, incremental: false });
|
|
68
|
+
});
|
|
69
|
+
}
|
|
52
70
|
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
71
|
+
// Caso especial: Watch Mode para Deploy/Dev
|
|
72
|
+
if ((commandName === "deploy" || commandName === "dev") && values.watch) {
|
|
73
|
+
const deployCmd = commands.deploy;
|
|
74
|
+
const watcher = new DeployWatcher(config, deployCmd);
|
|
75
|
+
|
|
76
|
+
try {
|
|
77
|
+
await watcher.start();
|
|
78
|
+
} catch (error) {
|
|
79
|
+
await ErrorHandler.getInstance().handle(error, { phase: "watch-mode", command: commandName });
|
|
80
|
+
}
|
|
81
|
+
} else {
|
|
82
|
+
// Executa comando do Registry
|
|
83
|
+
const registry = new CommandRegistry();
|
|
84
|
+
|
|
85
|
+
// Registra todos os comandos
|
|
86
|
+
registry.register("build", commands.build);
|
|
87
|
+
registry.register("start", commands.start);
|
|
88
|
+
registry.register("doctor", commands.doctor);
|
|
89
|
+
registry.register("run", commands.run);
|
|
90
|
+
registry.register("debug", commands.debug);
|
|
91
|
+
registry.register("logs", commands.logs);
|
|
92
|
+
registry.register("docs", commands.docs);
|
|
93
|
+
registry.register("audit", commands.audit);
|
|
94
|
+
registry.register("profiles", commands.profiles);
|
|
95
|
+
registry.register("deps", commands.deps);
|
|
96
|
+
registry.register("tomcat", commands.tomcat);
|
|
97
|
+
registry.register("encoding", commands.encoding);
|
|
98
|
+
registry.register("deploy", commands.deploy);
|
|
99
|
+
registry.register("dev", commands.dev);
|
|
100
|
+
registry.register("init", commands.init);
|
|
101
|
+
registry.register("config", commands.config);
|
|
102
|
+
registry.register("history", commands.history);
|
|
103
|
+
registry.register("redo", commands.redo);
|
|
104
|
+
registry.register("health", commands.health);
|
|
105
|
+
registry.register("completion", commands.completion);
|
|
57
106
|
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
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);
|
|
107
|
+
// Configura flags específicas
|
|
108
|
+
if (commandName === "debug") values.debug = true;
|
|
109
|
+
if (commandName === "run") values.debug = false;
|
|
70
110
|
|
|
71
|
-
|
|
72
|
-
|
|
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);
|
|
111
|
+
// Registra comando no histórico antes da execução
|
|
112
|
+
const startTime = Date.now();
|
|
113
|
+
let success = true;
|
|
90
114
|
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
115
|
+
try {
|
|
116
|
+
await registry.execute(commandName, config, values as CLIArguments, positionals);
|
|
117
|
+
} catch (error) {
|
|
118
|
+
success = false;
|
|
119
|
+
await ErrorHandler.getInstance().handle(error, { phase: "command-execution", command: commandName });
|
|
120
|
+
} finally {
|
|
121
|
+
// Salva no histórico
|
|
122
|
+
const duration = (Date.now() - startTime) / 1000;
|
|
123
|
+
const filteredPositionals = positionals.filter(p => p !== commandName && !commandNames.includes(p));
|
|
124
|
+
services.historyService.add({
|
|
125
|
+
command: commandName,
|
|
126
|
+
args: [...filteredPositionals, ...Object.entries(values)
|
|
127
|
+
.filter(([, v]) => v !== undefined && typeof v !== "object")
|
|
128
|
+
.flatMap(([k, v]) => [`--${k}`, String(v)])],
|
|
129
|
+
success,
|
|
130
|
+
duration
|
|
131
|
+
}).catch(() => { /* ignore history errors */ });
|
|
100
132
|
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
133
|
+
// Envia notificação para comandos longos
|
|
134
|
+
if (duration > 5 && commandName !== "logs" && commandName !== "history") {
|
|
135
|
+
const { NotificationService } = await import("./services/NotificationService");
|
|
136
|
+
if (success) {
|
|
137
|
+
if (commandName === "build" || commandName === "deploy") {
|
|
138
|
+
NotificationService.buildSuccess(duration);
|
|
139
|
+
} else if (commandName === "start") {
|
|
140
|
+
NotificationService.deployComplete(config.project.appName);
|
|
141
|
+
}
|
|
142
|
+
} else {
|
|
143
|
+
NotificationService.buildFailed(`Comando ${commandName} falhou`);
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
}
|
|
110
148
|
}
|
|
111
149
|
|
|
150
|
+
// Entry point com tratamento global de erros
|
|
112
151
|
main().catch(async (error) => {
|
|
113
|
-
|
|
114
|
-
|
|
152
|
+
// Erro não tratado - possível bug
|
|
153
|
+
console.error("Erro fatal não tratado:", error);
|
|
154
|
+
await ProcessManager.getInstance().shutdown(1);
|
|
115
155
|
});
|
|
@@ -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
|
}
|
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* DeployWatcher - Específico para watch de deploy
|
|
3
|
+
* Usa FileWatcher genérico e adiciona lógica de deploy
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { FileWatcher, type FileChangeEvent } from "./FileWatcher";
|
|
7
|
+
import { DeployCommand } from "../commands/DeployCommand";
|
|
8
|
+
import { Logger } from "../utils/ui";
|
|
9
|
+
import type { AppConfig } from "../types/config";
|
|
10
|
+
import { WATCHER_DEBOUNCE_MS, WATCHER_COOLING_MS } from "../utils/constants";
|
|
11
|
+
|
|
12
|
+
export class DeployWatcher {
|
|
13
|
+
private fileWatcher: FileWatcher;
|
|
14
|
+
private isDeploying = false;
|
|
15
|
+
private pendingFullBuild = false;
|
|
16
|
+
private modifiedFiles = new Set<string>();
|
|
17
|
+
private pendingFiles = new Set<string>();
|
|
18
|
+
private hasPendingChanges = false;
|
|
19
|
+
|
|
20
|
+
constructor(
|
|
21
|
+
private config: AppConfig,
|
|
22
|
+
private deployCmd: DeployCommand
|
|
23
|
+
) {
|
|
24
|
+
this.fileWatcher = new FileWatcher({
|
|
25
|
+
recursive: true,
|
|
26
|
+
debounceMs: WATCHER_DEBOUNCE_MS,
|
|
27
|
+
coolingMs: WATCHER_COOLING_MS,
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Inicia o watch de deploy
|
|
33
|
+
*/
|
|
34
|
+
async start(): Promise<void> {
|
|
35
|
+
// Executa deploy inicial
|
|
36
|
+
await this.run(false);
|
|
37
|
+
|
|
38
|
+
// Configura handlers
|
|
39
|
+
this.setupHandlers();
|
|
40
|
+
|
|
41
|
+
// Inicia o watcher
|
|
42
|
+
this.fileWatcher.start();
|
|
43
|
+
|
|
44
|
+
Logger.info("DeployWatcher", "Monitorando alterações...");
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Para o watching
|
|
49
|
+
*/
|
|
50
|
+
stop(): void {
|
|
51
|
+
this.fileWatcher.stop();
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Configura handlers para diferentes tipos de arquivos
|
|
56
|
+
*/
|
|
57
|
+
private setupHandlers(): void {
|
|
58
|
+
// Handler para configurações de build
|
|
59
|
+
this.fileWatcher.on(/(pom\.xml|build\.gradle|build\.gradle\.kts)$/, (event) => {
|
|
60
|
+
this.handleBuildConfigChange(event);
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
// Handler para arquivos Java
|
|
64
|
+
this.fileWatcher.on(/\.java$/, (event) => {
|
|
65
|
+
this.handleJavaChange(event);
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
// Handler para recursos estáticos (JSP, HTML, CSS, etc)
|
|
69
|
+
this.fileWatcher.on(/\.(jsp|html|css|js|xml|properties)$/, (event) => {
|
|
70
|
+
this.handleResourceChange(event);
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Trata mudança em arquivo de configuração de build
|
|
76
|
+
*/
|
|
77
|
+
private async handleBuildConfigChange(event: FileChangeEvent): Promise<void> {
|
|
78
|
+
if (!event.filename) return;
|
|
79
|
+
|
|
80
|
+
Logger.watcher(`Build configuration changed: ${event.filename}`, 'warn');
|
|
81
|
+
|
|
82
|
+
// Limpa cache quando config muda
|
|
83
|
+
const { BuildCacheService } = await import("./BuildCacheService");
|
|
84
|
+
new BuildCacheService().clearCache();
|
|
85
|
+
|
|
86
|
+
this.pendingFullBuild = true;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Trata mudança em arquivo Java
|
|
91
|
+
*/
|
|
92
|
+
private handleJavaChange(event: FileChangeEvent): void {
|
|
93
|
+
if (!event.filename || this.isDeploying) {
|
|
94
|
+
if (event.filename) {
|
|
95
|
+
this.pendingFiles.add(event.filename);
|
|
96
|
+
this.hasPendingChanges = true;
|
|
97
|
+
}
|
|
98
|
+
return;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
Logger.watcher(event.filename, 'watch');
|
|
102
|
+
this.modifiedFiles.add(event.filename);
|
|
103
|
+
|
|
104
|
+
// Debounce para acumular múltiplas mudanças
|
|
105
|
+
this.scheduleDeploy();
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Trata mudança em recurso estático
|
|
110
|
+
*/
|
|
111
|
+
private async handleResourceChange(event: FileChangeEvent): Promise<void> {
|
|
112
|
+
if (!event.filename) return;
|
|
113
|
+
|
|
114
|
+
Logger.watcher(event.filename, 'resource');
|
|
115
|
+
|
|
116
|
+
try {
|
|
117
|
+
await this.deployCmd.syncResource(this.config, event.filename);
|
|
118
|
+
} catch (error) {
|
|
119
|
+
Logger.error(`Falha ao sincronizar recurso: ${event.filename}`);
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* Agenda deploy após debounce
|
|
125
|
+
*/
|
|
126
|
+
private scheduleDeploy(): void {
|
|
127
|
+
// O debounce já é feito pelo FileWatcher
|
|
128
|
+
// Aqui apenas executamos o deploy
|
|
129
|
+
const filesToCompile = [...this.modifiedFiles];
|
|
130
|
+
this.modifiedFiles.clear();
|
|
131
|
+
|
|
132
|
+
this.run(this.pendingFullBuild ? false : true, filesToCompile);
|
|
133
|
+
this.pendingFullBuild = false;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* Executa o deploy
|
|
138
|
+
*/
|
|
139
|
+
private async run(incremental = false, changedFiles?: string[]): Promise<void> {
|
|
140
|
+
if (this.isDeploying) return;
|
|
141
|
+
|
|
142
|
+
this.isDeploying = true;
|
|
143
|
+
|
|
144
|
+
try {
|
|
145
|
+
await this.deployCmd.execute(this.config, {
|
|
146
|
+
watch: true,
|
|
147
|
+
incremental,
|
|
148
|
+
changedFiles,
|
|
149
|
+
});
|
|
150
|
+
} catch (error) {
|
|
151
|
+
// Erro já é tratado pelo comando
|
|
152
|
+
} finally {
|
|
153
|
+
this.isDeploying = false;
|
|
154
|
+
|
|
155
|
+
// Processa mudanças pendentes
|
|
156
|
+
if (this.hasPendingChanges) {
|
|
157
|
+
const pending = [...this.pendingFiles];
|
|
158
|
+
this.pendingFiles.clear();
|
|
159
|
+
this.hasPendingChanges = false;
|
|
160
|
+
|
|
161
|
+
Logger.watcher(`Processing ${pending.length} pending change(s)...`, 'warn');
|
|
162
|
+
|
|
163
|
+
setTimeout(() => {
|
|
164
|
+
this.run(true, pending);
|
|
165
|
+
}, 100);
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
/**
|
|
171
|
+
* Verifica se é arquivo de recurso (não Java)
|
|
172
|
+
*/
|
|
173
|
+
static isResourceFile(filename: string): boolean {
|
|
174
|
+
return /\.(jsp|html|css|js|xml|properties)$/.test(filename);
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
/**
|
|
178
|
+
* Verifica se é arquivo de configuração de build
|
|
179
|
+
*/
|
|
180
|
+
static isBuildConfig(filename: string): boolean {
|
|
181
|
+
return /^(pom\.xml|build\.gradle|build\.gradle\.kts)$/.test(filename);
|
|
182
|
+
}
|
|
183
|
+
}
|