@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.
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 +43 -14
  9. package/src/commands/DeployCommand.ts +252 -229
  10. package/src/commands/DepsCommand.ts +174 -174
  11. package/src/commands/DockerCommand.ts +35 -4
  12. package/src/commands/DoctorCommand.ts +252 -239
  13. package/src/commands/EncodingCommand.ts +26 -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 +27 -1
  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 +25 -1
  26. package/src/commands/TomcatCommand.ts +232 -88
  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 +20 -6
  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,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
+ }