@archznn/xavva 3.1.2 → 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 +43 -14
- package/src/commands/DeployCommand.ts +252 -229
- package/src/commands/DepsCommand.ts +174 -174
- package/src/commands/DockerCommand.ts +35 -4
- package/src/commands/DoctorCommand.ts +252 -239
- package/src/commands/EncodingCommand.ts +26 -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 +27 -1
- 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 +25 -1
- package/src/commands/TomcatCommand.ts +232 -88
- 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 +20 -6
- 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,330 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Gerenciador de backup e rollback do Tomcat
|
|
3
|
+
*
|
|
4
|
+
* Features:
|
|
5
|
+
* - Backup automático antes de atualizações
|
|
6
|
+
* - Restore para versão anterior
|
|
7
|
+
* - Múltiplos backups
|
|
8
|
+
* - Limpeza de backups antigos
|
|
9
|
+
*/
|
|
10
|
+
import { Logger } from "../../logging";
|
|
11
|
+
import { existsSync, promises as fsPromises } from "fs";
|
|
12
|
+
import path from "path";
|
|
13
|
+
import os from "os";
|
|
14
|
+
import { createHash } from "crypto";
|
|
15
|
+
import type { BackupResult, RestoreResult, BackupConfig } from "./types";
|
|
16
|
+
|
|
17
|
+
// Configuração padrão
|
|
18
|
+
const DEFAULT_CONFIG: BackupConfig = {
|
|
19
|
+
enabled: true,
|
|
20
|
+
dir: path.join(os.homedir(), ".xavva", "backups"),
|
|
21
|
+
maxBackups: 3,
|
|
22
|
+
autoBackup: true
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
export class TomcatBackupManager {
|
|
26
|
+
private logger = Logger.getInstance();
|
|
27
|
+
private config: BackupConfig;
|
|
28
|
+
private tomcatBaseDir: string;
|
|
29
|
+
|
|
30
|
+
constructor(config?: Partial<BackupConfig>) {
|
|
31
|
+
this.config = { ...DEFAULT_CONFIG, ...config };
|
|
32
|
+
this.tomcatBaseDir = path.join(os.homedir(), ".xavva", "tomcat");
|
|
33
|
+
this.ensureBackupDir();
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Cria backup de uma versão instalada
|
|
38
|
+
*/
|
|
39
|
+
async backup(version: string): Promise<BackupResult> {
|
|
40
|
+
const sourceDir = path.join(this.tomcatBaseDir, version);
|
|
41
|
+
|
|
42
|
+
if (!existsSync(sourceDir)) {
|
|
43
|
+
throw new Error(`Versão ${version} não está instalada`);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
if (!this.config.enabled) {
|
|
47
|
+
this.logger.warn("Backups desabilitados");
|
|
48
|
+
return {
|
|
49
|
+
success: false,
|
|
50
|
+
backupPath: "",
|
|
51
|
+
size: 0,
|
|
52
|
+
timestamp: new Date()
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const timestamp = new Date();
|
|
57
|
+
const backupName = `tomcat-${version}-${this.formatTimestamp(timestamp)}`;
|
|
58
|
+
const backupPath = path.join(this.config.dir, backupName);
|
|
59
|
+
|
|
60
|
+
this.logger.info(`Criando backup de Tomcat ${version}...`);
|
|
61
|
+
|
|
62
|
+
try {
|
|
63
|
+
// Copia diretório
|
|
64
|
+
await this.copyDir(sourceDir, backupPath);
|
|
65
|
+
|
|
66
|
+
// Calcula tamanho
|
|
67
|
+
const size = await this.calculateDirSize(backupPath);
|
|
68
|
+
|
|
69
|
+
// Limpa backups antigos
|
|
70
|
+
await this.cleanupOldBackups(version);
|
|
71
|
+
|
|
72
|
+
this.logger.success(`Backup criado: ${backupName} (${this.formatBytes(size)})`);
|
|
73
|
+
|
|
74
|
+
return {
|
|
75
|
+
success: true,
|
|
76
|
+
backupPath,
|
|
77
|
+
size,
|
|
78
|
+
timestamp
|
|
79
|
+
};
|
|
80
|
+
} catch (error) {
|
|
81
|
+
this.logger.error(`Falha ao criar backup: ${error}`);
|
|
82
|
+
|
|
83
|
+
// Limpa backup parcial
|
|
84
|
+
if (existsSync(backupPath)) {
|
|
85
|
+
await fsPromises.rm(backupPath, { recursive: true, force: true });
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
return {
|
|
89
|
+
success: false,
|
|
90
|
+
backupPath: "",
|
|
91
|
+
size: 0,
|
|
92
|
+
timestamp
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Restaura backup de uma versão
|
|
99
|
+
*/
|
|
100
|
+
async restore(version: string, backupPath?: string): Promise<RestoreResult> {
|
|
101
|
+
const targetDir = path.join(this.tomcatBaseDir, version);
|
|
102
|
+
|
|
103
|
+
// Se não especificado, usa o backup mais recente
|
|
104
|
+
if (!backupPath) {
|
|
105
|
+
const backups = await this.listBackups(version);
|
|
106
|
+
if (backups.length === 0) {
|
|
107
|
+
throw new Error(`Nenhum backup encontrado para versão ${version}`);
|
|
108
|
+
}
|
|
109
|
+
backupPath = backups[0].path;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
if (!existsSync(backupPath)) {
|
|
113
|
+
throw new Error(`Backup não encontrado: ${backupPath}`);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
this.logger.info(`Restaurando backup de Tomcat ${version}...`);
|
|
117
|
+
|
|
118
|
+
// Guarda versão atual se existir
|
|
119
|
+
const currentBackup = existsSync(targetDir)
|
|
120
|
+
? await this.backup(version).catch(() => null)
|
|
121
|
+
: null;
|
|
122
|
+
|
|
123
|
+
try {
|
|
124
|
+
// Remove instalação atual
|
|
125
|
+
if (existsSync(targetDir)) {
|
|
126
|
+
await fsPromises.rm(targetDir, { recursive: true, force: true });
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
// Copia backup
|
|
130
|
+
await this.copyDir(backupPath, targetDir);
|
|
131
|
+
|
|
132
|
+
this.logger.success(`Tomcat ${version} restaurado com sucesso!`);
|
|
133
|
+
|
|
134
|
+
return {
|
|
135
|
+
success: true,
|
|
136
|
+
fromVersion: version,
|
|
137
|
+
toVersion: version,
|
|
138
|
+
backupPath
|
|
139
|
+
};
|
|
140
|
+
} catch (error) {
|
|
141
|
+
this.logger.error(`Falha ao restaurar: ${error}`);
|
|
142
|
+
|
|
143
|
+
// Tenta restaurar backup anterior
|
|
144
|
+
if (currentBackup?.success) {
|
|
145
|
+
this.logger.info("Tentando restaurar versão anterior...");
|
|
146
|
+
await this.copyDir(currentBackup.backupPath, targetDir);
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
return {
|
|
150
|
+
success: false,
|
|
151
|
+
fromVersion: version,
|
|
152
|
+
toVersion: version,
|
|
153
|
+
backupPath
|
|
154
|
+
};
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
/**
|
|
159
|
+
* Lista backups disponíveis
|
|
160
|
+
*/
|
|
161
|
+
async listBackups(version?: string): Promise<Array<{
|
|
162
|
+
version: string;
|
|
163
|
+
path: string;
|
|
164
|
+
size: number;
|
|
165
|
+
timestamp: Date;
|
|
166
|
+
}>> {
|
|
167
|
+
if (!existsSync(this.config.dir)) {
|
|
168
|
+
return [];
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
const entries = await fsPromises.readdir(this.config.dir, { withFileTypes: true });
|
|
172
|
+
const backups = [];
|
|
173
|
+
|
|
174
|
+
for (const entry of entries) {
|
|
175
|
+
if (!entry.isDirectory()) continue;
|
|
176
|
+
|
|
177
|
+
const match = entry.name.match(/^tomcat-(\d+\.\d+\.\d+)-(\d{8}-\d{6})$/);
|
|
178
|
+
if (!match) continue;
|
|
179
|
+
|
|
180
|
+
const [, backupVersion, timestamp] = match;
|
|
181
|
+
|
|
182
|
+
if (version && backupVersion !== version) continue;
|
|
183
|
+
|
|
184
|
+
const backupPath = path.join(this.config.dir, entry.name);
|
|
185
|
+
const size = await this.calculateDirSize(backupPath);
|
|
186
|
+
|
|
187
|
+
backups.push({
|
|
188
|
+
version: backupVersion,
|
|
189
|
+
path: backupPath,
|
|
190
|
+
size,
|
|
191
|
+
timestamp: this.parseTimestamp(timestamp)
|
|
192
|
+
});
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
// Ordena por data (mais recente primeiro)
|
|
196
|
+
backups.sort((a, b) => b.timestamp.getTime() - a.timestamp.getTime());
|
|
197
|
+
|
|
198
|
+
return backups;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
/**
|
|
202
|
+
* Remove todos os backups de uma versão
|
|
203
|
+
*/
|
|
204
|
+
async removeBackups(version: string): Promise<number> {
|
|
205
|
+
const backups = await this.listBackups(version);
|
|
206
|
+
let count = 0;
|
|
207
|
+
|
|
208
|
+
for (const backup of backups) {
|
|
209
|
+
await fsPromises.rm(backup.path, { recursive: true, force: true });
|
|
210
|
+
count++;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
return count;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
/**
|
|
217
|
+
* Limpa backups antigos mantendo apenas os mais recentes
|
|
218
|
+
*/
|
|
219
|
+
async cleanupOldBackups(version: string): Promise<void> {
|
|
220
|
+
const backups = await this.listBackups(version);
|
|
221
|
+
|
|
222
|
+
if (backups.length <= this.config.maxBackups) {
|
|
223
|
+
return;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
// Remove backups excedentes
|
|
227
|
+
const toRemove = backups.slice(this.config.maxBackups);
|
|
228
|
+
|
|
229
|
+
for (const backup of toRemove) {
|
|
230
|
+
this.logger.debug(`Removendo backup antigo: ${path.basename(backup.path)}`);
|
|
231
|
+
await fsPromises.rm(backup.path, { recursive: true, force: true });
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
/**
|
|
236
|
+
* Verifica se existe backup para versão
|
|
237
|
+
*/
|
|
238
|
+
async hasBackup(version: string): Promise<boolean> {
|
|
239
|
+
const backups = await this.listBackups(version);
|
|
240
|
+
return backups.length > 0;
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
/**
|
|
244
|
+
* Copia diretório recursivamente
|
|
245
|
+
*/
|
|
246
|
+
private async copyDir(source: string, target: string): Promise<void> {
|
|
247
|
+
await fsPromises.mkdir(target, { recursive: true });
|
|
248
|
+
|
|
249
|
+
const entries = await fsPromises.readdir(source, { withFileTypes: true });
|
|
250
|
+
|
|
251
|
+
for (const entry of entries) {
|
|
252
|
+
const sourcePath = path.join(source, entry.name);
|
|
253
|
+
const targetPath = path.join(target, entry.name);
|
|
254
|
+
|
|
255
|
+
if (entry.isDirectory()) {
|
|
256
|
+
await this.copyDir(sourcePath, targetPath);
|
|
257
|
+
} else {
|
|
258
|
+
await fsPromises.copyFile(sourcePath, targetPath);
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
/**
|
|
264
|
+
* Calcula tamanho de diretório
|
|
265
|
+
*/
|
|
266
|
+
private async calculateDirSize(dir: string): Promise<number> {
|
|
267
|
+
let size = 0;
|
|
268
|
+
|
|
269
|
+
const entries = await fsPromises.readdir(dir, { withFileTypes: true });
|
|
270
|
+
|
|
271
|
+
for (const entry of entries) {
|
|
272
|
+
const entryPath = path.join(dir, entry.name);
|
|
273
|
+
|
|
274
|
+
if (entry.isDirectory()) {
|
|
275
|
+
size += await this.calculateDirSize(entryPath);
|
|
276
|
+
} else {
|
|
277
|
+
const stats = await fsPromises.stat(entryPath);
|
|
278
|
+
size += stats.size;
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
return size;
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
/**
|
|
286
|
+
* Formata timestamp para nome de arquivo
|
|
287
|
+
*/
|
|
288
|
+
private formatTimestamp(date: Date): string {
|
|
289
|
+
const year = date.getFullYear();
|
|
290
|
+
const month = String(date.getMonth() + 1).padStart(2, "0");
|
|
291
|
+
const day = String(date.getDate()).padStart(2, "0");
|
|
292
|
+
const hour = String(date.getHours()).padStart(2, "0");
|
|
293
|
+
const min = String(date.getMinutes()).padStart(2, "0");
|
|
294
|
+
const sec = String(date.getSeconds()).padStart(2, "0");
|
|
295
|
+
return `${year}${month}${day}-${hour}${min}${sec}`;
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
/**
|
|
299
|
+
* Parse de timestamp
|
|
300
|
+
*/
|
|
301
|
+
private parseTimestamp(ts: string): Date {
|
|
302
|
+
const year = parseInt(ts.substring(0, 4));
|
|
303
|
+
const month = parseInt(ts.substring(4, 6)) - 1;
|
|
304
|
+
const day = parseInt(ts.substring(6, 8));
|
|
305
|
+
const hour = parseInt(ts.substring(9, 11));
|
|
306
|
+
const min = parseInt(ts.substring(11, 13));
|
|
307
|
+
const sec = parseInt(ts.substring(13, 15));
|
|
308
|
+
return new Date(year, month, day, hour, min, sec);
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
/**
|
|
312
|
+
* Formata bytes
|
|
313
|
+
*/
|
|
314
|
+
private formatBytes(bytes: number): string {
|
|
315
|
+
if (bytes === 0) return "0 B";
|
|
316
|
+
const k = 1024;
|
|
317
|
+
const sizes = ["B", "KB", "MB", "GB"];
|
|
318
|
+
const i = Math.floor(Math.log(bytes) / Math.log(k));
|
|
319
|
+
return parseFloat((bytes / Math.pow(k, i)).toFixed(1)) + " " + sizes[i];
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
/**
|
|
323
|
+
* Garante que diretório existe
|
|
324
|
+
*/
|
|
325
|
+
private ensureBackupDir(): void {
|
|
326
|
+
if (!existsSync(this.config.dir)) {
|
|
327
|
+
fsPromises.mkdir(this.config.dir, { recursive: true });
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
}
|
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Verificador de checksum SHA512 para arquivos Tomcat
|
|
3
|
+
*
|
|
4
|
+
* Features:
|
|
5
|
+
* - Download e validação de checksums oficiais
|
|
6
|
+
* - Verificação SHA512 local
|
|
7
|
+
* - Cache de checksums
|
|
8
|
+
*/
|
|
9
|
+
import { Logger } from "../../logging";
|
|
10
|
+
import { createHash } from "crypto";
|
|
11
|
+
import { existsSync, promises as fsPromises } from "fs";
|
|
12
|
+
import path from "path";
|
|
13
|
+
import os from "os";
|
|
14
|
+
|
|
15
|
+
export interface ChecksumResult {
|
|
16
|
+
valid: boolean;
|
|
17
|
+
expected: string;
|
|
18
|
+
actual: string;
|
|
19
|
+
algorithm: string;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export class TomcatChecksumVerifier {
|
|
23
|
+
private logger = Logger.getInstance();
|
|
24
|
+
private cacheDir: string;
|
|
25
|
+
private checksumCache: Map<string, string> = new Map();
|
|
26
|
+
|
|
27
|
+
constructor() {
|
|
28
|
+
this.cacheDir = path.join(os.homedir(), ".xavva", "checksums");
|
|
29
|
+
this.ensureCacheDir();
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Calcula SHA512 de um arquivo local
|
|
34
|
+
*/
|
|
35
|
+
async calculateHash(filePath: string): Promise<string> {
|
|
36
|
+
if (!existsSync(filePath)) {
|
|
37
|
+
throw new Error(`Arquivo não encontrado: ${filePath}`);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const hash = createHash("sha512");
|
|
41
|
+
const file = await fsPromises.open(filePath, "r");
|
|
42
|
+
|
|
43
|
+
try {
|
|
44
|
+
const buffer = Buffer.alloc(64 * 1024); // 64KB chunks
|
|
45
|
+
let bytesRead: number;
|
|
46
|
+
|
|
47
|
+
do {
|
|
48
|
+
bytesRead = (await file.read(buffer, 0, buffer.length, null)).bytesRead;
|
|
49
|
+
if (bytesRead > 0) {
|
|
50
|
+
hash.update(buffer.subarray(0, bytesRead));
|
|
51
|
+
}
|
|
52
|
+
} while (bytesRead === buffer.length);
|
|
53
|
+
|
|
54
|
+
return hash.digest("hex").toLowerCase();
|
|
55
|
+
} finally {
|
|
56
|
+
await file.close();
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Baixa checksum oficial do Apache
|
|
62
|
+
*/
|
|
63
|
+
async downloadChecksum(url: string): Promise<string | null> {
|
|
64
|
+
try {
|
|
65
|
+
// Verifica cache primeiro
|
|
66
|
+
const cacheKey = this.getCacheKey(url);
|
|
67
|
+
const cached = this.checksumCache.get(cacheKey);
|
|
68
|
+
if (cached) {
|
|
69
|
+
return cached;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
const response = await fetch(url, { method: "GET" });
|
|
73
|
+
|
|
74
|
+
if (!response.ok) {
|
|
75
|
+
this.logger.warn(`Não foi possível baixar checksum: HTTP ${response.status}`);
|
|
76
|
+
return null;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
const content = await response.text();
|
|
80
|
+
|
|
81
|
+
// Parse do formato: <hash> *<filename>
|
|
82
|
+
// Ex: a1b2c3... *apache-tomcat-10.1.52.zip
|
|
83
|
+
const match = content.match(/^([a-f0-9]+)\s+\*/mi);
|
|
84
|
+
if (match) {
|
|
85
|
+
const checksum = match[1].toLowerCase();
|
|
86
|
+
this.checksumCache.set(cacheKey, checksum);
|
|
87
|
+
return checksum;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// Formato alternativo: só o hash
|
|
91
|
+
const hashMatch = content.trim().match(/^([a-f0-9]{128})$/i);
|
|
92
|
+
if (hashMatch) {
|
|
93
|
+
const checksum = hashMatch[1].toLowerCase();
|
|
94
|
+
this.checksumCache.set(cacheKey, checksum);
|
|
95
|
+
return checksum;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
return null;
|
|
99
|
+
} catch (error) {
|
|
100
|
+
this.logger.warn(`Erro ao baixar checksum: ${error}`);
|
|
101
|
+
return null;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Verifica arquivo contra checksum esperado
|
|
107
|
+
*/
|
|
108
|
+
async verify(filePath: string, expectedChecksum: string): Promise<ChecksumResult> {
|
|
109
|
+
this.logger.info("Verificando integridade do arquivo...");
|
|
110
|
+
|
|
111
|
+
const actualChecksum = await this.calculateHash(filePath);
|
|
112
|
+
const normalized = expectedChecksum.toLowerCase().trim();
|
|
113
|
+
|
|
114
|
+
const result: ChecksumResult = {
|
|
115
|
+
valid: actualChecksum === normalized,
|
|
116
|
+
expected: normalized,
|
|
117
|
+
actual: actualChecksum,
|
|
118
|
+
algorithm: "SHA512"
|
|
119
|
+
};
|
|
120
|
+
|
|
121
|
+
if (result.valid) {
|
|
122
|
+
this.logger.success("Checksum válido!");
|
|
123
|
+
} else {
|
|
124
|
+
this.logger.error("Checksum inválido!");
|
|
125
|
+
this.logger.error(` Esperado: ${result.expected.substring(0, 16)}...`);
|
|
126
|
+
this.logger.error(` Obtido: ${result.actual.substring(0, 16)}...`);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
return result;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* Verifica arquivo baixando checksum automaticamente
|
|
134
|
+
*/
|
|
135
|
+
async verifyFromUrl(filePath: string, checksumUrl: string): Promise<ChecksumResult> {
|
|
136
|
+
const expected = await this.downloadChecksum(checksumUrl);
|
|
137
|
+
|
|
138
|
+
if (!expected) {
|
|
139
|
+
this.logger.warn("Não foi possível obter checksum oficial");
|
|
140
|
+
return {
|
|
141
|
+
valid: false,
|
|
142
|
+
expected: "",
|
|
143
|
+
actual: "",
|
|
144
|
+
algorithm: "SHA512"
|
|
145
|
+
};
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
return this.verify(filePath, expected);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
* Salva checksum em cache local
|
|
153
|
+
*/
|
|
154
|
+
async saveToCache(url: string, checksum: string): Promise<void> {
|
|
155
|
+
const cacheFile = path.join(this.cacheDir, this.getCacheKey(url) + ".sha512");
|
|
156
|
+
await fsPromises.writeFile(cacheFile, checksum, "utf-8");
|
|
157
|
+
this.checksumCache.set(this.getCacheKey(url), checksum);
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
/**
|
|
161
|
+
* Carrega checksum do cache local
|
|
162
|
+
*/
|
|
163
|
+
async loadFromCache(url: string): Promise<string | null> {
|
|
164
|
+
const cacheKey = this.getCacheKey(url);
|
|
165
|
+
|
|
166
|
+
if (this.checksumCache.has(cacheKey)) {
|
|
167
|
+
return this.checksumCache.get(cacheKey)!;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
const cacheFile = path.join(this.cacheDir, cacheKey + ".sha512");
|
|
171
|
+
|
|
172
|
+
if (existsSync(cacheFile)) {
|
|
173
|
+
const checksum = await fsPromises.readFile(cacheFile, "utf-8");
|
|
174
|
+
this.checksumCache.set(cacheKey, checksum.trim());
|
|
175
|
+
return checksum.trim();
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
return null;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
/**
|
|
182
|
+
* Gera chave de cache para URL
|
|
183
|
+
*/
|
|
184
|
+
private getCacheKey(url: string): string {
|
|
185
|
+
// Hash da URL para nome de arquivo seguro
|
|
186
|
+
return createHash("md5").update(url).digest("hex");
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
/**
|
|
190
|
+
* Garante que diretório de cache existe
|
|
191
|
+
*/
|
|
192
|
+
private ensureCacheDir(): void {
|
|
193
|
+
if (!existsSync(this.cacheDir)) {
|
|
194
|
+
fsPromises.mkdir(this.cacheDir, { recursive: true });
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
/**
|
|
199
|
+
* Limpa cache de checksums
|
|
200
|
+
*/
|
|
201
|
+
async clearCache(): Promise<void> {
|
|
202
|
+
this.checksumCache.clear();
|
|
203
|
+
|
|
204
|
+
if (existsSync(this.cacheDir)) {
|
|
205
|
+
const files = await fsPromises.readdir(this.cacheDir);
|
|
206
|
+
for (const file of files) {
|
|
207
|
+
await fsPromises.unlink(path.join(this.cacheDir, file));
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
}
|