@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.
- package/README.md +221 -12
- package/package.json +3 -2
- package/src/commands/AuditCommand.ts +12 -10
- package/src/commands/BuildCommand.ts +9 -7
- package/src/commands/ChangelogCommand.ts +5 -5
- package/src/commands/CleanCommand.ts +242 -0
- package/src/commands/CompletionCommand.ts +7 -7
- package/src/commands/DbCommand.ts +33 -31
- package/src/commands/DeployCommand.ts +252 -229
- package/src/commands/DepsCommand.ts +174 -174
- package/src/commands/DockerCommand.ts +14 -14
- package/src/commands/DoctorCommand.ts +252 -239
- package/src/commands/EncodingCommand.ts +19 -19
- package/src/commands/HealthCommand.ts +7 -7
- package/src/commands/HelpCommand.ts +34 -14
- package/src/commands/HistoryCommand.ts +5 -5
- package/src/commands/HttpCommand.ts +6 -6
- package/src/commands/IdeCommand.ts +313 -0
- package/src/commands/InitCommand.ts +26 -25
- package/src/commands/LogsCommand.ts +8 -6
- package/src/commands/ProfilesCommand.ts +6 -6
- package/src/commands/RedoCommand.ts +2 -2
- package/src/commands/RunCommand.ts +64 -24
- package/src/commands/StartCommand.ts +9 -7
- package/src/commands/TestCommand.ts +4 -4
- package/src/commands/TomcatCommand.ts +219 -100
- package/src/config/versions.ts +111 -9
- package/src/di/container.ts +239 -105
- package/src/errors/ErrorHandler.ts +23 -19
- package/src/errors/errorMessages.ts +235 -0
- package/src/index.ts +11 -3
- package/src/logging/FileLogger.ts +235 -0
- package/src/logging/Logger.ts +545 -0
- package/src/logging/OperationLogger.ts +296 -0
- package/src/logging/ProgressLogger.ts +187 -0
- package/src/logging/TableLogger.ts +246 -0
- package/src/logging/colors.ts +167 -0
- package/src/logging/constants.ts +176 -0
- package/src/logging/formatters.ts +337 -0
- package/src/logging/index.ts +93 -0
- package/src/logging/types.ts +64 -0
- package/src/plugins/PluginManager.ts +325 -0
- package/src/plugins/types.ts +82 -0
- package/src/services/AuditService.ts +5 -3
- package/src/services/BuildService.ts +15 -17
- package/src/services/DashboardService.ts +14 -3
- package/src/services/DbService.ts +35 -34
- package/src/services/DependencyAnalyzerService.ts +18 -18
- package/src/services/DependencyCacheService.ts +303 -0
- package/src/services/DeployWatcher.ts +127 -23
- package/src/services/DockerService.ts +3 -3
- package/src/services/EmbeddedTomcatService.ts +13 -12
- package/src/services/FileWatcher.ts +15 -7
- package/src/services/HttpService.ts +5 -5
- package/src/services/LogAnalyzer.ts +26 -22
- package/src/services/PerformanceProfiler.ts +267 -0
- package/src/services/ProjectService.ts +3 -0
- package/src/services/TestService.ts +3 -3
- package/src/services/TomcatService.ts +46 -25
- package/src/services/tomcat/TomcatBackupManager.ts +330 -0
- package/src/services/tomcat/TomcatChecksumVerifier.ts +211 -0
- package/src/services/tomcat/TomcatCompatibilityChecker.ts +298 -0
- package/src/services/tomcat/TomcatDownloadCache.ts +250 -0
- package/src/services/tomcat/TomcatDownloadService.ts +335 -0
- package/src/services/tomcat/TomcatInstallerService.ts +474 -0
- package/src/services/tomcat/TomcatMirrorManager.ts +181 -0
- package/src/services/tomcat/index.ts +36 -0
- package/src/services/tomcat/types.ts +120 -0
- package/src/types/args.ts +68 -1
- package/src/types/configSchema.ts +174 -0
- package/src/utils/ChangelogGenerator.ts +11 -11
- package/src/utils/LoggerLevel.ts +44 -20
- package/src/utils/ProgressBar.ts +87 -46
- package/src/utils/argsParser.ts +260 -0
- package/src/utils/config.ts +340 -189
- package/src/utils/constants.ts +87 -9
- package/src/utils/dryRun.ts +192 -0
- package/src/utils/processManager.ts +23 -7
- package/src/utils/security.ts +293 -0
- 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";
|