@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,335 @@
1
+ /**
2
+ * Serviço de download avançado do Tomcat
3
+ *
4
+ * Features:
5
+ * - Retry automático com backoff exponencial
6
+ * - Resume de download (HTTP Range)
7
+ * - Progresso detalhado (MB/s, ETA)
8
+ * - Suporte a proxy
9
+ * - Modo headless/silencioso
10
+ */
11
+ import { Logger } from "../../logging";
12
+ import { ProgressBar, ThemedSpinner } from "../../utils/ProgressBar";
13
+ import { existsSync, createWriteStream, promises as fsPromises } from "fs";
14
+ import path from "path";
15
+ import type { TomcatProxyConfig } from "./types";
16
+
17
+ export interface DownloadOptions {
18
+ url: string;
19
+ destPath: string;
20
+ /** Tamanho total esperado (para validação) */
21
+ expectedSize?: number;
22
+ /** Checksum esperado (SHA512) */
23
+ expectedChecksum?: string;
24
+ /** Retry em caso de falha */
25
+ retries?: number;
26
+ /** Timeout em ms */
27
+ timeout?: number;
28
+ /** Configuração de proxy */
29
+ proxy?: TomcatProxyConfig;
30
+ /** Continuar download parcial */
31
+ resume?: boolean;
32
+ /** Modo silencioso (para CI/CD) */
33
+ silent?: boolean;
34
+ /** Título para progresso */
35
+ title?: string;
36
+ /** Callback de progresso */
37
+ onProgress?: (downloaded: number, total: number, speed: number, eta: number) => void;
38
+ }
39
+
40
+ export interface DownloadResult {
41
+ success: boolean;
42
+ destPath: string;
43
+ size: number;
44
+ duration: number;
45
+ speed: number; // MB/s
46
+ resumed: boolean;
47
+ attempts: number;
48
+ error?: string;
49
+ }
50
+
51
+ export class TomcatDownloadService {
52
+ private logger = Logger.getInstance();
53
+ private abortController?: AbortController;
54
+
55
+ /**
56
+ * Download com retry automático e resume
57
+ */
58
+ async download(options: DownloadOptions): Promise<DownloadResult> {
59
+ const {
60
+ url,
61
+ destPath,
62
+ retries = 3,
63
+ timeout = 300000, // 5 minutos
64
+ resume = true,
65
+ silent = false,
66
+ title = "Download",
67
+ proxy,
68
+ onProgress
69
+ } = options;
70
+
71
+ const startTime = Date.now();
72
+ let lastError: Error | undefined;
73
+ let attempts = 0;
74
+ let resumed = false;
75
+
76
+ // Verifica se pode resumir
77
+ let startByte = 0;
78
+ if (resume && existsSync(destPath)) {
79
+ const stats = await fsPromises.stat(destPath).catch(() => null);
80
+ if (stats && stats.size > 0) {
81
+ startByte = stats.size;
82
+ resumed = true;
83
+ if (!silent) {
84
+ this.logger.info(`Continuando download de ${this.formatBytes(startByte)}`);
85
+ }
86
+ }
87
+ }
88
+
89
+ for (let attempt = 1; attempt <= retries; attempt++) {
90
+ attempts = attempt;
91
+
92
+ try {
93
+ if (!silent && attempt > 1) {
94
+ this.logger.warn(`Tentativa ${attempt}/${retries}...`);
95
+ }
96
+
97
+ const result = await this.doDownload({
98
+ url,
99
+ destPath,
100
+ startByte,
101
+ timeout,
102
+ proxy,
103
+ silent,
104
+ title,
105
+ onProgress
106
+ });
107
+
108
+ const duration = (Date.now() - startTime) / 1000;
109
+ const sizeMB = result.size / 1024 / 1024;
110
+ const speed = duration > 0 ? sizeMB / duration : 0;
111
+
112
+ return {
113
+ success: true,
114
+ destPath,
115
+ size: result.size,
116
+ duration,
117
+ speed,
118
+ resumed,
119
+ attempts
120
+ };
121
+
122
+ } catch (error) {
123
+ lastError = error as Error;
124
+
125
+ if (!silent) {
126
+ this.logger.error(`Falha na tentativa ${attempt}: ${lastError.message}`);
127
+ }
128
+
129
+ if (attempt < retries) {
130
+ // Backoff exponencial: 1s, 2s, 4s
131
+ const delay = Math.min(1000 * Math.pow(2, attempt - 1), 10000);
132
+ if (!silent) {
133
+ this.logger.info(`Aguardando ${delay / 1000}s antes de retry...`);
134
+ }
135
+ await this.sleep(delay);
136
+ }
137
+ }
138
+ }
139
+
140
+ // Todas as tentativas falharam
141
+ return {
142
+ success: false,
143
+ destPath,
144
+ size: 0,
145
+ duration: (Date.now() - startTime) / 1000,
146
+ speed: 0,
147
+ resumed,
148
+ attempts,
149
+ error: lastError?.message || "Download falhou após todas as tentativas"
150
+ };
151
+ }
152
+
153
+ /**
154
+ * Download único com suporte a resume
155
+ */
156
+ private async doDownload(params: {
157
+ url: string;
158
+ destPath: string;
159
+ startByte: number;
160
+ timeout: number;
161
+ proxy?: TomcatProxyConfig;
162
+ silent: boolean;
163
+ title: string;
164
+ onProgress?: (downloaded: number, total: number, speed: number, eta: number) => void;
165
+ }): Promise<{ size: number }> {
166
+ const { url, destPath, startByte, timeout, proxy, silent, title, onProgress } = params;
167
+
168
+ // Prepara headers
169
+ const headers: Record<string, string> = {};
170
+ if (startByte > 0) {
171
+ headers["Range"] = `bytes=${startByte}-`;
172
+ }
173
+
174
+ // Configura proxy se necessário
175
+ let fetchUrl = url;
176
+ const fetchOptions: RequestInit = {
177
+ headers,
178
+ signal: this.createTimeoutSignal(timeout)
179
+ };
180
+
181
+ // Para proxy, usamos variáveis de ambiente ou config explícita
182
+ if (proxy?.http || proxy?.https) {
183
+ // Bun respeita HTTP_PROXY/HTTPS_PROXY automaticamente
184
+ if (proxy.http) {
185
+ process.env.HTTP_PROXY = proxy.http;
186
+ }
187
+ if (proxy.https) {
188
+ process.env.HTTPS_PROXY = proxy.https;
189
+ }
190
+ }
191
+
192
+ const response = await fetch(fetchUrl, fetchOptions);
193
+
194
+ if (!response.ok && response.status !== 206) { // 206 = Partial Content
195
+ throw new Error(`HTTP ${response.status}: ${response.statusText}`);
196
+ }
197
+
198
+ const contentLength = response.headers.get("content-length");
199
+ const totalSize = contentLength ? parseInt(contentLength) + startByte : startByte;
200
+ const isResumed = response.status === 206;
201
+
202
+ // Determina modo de escrita
203
+ const flags = isResumed || startByte > 0 ? "a" : "w";
204
+ const fileStream = createWriteStream(destPath, { flags });
205
+
206
+ const reader = response.body?.getReader();
207
+ if (!reader) {
208
+ throw new Error("Response body não disponível");
209
+ }
210
+
211
+ // Progress tracking
212
+ let downloaded = startByte;
213
+ const startTime = Date.now();
214
+ let lastProgressTime = startTime;
215
+ let lastDownloaded = startByte;
216
+
217
+ // Progress bar ou spinner
218
+ let progressBar: ProgressBar | null = null;
219
+ let spinnerStop: ((success: boolean) => void) | null = null;
220
+
221
+ if (!silent) {
222
+ if (totalSize > 0) {
223
+ progressBar = new ProgressBar({
224
+ title,
225
+ total: totalSize,
226
+ width: 30,
227
+ showSpeed: true,
228
+ showEta: true
229
+ });
230
+ } else {
231
+ const spinner = new ThemedSpinner();
232
+ spinnerStop = spinner.start(title, "dots", "download");
233
+ }
234
+ }
235
+
236
+ try {
237
+ while (true) {
238
+ const { done, value } = await reader.read();
239
+ if (done) break;
240
+
241
+ // Escreve chunk
242
+ fileStream.write(Buffer.from(value));
243
+ downloaded += value.length;
244
+
245
+ // Calcula velocidade e ETA
246
+ const now = Date.now();
247
+ const elapsed = (now - startTime) / 1000;
248
+ const speed = elapsed > 0 ? (downloaded / 1024 / 1024) / elapsed : 0;
249
+
250
+ // ETA
251
+ let eta = 0;
252
+ if (totalSize > 0 && speed > 0) {
253
+ const remaining = (totalSize - downloaded) / 1024 / 1024;
254
+ eta = remaining / speed;
255
+ }
256
+
257
+ // Atualiza progresso a cada 200ms ou quando completo
258
+ if (now - lastProgressTime > 200 || done) {
259
+ if (progressBar) {
260
+ progressBar.update(downloaded, speed, eta);
261
+ }
262
+
263
+ if (onProgress) {
264
+ onProgress(downloaded, totalSize, speed, eta);
265
+ }
266
+
267
+ lastProgressTime = now;
268
+ lastDownloaded = downloaded;
269
+ }
270
+ }
271
+
272
+ fileStream.end();
273
+
274
+ // Finaliza progresso
275
+ if (progressBar) {
276
+ progressBar.complete();
277
+ } else if (spinnerStop) {
278
+ spinnerStop(true);
279
+ }
280
+
281
+ // Verifica tamanho final
282
+ const stats = await fsPromises.stat(destPath);
283
+
284
+ return { size: stats.size };
285
+
286
+ } catch (error) {
287
+ fileStream.destroy();
288
+ if (progressBar) {
289
+ // não precisa fazer nada
290
+ } else if (spinnerStop) {
291
+ spinnerStop(false);
292
+ }
293
+ throw error;
294
+ }
295
+ }
296
+
297
+ /**
298
+ * Aborta download em andamento
299
+ */
300
+ abort(): void {
301
+ this.abortController?.abort();
302
+ }
303
+
304
+ /**
305
+ * Cria signal com timeout
306
+ */
307
+ private createTimeoutSignal(ms: number): AbortSignal {
308
+ this.abortController = new AbortController();
309
+ const timeoutId = setTimeout(() => this.abortController?.abort(), ms);
310
+
311
+ // Limpa timeout se signal for usado antes
312
+ const signal = this.abortController.signal;
313
+ signal.addEventListener("abort", () => clearTimeout(timeoutId));
314
+
315
+ return signal;
316
+ }
317
+
318
+ /**
319
+ * Formata bytes para string legível
320
+ */
321
+ private formatBytes(bytes: number): string {
322
+ if (bytes === 0) return "0 B";
323
+ const k = 1024;
324
+ const sizes = ["B", "KB", "MB", "GB"];
325
+ const i = Math.floor(Math.log(bytes) / Math.log(k));
326
+ return parseFloat((bytes / Math.pow(k, i)).toFixed(1)) + " " + sizes[i];
327
+ }
328
+
329
+ /**
330
+ * Sleep utilitário
331
+ */
332
+ private sleep(ms: number): Promise<void> {
333
+ return new Promise(resolve => setTimeout(resolve, ms));
334
+ }
335
+ }