@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,298 @@
1
+ /**
2
+ * Verificador de compatibilidade Tomcat
3
+ *
4
+ * Features:
5
+ * - Detecta versão Servlet/JSP suportada
6
+ * - Valida compatibilidade com projeto
7
+ * - Detecta uso de APIs deprecated
8
+ * - Sugere versões compatíveis
9
+ */
10
+ import { Logger } from "../../logging";
11
+ import { existsSync, promises as fsPromises } from "fs";
12
+ import path from "path";
13
+ import type { CompatibilityResult } from "./types";
14
+
15
+ // Mapeamento de versões Tomcat para Servlet/JSP
16
+ const TOMCAT_VERSIONS: Record<string, { servlet: string; jsp: string; java: string }> = {
17
+ "11.0": { servlet: "6.1", jsp: "4.0", java: "21" },
18
+ "10.1": { servlet: "6.0", jsp: "3.1", java: "11" },
19
+ "10.0": { servlet: "5.0", jsp: "3.0", java: "8" },
20
+ "9.0": { servlet: "4.0", jsp: "2.3", java: "8" },
21
+ "8.5": { servlet: "3.1", jsp: "2.3", java: "7" },
22
+ "8.0": { servlet: "3.1", jsp: "2.3", java: "7" },
23
+ "7.0": { servlet: "3.0", jsp: "2.2", java: "6" },
24
+ };
25
+
26
+ // APIs deprecated por versão
27
+ const DEPRECATED_APIS: Record<string, string[]> = {
28
+ "9.0": ["javax.servlet.* (migrar para jakarta.servlet.* no Tomcat 10+)"],
29
+ "10.0": ["javax.servlet.* (usar jakarta.servlet.*)"],
30
+ "10.1": [],
31
+ "11.0": []
32
+ };
33
+
34
+ export class TomcatCompatibilityChecker {
35
+ private logger = Logger.getInstance();
36
+
37
+ /**
38
+ * Extrai versão major do Tomcat (ex: 10.1.52 -> 10.1)
39
+ */
40
+ private getMajorVersion(version: string): string {
41
+ const parts = version.split(".");
42
+ return parts.slice(0, 2).join(".");
43
+ }
44
+
45
+ /**
46
+ * Obtém informações de uma versão Tomcat
47
+ */
48
+ getVersionInfo(version: string): { servlet: string; jsp: string; java: string } | null {
49
+ const major = this.getMajorVersion(version);
50
+ return TOMCAT_VERSIONS[major] || null;
51
+ }
52
+
53
+ /**
54
+ * Verifica compatibilidade do Tomcat com projeto
55
+ */
56
+ async checkCompatibility(
57
+ tomcatVersion: string,
58
+ projectPath: string
59
+ ): Promise<CompatibilityResult> {
60
+ const majorVersion = this.getMajorVersion(tomcatVersion);
61
+ const versionInfo = this.getVersionInfo(tomcatVersion);
62
+
63
+ const warnings: string[] = [];
64
+ const errors: string[] = [];
65
+
66
+ if (!versionInfo) {
67
+ errors.push(`Versão ${tomcatVersion} não reconhecida`);
68
+ return {
69
+ compatible: false,
70
+ tomcatVersion,
71
+ servletVersion: "",
72
+ jspVersion: "",
73
+ warnings,
74
+ errors
75
+ };
76
+ }
77
+
78
+ // Verifica pom.xml ou build.gradle
79
+ const pomPath = path.join(projectPath, "pom.xml");
80
+ const gradlePath = path.join(projectPath, "build.gradle");
81
+
82
+ if (existsSync(pomPath)) {
83
+ await this.checkMavenCompatibility(pomPath, majorVersion, warnings, errors);
84
+ } else if (existsSync(gradlePath)) {
85
+ await this.checkGradleCompatibility(gradlePath, majorVersion, warnings, errors);
86
+ }
87
+
88
+ // Verifica uso de APIs deprecated
89
+ const deprecated = DEPRECATED_APIS[majorVersion] || [];
90
+ if (deprecated.length > 0) {
91
+ warnings.push(...deprecated);
92
+ }
93
+
94
+ // Verifica Java version
95
+ const javaVersion = process.version; // Node.js version, mas usamos para lógica
96
+ // Nota: Em ambiente real, verificaríamos JAVA_HOME
97
+
98
+ const compatible = errors.length === 0;
99
+
100
+ return {
101
+ compatible,
102
+ tomcatVersion,
103
+ servletVersion: versionInfo.servlet,
104
+ jspVersion: versionInfo.jsp,
105
+ warnings,
106
+ errors
107
+ };
108
+ }
109
+
110
+ /**
111
+ * Verifica compatibilidade com Maven
112
+ */
113
+ private async checkMavenCompatibility(
114
+ pomPath: string,
115
+ tomcatMajor: string,
116
+ warnings: string[],
117
+ errors: string[]
118
+ ): Promise<void> {
119
+ try {
120
+ const content = await fsPromises.readFile(pomPath, "utf-8");
121
+
122
+ // Verifica servlet-api dependency
123
+ const servletMatch = content.match(/<artifactId>servlet-api<\/artifactId>\s*<version>([^<]+)<\/version>/);
124
+ if (servletMatch) {
125
+ const servletVersion = servletMatch[1];
126
+ const expectedServlet = TOMCAT_VERSIONS[tomcatMajor]?.servlet;
127
+
128
+ if (expectedServlet && !servletVersion.startsWith(expectedServlet.split(".")[0])) {
129
+ warnings.push(`pom.xml usa servlet-api ${servletVersion}, Tomcat ${tomcatMajor} requer Servlet ${expectedServlet}`);
130
+ }
131
+ }
132
+
133
+ // Verifica javax vs jakarta
134
+ if (tomcatMajor >= "10.0") {
135
+ if (content.includes("javax.servlet") && !content.includes("jakarta.servlet")) {
136
+ errors.push(`Tomcat ${tomcatMajor} requer Jakarta EE (jakarta.servlet.*), mas pom.xml usa javax.servlet`);
137
+ }
138
+ }
139
+
140
+ // Verifica versão do plugin tomcat
141
+ const tomcatPluginMatch = content.match(/<artifactId>tomcat[\w-]*plugin<\/artifactId>.*?<version>([^<]+)<\/version>/s);
142
+ if (tomcatPluginMatch) {
143
+ const pluginVersion = tomcatPluginMatch[1];
144
+ const pluginMajor = this.getMajorVersion(pluginVersion);
145
+
146
+ if (pluginMajor !== tomcatMajor) {
147
+ warnings.push(`Plugin Tomcat ${pluginVersion} pode não ser compatível com Tomcat ${tomcatMajor}`);
148
+ }
149
+ }
150
+ } catch (error) {
151
+ warnings.push("Não foi possível analisar pom.xml");
152
+ }
153
+ }
154
+
155
+ /**
156
+ * Verifica compatibilidade com Gradle
157
+ */
158
+ private async checkGradleCompatibility(
159
+ gradlePath: string,
160
+ tomcatMajor: string,
161
+ warnings: string[],
162
+ errors: string[]
163
+ ): Promise<void> {
164
+ try {
165
+ const content = await fsPromises.readFile(gradlePath, "utf-8");
166
+
167
+ // Verifica javax vs jakarta
168
+ if (tomcatMajor >= "10.0") {
169
+ if (content.includes("javax.servlet") && !content.includes("jakarta.servlet")) {
170
+ errors.push(`Tomcat ${tomcatMajor} requer Jakarta EE (jakarta.servlet.*), mas build.gradle usa javax.servlet`);
171
+ }
172
+ }
173
+
174
+ // Verifica versões de dependências
175
+ const servletMatch = content.match(/servlet-api['"]\s*:\s*['"]([^'"]+)['"]/);
176
+ if (servletMatch) {
177
+ const servletVersion = servletMatch[1];
178
+ const expectedServlet = TOMCAT_VERSIONS[tomcatMajor]?.servlet;
179
+
180
+ if (expectedServlet && !servletVersion.startsWith(expectedServlet.split(".")[0])) {
181
+ warnings.push(`build.gradle usa servlet-api ${servletVersion}, Tomcat ${tomcatMajor} requer Servlet ${expectedServlet}`);
182
+ }
183
+ }
184
+ } catch (error) {
185
+ warnings.push("Não foi possível analisar build.gradle");
186
+ }
187
+ }
188
+
189
+ /**
190
+ * Sugere versão Tomcat compatível com projeto
191
+ */
192
+ async suggestVersion(projectPath: string): Promise<string | null> {
193
+ const pomPath = path.join(projectPath, "pom.xml");
194
+ const gradlePath = path.join(projectPath, "build.gradle");
195
+
196
+ let usesJakarta = false;
197
+ let servletVersion: string | null = null;
198
+
199
+ try {
200
+ if (existsSync(pomPath)) {
201
+ const content = await fsPromises.readFile(pomPath, "utf-8");
202
+ usesJakarta = content.includes("jakarta.servlet");
203
+
204
+ const match = content.match(/<artifactId>servlet-api<\/artifactId>\s*<version>([^<]+)<\/version>/);
205
+ if (match) servletVersion = match[1];
206
+ } else if (existsSync(gradlePath)) {
207
+ const content = await fsPromises.readFile(gradlePath, "utf-8");
208
+ usesJakarta = content.includes("jakarta.servlet");
209
+
210
+ const match = content.match(/servlet-api['"]\s*:\s*['"]([^'"]+)['"]/);
211
+ if (match) servletVersion = match[1];
212
+ }
213
+ } catch {
214
+ return null;
215
+ }
216
+
217
+ // Se usa Jakarta EE, precisa Tomcat 10+
218
+ if (usesJakarta) {
219
+ return "10.1.52"; // Versão estável mais recente
220
+ }
221
+
222
+ // Se especificou versão do servlet
223
+ if (servletVersion) {
224
+ const major = parseInt(servletVersion.split(".")[0]);
225
+
226
+ if (major >= 6) return "10.1.52";
227
+ if (major >= 5) return "10.0.27";
228
+ if (major >= 4) return "9.0.96";
229
+ if (major >= 3) return "8.5.100";
230
+ }
231
+
232
+ // Padrão: Tomcat 9 (mais compatível)
233
+ return "9.0.96";
234
+ }
235
+
236
+ /**
237
+ * Retorna informações sobre migração entre versões
238
+ */
239
+ getMigrationGuide(fromVersion: string, toVersion: string): string[] {
240
+ const guides: string[] = [];
241
+ const fromMajor = this.getMajorVersion(fromVersion);
242
+ const toMajor = this.getMajorVersion(toVersion);
243
+
244
+ if (fromMajor === toMajor) {
245
+ guides.push("Mesma versão major - migração direta sem mudanças");
246
+ return guides;
247
+ }
248
+
249
+ // Migração 9.x -> 10.x
250
+ if (fromMajor.startsWith("9") && toMajor.startsWith("10")) {
251
+ guides.push("⚠️ MIGRAÇÃO IMPORTANTE: Tomcat 10+ usa Jakarta EE");
252
+ guides.push(" - javax.servlet.* → jakarta.servlet.*");
253
+ guides.push(" - javax.servlet.jsp.* → jakarta.servlet.jsp.*");
254
+ guides.push(" - Atualize dependências no pom.xml/build.gradle");
255
+ guides.push(" - Use ferramenta de migração: https://tomcat.apache.org/migration.html");
256
+ }
257
+
258
+ // Migração 8.x -> 9.x
259
+ if (fromMajor.startsWith("8") && toMajor.startsWith("9")) {
260
+ guides.push("Tomcat 9 requer Servlet 4.0 - verifique compatibilidade");
261
+ }
262
+
263
+ // Migração 10.x -> 11.x
264
+ if (fromMajor.startsWith("10") && toMajor.startsWith("11")) {
265
+ guides.push("Tomcat 11 requer Java 21+");
266
+ guides.push("Verifique se seu projeto é compatível com Java 21");
267
+ }
268
+
269
+ return guides;
270
+ }
271
+
272
+ /**
273
+ * Exibe relatório de compatibilidade
274
+ */
275
+ printCompatibilityReport(result: CompatibilityResult): void {
276
+ this.logger.section("Verificação de Compatibilidade");
277
+
278
+ this.logger.info(`Tomcat: ${result.tomcatVersion}`);
279
+ this.logger.info(`Servlet: ${result.servletVersion}`);
280
+ this.logger.info(`JSP: ${result.jspVersion}`);
281
+
282
+ if (result.compatible) {
283
+ this.logger.success("✓ Compatível");
284
+ } else {
285
+ this.logger.error("✗ Incompatível");
286
+ }
287
+
288
+ if (result.warnings.length > 0) {
289
+ this.logger.warn("\nAvisos:");
290
+ result.warnings.forEach(w => this.logger.warn(` • ${w}`));
291
+ }
292
+
293
+ if (result.errors.length > 0) {
294
+ this.logger.error("\nErros:");
295
+ result.errors.forEach(e => this.logger.error(` • ${e}`));
296
+ }
297
+ }
298
+ }
@@ -0,0 +1,250 @@
1
+ /**
2
+ * Sistema de cache de downloads Tomcat
3
+ *
4
+ * Features:
5
+ * - Cache persistente entre projetos
6
+ * - Identificação por checksum
7
+ * - Limpeza automática de arquivos antigos
8
+ * - Limite de tamanho do cache
9
+ */
10
+ import { Logger } from "../../logging";
11
+ import { existsSync, promises as fsPromises, statSync } from "fs";
12
+ import path from "path";
13
+ import os from "os";
14
+ import { createHash } from "crypto";
15
+ import type { CacheState, CacheFile } from "./types";
16
+
17
+ // Tamanho máximo padrão do cache: 2GB
18
+ const DEFAULT_MAX_CACHE_SIZE = 2 * 1024 * 1024 * 1024;
19
+ // Idade máxima: 30 dias
20
+ const DEFAULT_MAX_AGE_DAYS = 30;
21
+
22
+ export class TomcatDownloadCache {
23
+ private logger = Logger.getInstance();
24
+ private cacheDir: string;
25
+ private maxSize: number;
26
+ private maxAgeDays: number;
27
+
28
+ constructor(options?: {
29
+ cacheDir?: string;
30
+ maxSize?: number;
31
+ maxAgeDays?: number;
32
+ }) {
33
+ this.cacheDir = options?.cacheDir || path.join(os.homedir(), ".xavva", "downloads");
34
+ this.maxSize = options?.maxSize || DEFAULT_MAX_CACHE_SIZE;
35
+ this.maxAgeDays = options?.maxAgeDays || DEFAULT_MAX_AGE_DAYS;
36
+ this.ensureCacheDir();
37
+ }
38
+
39
+ /**
40
+ * Retorna diretório do cache
41
+ */
42
+ getCacheDir(): string {
43
+ return this.cacheDir;
44
+ }
45
+
46
+ /**
47
+ * Gera chave de cache baseada na URL
48
+ */
49
+ getCacheKey(url: string): string {
50
+ const urlHash = createHash("md5").update(url).digest("hex");
51
+ const filename = path.basename(url);
52
+ return `${urlHash}_${filename}`;
53
+ }
54
+
55
+ /**
56
+ * Retorna caminho completo do arquivo em cache
57
+ */
58
+ getCachePath(url: string): string {
59
+ return path.join(this.cacheDir, this.getCacheKey(url));
60
+ }
61
+
62
+ /**
63
+ * Verifica se arquivo está em cache
64
+ */
65
+ has(url: string): boolean {
66
+ return existsSync(this.getCachePath(url));
67
+ }
68
+
69
+ /**
70
+ * Busca arquivo em cache
71
+ */
72
+ async get(url: string): Promise<{ path: string; size: number } | null> {
73
+ const cachePath = this.getCachePath(url);
74
+
75
+ if (!existsSync(cachePath)) {
76
+ return null;
77
+ }
78
+
79
+ const stats = await fsPromises.stat(cachePath);
80
+
81
+ // Verifica idade
82
+ const age = Date.now() - stats.mtimeMs;
83
+ const maxAge = this.maxAgeDays * 24 * 60 * 60 * 1000;
84
+
85
+ if (age > maxAge) {
86
+ this.logger.debug(`Cache expirado para ${url}`);
87
+ await fsPromises.unlink(cachePath).catch(() => {});
88
+ return null;
89
+ }
90
+
91
+ // Atualiza timestamp de acesso
92
+ await fsPromises.utimes(cachePath, new Date(), stats.mtime);
93
+
94
+ return {
95
+ path: cachePath,
96
+ size: stats.size
97
+ };
98
+ }
99
+
100
+ /**
101
+ * Adiciona arquivo ao cache
102
+ */
103
+ async set(url: string, sourcePath: string): Promise<void> {
104
+ const cachePath = this.getCachePath(url);
105
+
106
+ // Verifica se precisa limpar antes
107
+ await this.cleanupIfNeeded();
108
+
109
+ // Copia arquivo
110
+ await fsPromises.copyFile(sourcePath, cachePath);
111
+
112
+ this.logger.debug(`Arquivo adicionado ao cache: ${path.basename(url)}`);
113
+ }
114
+
115
+ /**
116
+ * Copia do cache para destino
117
+ */
118
+ async copyFromCache(url: string, destPath: string): Promise<boolean> {
119
+ const cachePath = this.getCachePath(url);
120
+
121
+ if (!existsSync(cachePath)) {
122
+ return false;
123
+ }
124
+
125
+ await fsPromises.copyFile(cachePath, destPath);
126
+ return true;
127
+ }
128
+
129
+ /**
130
+ * Retorna estatísticas do cache
131
+ */
132
+ async getState(): Promise<CacheState> {
133
+ const files: CacheFile[] = [];
134
+ let totalSize = 0;
135
+
136
+ if (!existsSync(this.cacheDir)) {
137
+ return {
138
+ enabled: true,
139
+ dir: this.cacheDir,
140
+ size: 0,
141
+ files: []
142
+ };
143
+ }
144
+
145
+ const entries = await fsPromises.readdir(this.cacheDir);
146
+
147
+ for (const entry of entries) {
148
+ const filePath = path.join(this.cacheDir, entry);
149
+ const stats = await fsPromises.stat(filePath);
150
+
151
+ if (stats.isFile()) {
152
+ const file: CacheFile = {
153
+ name: entry,
154
+ size: stats.size,
155
+ modified: stats.mtime,
156
+ checksum: "" // Pode ser calculado sob demanda
157
+ };
158
+ files.push(file);
159
+ totalSize += stats.size;
160
+ }
161
+ }
162
+
163
+ // Ordena por data de modificação (mais recente primeiro)
164
+ files.sort((a, b) => b.modified.getTime() - a.modified.getTime());
165
+
166
+ return {
167
+ enabled: true,
168
+ dir: this.cacheDir,
169
+ size: totalSize,
170
+ files
171
+ };
172
+ }
173
+
174
+ /**
175
+ * Limpa arquivos antigos se necessário
176
+ */
177
+ async cleanupIfNeeded(): Promise<void> {
178
+ const state = await this.getState();
179
+
180
+ if (state.size <= this.maxSize) {
181
+ return;
182
+ }
183
+
184
+ this.logger.info(`Limpando cache (${this.formatBytes(state.size)} > ${this.formatBytes(this.maxSize)})`);
185
+
186
+ // Remove arquivos mais antigos até ficar abaixo do limite
187
+ const targetSize = this.maxSize * 0.8; // 80% do máximo
188
+ let currentSize = state.size;
189
+
190
+ for (const file of state.files) {
191
+ if (currentSize <= targetSize) break;
192
+
193
+ const filePath = path.join(this.cacheDir, file.name);
194
+ await fsPromises.unlink(filePath).catch(() => {});
195
+ currentSize -= file.size;
196
+ }
197
+
198
+ this.logger.info(`Cache limpo: ${this.formatBytes(currentSize)}`);
199
+ }
200
+
201
+ /**
202
+ * Limpa todo o cache
203
+ */
204
+ async clear(): Promise<void> {
205
+ this.logger.info("Limpando cache de downloads...");
206
+
207
+ if (!existsSync(this.cacheDir)) {
208
+ return;
209
+ }
210
+
211
+ const entries = await fsPromises.readdir(this.cacheDir);
212
+ let count = 0;
213
+
214
+ for (const entry of entries) {
215
+ const filePath = path.join(this.cacheDir, entry);
216
+ await fsPromises.unlink(filePath).catch(() => {});
217
+ count++;
218
+ }
219
+
220
+ this.logger.success(`${count} arquivo(s) removido(s) do cache`);
221
+ }
222
+
223
+ /**
224
+ * Retorna tamanho usado pelo cache
225
+ */
226
+ async getSize(): Promise<number> {
227
+ const state = await this.getState();
228
+ return state.size;
229
+ }
230
+
231
+ /**
232
+ * Formata bytes para legível
233
+ */
234
+ private formatBytes(bytes: number): string {
235
+ if (bytes === 0) return "0 B";
236
+ const k = 1024;
237
+ const sizes = ["B", "KB", "MB", "GB"];
238
+ const i = Math.floor(Math.log(bytes) / Math.log(k));
239
+ return parseFloat((bytes / Math.pow(k, i)).toFixed(1)) + " " + sizes[i];
240
+ }
241
+
242
+ /**
243
+ * Garante que diretório existe
244
+ */
245
+ private ensureCacheDir(): void {
246
+ if (!existsSync(this.cacheDir)) {
247
+ fsPromises.mkdir(this.cacheDir, { recursive: true });
248
+ }
249
+ }
250
+ }