@archznn/xavva 2.0.3 → 2.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 +1 -78
- package/package.json +1 -1
- package/src/commands/AuditCommand.ts +3 -2
- package/src/commands/BuildCommand.ts +5 -3
- package/src/commands/CommandRegistry.ts +9 -5
- package/src/commands/DeployCommand.ts +36 -30
- package/src/commands/DepsCommand.ts +123 -0
- package/src/commands/DoctorCommand.ts +5 -4
- package/src/commands/HelpCommand.ts +94 -40
- package/src/commands/RunCommand.ts +13 -12
- package/src/commands/StartCommand.ts +5 -3
- package/src/index.ts +15 -5
- package/src/services/AuditService.ts +30 -4
- package/src/services/BuildCacheService.ts +2 -1
- package/src/services/BuildService.ts +8 -1
- package/src/services/DashboardService.ts +181 -118
- package/src/services/DependencyAnalyzerService.ts +538 -0
- package/src/services/ProjectService.ts +3 -3
- package/src/services/TomcatService.ts +11 -11
- package/src/services/WatcherService.ts +3 -2
- package/src/types/config.ts +4 -0
- package/src/utils/config.ts +7 -2
- package/src/utils/constants.ts +54 -0
- package/src/utils/processManager.ts +145 -0
- package/src/utils/ui.ts +237 -311
|
@@ -0,0 +1,538 @@
|
|
|
1
|
+
import fs from "fs";
|
|
2
|
+
import path from "path";
|
|
3
|
+
import type { ProjectConfig } from "../types/config";
|
|
4
|
+
import { Logger } from "../utils/ui";
|
|
5
|
+
|
|
6
|
+
export interface Dependency {
|
|
7
|
+
groupId: string;
|
|
8
|
+
artifactId: string;
|
|
9
|
+
version: string;
|
|
10
|
+
scope?: string;
|
|
11
|
+
type: "direct" | "transitive";
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export interface DependencyConflict {
|
|
15
|
+
artifactId: string;
|
|
16
|
+
groupId: string;
|
|
17
|
+
versions: string[];
|
|
18
|
+
locations: string[];
|
|
19
|
+
severity: "warning" | "error";
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export interface DependencyUpdate {
|
|
23
|
+
groupId: string;
|
|
24
|
+
artifactId: string;
|
|
25
|
+
currentVersion: string;
|
|
26
|
+
latestVersion: string;
|
|
27
|
+
isMajor: boolean;
|
|
28
|
+
changelog?: string;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export interface DependencyAnalysisResult {
|
|
32
|
+
dependencies: Dependency[];
|
|
33
|
+
conflicts: DependencyConflict[];
|
|
34
|
+
updates: DependencyUpdate[];
|
|
35
|
+
outdated: Dependency[];
|
|
36
|
+
stats: {
|
|
37
|
+
total: number;
|
|
38
|
+
direct: number;
|
|
39
|
+
transitive: number;
|
|
40
|
+
vulnerable: number;
|
|
41
|
+
outdatedCount: number;
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
interface MavenDependency {
|
|
46
|
+
groupId: string;
|
|
47
|
+
artifactId: string;
|
|
48
|
+
version: string;
|
|
49
|
+
scope?: string;
|
|
50
|
+
optional?: boolean;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export class DependencyAnalyzerService {
|
|
54
|
+
constructor(private projectConfig: ProjectConfig) {}
|
|
55
|
+
|
|
56
|
+
async analyze(): Promise<DependencyAnalysisResult> {
|
|
57
|
+
const deps = await this.extractDependencies();
|
|
58
|
+
const conflicts = this.detectConflicts(deps);
|
|
59
|
+
const updates = await this.checkUpdates(deps);
|
|
60
|
+
const outdated = deps.filter(d => updates.some(u =>
|
|
61
|
+
u.groupId === d.groupId && u.artifactId === d.artifactId
|
|
62
|
+
));
|
|
63
|
+
|
|
64
|
+
return {
|
|
65
|
+
dependencies: deps,
|
|
66
|
+
conflicts,
|
|
67
|
+
updates,
|
|
68
|
+
outdated,
|
|
69
|
+
stats: {
|
|
70
|
+
total: deps.length,
|
|
71
|
+
direct: deps.filter(d => d.type === "direct").length,
|
|
72
|
+
transitive: deps.filter(d => d.type === "transitive").length,
|
|
73
|
+
vulnerable: 0, // Será preenchido pelo AuditService
|
|
74
|
+
outdatedCount: outdated.length
|
|
75
|
+
}
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
private async extractDependencies(): Promise<Dependency[]> {
|
|
80
|
+
if (this.projectConfig.buildTool === "maven") {
|
|
81
|
+
return this.extractMavenDependencies();
|
|
82
|
+
} else {
|
|
83
|
+
return this.extractGradleDependencies();
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
private verbose: boolean = false;
|
|
88
|
+
|
|
89
|
+
setVerbose(verbose: boolean) {
|
|
90
|
+
this.verbose = verbose;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
private async extractMavenDependencies(): Promise<Dependency[]> {
|
|
94
|
+
const deps: Dependency[] = [];
|
|
95
|
+
const pomPath = path.join(process.cwd(), "pom.xml");
|
|
96
|
+
|
|
97
|
+
if (!fs.existsSync(pomPath)) {
|
|
98
|
+
Logger.warn("pom.xml não encontrado");
|
|
99
|
+
return deps;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
if (this.verbose) {
|
|
103
|
+
Logger.info("Tentando extrair dependências do Maven...", "");
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// Tentar várias estratégias para extrair dependências
|
|
107
|
+
|
|
108
|
+
// 1. Tentar mvn dependency:tree
|
|
109
|
+
try {
|
|
110
|
+
const mvnCmd = process.platform === "win32" ? "mvn.cmd" : "mvn";
|
|
111
|
+
Logger.step("Executando mvn dependency:tree...");
|
|
112
|
+
|
|
113
|
+
const output = Bun.spawnSync([
|
|
114
|
+
mvnCmd,
|
|
115
|
+
"dependency:tree",
|
|
116
|
+
"-DoutputType=text",
|
|
117
|
+
"-q"
|
|
118
|
+
], { cwd: process.cwd() });
|
|
119
|
+
|
|
120
|
+
if (output.exitCode === 0) {
|
|
121
|
+
const treeOutput = output.stdout.toString();
|
|
122
|
+
const parsed = this.parseMavenTree(treeOutput);
|
|
123
|
+
if (parsed.length > 0) {
|
|
124
|
+
Logger.success(`Encontradas ${parsed.length} dependências via Maven tree`);
|
|
125
|
+
return parsed;
|
|
126
|
+
}
|
|
127
|
+
} else {
|
|
128
|
+
const errorOutput = output.stderr.toString();
|
|
129
|
+
if (this.verbose) {
|
|
130
|
+
Logger.warn(`mvn dependency:tree falhou: ${errorOutput.substring(0, 200)}`);
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
} catch (e) {
|
|
134
|
+
if (this.verbose) {
|
|
135
|
+
Logger.warn(`Não foi possível executar mvn dependency:tree: ${e}`);
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
// 2. Fallback: Parse direto do pom.xml
|
|
140
|
+
Logger.step("Analisando pom.xml diretamente...");
|
|
141
|
+
const pomDeps = this.parsePomDirect();
|
|
142
|
+
if (pomDeps.length > 0) {
|
|
143
|
+
Logger.success(`Encontradas ${pomDeps.length} dependências no pom.xml`);
|
|
144
|
+
return pomDeps;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
// 3. Último recurso: Verificar pasta lib do Tomcat (se existir)
|
|
148
|
+
const libDeps = await this.extractFromLibFolder();
|
|
149
|
+
if (libDeps.length > 0) {
|
|
150
|
+
Logger.success(`Encontradas ${libDeps.length} dependências na pasta lib`);
|
|
151
|
+
return libDeps;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
Logger.warn("Nenhuma dependência encontrada");
|
|
155
|
+
return deps;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
private async extractFromLibFolder(): Promise<Dependency[]> {
|
|
159
|
+
const deps: Dependency[] = [];
|
|
160
|
+
const libPath = path.join(process.cwd(), "target", "dependency");
|
|
161
|
+
|
|
162
|
+
if (!fs.existsSync(libPath)) {
|
|
163
|
+
// Tentar gerar com mvn dependency:copy-dependencies
|
|
164
|
+
try {
|
|
165
|
+
const mvnCmd = process.platform === "win32" ? "mvn.cmd" : "mvn";
|
|
166
|
+
Logger.step("Tentando baixar dependências...");
|
|
167
|
+
Bun.spawnSync([
|
|
168
|
+
mvnCmd,
|
|
169
|
+
"dependency:copy-dependencies",
|
|
170
|
+
"-DoutputDirectory=target/dependency",
|
|
171
|
+
"-q"
|
|
172
|
+
], { cwd: process.cwd() });
|
|
173
|
+
} catch (e) {
|
|
174
|
+
return deps;
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
if (fs.existsSync(libPath)) {
|
|
179
|
+
const files = fs.readdirSync(libPath).filter(f => f.endsWith(".jar"));
|
|
180
|
+
for (const file of files) {
|
|
181
|
+
const parsed = this.parseJarName(file);
|
|
182
|
+
if (parsed) {
|
|
183
|
+
deps.push({ ...parsed, type: "direct" });
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
return deps;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
private parseJarName(jarName: string): { groupId: string; artifactId: string; version: string } | null {
|
|
192
|
+
// Remove .jar extension
|
|
193
|
+
const name = jarName.replace(/\.jar$/, "");
|
|
194
|
+
|
|
195
|
+
// Tenta extrair: artifactId-version
|
|
196
|
+
const match = name.match(/^(.+?)-(\d[\d\.-]*(?:-SNAPSHOT)?)$/);
|
|
197
|
+
if (match) {
|
|
198
|
+
return {
|
|
199
|
+
groupId: "unknown", // Não conseguimos extrair do nome do JAR
|
|
200
|
+
artifactId: match[1],
|
|
201
|
+
version: match[2]
|
|
202
|
+
};
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
return null;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
private parseMavenTree(output: string): Dependency[] {
|
|
209
|
+
const deps: Dependency[] = [];
|
|
210
|
+
const lines = output.split("\n");
|
|
211
|
+
const seen = new Set<string>();
|
|
212
|
+
|
|
213
|
+
for (const line of lines) {
|
|
214
|
+
// Pattern: [INFO] | | \- org.springframework:spring-core:jar:5.3.9:compile
|
|
215
|
+
// ou: [INFO] +- org.springframework:spring-core:jar:5.3.9:compile
|
|
216
|
+
const match = line.match(/[\\|\-+]\s+([^:]+):([^:]+):[^:]+:([^:]+):?(\w+)?/);
|
|
217
|
+
if (match) {
|
|
218
|
+
const [, groupId, artifactId, version, scope] = match;
|
|
219
|
+
const key = `${groupId}:${artifactId}:${version}`;
|
|
220
|
+
|
|
221
|
+
if (!seen.has(key)) {
|
|
222
|
+
seen.add(key);
|
|
223
|
+
// Determina se é direto ou transitivo pelo nível de indentação
|
|
224
|
+
const level = (line.match(/[|\\]/g) || []).length;
|
|
225
|
+
deps.push({
|
|
226
|
+
groupId,
|
|
227
|
+
artifactId,
|
|
228
|
+
version,
|
|
229
|
+
scope: scope || "compile",
|
|
230
|
+
type: level <= 1 ? "direct" : "transitive"
|
|
231
|
+
});
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
return deps;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
private parsePomDirect(): Dependency[] {
|
|
240
|
+
const deps: Dependency[] = [];
|
|
241
|
+
const pomPath = path.join(process.cwd(), "pom.xml");
|
|
242
|
+
|
|
243
|
+
try {
|
|
244
|
+
const content = fs.readFileSync(pomPath, "utf-8");
|
|
245
|
+
|
|
246
|
+
// Remove comentários XML para não interferir no parse
|
|
247
|
+
const cleanContent = content.replace(/<!--[\s\S]*?-->/g, "");
|
|
248
|
+
|
|
249
|
+
// Parse de dependencies - suporta propriedades ${...}
|
|
250
|
+
const depRegex = /<dependency>\s*([\s\S]*?)<\/dependency>/g;
|
|
251
|
+
let match;
|
|
252
|
+
|
|
253
|
+
while ((match = depRegex.exec(cleanContent)) !== null) {
|
|
254
|
+
const depBlock = match[1];
|
|
255
|
+
|
|
256
|
+
// Extrai groupId
|
|
257
|
+
let groupId = depBlock.match(/<groupId>\s*([^<$]+)\s*<\/groupId>/)?.[1]?.trim();
|
|
258
|
+
// Extrai artifactId
|
|
259
|
+
let artifactId = depBlock.match(/<artifactId>\s*([^<$]+)\s*<\/artifactId>/)?.[1]?.trim();
|
|
260
|
+
// Extrai version
|
|
261
|
+
let version = depBlock.match(/<version>\s*([^<$]+)\s*<\/version>/)?.[1]?.trim();
|
|
262
|
+
// Extrai scope
|
|
263
|
+
const scope = depBlock.match(/<scope>\s*([^<$]+)\s*<\/scope>/)?.[1]?.trim();
|
|
264
|
+
|
|
265
|
+
// Resolve propriedades simples ${property}
|
|
266
|
+
if (version?.startsWith("${")) {
|
|
267
|
+
const propName = version.slice(2, -1);
|
|
268
|
+
const propValue = cleanContent.match(new RegExp(`<${propName}>([^<]+)</${propName}>`))?.[1];
|
|
269
|
+
if (propValue) version = propValue.trim();
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
if (groupId && artifactId) {
|
|
273
|
+
// Se não tiver versão, tenta encontrar no dependencyManagement
|
|
274
|
+
if (!version || version.startsWith("${")) {
|
|
275
|
+
version = this.findVersionInDependencyManagement(cleanContent, groupId, artifactId) || version || "managed";
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
deps.push({
|
|
279
|
+
groupId,
|
|
280
|
+
artifactId,
|
|
281
|
+
version,
|
|
282
|
+
scope,
|
|
283
|
+
type: "direct"
|
|
284
|
+
});
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
Logger.info("Dependências encontradas no pom.xml", String(deps.length));
|
|
289
|
+
} catch (e) {
|
|
290
|
+
Logger.warn(`Não foi possível analisar o pom.xml: ${e}`);
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
return deps;
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
private findVersionInDependencyManagement(content: string, groupId: string, artifactId: string): string | null {
|
|
297
|
+
// Busca no dependencyManagement
|
|
298
|
+
const dmMatch = content.match(/<dependencyManagement>\s*<dependencies>([\s\S]*?)<\/dependencies>\s*<\/dependencyManagement>/);
|
|
299
|
+
if (dmMatch) {
|
|
300
|
+
const dmBlock = dmMatch[1];
|
|
301
|
+
const depRegex = /<dependency>\s*([\s\S]*?)<\/dependency>/g;
|
|
302
|
+
let match;
|
|
303
|
+
|
|
304
|
+
while ((match = depRegex.exec(dmBlock)) !== null) {
|
|
305
|
+
const depBlock = match[1];
|
|
306
|
+
const depGroupId = depBlock.match(/<groupId>\s*([^<]+)\s*<\/groupId>/)?.[1]?.trim();
|
|
307
|
+
const depArtifactId = depBlock.match(/<artifactId>\s*([^<]+)\s*<\/artifactId>/)?.[1]?.trim();
|
|
308
|
+
const depVersion = depBlock.match(/<version>\s*([^<]+)\s*<\/version>/)?.[1]?.trim();
|
|
309
|
+
|
|
310
|
+
if (depGroupId === groupId && depArtifactId === artifactId && depVersion) {
|
|
311
|
+
return depVersion;
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
return null;
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
private async extractGradleDependencies(): Promise<Dependency[]> {
|
|
319
|
+
const deps: Dependency[] = [];
|
|
320
|
+
|
|
321
|
+
try {
|
|
322
|
+
const gradleCmd = process.platform === "win32" ? "gradle.bat" : "gradle";
|
|
323
|
+
const output = Bun.spawnSync([
|
|
324
|
+
gradleCmd,
|
|
325
|
+
"dependencies",
|
|
326
|
+
"--configuration",
|
|
327
|
+
"runtimeClasspath",
|
|
328
|
+
"-q"
|
|
329
|
+
], { cwd: process.cwd() });
|
|
330
|
+
|
|
331
|
+
if (output.exitCode === 0) {
|
|
332
|
+
deps.push(...this.parseGradleTree(output.stdout.toString()));
|
|
333
|
+
}
|
|
334
|
+
} catch (e) {
|
|
335
|
+
Logger.warn("Não foi possível executar gradle dependencies");
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
return deps;
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
private parseGradleTree(output: string): Dependency[] {
|
|
342
|
+
const deps: Dependency[] = [];
|
|
343
|
+
const lines = output.split("\n");
|
|
344
|
+
const seen = new Set<string>();
|
|
345
|
+
|
|
346
|
+
for (const line of lines) {
|
|
347
|
+
// Pattern: | | +--- org.springframework:spring-core:5.3.9
|
|
348
|
+
const match = line.match(/[\\|\-+]+\s+([^:]+):([^:]+):([^\s]+)/);
|
|
349
|
+
if (match) {
|
|
350
|
+
const [, groupId, artifactId, version] = match;
|
|
351
|
+
const key = `${groupId}:${artifactId}:${version}`;
|
|
352
|
+
|
|
353
|
+
if (!seen.has(key)) {
|
|
354
|
+
seen.add(key);
|
|
355
|
+
const level = (line.match(/[|\\]/g) || []).length;
|
|
356
|
+
deps.push({
|
|
357
|
+
groupId,
|
|
358
|
+
artifactId,
|
|
359
|
+
version,
|
|
360
|
+
type: level <= 1 ? "direct" : "transitive"
|
|
361
|
+
});
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
return deps;
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
private detectConflicts(deps: Dependency[]): DependencyConflict[] {
|
|
370
|
+
const conflicts: DependencyConflict[] = [];
|
|
371
|
+
const grouped = new Map<string, Dependency[]>();
|
|
372
|
+
|
|
373
|
+
// Agrupar por groupId:artifactId
|
|
374
|
+
for (const dep of deps) {
|
|
375
|
+
const key = `${dep.groupId}:${dep.artifactId}`;
|
|
376
|
+
if (!grouped.has(key)) {
|
|
377
|
+
grouped.set(key, []);
|
|
378
|
+
}
|
|
379
|
+
grouped.get(key)!.push(dep);
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
// Detectar conflitos (mesmo artifact, versões diferentes)
|
|
383
|
+
for (const [key, versions] of grouped) {
|
|
384
|
+
const uniqueVersions = [...new Set(versions.map(v => v.version))];
|
|
385
|
+
if (uniqueVersions.length > 1) {
|
|
386
|
+
const [groupId, artifactId] = key.split(":");
|
|
387
|
+
const hasDirect = versions.some(v => v.type === "direct");
|
|
388
|
+
|
|
389
|
+
conflicts.push({
|
|
390
|
+
groupId,
|
|
391
|
+
artifactId,
|
|
392
|
+
versions: uniqueVersions,
|
|
393
|
+
locations: versions.map(v => v.scope || "compile"),
|
|
394
|
+
severity: hasDirect ? "error" : "warning"
|
|
395
|
+
});
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
return conflicts;
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
private async checkUpdates(deps: Dependency[]): Promise<DependencyUpdate[]> {
|
|
403
|
+
const updates: DependencyUpdate[] = [];
|
|
404
|
+
const directDeps = deps.filter(d => d.type === "direct");
|
|
405
|
+
|
|
406
|
+
// Verificar atualizações em paralelo (com limite)
|
|
407
|
+
const chunkSize = 5;
|
|
408
|
+
for (let i = 0; i < directDeps.length; i += chunkSize) {
|
|
409
|
+
const chunk = directDeps.slice(i, i + chunkSize);
|
|
410
|
+
const promises = chunk.map(dep => this.checkSingleUpdate(dep));
|
|
411
|
+
const results = await Promise.all(promises);
|
|
412
|
+
updates.push(...results.filter((u): u is DependencyUpdate => u !== null));
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
return updates;
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
private async checkSingleUpdate(dep: Dependency): Promise<DependencyUpdate | null> {
|
|
419
|
+
try {
|
|
420
|
+
const latest = await this.fetchLatestVersion(dep.groupId, dep.artifactId);
|
|
421
|
+
if (latest && this.isNewer(latest, dep.version)) {
|
|
422
|
+
return {
|
|
423
|
+
groupId: dep.groupId,
|
|
424
|
+
artifactId: dep.artifactId,
|
|
425
|
+
currentVersion: dep.version,
|
|
426
|
+
latestVersion: latest,
|
|
427
|
+
isMajor: this.isMajorUpdate(dep.version, latest)
|
|
428
|
+
};
|
|
429
|
+
}
|
|
430
|
+
} catch (e) {
|
|
431
|
+
// Silenciar erros de rede
|
|
432
|
+
}
|
|
433
|
+
return null;
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
private async fetchLatestVersion(groupId: string, artifactId: string): Promise<string | null> {
|
|
437
|
+
// Usar Maven Central API
|
|
438
|
+
try {
|
|
439
|
+
const url = `https://search.maven.org/solrsearch/select?q=g:"${groupId}"+AND+a:"${artifactId}"&rows=1&wt=json`;
|
|
440
|
+
const response = await fetch(url, { signal: AbortSignal.timeout(5000) });
|
|
441
|
+
const data = await response.json();
|
|
442
|
+
|
|
443
|
+
if (data.response?.docs?.[0]?.latestVersion) {
|
|
444
|
+
return data.response.docs[0].latestVersion;
|
|
445
|
+
}
|
|
446
|
+
} catch (e) {
|
|
447
|
+
// Fallback silencioso
|
|
448
|
+
}
|
|
449
|
+
return null;
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
private isNewer(latest: string, current: string): boolean {
|
|
453
|
+
return this.compareVersions(latest, current) > 0;
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
private isMajorUpdate(current: string, latest: string): boolean {
|
|
457
|
+
const currentMajor = current.split(".")[0];
|
|
458
|
+
const latestMajor = latest.split(".")[0];
|
|
459
|
+
return currentMajor !== latestMajor;
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
private compareVersions(v1: string, v2: string): number {
|
|
463
|
+
const parts1 = v1.split(/[.-]/).filter(p => /^\d+$/.test(p)).map(Number);
|
|
464
|
+
const parts2 = v2.split(/[.-]/).filter(p => /^\d+$/.test(p)).map(Number);
|
|
465
|
+
|
|
466
|
+
for (let i = 0; i < Math.max(parts1.length, parts2.length); i++) {
|
|
467
|
+
const p1 = parts1[i] || 0;
|
|
468
|
+
const p2 = parts2[i] || 0;
|
|
469
|
+
if (p1 !== p2) return p1 - p2;
|
|
470
|
+
}
|
|
471
|
+
return 0;
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
generateReport(result: DependencyAnalysisResult): string {
|
|
475
|
+
const lines: string[] = [];
|
|
476
|
+
lines.push("");
|
|
477
|
+
lines.push(`${Logger.C.cyan}══════════════════════════════════════════════════════════${Logger.C.reset}`);
|
|
478
|
+
lines.push(`${Logger.C.bold}📊 ANÁLISE DE DEPENDÊNCIAS${Logger.C.reset}`);
|
|
479
|
+
lines.push(`${Logger.C.cyan}══════════════════════════════════════════════════════════${Logger.C.reset}`);
|
|
480
|
+
lines.push("");
|
|
481
|
+
|
|
482
|
+
// Estatísticas
|
|
483
|
+
lines.push(`${Logger.C.dim}Estatísticas:${Logger.C.reset}`);
|
|
484
|
+
lines.push(` Total: ${result.stats.total} dependências`);
|
|
485
|
+
lines.push(` Diretas: ${result.stats.direct} | Transitivas: ${result.stats.transitive}`);
|
|
486
|
+
lines.push("");
|
|
487
|
+
|
|
488
|
+
// Conflitos
|
|
489
|
+
if (result.conflicts.length > 0) {
|
|
490
|
+
lines.push(`${Logger.C.yellow}⚠️ CONFLITOS DE VERSÃO (${result.conflicts.length})${Logger.C.reset}`);
|
|
491
|
+
for (const conflict of result.conflicts) {
|
|
492
|
+
const icon = conflict.severity === "error" ? "✖" : "▲";
|
|
493
|
+
const color = conflict.severity === "error" ? Logger.C.red : Logger.C.yellow;
|
|
494
|
+
lines.push(` ${color}${icon}${Logger.C.reset} ${conflict.groupId}:${conflict.artifactId}`);
|
|
495
|
+
lines.push(` Versões: ${conflict.versions.join(", ")}`);
|
|
496
|
+
}
|
|
497
|
+
lines.push("");
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
// Atualizações
|
|
501
|
+
if (result.updates.length > 0) {
|
|
502
|
+
const majorUpdates = result.updates.filter(u => u.isMajor);
|
|
503
|
+
const minorUpdates = result.updates.filter(u => !u.isMajor);
|
|
504
|
+
|
|
505
|
+
if (minorUpdates.length > 0) {
|
|
506
|
+
lines.push(`${Logger.C.green}⬆️ ATUALIZAÇÕES DISPONÍVEIS (${minorUpdates.length})${Logger.C.reset}`);
|
|
507
|
+
for (const update of minorUpdates.slice(0, 5)) {
|
|
508
|
+
lines.push(` ${Logger.C.green}↑${Logger.C.reset} ${update.groupId}:${update.artifactId}`);
|
|
509
|
+
lines.push(` ${update.currentVersion} → ${Logger.C.green}${update.latestVersion}${Logger.C.reset}`);
|
|
510
|
+
}
|
|
511
|
+
if (minorUpdates.length > 5) {
|
|
512
|
+
lines.push(` ${Logger.C.dim}... e mais ${minorUpdates.length - 5}${Logger.C.reset}`);
|
|
513
|
+
}
|
|
514
|
+
lines.push("");
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
if (majorUpdates.length > 0) {
|
|
518
|
+
lines.push(`${Logger.C.yellow}⚠️ ATUALIZAÇÕES MAJOR (${majorUpdates.length})${Logger.C.reset}`);
|
|
519
|
+
lines.push(` ${Logger.C.dim}Podem conter breaking changes${Logger.C.reset}`);
|
|
520
|
+
for (const update of majorUpdates.slice(0, 3)) {
|
|
521
|
+
lines.push(` ${Logger.C.yellow}!${Logger.C.reset} ${update.groupId}:${update.artifactId}`);
|
|
522
|
+
lines.push(` ${update.currentVersion} → ${Logger.C.yellow}${update.latestVersion}${Logger.C.reset}`);
|
|
523
|
+
}
|
|
524
|
+
lines.push("");
|
|
525
|
+
}
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
// Resumo
|
|
529
|
+
if (result.conflicts.length === 0 && result.updates.length === 0) {
|
|
530
|
+
lines.push(`${Logger.C.green}✔ Todas as dependências estão atualizadas!${Logger.C.reset}`);
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
lines.push("");
|
|
534
|
+
lines.push(`${Logger.C.dim}Dica: Execute 'xavva audit' para verificar vulnerabilidades${Logger.C.reset}`);
|
|
535
|
+
|
|
536
|
+
return lines.join("\n");
|
|
537
|
+
}
|
|
538
|
+
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { existsSync, readdirSync, statSync } from "fs";
|
|
1
|
+
import { existsSync, readdirSync, statSync, readFileSync } from "fs";
|
|
2
2
|
import path from "path";
|
|
3
3
|
import type { ProjectConfig } from "../types/config";
|
|
4
4
|
|
|
@@ -96,7 +96,7 @@ export class ProjectService {
|
|
|
96
96
|
const pomPath = path.join(root, "pom.xml");
|
|
97
97
|
if (existsSync(pomPath)) {
|
|
98
98
|
try {
|
|
99
|
-
const content =
|
|
99
|
+
const content = readFileSync(pomPath, "utf8");
|
|
100
100
|
// Regex simples para capturar IDs de profiles no pom.xml
|
|
101
101
|
const profileRegex = /<profile>[\s\S]*?<id>(.*?)<\/id>/g;
|
|
102
102
|
let match;
|
|
@@ -112,7 +112,7 @@ export class ProjectService {
|
|
|
112
112
|
|
|
113
113
|
if (targetPath) {
|
|
114
114
|
try {
|
|
115
|
-
const content =
|
|
115
|
+
const content = readFileSync(targetPath, "utf8");
|
|
116
116
|
// Em Gradle, perfis costumam ser tratados via propriedades ou tasks de ambiente
|
|
117
117
|
// Vamos procurar por padrões comuns como "if (project.hasProperty('profile'))"
|
|
118
118
|
// ou simplesmente sugerir o uso de -P
|
|
@@ -2,6 +2,9 @@ import type { TomcatConfig, AppConfig } from "../types/config";
|
|
|
2
2
|
import { Logger } from "../utils/ui";
|
|
3
3
|
import type { Subprocess } from "bun";
|
|
4
4
|
import { ProjectService } from "./ProjectService";
|
|
5
|
+
import { existsSync, mkdirSync, writeFileSync, promises as fs } from "fs";
|
|
6
|
+
import path from "path";
|
|
7
|
+
import os from "os";
|
|
5
8
|
|
|
6
9
|
export class TomcatService {
|
|
7
10
|
private activeConfig: TomcatConfig;
|
|
@@ -43,9 +46,6 @@ export class TomcatService {
|
|
|
43
46
|
}
|
|
44
47
|
|
|
45
48
|
async clearWebapps() {
|
|
46
|
-
const fs = require("fs").promises;
|
|
47
|
-
const { existsSync } = require("fs");
|
|
48
|
-
const path = require("path");
|
|
49
49
|
const webappsPath = path.join(this.activeConfig.path, "webapps");
|
|
50
50
|
const workPath = path.join(this.activeConfig.path, "work");
|
|
51
51
|
const tempPath = path.join(this.activeConfig.path, "temp");
|
|
@@ -95,9 +95,6 @@ export class TomcatService {
|
|
|
95
95
|
}
|
|
96
96
|
|
|
97
97
|
private async ensureHotswapAgent(): Promise<string | null> {
|
|
98
|
-
const fs = require("fs");
|
|
99
|
-
const path = require("path");
|
|
100
|
-
const os = require("os");
|
|
101
98
|
const agentDir = path.join(os.homedir(), ".xavva", "agents");
|
|
102
99
|
const agentPath = path.join(agentDir, "hotswap-agent-2.0.3.jar");
|
|
103
100
|
|
|
@@ -134,7 +131,7 @@ export class TomcatService {
|
|
|
134
131
|
|
|
135
132
|
let javaBin = "java";
|
|
136
133
|
if (process.env.JAVA_HOME) {
|
|
137
|
-
javaBin =
|
|
134
|
+
javaBin = path.join(process.env.JAVA_HOME, "bin", "java.exe");
|
|
138
135
|
}
|
|
139
136
|
|
|
140
137
|
const javaVer = Bun.spawnSync([javaBin, "-version"]);
|
|
@@ -159,14 +156,13 @@ export class TomcatService {
|
|
|
159
156
|
"--add-opens=java.desktop/java.beans=ALL-UNNAMED"
|
|
160
157
|
);
|
|
161
158
|
|
|
162
|
-
|
|
163
|
-
const path = require("path");
|
|
159
|
+
|
|
164
160
|
const xavvaDir = path.join(process.cwd(), ".xavva");
|
|
165
|
-
if (!
|
|
161
|
+
if (!existsSync(xavvaDir)) mkdirSync(xavvaDir, { recursive: true });
|
|
166
162
|
|
|
167
163
|
const propsPath = path.join(xavvaDir, "hotswap-agent.properties");
|
|
168
164
|
const propsContent = `autoHotswap=true\nautoHotswap.delay=500\nwatchResources=false\nLOGGER=info`;
|
|
169
|
-
|
|
165
|
+
writeFileSync(propsPath, propsContent);
|
|
170
166
|
|
|
171
167
|
catalinaOpts.push(`-Dhotswap-agent.properties.path=${propsPath}`);
|
|
172
168
|
|
|
@@ -186,6 +182,10 @@ export class TomcatService {
|
|
|
186
182
|
"-Dorg.apache.jasper.compiler.classdebuginfo=true"
|
|
187
183
|
);
|
|
188
184
|
|
|
185
|
+
if (config.project.encoding) {
|
|
186
|
+
catalinaOpts.push(`-Dfile.encoding=${config.project.encoding}`);
|
|
187
|
+
}
|
|
188
|
+
|
|
189
189
|
if (config.project.skipScan) {
|
|
190
190
|
catalinaOpts.push(
|
|
191
191
|
"-Dtomcat.util.scan.StandardJarScanFilter.jarsToSkip=*.jar",
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { watch } from "fs";
|
|
2
2
|
import { Logger } from "../utils/ui";
|
|
3
3
|
import { DeployCommand } from "../commands/DeployCommand";
|
|
4
|
+
import { WATCHER_DEBOUNCE_MS, WATCHER_COOLING_MS } from "../utils/constants";
|
|
4
5
|
import type { AppConfig } from "../types/config";
|
|
5
6
|
|
|
6
7
|
export class WatcherService {
|
|
@@ -19,7 +20,7 @@ export class WatcherService {
|
|
|
19
20
|
|
|
20
21
|
if (this.coolingFiles.has(filename)) return;
|
|
21
22
|
this.coolingFiles.add(filename);
|
|
22
|
-
setTimeout(() => this.coolingFiles.delete(filename),
|
|
23
|
+
setTimeout(() => this.coolingFiles.delete(filename), WATCHER_COOLING_MS);
|
|
23
24
|
|
|
24
25
|
if (this.isIgnored(filename)) return;
|
|
25
26
|
|
|
@@ -47,7 +48,7 @@ export class WatcherService {
|
|
|
47
48
|
this.debounceTimer = setTimeout(() => {
|
|
48
49
|
this.run(this.pendingFullBuild ? false : true);
|
|
49
50
|
this.pendingFullBuild = false;
|
|
50
|
-
},
|
|
51
|
+
}, WATCHER_DEBOUNCE_MS);
|
|
51
52
|
});
|
|
52
53
|
}
|
|
53
54
|
|
package/src/types/config.ts
CHANGED
|
@@ -19,6 +19,7 @@ export interface ProjectConfig {
|
|
|
19
19
|
cleanLogs: boolean;
|
|
20
20
|
grep?: string;
|
|
21
21
|
tui: boolean;
|
|
22
|
+
encoding?: string;
|
|
22
23
|
}
|
|
23
24
|
|
|
24
25
|
export interface AppConfig {
|
|
@@ -31,6 +32,7 @@ export interface CLIArguments {
|
|
|
31
32
|
tool?: string;
|
|
32
33
|
name?: string;
|
|
33
34
|
port?: string;
|
|
35
|
+
encoding?: string;
|
|
34
36
|
"no-build"?: boolean;
|
|
35
37
|
scan?: boolean;
|
|
36
38
|
clean?: boolean;
|
|
@@ -46,6 +48,8 @@ export interface CLIArguments {
|
|
|
46
48
|
fix?: boolean;
|
|
47
49
|
incremental?: boolean;
|
|
48
50
|
tui?: boolean;
|
|
51
|
+
output?: string;
|
|
52
|
+
strict?: boolean;
|
|
49
53
|
}
|
|
50
54
|
|
|
51
55
|
export interface CommandContext {
|