@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,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
|
+
}
|