@archznn/xavva 3.1.3 → 3.2.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.
Files changed (80) hide show
  1. package/README.md +221 -12
  2. package/package.json +3 -2
  3. package/src/commands/AuditCommand.ts +12 -10
  4. package/src/commands/BuildCommand.ts +9 -7
  5. package/src/commands/ChangelogCommand.ts +5 -5
  6. package/src/commands/CleanCommand.ts +242 -0
  7. package/src/commands/CompletionCommand.ts +7 -7
  8. package/src/commands/DbCommand.ts +33 -31
  9. package/src/commands/DeployCommand.ts +252 -229
  10. package/src/commands/DepsCommand.ts +174 -174
  11. package/src/commands/DockerCommand.ts +14 -14
  12. package/src/commands/DoctorCommand.ts +252 -239
  13. package/src/commands/EncodingCommand.ts +19 -19
  14. package/src/commands/HealthCommand.ts +7 -7
  15. package/src/commands/HelpCommand.ts +34 -14
  16. package/src/commands/HistoryCommand.ts +5 -5
  17. package/src/commands/HttpCommand.ts +6 -6
  18. package/src/commands/IdeCommand.ts +313 -0
  19. package/src/commands/InitCommand.ts +26 -25
  20. package/src/commands/LogsCommand.ts +8 -6
  21. package/src/commands/ProfilesCommand.ts +6 -6
  22. package/src/commands/RedoCommand.ts +2 -2
  23. package/src/commands/RunCommand.ts +64 -24
  24. package/src/commands/StartCommand.ts +9 -7
  25. package/src/commands/TestCommand.ts +4 -4
  26. package/src/commands/TomcatCommand.ts +219 -100
  27. package/src/config/versions.ts +111 -9
  28. package/src/di/container.ts +239 -105
  29. package/src/errors/ErrorHandler.ts +23 -19
  30. package/src/errors/errorMessages.ts +235 -0
  31. package/src/index.ts +11 -3
  32. package/src/logging/FileLogger.ts +235 -0
  33. package/src/logging/Logger.ts +545 -0
  34. package/src/logging/OperationLogger.ts +296 -0
  35. package/src/logging/ProgressLogger.ts +187 -0
  36. package/src/logging/TableLogger.ts +246 -0
  37. package/src/logging/colors.ts +167 -0
  38. package/src/logging/constants.ts +176 -0
  39. package/src/logging/formatters.ts +337 -0
  40. package/src/logging/index.ts +93 -0
  41. package/src/logging/types.ts +64 -0
  42. package/src/plugins/PluginManager.ts +325 -0
  43. package/src/plugins/types.ts +82 -0
  44. package/src/services/AuditService.ts +5 -3
  45. package/src/services/BuildService.ts +15 -17
  46. package/src/services/DashboardService.ts +14 -3
  47. package/src/services/DbService.ts +35 -34
  48. package/src/services/DependencyAnalyzerService.ts +18 -18
  49. package/src/services/DependencyCacheService.ts +303 -0
  50. package/src/services/DeployWatcher.ts +127 -23
  51. package/src/services/DockerService.ts +3 -3
  52. package/src/services/EmbeddedTomcatService.ts +13 -12
  53. package/src/services/FileWatcher.ts +15 -7
  54. package/src/services/HttpService.ts +5 -5
  55. package/src/services/LogAnalyzer.ts +26 -22
  56. package/src/services/PerformanceProfiler.ts +267 -0
  57. package/src/services/ProjectService.ts +3 -0
  58. package/src/services/TestService.ts +3 -3
  59. package/src/services/TomcatService.ts +46 -25
  60. package/src/services/tomcat/TomcatBackupManager.ts +330 -0
  61. package/src/services/tomcat/TomcatChecksumVerifier.ts +211 -0
  62. package/src/services/tomcat/TomcatCompatibilityChecker.ts +298 -0
  63. package/src/services/tomcat/TomcatDownloadCache.ts +250 -0
  64. package/src/services/tomcat/TomcatDownloadService.ts +335 -0
  65. package/src/services/tomcat/TomcatInstallerService.ts +474 -0
  66. package/src/services/tomcat/TomcatMirrorManager.ts +181 -0
  67. package/src/services/tomcat/index.ts +36 -0
  68. package/src/services/tomcat/types.ts +120 -0
  69. package/src/types/args.ts +68 -1
  70. package/src/types/configSchema.ts +174 -0
  71. package/src/utils/ChangelogGenerator.ts +11 -11
  72. package/src/utils/LoggerLevel.ts +44 -20
  73. package/src/utils/ProgressBar.ts +87 -46
  74. package/src/utils/argsParser.ts +260 -0
  75. package/src/utils/config.ts +340 -189
  76. package/src/utils/constants.ts +87 -9
  77. package/src/utils/dryRun.ts +192 -0
  78. package/src/utils/processManager.ts +23 -7
  79. package/src/utils/security.ts +293 -0
  80. package/src/utils/ui.ts +299 -428
