@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,323 @@
1
+ /**
2
+ * Platform Utilities - Helpers para detecção e adaptação multiplataforma
3
+ *
4
+ * Centraliza toda a lógica de diferenciação entre Windows, Linux e macOS.
5
+ */
6
+
7
+ import os from "os";
8
+ import path from "path";
9
+
10
+ export type Platform = "win32" | "linux" | "darwin";
11
+
12
+ /**
13
+ * Retorna a plataforma atual normalizada
14
+ */
15
+ export function getPlatform(): Platform {
16
+ const platform = process.platform;
17
+ if (platform === "win32") return "win32";
18
+ if (platform === "darwin") return "darwin";
19
+ return "linux";
20
+ }
21
+
22
+ /**
23
+ * Verifica se está rodando no Windows
24
+ */
25
+ export function isWindows(): boolean {
26
+ return getPlatform() === "win32";
27
+ }
28
+
29
+ /**
30
+ * Verifica se está rodando no Linux
31
+ */
32
+ export function isLinux(): boolean {
33
+ return getPlatform() === "linux";
34
+ }
35
+
36
+ /**
37
+ * Verifica se está rodando no macOS
38
+ */
39
+ export function isMacOS(): boolean {
40
+ return getPlatform() === "darwin";
41
+ }
42
+
43
+ /**
44
+ * Retorna a extensão de script apropriada (.bat para Windows, .sh para Unix)
45
+ */
46
+ export function getScriptExt(): string {
47
+ return isWindows() ? ".bat" : ".sh";
48
+ }
49
+
50
+ /**
51
+ * Retorna o nome do binário Java apropriado (java.exe para Windows, java para Unix)
52
+ */
53
+ export function getJavaBinary(): string {
54
+ return isWindows() ? "java.exe" : "java";
55
+ }
56
+
57
+ /**
58
+ * Retorna o caminho completo para o binário Java se JAVA_HOME estiver definido
59
+ */
60
+ export function getJavaPath(): string {
61
+ if (process.env.JAVA_HOME) {
62
+ const javaBin = path.join(process.env.JAVA_HOME, "bin", getJavaBinary());
63
+ return javaBin;
64
+ }
65
+ return getJavaBinary();
66
+ }
67
+
68
+ /**
69
+ * Retorna o script catalina apropriado (catalina.bat ou catalina.sh)
70
+ */
71
+ export function getCatalinaScript(): string {
72
+ return `catalina${getScriptExt()}`;
73
+ }
74
+
75
+ /**
76
+ * Retorna o script startup apropriado
77
+ */
78
+ export function getStartupScript(): string {
79
+ return `startup${getScriptExt()}`;
80
+ }
81
+
82
+ /**
83
+ * Retorna o script shutdown apropriado
84
+ */
85
+ export function getShutdownScript(): string {
86
+ return `shutdown${getScriptExt()}`;
87
+ }
88
+
89
+ /**
90
+ * Retorna o comando Maven apropriado
91
+ */
92
+ export function getMavenCommand(): string {
93
+ return isWindows() ? "mvn.cmd" : "mvn";
94
+ }
95
+
96
+ /**
97
+ * Retorna o comando Gradle apropriado
98
+ */
99
+ export function getGradleCommand(): string {
100
+ return isWindows() ? "gradle.bat" : "gradle";
101
+ }
102
+
103
+ /**
104
+ * Retorna a extensão de arquivo de download do Tomcat (.zip para Windows, .tar.gz para Unix)
105
+ */
106
+ export function getTomcatArchiveExt(): string {
107
+ return isWindows() ? ".zip" : ".tar.gz";
108
+ }
109
+
110
+ /**
111
+ * Retorna o nome do arquivo do Tomcat para download
112
+ */
113
+ export function getTomcatArchiveName(version: string): string {
114
+ const baseName = `apache-tomcat-${version}`;
115
+ if (isWindows()) {
116
+ return `${baseName}-windows-x64.zip`;
117
+ }
118
+ if (isMacOS()) {
119
+ // macOS usa a mesma versão do Linux (tar.gz)
120
+ return `${baseName}.tar.gz`;
121
+ }
122
+ return `${baseName}.tar.gz`;
123
+ }
124
+
125
+ /**
126
+ * Retorna a URL de download do Tomcat baseada na versão e plataforma
127
+ */
128
+ export function getTomcatDownloadUrl(version: string): string {
129
+ const majorVersion = version.split(".")[0];
130
+ const archiveName = getTomcatArchiveName(version);
131
+
132
+ // URL primária (CDN Apache)
133
+ return `https://dlcdn.apache.org/tomcat/tomcat-${majorVersion}/v${version}/bin/${archiveName}`;
134
+ }
135
+
136
+ /**
137
+ * Retorna a URL alternativa (archive) caso a primária falhe
138
+ */
139
+ export function getTomcatArchiveUrl(version: string): string {
140
+ const majorVersion = version.split(".")[0];
141
+ const archiveName = getTomcatArchiveName(version);
142
+
143
+ // URL alternativa (archive Apache)
144
+ return `https://archive.apache.org/dist/tomcat/tomcat-${majorVersion}/v${version}/bin/${archiveName}`;
145
+ }
146
+
147
+ /**
148
+ * Retorna o comando para extrair um arquivo
149
+ * Retorna null se não for possível determinar
150
+ */
151
+ export function getExtractCommand(archivePath: string, destDir: string): string[] | null {
152
+ if (isWindows()) {
153
+ // Windows: usa PowerShell para extrair
154
+ if (archivePath.endsWith(".zip")) {
155
+ return [
156
+ "powershell",
157
+ "-command",
158
+ `Expand-Archive -Path '${archivePath}' -DestinationPath '${destDir}' -Force`,
159
+ ];
160
+ }
161
+ // Para .tar.gz no Windows (PowerShell 5.1+)
162
+ if (archivePath.endsWith(".tar.gz") || archivePath.endsWith(".tgz")) {
163
+ return [
164
+ "powershell",
165
+ "-command",
166
+ `tar -xzf '${archivePath}' -C '${destDir}'`,
167
+ ];
168
+ }
169
+ } else {
170
+ // Linux/Mac: usa tar
171
+ if (archivePath.endsWith(".tar.gz") || archivePath.endsWith(".tgz")) {
172
+ return ["tar", "-xzf", archivePath, "-C", destDir];
173
+ }
174
+ // Para .zip no Linux/Mac
175
+ if (archivePath.endsWith(".zip")) {
176
+ // Tenta unzip primeiro, depois tar
177
+ return ["unzip", "-q", "-o", archivePath, "-d", destDir];
178
+ }
179
+ }
180
+ return null;
181
+ }
182
+
183
+ /**
184
+ * Retorna o comando para verificar se uma porta está em uso
185
+ */
186
+ export function getPortCheckCommand(port: number): string[] {
187
+ if (isWindows()) {
188
+ return ["cmd", "/c", `netstat -ano | findstr :${port}`];
189
+ }
190
+ // Linux/Mac: tenta lsof, ss, ou netstat
191
+ // Preferência por lsof (mais comum)
192
+ return ["sh", "-c", `lsof -i :${port} 2>/dev/null || ss -tlnp 2>/dev/null | grep ':${port}' || netstat -tlnp 2>/dev/null | grep ':${port}'`];
193
+ }
194
+
195
+ /**
196
+ * Retorna o comando para matar um processo pelo PID
197
+ */
198
+ export function getKillCommand(pid: number | string): string[] {
199
+ if (isWindows()) {
200
+ return ["taskkill", "/F", "/PID", String(pid)];
201
+ }
202
+ return ["kill", "-9", String(pid)];
203
+ }
204
+
205
+ /**
206
+ * Retorna o comando para obter uso de memória de um processo
207
+ */
208
+ export function getMemoryCommand(pid: number): string[] | null {
209
+ if (isWindows()) {
210
+ return [
211
+ "powershell",
212
+ "-command",
213
+ `(Get-Process -Id ${pid}).WorkingSet64 / 1MB`,
214
+ ];
215
+ }
216
+ // Linux: /proc/<pid>/status tem VmRSS em kB
217
+ if (isLinux()) {
218
+ return ["sh", "-c", `cat /proc/${pid}/status 2>/dev/null | grep VmRSS | awk '{print int($2/1024)}'`];
219
+ }
220
+ // macOS: usa ps
221
+ if (isMacOS()) {
222
+ return ["ps", "-o", "rss=", "-p", String(pid)];
223
+ }
224
+ return null;
225
+ }
226
+
227
+ /**
228
+ * Retorna o comando para abrir uma URL no navegador padrão
229
+ */
230
+ export function getOpenBrowserCommand(): string {
231
+ if (isWindows()) {
232
+ return "start";
233
+ }
234
+ if (isMacOS()) {
235
+ return "open";
236
+ }
237
+ return "xdg-open";
238
+ }
239
+
240
+ /**
241
+ * Retorna o comando para abrir uma URL no navegador (array para spawn)
242
+ */
243
+ export function getOpenBrowserArgs(url: string): string[] {
244
+ const cmd = getOpenBrowserCommand();
245
+ if (isWindows()) {
246
+ // Windows: usa cmd /c start "" "url" (start é comando interno do cmd)
247
+ return ["cmd", "/c", "start", "", url];
248
+ }
249
+ return [cmd, url];
250
+ }
251
+
252
+ /**
253
+ * Retorna o comando para encontrar um binário no PATH
254
+ */
255
+ export function getWhichCommand(binary: string): string[] {
256
+ if (isWindows()) {
257
+ return ["where", binary];
258
+ }
259
+ return ["which", binary];
260
+ }
261
+
262
+ /**
263
+ * Retorna o separador de classpath apropriado
264
+ */
265
+ export function getClasspathSeparator(): string {
266
+ return path.delimiter;
267
+ }
268
+
269
+ /**
270
+ * Normaliza um caminho para uso em classpath (converte backslash para forward slash)
271
+ */
272
+ export function normalizeClasspathPath(p: string): string {
273
+ return p.replace(/\\/g, "/");
274
+ }
275
+
276
+ /**
277
+ * Retorna o comando para extrair um WAR/JAR (usando jar ou unzip)
278
+ */
279
+ export function getWarExtractCommand(warPath: string, destDir: string): string[] {
280
+ // Tenta usar jar (disponível em qualquer JDK)
281
+ return ["jar", "xf", warPath];
282
+ }
283
+
284
+ /**
285
+ * Retorna a URL de download do JetBrains Runtime (JBR) com DCEVM
286
+ */
287
+ export function getJbrDownloadUrl(version: string = "21"): string {
288
+ // JBR 21 é a versão recomendada
289
+ if (isWindows()) {
290
+ return `https://cache-redirector.jetbrains.com/intellij-jbr/jbrsdk-${version}.0.6-windows-x64-b895.97.tar.gz`;
291
+ }
292
+ if (isMacOS()) {
293
+ return `https://cache-redirector.jetbrains.com/intellij-jbr/jbrsdk-${version}.0.6-osx-x64-b895.97.tar.gz`;
294
+ }
295
+ // Linux x64
296
+ return `https://cache-redirector.jetbrains.com/intellij-jbr/jbrsdk-${version}.0.6-linux-x64-b895.97.tar.gz`;
297
+ }
298
+
299
+ /**
300
+ * Retorna o comando para extrair um .tar.gz
301
+ */
302
+ export function getTarExtractCommand(tarPath: string, destDir: string): string[] {
303
+ if (isWindows()) {
304
+ // Windows 10+ tem tar nativo via PowerShell
305
+ return ["powershell", "-command", `tar -xzf '${tarPath}' -C '${destDir}'`];
306
+ }
307
+ return ["tar", "-xzf", tarPath, "-C", destDir];
308
+ }
309
+
310
+ /**
311
+ * Constrói o caminho completo para o script catalina
312
+ */
313
+ export function getCatalinaPath(tomcatHome: string): string {
314
+ return path.join(tomcatHome, "bin", getCatalinaScript());
315
+ }
316
+
317
+ /**
318
+ * Verifica se o script catalina existe no diretório do Tomcat
319
+ */
320
+ export function hasCatalinaScript(tomcatHome: string): boolean {
321
+ const { existsSync } = require("fs");
322
+ return existsSync(getCatalinaPath(tomcatHome));
323
+ }
@@ -1,117 +0,0 @@
1
- import { watch } from "fs";
2
- import { Logger } from "../utils/ui";
3
- import { DeployCommand } from "../commands/DeployCommand";
4
- import { WATCHER_DEBOUNCE_MS, WATCHER_COOLING_MS } from "../utils/constants";
5
- import type { AppConfig } from "../types/config";
6
-
7
- export class WatcherService {
8
- private isDeploying = false;
9
- private pendingFullBuild = false;
10
- private coolingFiles = new Set<string>();
11
- private debounceTimer?: Timer;
12
-
13
- // Rastreamento de arquivos modificados para build incremental inteligente
14
- private modifiedFiles = new Set<string>();
15
- private pendingFiles = new Set<string>(); // Arquivos modificados durante compilação
16
- private hasPendingChanges = false;
17
-
18
- constructor(private config: AppConfig, private deployCmd: DeployCommand) {}
19
-
20
- public async start() {
21
- await this.run(false);
22
-
23
- watch(process.cwd(), { recursive: true }, async (event, filename) => {
24
- if (!filename) return;
25
-
26
- if (this.coolingFiles.has(filename)) return;
27
- this.coolingFiles.add(filename);
28
- setTimeout(() => this.coolingFiles.delete(filename), WATCHER_COOLING_MS);
29
-
30
- if (this.isIgnored(filename)) return;
31
-
32
- const isBuildConfig = filename === "pom.xml" || filename === "build.gradle" || filename === "build.gradle.kts";
33
- const isJava = filename.endsWith(".java") || isBuildConfig;
34
- const isResource = this.isResourceFile(filename);
35
-
36
- if (isBuildConfig) {
37
- Logger.watcher(`Build configuration changed: ${filename}`, 'warn');
38
- const { BuildCacheService } = await import("./BuildCacheService");
39
- new BuildCacheService().clearCache();
40
- this.pendingFullBuild = true;
41
- }
42
-
43
- if (isResource && !isJava) {
44
- await this.deployCmd.syncResource(this.config, filename);
45
- return;
46
- }
47
-
48
- if (!isJava) return;
49
-
50
- Logger.watcher(filename, 'watch');
51
-
52
- // Se estiver compilando, acumula na fila de pendentes
53
- if (this.isDeploying) {
54
- this.pendingFiles.add(filename);
55
- this.hasPendingChanges = true;
56
- return;
57
- }
58
-
59
- // Acumula arquivos modificados para o próximo build
60
- this.modifiedFiles.add(filename);
61
-
62
- clearTimeout(this.debounceTimer);
63
-
64
- this.debounceTimer = setTimeout(() => {
65
- const filesToCompile = [...this.modifiedFiles];
66
- this.modifiedFiles.clear();
67
- this.run(this.pendingFullBuild ? false : true, filesToCompile);
68
- this.pendingFullBuild = false;
69
- }, WATCHER_DEBOUNCE_MS);
70
- });
71
- }
72
-
73
- private async run(incremental = false, changedFiles?: string[]) {
74
- if (this.isDeploying) return;
75
- this.isDeploying = true;
76
-
77
- try {
78
- // Passa os arquivos específicos que foram modificados
79
- await this.deployCmd.execute(this.config, {
80
- watch: true,
81
- incremental,
82
- changedFiles
83
- });
84
- } catch (e) {
85
- // Error handled by command
86
- } finally {
87
- this.isDeploying = false;
88
-
89
- // Se houve mudanças durante a compilação, processa imediatamente
90
- if (this.hasPendingChanges) {
91
- const pending = [...this.pendingFiles];
92
- this.pendingFiles.clear();
93
- this.hasPendingChanges = false;
94
-
95
- Logger.watcher(`Processing ${pending.length} pending change(s)...`, 'warn');
96
-
97
- // Pequeno delay para garantir que os arquivos foram salvos completamente
98
- setTimeout(() => {
99
- this.run(true, pending);
100
- }, 100);
101
- }
102
- }
103
- }
104
-
105
- private isIgnored(filename: string): boolean {
106
- return filename.includes("target") ||
107
- filename.includes("build") ||
108
- filename.includes("node_modules") ||
109
- filename.split(/[/\\]/).some(part => part.startsWith("."));
110
- }
111
-
112
- private isResourceFile(filename: string): boolean {
113
- return filename.endsWith(".jsp") || filename.endsWith(".html") ||
114
- filename.endsWith(".css") || filename.endsWith(".js") ||
115
- filename.endsWith(".xml") || filename.endsWith(".properties");
116
- }
117
- }