@@ -0,0 +1,474 @@
1
+ /**
2
+ * Serviço integrado de instalação do Tomcat
3
+ *
4
+ * Combina todos os serviços especializados:
5
+ * - Download com retry, resume e progresso
6
+ * - Seleção automática de mirrors
7
+ * - Verificação de checksum SHA512
8
+ * - Cache de downloads
9
+ * - Backup e rollback
10
+ * - Validação de compatibilidade
11
+ * - Instalação paralela
12
+ */
13
+ import { Logger } from "../../logging";
14
+ import { existsSync, promises as fsPromises } from "fs";
15
+ import path from "path";
16
+ import os from "os";
17
+ import { spawn } from "child_process";
18
+
19
+ import { TomcatDownloadService } from "./TomcatDownloadService";
20
+ import { TomcatMirrorManager } from "./TomcatMirrorManager";
21
+ import { TomcatChecksumVerifier } from "./TomcatChecksumVerifier";
22
+ import { TomcatDownloadCache } from "./TomcatDownloadCache";
23
+ import { TomcatBackupManager } from "./TomcatBackupManager";
24
+ import { TomcatCompatibilityChecker } from "./TomcatCompatibilityChecker";
25
+
26
+ import type {
27
+ TomcatDownloadConfig,
28
+ TomcatInstallResult,
29
+ TomcatProxyConfig,
30
+ InstalledVersion,
31
+ BackupResult
32
+ } from "./types";
33
+
34
+ import { getTomcatArchiveName, getExtractCommand } from "../../utils/platform";
35
+
36
+ export interface InstallOptions {
37
+ version: string;
38
+ /** Mirror específico ou 'auto' */
39
+ mirror?: string;
40
+ /** Verificar checksum */
41
+ verifyChecksum?: boolean;
42
+ /** Usar cache */
43
+ useCache?: boolean;
44
+ /** Criar backup se já instalado */
45
+ backup?: boolean;
46
+ /** Modo silencioso */
47
+ silent?: boolean;
48
+ /** Timeout em segundos */
49
+ timeout?: number;
50
+ /** Retries */
51
+ retries?: number;
52
+ /** Proxy */
53
+ proxy?: TomcatProxyConfig;
54
+ /** Diretório de instalação (padrão: ~/.xavva/tomcat) */
55
+ installDir?: string;
56
+ /** Verificar compatibilidade com projeto */
57
+ projectPath?: string;
58
+ /** Forçar reinstalação */
59
+ force?: boolean;
60
+ }
61
+
62
+ export class TomcatInstallerService {
63
+ private logger = Logger.getInstance();
64
+ private downloadService: TomcatDownloadService;
65
+ private mirrorManager: TomcatMirrorManager;
66
+ private checksumVerifier: TomcatChecksumVerifier;
67
+ private downloadCache: TomcatDownloadCache;
68
+ private backupManager: TomcatBackupManager;
69
+ private compatibilityChecker: TomcatCompatibilityChecker;
70
+
71
+ constructor() {
72
+ this.downloadService = new TomcatDownloadService();
73
+ this.mirrorManager = new TomcatMirrorManager();
74
+ this.checksumVerifier = new TomcatChecksumVerifier();
75
+ this.downloadCache = new TomcatDownloadCache();
76
+ this.backupManager = new TomcatBackupManager();
77
+ this.compatibilityChecker = new TomcatCompatibilityChecker();
78
+ }
79
+
80
+ /**
81
+ * Instala uma versão do Tomcat com todas as features
82
+ */
83
+ async install(options: InstallOptions): Promise<TomcatInstallResult> {
84
+ const startTime = Date.now();
85
+ const {
86
+ version,
87
+ mirror = "auto",
88
+ verifyChecksum = true,
89
+ useCache = true,
90
+ backup = true,
91
+ silent = false,
92
+ timeout = 300,
93
+ retries = 3,
94
+ proxy,
95
+ installDir = path.join(os.homedir(), ".xavva", "tomcat"),
96
+ projectPath,
97
+ force = false
98
+ } = options;
99
+
100
+ const result: TomcatInstallResult = {
101
+ success: false,
102
+ version,
103
+ home: path.join(installDir, version),
104
+ downloaded: false,
105
+ cached: false,
106
+ resumed: false,
107
+ attempts: 0,
108
+ duration: 0,
109
+ speed: 0,
110
+ size: 0
111
+ };
112
+
113
+ if (!silent) {
114
+ this.logger.section(`Instalando Tomcat ${version}`);
115
+ }
116
+
117
+ // Verifica compatibilidade
118
+ if (projectPath) {
119
+ const compat = await this.compatibilityChecker.checkCompatibility(version, projectPath);
120
+ this.compatibilityChecker.printCompatibilityReport(compat);
121
+
122
+ if (!compat.compatible && !force) {
123
+ result.error = "Incompatibilidade detectada. Use --force para ignorar.";
124
+ return result;
125
+ }
126
+ }
127
+
128
+ // Verifica se já está instalado
129
+ if (existsSync(result.home)) {
130
+ if (!force) {
131
+ if (!silent) {
132
+ this.logger.info(`Tomcat ${version} já está instalado em ${result.home}`);
133
+ }
134
+ result.success = true;
135
+ return result;
136
+ }
137
+
138
+ // Cria backup antes de sobrescrever
139
+ if (backup) {
140
+ if (!silent) {
141
+ this.logger.info("Criando backup da versão atual...");
142
+ }
143
+ await this.backupManager.backup(version);
144
+ }
145
+
146
+ // Remove instalação atual
147
+ await fsPromises.rm(result.home, { recursive: true, force: true });
148
+ }
149
+
150
+ // Seleciona mirror
151
+ let selectedMirror = mirror === "auto"
152
+ ? await this.mirrorManager.selectBestMirror()
153
+ : this.mirrorManager.getMirrorByName(mirror) || await this.mirrorManager.selectBestMirror();
154
+
155
+ if (!selectedMirror) {
156
+ result.error = "Nenhum mirror disponível";
157
+ return result;
158
+ }
159
+
160
+ if (!silent) {
161
+ this.logger.info(`Mirror: ${selectedMirror.name} (${selectedMirror.region})`);
162
+ }
163
+
164
+ // Prepara URLs
165
+ const archiveName = getTomcatArchiveName(version);
166
+ const downloadUrl = this.mirrorManager.buildDownloadUrl(selectedMirror, version, archiveName);
167
+ const checksumUrl = this.mirrorManager.buildChecksumUrl(selectedMirror, version, archiveName);
168
+
169
+ // Verifica cache
170
+ const cachePath = this.downloadCache.getCachePath(downloadUrl);
171
+ let downloadPath: string;
172
+
173
+ if (useCache && this.downloadCache.has(downloadUrl)) {
174
+ if (!silent) {
175
+ this.logger.success("Usando arquivo em cache");
176
+ }
177
+ result.cached = true;
178
+ downloadPath = cachePath;
179
+ const stats = await fsPromises.stat(downloadPath);
180
+ result.size = stats.size;
181
+ } else {
182
+ // Download
183
+ downloadPath = path.join(installDir, archiveName);
184
+
185
+ // Garante diretório existe
186
+ await fsPromises.mkdir(installDir, { recursive: true });
187
+
188
+ if (!silent) {
189
+ this.logger.info(`Baixando ${archiveName}...`);
190
+ }
191
+
192
+ const downloadResult = await this.downloadService.download({
193
+ url: downloadUrl,
194
+ destPath: downloadPath,
195
+ retries,
196
+ timeout: timeout * 1000,
197
+ proxy,
198
+ resume: true,
199
+ silent,
200
+ title: `Tomcat ${version}`
201
+ });
202
+
203
+ if (!downloadResult.success) {
204
+ result.error = downloadResult.error;
205
+ return result;
206
+ }
207
+
208
+ result.downloaded = true;
209
+ result.resumed = downloadResult.resumed;
210
+ result.attempts = downloadResult.attempts;
211
+ result.size = downloadResult.size;
212
+ result.speed = downloadResult.speed;
213
+
214
+ // Verifica checksum
215
+ if (verifyChecksum) {
216
+ if (!silent) {
217
+ this.logger.info("Verificando integridade...");
218
+ }
219
+
220
+ const checksumResult = await this.checksumVerifier.verifyFromUrl(downloadPath, checksumUrl);
221
+ result.checksum = {
222
+ expected: checksumResult.expected,
223
+ actual: checksumResult.actual,
224
+ valid: checksumResult.valid
225
+ };
226
+
227
+ if (!checksumResult.valid && !force) {
228
+ result.error = "Checksum inválido - arquivo pode estar corrompido";
229
+ await fsPromises.unlink(downloadPath).catch(() => {});
230
+ return result;
231
+ }
232
+ }
233
+
234
+ // Adiciona ao cache
235
+ if (useCache) {
236
+ await this.downloadCache.set(downloadUrl, downloadPath);
237
+ }
238
+ }
239
+
240
+ // Extração
241
+ if (!silent) {
242
+ this.logger.info("Extraindo arquivos...");
243
+ }
244
+
245
+ try {
246
+ await this.extractArchive(downloadPath, installDir, silent);
247
+
248
+ // Renomeia diretório extraído
249
+ const extractedDir = path.join(installDir, `apache-tomcat-${version}`);
250
+ if (existsSync(extractedDir) && extractedDir !== result.home) {
251
+ await fsPromises.rename(extractedDir, result.home);
252
+ }
253
+
254
+ // Limpa arquivo de download (se não veio do cache)
255
+ if (!result.cached && existsSync(downloadPath)) {
256
+ await fsPromises.unlink(downloadPath).catch(() => {});
257
+ }
258
+
259
+ result.duration = (Date.now() - startTime) / 1000;
260
+ result.success = true;
261
+
262
+ if (!silent) {
263
+ this.logger.success(`Tomcat ${version} instalado com sucesso!`);
264
+ this.logger.info(`Local: ${result.home}`);
265
+ this.logger.info(`Tamanho: ${this.formatBytes(result.size)}`);
266
+ if (result.speed > 0) {
267
+ this.logger.info(`Velocidade: ${result.speed.toFixed(1)} MB/s`);
268
+ }
269
+ }
270
+
271
+ return result;
272
+
273
+ } catch (error) {
274
+ // Rollback em caso de falha
275
+ if (backup) {
276
+ const hasBackup = await this.backupManager.hasBackup(version);
277
+ if (hasBackup) {
278
+ if (!silent) {
279
+ this.logger.warn("Falha na extração. Restaurando backup...");
280
+ }
281
+ await this.backupManager.restore(version);
282
+ }
283
+ }
284
+
285
+ result.error = `Falha na extração: ${error}`;
286
+ return result;
287
+ }
288
+ }
289
+
290
+ /**
291
+ * Instala múltiplas versões em paralelo
292
+ */
293
+ async installParallel(versions: string[], options: Omit<InstallOptions, "version">): Promise<TomcatInstallResult[]> {
294
+ this.logger.section("Instalação Paralela");
295
+ this.logger.info(`Instalando ${versions.length} versões...`);
296
+
297
+ const promises = versions.map(version =>
298
+ this.install({ ...options, version })
299
+ );
300
+
301
+ const results = await Promise.all(promises);
302
+
303
+ const success = results.filter(r => r.success).length;
304
+ const failed = results.length - success;
305
+
306
+ this.logger.success(`Concluído: ${success} sucesso, ${failed} falha(s)`);
307
+
308
+ return results;
309
+ }
310
+
311
+ /**
312
+ * Desinstala uma versão
313
+ */
314
+ async uninstall(version: string, installDir?: string): Promise<boolean> {
315
+ const home = path.join(installDir || path.join(os.homedir(), ".xavva", "tomcat"), version);
316
+
317
+ if (!existsSync(home)) {
318
+ this.logger.warn(`Tomcat ${version} não está instalado`);
319
+ return false;
320
+ }
321
+
322
+ // Cria backup antes de desinstalar
323
+ await this.backupManager.backup(version);
324
+
325
+ await fsPromises.rm(home, { recursive: true, force: true });
326
+ this.logger.success(`Tomcat ${version} desinstalado`);
327
+
328
+ return true;
329
+ }
330
+
331
+ /**
332
+ * Lista versões instaladas
333
+ */
334
+ async listInstalled(installDir?: string): Promise<InstalledVersion[]> {
335
+ const baseDir = installDir || path.join(os.homedir(), ".xavva", "tomcat");
336
+
337
+ if (!existsSync(baseDir)) {
338
+ return [];
339
+ }
340
+
341
+ const versions: InstalledVersion[] = [];
342
+ const entries = await fsPromises.readdir(baseDir, { withFileTypes: true });
343
+
344
+ for (const entry of entries) {
345
+ if (!entry.isDirectory()) continue;
346
+
347
+ const home = path.join(baseDir, entry.name);
348
+ const catalinaScript = path.join(home, "bin", os.platform() === "win32" ? "catalina.bat" : "catalina.sh");
349
+
350
+ if (existsSync(catalinaScript)) {
351
+ const stats = await fsPromises.stat(home);
352
+ versions.push({
353
+ version: entry.name,
354
+ home,
355
+ size: await this.calculateDirSize(home),
356
+ installedAt: stats.birthtime,
357
+ lastUsed: stats.mtime
358
+ });
359
+ }
360
+ }
361
+
362
+ return versions.sort((a, b) => a.version.localeCompare(b.version));
363
+ }
364
+
365
+ /**
366
+ * Verifica se versão está instalada
367
+ */
368
+ isInstalled(version: string, installDir?: string): boolean {
369
+ const home = path.join(installDir || path.join(os.homedir(), ".xavva", "tomcat"), version);
370
+ const catalinaScript = path.join(home, "bin", os.platform() === "win32" ? "catalina.bat" : "catalina.sh");
371
+ return existsSync(catalinaScript);
372
+ }
373
+
374
+ /**
375
+ * Retorna estatísticas do cache
376
+ */
377
+ async getCacheStats(): Promise<{ size: number; files: number }> {
378
+ const state = await this.downloadCache.getState();
379
+ return {
380
+ size: state.size,
381
+ files: state.files.length
382
+ };
383
+ }
384
+
385
+ /**
386
+ * Limpa cache de downloads
387
+ */
388
+ async clearCache(): Promise<void> {
389
+ await this.downloadCache.clear();
390
+ }
391
+
392
+ /**
393
+ * Retorna backups disponíveis
394
+ */
395
+ async listBackups(version?: string): Promise<Array<{
396
+ version: string;
397
+ path: string;
398
+ size: number;
399
+ timestamp: Date;
400
+ }>> {
401
+ return this.backupManager.listBackups(version);
402
+ }
403
+
404
+ /**
405
+ * Restaura backup
406
+ */
407
+ async restoreBackup(version: string, backupPath?: string): Promise<boolean> {
408
+ const result = await this.backupManager.restore(version, backupPath);
409
+ return result.success;
410
+ }
411
+
412
+ /**
413
+ * Extrai arquivo
414
+ */
415
+ private async extractArchive(archivePath: string, destDir: string, silent: boolean): Promise<void> {
416
+ return new Promise((resolve, reject) => {
417
+ const cmd = getExtractCommand(archivePath, destDir);
418
+
419
+ if (!cmd) {
420
+ reject(new Error(`Formato não suportado: ${path.extname(archivePath)}`));
421
+ return;
422
+ }
423
+
424
+ const extractProcess = spawn(cmd[0], cmd.slice(1), {
425
+ stdio: silent ? "ignore" : "inherit"
426
+ });
427
+
428
+ extractProcess.on("close", (code) => {
429
+ if (code === 0) {
430
+ resolve();
431
+ } else {
432
+ reject(new Error(`Código ${code}`));
433
+ }
434
+ });
435
+
436
+ extractProcess.on("error", (err) => {
437
+ reject(err);
438
+ });
439
+ });
440
+ }
441
+
442
+ /**
443
+ * Calcula tamanho de diretório
444
+ */
445
+ private async calculateDirSize(dir: string): Promise<number> {
446
+ let size = 0;
447
+
448
+ const entries = await fsPromises.readdir(dir, { withFileTypes: true });
449
+
450
+ for (const entry of entries) {
451
+ const entryPath = path.join(dir, entry.name);
452
+
453
+ if (entry.isDirectory()) {
454
+ size += await this.calculateDirSize(entryPath);
455
+ } else {
456
+ const stats = await fsPromises.stat(entryPath);
457
+ size += stats.size;
458
+ }
459
+ }
460
+
461
+ return size;
462
+ }
463
+
464
+ /**
465
+ * Formata bytes
466
+ */
467
+ private formatBytes(bytes: number): string {
468
+ if (bytes === 0) return "0 B";
469
+ const k = 1024;
470
+ const sizes = ["B", "KB", "MB", "GB"];
471
+ const i = Math.floor(Math.log(bytes) / Math.log(k));
472
+ return parseFloat((bytes / Math.pow(k, i)).toFixed(1)) + " " + sizes[i];
473
+ }
474
+ }
@@ -0,0 +1,181 @@
1
+ /**
2
+ * Gerenciador de mirrors do Apache Tomcat
3
+ *
4
+ * Features:
5
+ * - Lista de mirrors oficiais do Apache
6
+ * - Teste automático de latência
7
+ * - Seleção do mirror mais rápido
8
+ * - Fallback automático
9
+ */
10
+ import { Logger } from "../../logging";
11
+ import type { TomcatMirror } from "./types";
12
+
13
+ // Mirrors oficiais do Apache Tomcat (priorizados por região)
14
+ const DEFAULT_MIRRORS: TomcatMirror[] = [
15
+ // Américas
16
+ { name: "Apache (USA)", url: "https://dlcdn.apache.org/tomcat", region: "US", priority: 1 },
17
+ { name: "Apache Archive (USA)", url: "https://archive.apache.org/dist/tomcat", region: "US", priority: 2 },
18
+ { name: "UFPR (Brazil)", url: "https://www.apache.dyn.ufpr.br/tomcat", region: "BR", priority: 1 },
19
+
20
+ // Europa
21
+ { name: "Apache (UK)", url: "https://downloads.apache.org/tomcat", region: "UK", priority: 1 },
22
+ { name: "XMission (USA)", url: "https://apache.xmission.com/tomcat", region: "US", priority: 3 },
23
+
24
+ // Ásia
25
+ { name: "Tsinghua (China)", url: "https://mirrors.tuna.tsinghua.edu.cn/apache/tomcat", region: "CN", priority: 2 },
26
+ { name: "NAVER (Korea)", url: "https://mirror.navercorp.com/apache/tomcat", region: "KR", priority: 2 },
27
+ { name: "YAMAGATA (Japan)", url: "https://ftp.yz.yamagata-u.ac.jp/pub/network/apache/tomcat", region: "JP", priority: 2 },
28
+
29
+ // Oceania
30
+ { name: "AARNet (Australia)", url: "https://mirror.aarnet.edu.au/pub/apache/tomcat", region: "AU", priority: 3 },
31
+ ];
32
+
33
+ // Tempo máximo de teste por mirror (ms)
34
+ const MIRROR_TEST_TIMEOUT = 5000;
35
+
36
+ export class TomcatMirrorManager {
37
+ private logger = Logger.getInstance();
38
+ private mirrors: TomcatMirror[];
39
+ private latencyCache: Map<string, number> = new Map();
40
+
41
+ constructor(customMirrors?: TomcatMirror[]) {
42
+ this.mirrors = customMirrors || DEFAULT_MIRRORS;
43
+ }
44
+
45
+ /**
46
+ * Retorna lista de mirrors disponíveis
47
+ */
48
+ getMirrors(): TomcatMirror[] {
49
+ return [...this.mirrors];
50
+ }
51
+
52
+ /**
53
+ * Testa latência de um mirror específico
54
+ */
55
+ async testMirror(mirror: TomcatMirror): Promise<number> {
56
+ const cacheKey = mirror.url;
57
+
58
+ // Verifica cache
59
+ if (this.latencyCache.has(cacheKey)) {
60
+ return this.latencyCache.get(cacheKey)!;
61
+ }
62
+
63
+ const startTime = Date.now();
64
+
65
+ try {
66
+ // Testa HEAD request em um arquivo pequeno (KEYS)
67
+ const testUrl = `${mirror.url}/KEYS`;
68
+ const controller = new AbortController();
69
+ const timeoutId = setTimeout(() => controller.abort(), MIRROR_TEST_TIMEOUT);
70
+
71
+ const response = await fetch(testUrl, {
72
+ method: "HEAD",
73
+ signal: controller.signal
74
+ });
75
+
76
+ clearTimeout(timeoutId);
77
+
78
+ if (response.ok) {
79
+ const latency = Date.now() - startTime;
80
+ this.latencyCache.set(cacheKey, latency);
81
+ return latency;
82
+ }
83
+
84
+ return Infinity; // Mirror não respondeu corretamente
85
+ } catch {
86
+ return Infinity; // Mirror inacessível
87
+ }
88
+ }
89
+
90
+ /**
91
+ * Testa todos os mirrors e retorna ordenados por velocidade
92
+ */
93
+ async testAllMirrors(): Promise<Array<TomcatMirror & { latency: number }>> {
94
+ this.logger.info("Testando mirrors disponíveis...");
95
+
96
+ const results = await Promise.all(
97
+ this.mirrors.map(async (mirror) => {
98
+ const latency = await this.testMirror(mirror);
99
+ return { ...mirror, latency };
100
+ })
101
+ );
102
+
103
+ // Ordena por latência (menor primeiro), excluindo inacessíveis
104
+ const sorted = results
105
+ .filter(r => r.latency !== Infinity)
106
+ .sort((a, b) => {
107
+ // Prioridade + latência
108
+ const scoreA = a.latency + (a.priority * 100);
109
+ const scoreB = b.latency + (b.priority * 100);
110
+ return scoreA - scoreB;
111
+ });
112
+
113
+ return sorted;
114
+ }
115
+
116
+ /**
117
+ * Seleciona o melhor mirror automaticamente
118
+ */
119
+ async selectBestMirror(): Promise<TomcatMirror | null> {
120
+ const tested = await this.testAllMirrors();
121
+
122
+ if (tested.length === 0) {
123
+ this.logger.warn("Nenhum mirror acessível encontrado");
124
+ return null;
125
+ }
126
+
127
+ const best = tested[0];
128
+ this.logger.success(`Mirror selecionado: ${best.name} (${best.latency}ms)`);
129
+
130
+ // Mostra top 3 para debug
131
+ if (tested.length > 1) {
132
+ this.logger.debug("Top mirrors:");
133
+ tested.slice(0, 3).forEach((m, i) => {
134
+ this.logger.debug(` ${i + 1}. ${m.name}: ${m.latency}ms`);
135
+ });
136
+ }
137
+
138
+ return best;
139
+ }
140
+
141
+ /**
142
+ * Constrói URL de download completo
143
+ */
144
+ buildDownloadUrl(mirror: TomcatMirror, version: string, filename: string): string {
145
+ // Determina versão major (ex: 10.1.52 -> v10.1)
146
+ const majorVersion = version.split(".").slice(0, 2).join(".");
147
+ return `${mirror.url}/tomcat-${majorVersion}/v${version}/bin/${filename}`;
148
+ }
149
+
150
+ /**
151
+ * Constrói URL do checksum
152
+ */
153
+ buildChecksumUrl(mirror: TomcatMirror, version: string, filename: string): string {
154
+ const downloadUrl = this.buildDownloadUrl(mirror, version, filename);
155
+ return `${downloadUrl}.sha512`;
156
+ }
157
+
158
+ /**
159
+ * Adiciona mirror customizado
160
+ */
161
+ addMirror(mirror: TomcatMirror): void {
162
+ this.mirrors.push(mirror);
163
+ this.latencyCache.delete(mirror.url);
164
+ }
165
+
166
+ /**
167
+ * Limpa cache de latência
168
+ */
169
+ clearCache(): void {
170
+ this.latencyCache.clear();
171
+ }
172
+
173
+ /**
174
+ * Retorna mirror por nome
175
+ */
176
+ getMirrorByName(name: string): TomcatMirror | undefined {
177
+ return this.mirrors.find(m =>
178
+ m.name.toLowerCase() === name.toLowerCase()
179
+ );
180
+ }
181
+ }
@@ -0,0 +1,36 @@
1
+ /**
2
+ * Serviços de instalação e gerenciamento do Tomcat
3
+ *
4
+ * Módulos:
5
+ * - TomcatDownloadService: Download com retry, resume e progresso
6
+ * - TomcatMirrorManager: Seleção automática de mirrors
7
+ * - TomcatChecksumVerifier: Validação SHA512
8
+ * - TomcatDownloadCache: Cache persistente
9
+ * - TomcatBackupManager: Backup e rollback
10
+ * - TomcatCompatibilityChecker: Validação de compatibilidade
11
+ * - TomcatInstallerService: Integração de todos os serviços
12
+ */
13
+
14
+ export { TomcatDownloadService } from "./TomcatDownloadService";
15
+ export type { DownloadOptions, DownloadResult } from "./TomcatDownloadService";
16
+ export { TomcatMirrorManager } from "./TomcatMirrorManager";
17
+ export { TomcatChecksumVerifier } from "./TomcatChecksumVerifier";
18
+ export type { ChecksumResult } from "./TomcatChecksumVerifier";
19
+ export { TomcatDownloadCache } from "./TomcatDownloadCache";
20
+ export { TomcatBackupManager } from "./TomcatBackupManager";
21
+ export { TomcatCompatibilityChecker } from "./TomcatCompatibilityChecker";
22
+ export { TomcatInstallerService } from "./TomcatInstallerService";
23
+
24
+ export type {
25
+ TomcatProxyConfig,
26
+ TomcatMirror,
27
+ TomcatDownloadConfig,
28
+ TomcatInstallResult,
29
+ InstalledVersion,
30
+ BackupResult,
31
+ RestoreResult,
32
+ CompatibilityResult,
33
+ CacheState,
34
+ CacheFile,
35
+ BackupConfig
36
+ } from "./types";