@archznn/xavva 2.2.0 β 2.2.1
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 +3 -1
- package/package.json +1 -1
- package/src/commands/DepsCommand.ts +60 -0
- package/src/commands/HelpCommand.ts +3 -0
- package/src/services/BuildService.ts +12 -4
- package/src/services/DependencyAnalyzerService.ts +119 -11
- package/src/services/TomcatService.ts +33 -12
- package/src/utils/ui.ts +435 -30
package/README.md
CHANGED
|
@@ -1 +1,3 @@
|
|
|
1
|
-
# XAVVA CLI π> Ultra-fast development toolkit for Java Enterprise (Tomcat) on Windows[ on Windows[](https://github.com/leorsousa05/Xavva)[](LICENSE)Xavva is a high-performance CLI built with **Bun** that transforms the Java/Tomcat development experience. It brings modern development workflows (like Node.js/Vite) to the Java Enterprise ecosystem with hot-reload, smart logging, and automated deployment.---## β¨ Features- β‘ **Hot Reload** β Incremental compilation and class injection without server restart- π **Interactive Dashboard** β Real-time TUI with system metrics and shortcuts- π§ **Smart Log Analyzer** β Stack trace folding and root cause highlighting- π **Security Audit** β Automated vulnerability scanning via OSV.dev- π¦ **Dependency Analysis** β Detect conflicts and outdated dependencies- π― **Maven & Gradle** β Native support for both build tools- π§ **Auto-Healing** β Automatic diagnosis and repair of common issues---## π¦ Installation```powershell# Via NPMnpm install -g @archznn/xavva# Or run directly with Bunbunx @archznn/xavva dev```---## π Quick Start```bash# Start development mode with dashboardxavva dev --tui# Deploy to Tomcatxavva deploy# Analyze dependencies for issuesxavva deps
|
|
2
|
+
# Update safe dependencies (non-breaking)
|
|
3
|
+
xavva deps --update-safe# Check for security vulnerabilitiesxavva audit```---## π Commands### Core Development| Command | Description ||---------|-------------|| `xavva dev` | Full development mode (build + deploy + watch + debug) || `xavva deploy` | Build and deploy application to Tomcat || `xavva build` | Compile project only || `xavva start` | Start Tomcat server only |### Code Execution| Command | Description ||---------|-------------|| `xavva run <class>` | Execute a Java class with automatic classpath || `xavva debug <class>` | Debug a Java class (port 5005) |### Analysis & Monitoring| Command | Description ||---------|-------------|| `xavva logs` | Stream and analyze Tomcat logs in real-time || `xavva deps` | **Analyze dependencies** β detect conflicts, find updates || `xavva audit` | Security audit of JAR files via OSV.dev || `xavva doctor` | Diagnose environment issues (JAVA_HOME, DCEVM) || `xavva profiles` | List available Maven/Gradle profiles || `xavva docs` | Generate endpoint documentation |---## π Dependency AnalysisThe `xavva deps` command provides comprehensive dependency analysis:```bash# Basic analysisxavva deps# With verbose output for debuggingxavva deps --verbose# Show fix suggestions for conflictsxavva deps --fix# Export report as JSONxavva deps --output report.json# Fail on critical conflicts (useful in CI/CD)xavva deps --strict```### What it detects:- β οΈ **Version Conflicts** β Same dependency with different versions- β¬οΈ **Available Updates** β Newer versions in Maven Central- π΄ **Major Updates** β Breaking changes that need attention- π **Statistics** β Direct vs transitive dependencies### Sample output:```ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββπ DEPENDENCY ANALYSISββββββββββββββββββββββββββββββββββββββββββββββββββββββββββStatistics: Total: 183 dependencies Direct: 45 | Transitive: 138β οΈ VERSION CONFLICTS (2) β com.fasterxml.jackson.core:jackson-databind Versions: 2.13.0, 2.12.6β¬οΈ UPDATES AVAILABLE (5) β org.postgresql:postgresql 42.2.5 β 42.7.1β οΈ MAJOR UPDATES (1) ! org.springframework.boot:spring-boot-starter 2.5.0 β 3.1.0```---## βοΈ ConfigurationCreate `xavva.json` in your project root:```json{ "project": { "appName": "my-application", "buildTool": "maven", "profile": "dev", "tui": false }, "tomcat": { "path": "C:/apache-tomcat", "port": 8080 }}```### CLI Options| Option | Description ||--------|-------------|| `-p, --path <path>` | Tomcat installation path || `-t, --tool <tool>` | Build tool: `maven` or `gradle` || `-n, --name <name>` | Application name (WAR context) || `--port <port>` | Tomcat port (default: 8080) || `-P, --profile <prof>` | Maven/Gradle profile || `-e, --encoding <enc>` | Source encoding (utf8, cp1252) || `-w, --watch` | Enable file watching || `--tui` | Interactive dashboard mode || `-d, --debug` | Enable JPDA debugger || `-c, --clean` | Clean logs before start || `-s, --no-build` | Skip initial build || `-V, --verbose` | Detailed output |---## ποΈ ArchitectureXavva uses a modular service-oriented architecture:- **DashboardService** β TUI management and interactivity- **LogAnalyzer** β Intelligent log processing- **DependencyAnalyzerService** β Dependency conflict detection- **ProjectService** β Project structure discovery- **BuildService** β Maven/Gradle integration- **TomcatService** β Server lifecycle management---## π€ ContributingContributions are welcome! Please feel free to submit a Pull Request.---## π LicenseMIT License β see [LICENSE](LICENSE) for details.---<p align="center"> <sub>Built with β€οΈ for Java developers who miss modern tooling</sub></p>
|
package/package.json
CHANGED
|
@@ -50,6 +50,10 @@ export class DepsCommand implements Command {
|
|
|
50
50
|
console.log(report);
|
|
51
51
|
|
|
52
52
|
// AΓ§Γ΅es adicionais baseadas em flags
|
|
53
|
+
if (args?.["update-safe"] || args?.["updateSafe"]) {
|
|
54
|
+
await this.performUpdateSafe(analyzer, result, config.project.buildTool);
|
|
55
|
+
}
|
|
56
|
+
|
|
53
57
|
if (args?.["fix"]) {
|
|
54
58
|
await this.suggestFixes(result, config.project.buildTool);
|
|
55
59
|
}
|
|
@@ -103,6 +107,62 @@ export class DepsCommand implements Command {
|
|
|
103
107
|
}
|
|
104
108
|
}
|
|
105
109
|
|
|
110
|
+
private async performUpdateSafe(
|
|
111
|
+
analyzer: DependencyAnalyzerService,
|
|
112
|
+
result: DependencyAnalysisResult,
|
|
113
|
+
buildTool: string
|
|
114
|
+
): Promise<void> {
|
|
115
|
+
const safeUpdates = result.updates.filter(u => !u.isMajor);
|
|
116
|
+
|
|
117
|
+
if (safeUpdates.length === 0) {
|
|
118
|
+
Logger.info("", "Nenhuma atualizaΓ§Γ£o segura disponΓvel");
|
|
119
|
+
return;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
Logger.newline();
|
|
123
|
+
Logger.section("Atualizando DependΓͺncias (Safe Mode)");
|
|
124
|
+
Logger.info("AtualizaΓ§Γ΅es a aplicar", String(safeUpdates.length));
|
|
125
|
+
|
|
126
|
+
const spinner = Logger.spinner("Atualizando arquivos de configuraΓ§Γ£o...");
|
|
127
|
+
|
|
128
|
+
try {
|
|
129
|
+
const updateResult = await analyzer.updateSafe(safeUpdates);
|
|
130
|
+
spinner();
|
|
131
|
+
|
|
132
|
+
if (updateResult.updated > 0) {
|
|
133
|
+
Logger.success(`${updateResult.updated} dependΓͺncias atualizadas`);
|
|
134
|
+
Logger.info("", `Backup criado: ${buildTool === "maven" ? "pom.xml.backup" : "build.gradle.backup"}`);
|
|
135
|
+
|
|
136
|
+
// Listar o que foi atualizado
|
|
137
|
+
for (const update of safeUpdates.slice(0, 5)) {
|
|
138
|
+
Logger.log(` ${Logger.C.success}β${Logger.C.reset} ${update.groupId}:${update.artifactId} ${update.currentVersion} β ${Logger.C.success}${update.latestVersion}${Logger.C.reset}`);
|
|
139
|
+
}
|
|
140
|
+
if (safeUpdates.length > 5) {
|
|
141
|
+
Logger.log(` ${Logger.C.dim}... e mais ${safeUpdates.length - 5}${Logger.C.reset}`);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
Logger.newline();
|
|
145
|
+
Logger.log(`${Logger.C.warning}β οΈ Execute 'mvn compile' ou 'gradle build' para aplicar as mudanΓ§as${Logger.C.reset}`);
|
|
146
|
+
} else {
|
|
147
|
+
Logger.warn("Nenhuma dependΓͺncia foi atualizada");
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
if (updateResult.skipped > 0) {
|
|
151
|
+
Logger.info("DependΓͺncias ignoradas", `${updateResult.skipped} (definidas via propriedades)`);
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
if (updateResult.errors.length > 0) {
|
|
155
|
+
for (const error of updateResult.errors) {
|
|
156
|
+
Logger.warn(error);
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
} catch (error) {
|
|
160
|
+
spinner(false);
|
|
161
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
162
|
+
Logger.error(`Falha na atualizaΓ§Γ£o: ${message}`);
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
|
|
106
166
|
private exportReport(
|
|
107
167
|
result: DependencyAnalysisResult,
|
|
108
168
|
outputPath: string
|
|
@@ -63,6 +63,9 @@ export class HelpCommand implements Command {
|
|
|
63
63
|
${this.c("dim", "# Analyze dependencies for conflicts")}
|
|
64
64
|
xavva deps --verbose
|
|
65
65
|
|
|
66
|
+
${this.c("dim", "# Update safe dependencies (non-breaking only)")}
|
|
67
|
+
xavva deps --update-safe
|
|
68
|
+
|
|
66
69
|
${this.c("dim", "# Security audit with auto-fix suggestions")}
|
|
67
70
|
xavva audit --fix
|
|
68
71
|
|
|
@@ -162,6 +162,7 @@ export class BuildService {
|
|
|
162
162
|
const decoder = new TextDecoder();
|
|
163
163
|
let errorCount = 0;
|
|
164
164
|
const maxErrors = 15;
|
|
165
|
+
const buildTool = this.projectConfig.buildTool as 'maven' | 'gradle';
|
|
165
166
|
|
|
166
167
|
while (true) {
|
|
167
168
|
const { done, value } = await reader.read();
|
|
@@ -174,20 +175,27 @@ export class BuildService {
|
|
|
174
175
|
const cleanLine = line.trim();
|
|
175
176
|
if (!cleanLine) continue;
|
|
176
177
|
|
|
177
|
-
if (cleanLine.includes("[ERROR]")) {
|
|
178
|
+
if (cleanLine.includes("[ERROR]") || cleanLine.includes("error:")) {
|
|
178
179
|
errorCount++;
|
|
179
180
|
if (errorCount > maxErrors && !this.projectConfig.verbose) {
|
|
180
181
|
if (errorCount === maxErrors + 1) {
|
|
181
|
-
Logger.warn("...
|
|
182
|
+
Logger.warn("... and more errors hidden. Use -V to see all.");
|
|
182
183
|
}
|
|
183
184
|
continue;
|
|
184
185
|
}
|
|
185
186
|
}
|
|
186
187
|
|
|
187
188
|
if (!this.projectConfig.verbose) {
|
|
188
|
-
//
|
|
189
|
+
// Modo nΓ£o-verbose: usa sumarizaΓ§Γ£o existente
|
|
190
|
+
const formatted = Logger.formatBuildLog(cleanLine, buildTool);
|
|
191
|
+
if (formatted) console.log(formatted);
|
|
189
192
|
} else {
|
|
190
|
-
|
|
193
|
+
// Modo verbose: formata mas mantΓ©m estrutura
|
|
194
|
+
const formatted = Logger.formatBuildLog(cleanLine, buildTool);
|
|
195
|
+
if (formatted) {
|
|
196
|
+
console.log(formatted);
|
|
197
|
+
}
|
|
198
|
+
// Silencia linhas que sΓ£o noise puro
|
|
191
199
|
}
|
|
192
200
|
}
|
|
193
201
|
}
|
|
@@ -471,12 +471,120 @@ export class DependencyAnalyzerService {
|
|
|
471
471
|
return 0;
|
|
472
472
|
}
|
|
473
473
|
|
|
474
|
+
async updateSafe(updates: DependencyUpdate[]): Promise<{ updated: number; skipped: number; errors: string[] }> {
|
|
475
|
+
const safeUpdates = updates.filter(u => !u.isMajor);
|
|
476
|
+
const result = { updated: 0, skipped: 0, errors: [] as string[] };
|
|
477
|
+
|
|
478
|
+
if (safeUpdates.length === 0) {
|
|
479
|
+
return result;
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
if (this.projectConfig.buildTool === "maven") {
|
|
483
|
+
return this.updateMavenSafe(safeUpdates);
|
|
484
|
+
} else {
|
|
485
|
+
return this.updateGradleSafe(safeUpdates);
|
|
486
|
+
}
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
private async updateMavenSafe(updates: DependencyUpdate[]): Promise<{ updated: number; skipped: number; errors: string[] }> {
|
|
490
|
+
const result = { updated: 0, skipped: 0, errors: [] as string[] };
|
|
491
|
+
const pomPath = path.join(process.cwd(), "pom.xml");
|
|
492
|
+
|
|
493
|
+
if (!fs.existsSync(pomPath)) {
|
|
494
|
+
result.errors.push("pom.xml nΓ£o encontrado");
|
|
495
|
+
return result;
|
|
496
|
+
}
|
|
497
|
+
|
|
498
|
+
let content = fs.readFileSync(pomPath, "utf-8");
|
|
499
|
+
let modified = false;
|
|
500
|
+
|
|
501
|
+
for (const update of updates) {
|
|
502
|
+
// Pattern para encontrar a versΓ£o especΓfica desta dependΓͺncia
|
|
503
|
+
const groupId = update.groupId.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
504
|
+
const artifactId = update.artifactId.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
505
|
+
const currentVersion = update.currentVersion.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
506
|
+
|
|
507
|
+
// Regex para encontrar <version> dentro do bloco da dependΓͺncia
|
|
508
|
+
const depPattern = new RegExp(
|
|
509
|
+
`(<dependency>\\s*<groupId>${groupId}</groupId>\\s*<artifactId>${artifactId}</artifactId>(?:\\s*<version>)${currentVersion}(</version>))`,
|
|
510
|
+
'g'
|
|
511
|
+
);
|
|
512
|
+
|
|
513
|
+
if (depPattern.test(content)) {
|
|
514
|
+
content = content.replace(depPattern, `$1${update.latestVersion}$2`);
|
|
515
|
+
result.updated++;
|
|
516
|
+
modified = true;
|
|
517
|
+
} else {
|
|
518
|
+
// Pode ser definida via property
|
|
519
|
+
result.skipped++;
|
|
520
|
+
}
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
if (modified) {
|
|
524
|
+
// Backup do pom.xml
|
|
525
|
+
fs.writeFileSync(`${pomPath}.backup`, fs.readFileSync(pomPath));
|
|
526
|
+
fs.writeFileSync(pomPath, content);
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
return result;
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
private async updateGradleSafe(updates: DependencyUpdate[]): Promise<{ updated: number; skipped: number; errors: string[] }> {
|
|
533
|
+
const result = { updated: 0, skipped: 0, errors: [] as string[] };
|
|
534
|
+
const gradlePath = path.join(process.cwd(), "build.gradle");
|
|
535
|
+
|
|
536
|
+
if (!fs.existsSync(gradlePath)) {
|
|
537
|
+
result.errors.push("build.gradle nΓ£o encontrado");
|
|
538
|
+
return result;
|
|
539
|
+
}
|
|
540
|
+
|
|
541
|
+
let content = fs.readFileSync(gradlePath, "utf-8");
|
|
542
|
+
let modified = false;
|
|
543
|
+
|
|
544
|
+
for (const update of updates) {
|
|
545
|
+
const groupId = update.groupId.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
546
|
+
const artifactId = update.artifactId.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
547
|
+
const currentVersion = update.currentVersion.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
548
|
+
|
|
549
|
+
// Pattern: implementation("group:artifact:version") ou implementation 'group:artifact:version'
|
|
550
|
+
const patterns = [
|
|
551
|
+
new RegExp(`(implementation\\s*\\(\\s*["']${groupId}:${artifactId}:)${currentVersion}(["']\\s*\\))`, 'g'),
|
|
552
|
+
new RegExp(`(implementation\\s+["']${groupId}:${artifactId}:)${currentVersion}(["'])`, 'g'),
|
|
553
|
+
new RegExp(`(compile\\s*\\(\\s*["']${groupId}:${artifactId}:)${currentVersion}(["']\\s*\\))`, 'g'),
|
|
554
|
+
new RegExp(`(compile\\s+["']${groupId}:${artifactId}:)${currentVersion}(["'])`, 'g'),
|
|
555
|
+
];
|
|
556
|
+
|
|
557
|
+
let updated = false;
|
|
558
|
+
for (const pattern of patterns) {
|
|
559
|
+
if (pattern.test(content)) {
|
|
560
|
+
content = content.replace(pattern, `$1${update.latestVersion}$2`);
|
|
561
|
+
updated = true;
|
|
562
|
+
break;
|
|
563
|
+
}
|
|
564
|
+
}
|
|
565
|
+
|
|
566
|
+
if (updated) {
|
|
567
|
+
result.updated++;
|
|
568
|
+
modified = true;
|
|
569
|
+
} else {
|
|
570
|
+
result.skipped++;
|
|
571
|
+
}
|
|
572
|
+
}
|
|
573
|
+
|
|
574
|
+
if (modified) {
|
|
575
|
+
fs.writeFileSync(`${gradlePath}.backup`, fs.readFileSync(gradlePath));
|
|
576
|
+
fs.writeFileSync(gradlePath, content);
|
|
577
|
+
}
|
|
578
|
+
|
|
579
|
+
return result;
|
|
580
|
+
}
|
|
581
|
+
|
|
474
582
|
generateReport(result: DependencyAnalysisResult): string {
|
|
475
583
|
const lines: string[] = [];
|
|
476
584
|
lines.push("");
|
|
477
|
-
lines.push(`${Logger.C.
|
|
585
|
+
lines.push(`${Logger.C.primary}ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ${Logger.C.reset}`);
|
|
478
586
|
lines.push(`${Logger.C.bold}π ANΓLISE DE DEPENDΓNCIAS${Logger.C.reset}`);
|
|
479
|
-
lines.push(`${Logger.C.
|
|
587
|
+
lines.push(`${Logger.C.primary}ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ${Logger.C.reset}`);
|
|
480
588
|
lines.push("");
|
|
481
589
|
|
|
482
590
|
// EstatΓsticas
|
|
@@ -487,10 +595,10 @@ export class DependencyAnalyzerService {
|
|
|
487
595
|
|
|
488
596
|
// Conflitos
|
|
489
597
|
if (result.conflicts.length > 0) {
|
|
490
|
-
lines.push(`${Logger.C.
|
|
598
|
+
lines.push(`${Logger.C.warning}β οΈ CONFLITOS DE VERSΓO (${result.conflicts.length})${Logger.C.reset}`);
|
|
491
599
|
for (const conflict of result.conflicts) {
|
|
492
600
|
const icon = conflict.severity === "error" ? "β" : "β²";
|
|
493
|
-
const color = conflict.severity === "error" ? Logger.C.
|
|
601
|
+
const color = conflict.severity === "error" ? Logger.C.error : Logger.C.warning;
|
|
494
602
|
lines.push(` ${color}${icon}${Logger.C.reset} ${conflict.groupId}:${conflict.artifactId}`);
|
|
495
603
|
lines.push(` VersΓ΅es: ${conflict.versions.join(", ")}`);
|
|
496
604
|
}
|
|
@@ -503,10 +611,10 @@ export class DependencyAnalyzerService {
|
|
|
503
611
|
const minorUpdates = result.updates.filter(u => !u.isMajor);
|
|
504
612
|
|
|
505
613
|
if (minorUpdates.length > 0) {
|
|
506
|
-
lines.push(`${Logger.C.
|
|
614
|
+
lines.push(`${Logger.C.success}β¬οΈ ATUALIZAΓΓES DISPONΓVEIS (${minorUpdates.length})${Logger.C.reset}`);
|
|
507
615
|
for (const update of minorUpdates.slice(0, 5)) {
|
|
508
|
-
lines.push(` ${Logger.C.
|
|
509
|
-
lines.push(` ${update.currentVersion} β ${Logger.C.
|
|
616
|
+
lines.push(` ${Logger.C.success}β${Logger.C.reset} ${update.groupId}:${update.artifactId}`);
|
|
617
|
+
lines.push(` ${update.currentVersion} β ${Logger.C.success}${update.latestVersion}${Logger.C.reset}`);
|
|
510
618
|
}
|
|
511
619
|
if (minorUpdates.length > 5) {
|
|
512
620
|
lines.push(` ${Logger.C.dim}... e mais ${minorUpdates.length - 5}${Logger.C.reset}`);
|
|
@@ -515,11 +623,11 @@ export class DependencyAnalyzerService {
|
|
|
515
623
|
}
|
|
516
624
|
|
|
517
625
|
if (majorUpdates.length > 0) {
|
|
518
|
-
lines.push(`${Logger.C.
|
|
626
|
+
lines.push(`${Logger.C.warning}β οΈ ATUALIZAΓΓES MAJOR (${majorUpdates.length})${Logger.C.reset}`);
|
|
519
627
|
lines.push(` ${Logger.C.dim}Podem conter breaking changes${Logger.C.reset}`);
|
|
520
628
|
for (const update of majorUpdates.slice(0, 3)) {
|
|
521
|
-
lines.push(` ${Logger.C.
|
|
522
|
-
lines.push(` ${update.currentVersion} β ${Logger.C.
|
|
629
|
+
lines.push(` ${Logger.C.warning}!${Logger.C.reset} ${update.groupId}:${update.artifactId}`);
|
|
630
|
+
lines.push(` ${update.currentVersion} β ${Logger.C.warning}${update.latestVersion}${Logger.C.reset}`);
|
|
523
631
|
}
|
|
524
632
|
lines.push("");
|
|
525
633
|
}
|
|
@@ -527,7 +635,7 @@ export class DependencyAnalyzerService {
|
|
|
527
635
|
|
|
528
636
|
// Resumo
|
|
529
637
|
if (result.conflicts.length === 0 && result.updates.length === 0) {
|
|
530
|
-
lines.push(`${Logger.C.
|
|
638
|
+
lines.push(`${Logger.C.success}β Todas as dependΓͺncias estΓ£o atualizadas!${Logger.C.reset}`);
|
|
531
639
|
}
|
|
532
640
|
|
|
533
641
|
lines.push("");
|
|
@@ -2,7 +2,7 @@ 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";
|
|
5
|
+
import { existsSync, mkdirSync, writeFileSync, statSync, promises as fs } from "fs";
|
|
6
6
|
import path from "path";
|
|
7
7
|
import os from "os";
|
|
8
8
|
|
|
@@ -98,10 +98,10 @@ export class TomcatService {
|
|
|
98
98
|
const agentDir = path.join(os.homedir(), ".xavva", "agents");
|
|
99
99
|
const agentPath = path.join(agentDir, "hotswap-agent-2.0.3.jar");
|
|
100
100
|
|
|
101
|
-
if (
|
|
101
|
+
if (existsSync(agentPath) && statSync(agentPath).size > 1000) return agentPath;
|
|
102
102
|
|
|
103
103
|
try {
|
|
104
|
-
if (!
|
|
104
|
+
if (!existsSync(agentDir)) mkdirSync(agentDir, { recursive: true });
|
|
105
105
|
|
|
106
106
|
Logger.step("Downloading HotswapAgent v2.0.3 (Global)...");
|
|
107
107
|
const url = "https://github.com/HotswapProjects/HotswapAgent/releases/download/RELEASE-2.0.3/hotswap-agent-2.0.3.jar";
|
|
@@ -109,7 +109,7 @@ export class TomcatService {
|
|
|
109
109
|
if (!response.ok) throw new Error(`Status: ${response.status}`);
|
|
110
110
|
|
|
111
111
|
const buffer = await response.arrayBuffer();
|
|
112
|
-
|
|
112
|
+
writeFileSync(agentPath, Buffer.from(buffer));
|
|
113
113
|
Logger.success("HotswapAgent v2.0.3 installed globally!");
|
|
114
114
|
return agentPath;
|
|
115
115
|
} catch (e) {
|
|
@@ -237,18 +237,21 @@ export class TomcatService {
|
|
|
237
237
|
private async processLogStream(stream: ReadableStream, clean: boolean, quiet: boolean, verbose: boolean, grep: string) {
|
|
238
238
|
const reader = stream.getReader();
|
|
239
239
|
const decoder = new TextDecoder();
|
|
240
|
+
let buffer = "";
|
|
240
241
|
|
|
241
242
|
while (true) {
|
|
242
243
|
const { done, value } = await reader.read();
|
|
243
244
|
if (done) break;
|
|
244
245
|
|
|
245
|
-
|
|
246
|
-
const lines =
|
|
246
|
+
buffer += decoder.decode(value, { stream: true });
|
|
247
|
+
const lines = buffer.split(/\r?\n/);
|
|
248
|
+
buffer = lines.pop() || ""; // Keep incomplete line in buffer
|
|
247
249
|
|
|
248
250
|
for (const line of lines) {
|
|
249
251
|
const cleanLine = line.trim();
|
|
250
252
|
if (!cleanLine || cleanLine.startsWith("Listening for transport")) continue;
|
|
251
253
|
|
|
254
|
+
// Detect startup completion
|
|
252
255
|
if (cleanLine.includes("Server startup in") || cleanLine.includes("SEVERE") || cleanLine.includes("Exception")) {
|
|
253
256
|
const isSuccess = cleanLine.includes("Server startup in");
|
|
254
257
|
if (this.stopStartupSpinner) {
|
|
@@ -260,26 +263,44 @@ export class TomcatService {
|
|
|
260
263
|
}
|
|
261
264
|
}
|
|
262
265
|
|
|
266
|
+
// Verbose: formata logs do Tomcat
|
|
263
267
|
if (verbose) {
|
|
264
|
-
Logger.
|
|
268
|
+
if (Logger.isTomcatNoise(cleanLine)) {
|
|
269
|
+
continue; // Silencia noise completamente
|
|
270
|
+
}
|
|
271
|
+
const formatted = Logger.formatTomcatLog(cleanLine);
|
|
272
|
+
if (formatted) {
|
|
273
|
+
console.log(formatted);
|
|
274
|
+
}
|
|
265
275
|
continue;
|
|
266
276
|
}
|
|
267
277
|
|
|
278
|
+
// Clean mode: filtra noise
|
|
268
279
|
if (clean) {
|
|
280
|
+
// Sempre filtra noise do sistema
|
|
281
|
+
if (Logger.isSystemNoise(cleanLine)) continue;
|
|
282
|
+
|
|
283
|
+
// Quiet mode: sΓ³ mostra essencial
|
|
269
284
|
if (quiet && !Logger.isEssential(cleanLine)) {
|
|
270
|
-
if (
|
|
271
|
-
if (cleanLine.includes("INFO")) continue;
|
|
272
|
-
} else if (Logger.isSystemNoise(cleanLine)) {
|
|
273
|
-
continue;
|
|
285
|
+
if (cleanLine.includes("INFO") && !cleanLine.includes("ERROR")) continue;
|
|
274
286
|
}
|
|
275
287
|
|
|
288
|
+
// Grep filter
|
|
276
289
|
if (grep && !cleanLine.toLowerCase().includes(grep.toLowerCase())) {
|
|
277
290
|
if (!Logger.isEssential(cleanLine)) continue;
|
|
278
291
|
}
|
|
279
292
|
|
|
280
293
|
const summarized = Logger.summarize(cleanLine);
|
|
281
|
-
if (summarized)
|
|
294
|
+
if (summarized) {
|
|
295
|
+
// SΓ³ mostra se nΓ£o for vazio (rate limiting)
|
|
296
|
+
if (summarized.trim()) Logger.log(summarized);
|
|
297
|
+
}
|
|
282
298
|
} else {
|
|
299
|
+
// Non-clean: mostra tudo mas formata
|
|
300
|
+
if (Logger.isSystemNoise(cleanLine)) {
|
|
301
|
+
// Silencia completamente noise em non-clean tambΓ©m
|
|
302
|
+
continue;
|
|
303
|
+
}
|
|
283
304
|
Logger.log(cleanLine);
|
|
284
305
|
}
|
|
285
306
|
}
|
package/src/utils/ui.ts
CHANGED
|
@@ -39,34 +39,110 @@ export class Logger {
|
|
|
39
39
|
this.dashboard = dashboard;
|
|
40
40
|
}
|
|
41
41
|
|
|
42
|
-
// Banner
|
|
42
|
+
// Banner completo com informaΓ§Γ΅es do ambiente
|
|
43
43
|
static banner(command?: string, profile?: string, encoding?: string) {
|
|
44
44
|
console.clear();
|
|
45
45
|
const git = this.getGitContext();
|
|
46
46
|
const name = process.cwd().split(/[/\\]/).pop() || "project";
|
|
47
|
+
const now = new Date().toLocaleTimeString('pt-BR', { hour: '2-digit', minute: '2-digit' });
|
|
48
|
+
const W = 52; // Largura interna do box (espaΓ§o entre os β)
|
|
47
49
|
|
|
48
|
-
//
|
|
49
|
-
|
|
50
|
+
// Remove ANSI codes para calcular tamanho
|
|
51
|
+
const plain = (s: string) => s.replace(/\x1b\[\d+m/g, '');
|
|
50
52
|
|
|
51
|
-
//
|
|
52
|
-
|
|
53
|
+
// Cria uma linha com conteΓΊdo alinhado Γ esquerda
|
|
54
|
+
const row = (content: string) => {
|
|
55
|
+
const pad = W - plain(content).length;
|
|
56
|
+
return `${C.gray}β${C.reset} ${content}${' '.repeat(Math.max(0, pad))}${C.gray}β${C.reset}`;
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
// Linha superior
|
|
60
|
+
console.log(`${C.gray}ββββββββββββββββββββββββββββββββββββββββββββββββββββββββ${C.reset}`);
|
|
61
|
+
|
|
62
|
+
// Linha 1: XAVVA v2.2.0 + hora alinhada Γ direita
|
|
63
|
+
const verPlain = `XAVVA v${pkg.version}`; // "XAVVA v2.2.0" = 12 chars
|
|
64
|
+
const timePlain = now; // 5 chars
|
|
65
|
+
const gap1 = W - verPlain.length - timePlain.length - 1; // -1 para deixar 1 espaΓ§o antes do β
|
|
66
|
+
const line1Content = `${C.primary}${C.bold}XAVVA${C.reset}${C.gray} v${pkg.version}${C.reset}${' '.repeat(Math.max(1, gap1))}${C.dim}${now}${C.reset}`;
|
|
67
|
+
console.log(row(line1Content));
|
|
53
68
|
|
|
54
|
-
//
|
|
55
|
-
|
|
56
|
-
if (command) parts.push(`${C.primary}${command}${C.reset}`);
|
|
57
|
-
if (profile) parts.push(`${C.warning}profile:${profile}${C.reset}`);
|
|
58
|
-
if (encoding) parts.push(`${C.info}${encoding}${C.reset}`);
|
|
59
|
-
if (git.branch) parts.push(`${C.secondary}git:${git.branch}${C.reset}`);
|
|
69
|
+
// Linha 2: Nome do projeto
|
|
70
|
+
console.log(row(`${C.white}${C.bold}${name}${C.reset}`));
|
|
60
71
|
|
|
61
|
-
|
|
62
|
-
|
|
72
|
+
// Linha 3: Git info
|
|
73
|
+
if (git.branch) {
|
|
74
|
+
const gitStatus = this.getGitStatus();
|
|
75
|
+
const dirty = gitStatus.dirty ? '*' : '';
|
|
76
|
+
const author = git.author ? git.author.split(' ')[0].slice(0, 10) : '';
|
|
77
|
+
const hash = git.hash ? git.hash.slice(0, 7) : '';
|
|
78
|
+
const branchDisplay = git.branch.slice(0, 20); // Limita branch
|
|
79
|
+
const gitLine = `${C.gray}git:${C.reset}${C.secondary}${branchDisplay}${dirty}${C.reset} ${C.dim}${hash}${C.reset} ${C.gray}by${C.reset} ${C.dim}${author}${C.reset}`;
|
|
80
|
+
console.log(row(gitLine));
|
|
63
81
|
}
|
|
64
82
|
|
|
65
|
-
//
|
|
66
|
-
console.log(`${C.gray}
|
|
83
|
+
// Divisor
|
|
84
|
+
console.log(`${C.gray}β βββββββββββββββββββββββββββββββββββββββββββββββββββββββ£${C.reset}`);
|
|
85
|
+
|
|
86
|
+
// Config: mode, profile, java, encoding
|
|
87
|
+
const cfg: string[] = [];
|
|
88
|
+
if (command) cfg.push(`${C.primary}${command}${C.reset}`);
|
|
89
|
+
if (profile) cfg.push(`${C.warning}${profile}${C.reset}`);
|
|
90
|
+
const jv = this.getJavaVersion();
|
|
91
|
+
if (jv) cfg.push(`${C.info}java:${jv}${C.reset}`);
|
|
92
|
+
if (encoding) cfg.push(`${C.gray}${encoding}${C.reset}`);
|
|
93
|
+
|
|
94
|
+
if (cfg.length) {
|
|
95
|
+
const sep = `${C.gray} β ${C.reset}`;
|
|
96
|
+
const cfgLine = `${C.dim}mode${C.reset} : ${cfg.join(sep)}`;
|
|
97
|
+
console.log(row(cfgLine));
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// Memory
|
|
101
|
+
const mem = process.memoryUsage();
|
|
102
|
+
const mb = Math.round((mem.heapUsed || mem.rss || 0) / 1024 / 1024);
|
|
103
|
+
console.log(row(`${C.dim}mem${C.reset} : ${mb}MB ${C.gray}heap${C.reset}`));
|
|
104
|
+
|
|
105
|
+
// OS
|
|
106
|
+
const plat = process.platform === 'win32' ? 'windows' : process.platform;
|
|
107
|
+
console.log(row(`${C.dim}os${C.reset} : ${plat} ${C.gray}|${C.reset} ${process.arch}`));
|
|
108
|
+
|
|
109
|
+
// RodapΓ©
|
|
110
|
+
console.log(`${C.gray}ββββββββββββββββββββββββββββββββββββββββββββββββββββββββ${C.reset}`);
|
|
67
111
|
console.log();
|
|
68
112
|
}
|
|
69
113
|
|
|
114
|
+
private static getGitStatus(): { dirty: boolean; modified: number } {
|
|
115
|
+
try {
|
|
116
|
+
const result = Bun.spawnSync(["git", "status", "--porcelain"]);
|
|
117
|
+
const lines = result.stdout.toString().trim().split('\n').filter(l => l.trim());
|
|
118
|
+
return { dirty: lines.length > 0, modified: lines.length };
|
|
119
|
+
} catch {
|
|
120
|
+
return { dirty: false, modified: 0 };
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
private static getJavaVersion(): string | null {
|
|
125
|
+
try {
|
|
126
|
+
const javaBin = process.env.JAVA_HOME
|
|
127
|
+
? `${process.env.JAVA_HOME}/bin/java`
|
|
128
|
+
: 'java';
|
|
129
|
+
const result = Bun.spawnSync([javaBin, "-version"]);
|
|
130
|
+
const output = (result.stderr?.toString() || result.stdout?.toString() || '');
|
|
131
|
+
const match = output.match(/version "?(\d+(?:\.\d+)?)/);
|
|
132
|
+
if (match) {
|
|
133
|
+
const v = match[1];
|
|
134
|
+
// Check for DCEVM
|
|
135
|
+
if (output.toLowerCase().includes('dcevm') || output.toLowerCase().includes('jbr')) {
|
|
136
|
+
return `${v}+dcevm`;
|
|
137
|
+
}
|
|
138
|
+
return v;
|
|
139
|
+
}
|
|
140
|
+
return null;
|
|
141
|
+
} catch {
|
|
142
|
+
return null;
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
70
146
|
// SeΓ§Γ΅es com divisΓ³rias clean
|
|
71
147
|
static section(title: string) {
|
|
72
148
|
console.log(`${C.gray}ββ ${C.white}${C.bold}${title}${C.reset}`);
|
|
@@ -112,18 +188,46 @@ export class Logger {
|
|
|
112
188
|
console.log(`${C.gray}β${C.reset} ${C.warning}β ${C.reset} ${msg}`);
|
|
113
189
|
}
|
|
114
190
|
|
|
115
|
-
static
|
|
116
|
-
console.log(`${C.gray}β${C.reset} ${C.
|
|
191
|
+
static debug(msg: string) {
|
|
192
|
+
console.log(`${C.gray}β${C.reset} ${C.gray}π ${msg}${C.reset}`);
|
|
117
193
|
}
|
|
118
194
|
|
|
119
|
-
static
|
|
120
|
-
console.log(`${C.gray}β${C.reset} ${C.
|
|
195
|
+
static step(msg: string) {
|
|
196
|
+
console.log(`${C.gray}β${C.reset} ${C.gray}βΈ ${msg}${C.reset}`);
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
static log(msg: string) {
|
|
200
|
+
console.log(msg);
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
static dim(msg: string) {
|
|
204
|
+
console.log(`${C.dim}${msg}${C.reset}`);
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
static newline() {
|
|
208
|
+
console.log();
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
static watcher(msg: string, _type?: string) {
|
|
212
|
+
console.log(`${C.gray}β${C.reset} ${C.secondary}β${C.reset} ${C.dim}watch${C.reset} ${C.gray}:${C.reset} ${msg}`);
|
|
121
213
|
}
|
|
122
214
|
|
|
123
215
|
static watch(msg: string) {
|
|
124
216
|
console.log(`${C.gray}β${C.reset} ${C.secondary}β${C.reset} ${C.dim}watch${C.reset} ${C.gray}:${C.reset} ${msg}`);
|
|
125
217
|
}
|
|
126
218
|
|
|
219
|
+
static process(msg: string) {
|
|
220
|
+
console.log(`${C.gray}β${C.reset} ${C.primary}βΈ${C.reset} ${C.dim}process${C.reset} ${C.gray}:${C.reset} ${msg}`);
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
static build(msg: string) {
|
|
224
|
+
console.log(`${C.gray}β${C.reset} ${C.primary}βΈ${C.reset} ${C.dim}build${C.reset} ${C.gray}:${C.reset} ${msg}`);
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
static server(msg: string) {
|
|
228
|
+
console.log(`${C.gray}β${C.reset} ${C.primary}βΈ${C.reset} ${C.dim}server${C.reset} ${C.gray}:${C.reset} ${msg}`);
|
|
229
|
+
}
|
|
230
|
+
|
|
127
231
|
static hotswap(msg: string) {
|
|
128
232
|
console.log(`${C.gray}β${C.reset} ${C.secondary}β»${C.reset} ${C.dim}hotswap${C.reset} ${C.gray}:${C.reset} ${msg}`);
|
|
129
233
|
}
|
|
@@ -161,11 +265,6 @@ export class Logger {
|
|
|
161
265
|
};
|
|
162
266
|
}
|
|
163
267
|
|
|
164
|
-
// Linha em branco
|
|
165
|
-
static newline() {
|
|
166
|
-
console.log();
|
|
167
|
-
}
|
|
168
|
-
|
|
169
268
|
// DivisΓ³ria simples
|
|
170
269
|
static divider() {
|
|
171
270
|
console.log(`${C.gray}ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ€${C.reset}`);
|
|
@@ -180,8 +279,8 @@ export class Logger {
|
|
|
180
279
|
// Helper para contexto git
|
|
181
280
|
static getGitContext(): { branch: string; author: string; hash: string } {
|
|
182
281
|
try {
|
|
183
|
-
const branch = Bun.spawnSync(["git", "rev-parse", "abbrev-ref", "HEAD"]).stdout.toString().trim();
|
|
184
|
-
const author = Bun.spawnSync(["git", "log", "-1", "format=%an"]).stdout.toString().trim();
|
|
282
|
+
const branch = Bun.spawnSync(["git", "rev-parse", "--abbrev-ref", "HEAD"]).stdout.toString().trim();
|
|
283
|
+
const author = Bun.spawnSync(["git", "log", "-1", "--format=%an"]).stdout.toString().trim();
|
|
185
284
|
const hash = Bun.spawnSync(["git", "rev-parse", "--short", "HEAD"]).stdout.toString().trim();
|
|
186
285
|
return { branch, author, hash };
|
|
187
286
|
} catch {
|
|
@@ -190,6 +289,10 @@ export class Logger {
|
|
|
190
289
|
}
|
|
191
290
|
|
|
192
291
|
// Filtros de noise (mantidos)
|
|
292
|
+
// Controle de rate limiting para hotswap
|
|
293
|
+
private static lastHotswapTime = 0;
|
|
294
|
+
private static hotswapCount = 0;
|
|
295
|
+
|
|
193
296
|
static isSystemNoise(line: string): boolean {
|
|
194
297
|
const noise = [
|
|
195
298
|
"Using CATALINA_", "Using JRE_HOME", "Using CLASSPATH",
|
|
@@ -198,10 +301,22 @@ export class Logger {
|
|
|
198
301
|
"SLF4J: ", "Discovered plugins:",
|
|
199
302
|
"enhanced with plugin initialization", "Hotswap ready",
|
|
200
303
|
"autoHotswap.delay", "watchResources=false",
|
|
304
|
+
// HotswapAgent noise
|
|
305
|
+
"TreeWatcherNIO", "HOTSWAP AGENT", "org.hotswap.agent",
|
|
306
|
+
// Jersey/JAX-RS noise
|
|
307
|
+
"org.glassfish.jersey", "The (sub)resource method",
|
|
308
|
+
// Tomcat noise
|
|
309
|
+
"org.apache.catalina", "org.apache.jasper",
|
|
201
310
|
];
|
|
202
311
|
return noise.some(n => line.includes(n));
|
|
203
312
|
}
|
|
204
313
|
|
|
314
|
+
static isEssential(line: string): boolean {
|
|
315
|
+
return line.includes("SEVERE") || line.includes("ERROR") || line.includes("Exception") ||
|
|
316
|
+
line.includes("Caused by") || line.includes("at ") || line.includes("... ") ||
|
|
317
|
+
line.includes("Server startup in") || line.includes("HOTSWAP AGENT:");
|
|
318
|
+
}
|
|
319
|
+
|
|
205
320
|
// SumarizaΓ§Γ£o de logs do Tomcat (simplificada)
|
|
206
321
|
static summarize(line: string): string {
|
|
207
322
|
if (this.isSystemNoise(line)) return "";
|
|
@@ -214,9 +329,17 @@ export class Logger {
|
|
|
214
329
|
return `${C.success}ready ${C.gray}in ${C.white}${seconds}s${C.reset}`;
|
|
215
330
|
}
|
|
216
331
|
|
|
217
|
-
// Hotswap
|
|
332
|
+
// Hotswap com rate limiting (evita spam)
|
|
218
333
|
if (line.includes("HOTSWAP AGENT") && line.includes("RELOAD")) {
|
|
219
|
-
|
|
334
|
+
const now = Date.now();
|
|
335
|
+
if (now - this.lastHotswapTime < 2000) {
|
|
336
|
+
this.hotswapCount++;
|
|
337
|
+
return ""; // Silencia se dentro de 2s
|
|
338
|
+
}
|
|
339
|
+
this.lastHotswapTime = now;
|
|
340
|
+
const count = this.hotswapCount > 0 ? ` ${C.gray}(${this.hotswapCount} more)${C.reset}` : "";
|
|
341
|
+
this.hotswapCount = 0;
|
|
342
|
+
return `${C.secondary}β» hotswap${C.reset}${count}`;
|
|
220
343
|
}
|
|
221
344
|
|
|
222
345
|
// Erros de compilaΓ§Γ£o
|
|
@@ -231,11 +354,293 @@ export class Logger {
|
|
|
231
354
|
return `${C.error}β ${C.gray}${line.slice(0, 80)}${C.reset}`;
|
|
232
355
|
}
|
|
233
356
|
|
|
234
|
-
// Warnings
|
|
235
|
-
if (line.includes("WARNING")) {
|
|
357
|
+
// Warnings (filtra noise conhecido)
|
|
358
|
+
if (line.includes("WARNING") || line.includes("ADVERTΓNCIA")) {
|
|
359
|
+
if (this.isSystemNoise(line)) return "";
|
|
236
360
|
return `${C.warning}β ${C.gray}${line.slice(0, 80)}${C.reset}`;
|
|
237
361
|
}
|
|
238
362
|
|
|
239
363
|
return "";
|
|
240
364
|
}
|
|
365
|
+
|
|
366
|
+
// ========== Tomcat Log Formatting ==========
|
|
367
|
+
|
|
368
|
+
private static tomcatNoisePatterns = [
|
|
369
|
+
/^Using CATALINA_/,
|
|
370
|
+
/^Using JRE_HOME/,
|
|
371
|
+
/^Using CLASSPATH/,
|
|
372
|
+
/^Using CATALINA_OPTS/,
|
|
373
|
+
/^NOTE: Picked up JDK_JAVA_OPTIONS/,
|
|
374
|
+
/^HOTSWAP AGENT:.*Plugin.*initialized in ClassLoader/,
|
|
375
|
+
/^HOTSWAP AGENT:.*Registering directory/,
|
|
376
|
+
/^HOTSWAP AGENT:.*WARNING.*TreeWatcherNIO.*Unable to watch/,
|
|
377
|
+
/^HOTSWAP AGENT:.*INFO.*TreeWatcherNIO/,
|
|
378
|
+
/^HOTSWAP AGENT:.*INFO.*PluginRegistry.*Discovered plugins/,
|
|
379
|
+
/^HOTSWAP AGENT:.*INFO.*HotswapAgent.*Loading Hotswap agent/,
|
|
380
|
+
/^HOTSWAP AGENT:.*INFO.*TomcatPlugin.*Tomcat plugin initialized/,
|
|
381
|
+
/^\d{2}-[A-Za-z]+-\d{4}\s+\d{2}:\d{2}:\d{2}\.\d+\s+(INFORMAΓΓES|INFO)\s+\[main\].*VersionLoggerListener/,
|
|
382
|
+
/^\d{2}-[A-Za-z]+-\d{4}\s+\d{2}:\d{2}:\d{2}\.\d+\s+(INFORMAΓΓES|INFO)\s+\[main\].*AprLifecycleListener/,
|
|
383
|
+
/^\d{2}-[A-Za-z]+-\d{4}\s+\d{2}:\d{2}:\d{2}\.\d+\s+(INFORMAΓΓES|INFO)\s+\[main\].*Command line argument/,
|
|
384
|
+
/^\d{2}-[A-Za-z]+-\d{4}\s+\d{2}:\d{2}:\d{2}\.\d+\s+(INFORMAΓΓES|INFO)\s+\[main\].*CATALINA_BASE/,
|
|
385
|
+
/^\d{2}-[A-Za-z]+-\d{4}\s+\d{2}:\d{2}:\d{2}\.\d+\s+(INFORMAΓΓES|INFO)\s+\[main\].*CATALINA_HOME/,
|
|
386
|
+
/^\d{2}-[A-Za-z]+-\d{4}\s+\d{2}:\d{2}:\d{2}\.\d+\s+(INFORMAΓΓES|INFO)\s+\[main\].*Server version/,
|
|
387
|
+
/^\d{2}-[A-Za-z]+-\d{4}\s+\d{2}:\d{2}:\d{2}\.\d+\s+(INFORMAΓΓES|INFO)\s+\[main\].*Server built/,
|
|
388
|
+
/^\d{2}-[A-Za-z]+-\d{4}\s+\d{2}:\d{2}:\d{2}\.\d+\s+(INFORMAΓΓES|INFO)\s+\[main\].*OS Name/,
|
|
389
|
+
/^\d{2}-[A-Za-z]+-\d{4}\s+\d{2}:\d{2}:\d{2}\.\d+\s+(INFORMAΓΓES|INFO)\s+\[main\].*OS Version/,
|
|
390
|
+
/^\d{2}-[A-Za-z]+-\d{4}\s+\d{2}:\d{2}:\d{2}\.\d+\s+(INFORMAΓΓES|INFO)\s+\[main\].*Architecture/,
|
|
391
|
+
/^\d{2}-[A-Za-z]+-\d{4}\s+\d{2}:\d{2}:\d{2}\.\d+\s+(INFORMAΓΓES|INFO)\s+\[main\].*Java Home/,
|
|
392
|
+
/^\d{2}-[A-Za-z]+-\d{4}\s+\d{2}:\d{2}:\d{2}\.\d+\s+(INFORMAΓΓES|INFO)\s+\[main\].*JVM Version/,
|
|
393
|
+
/^\d{2}-[A-Za-z]+-\d{4}\s+\d{2}:\d{2}:\d{2}\.\d+\s+(INFORMAΓΓES|INFO)\s+\[main\].*JVM Vendor/,
|
|
394
|
+
/^\d{2}-[A-Za-z]+-\d{4}\s+\d{2}:\d{2}:\d{2}\.\d+\s+(INFORMAΓΓES|INFO)\s+\[main\].*Loaded Apache Tomcat Native/,
|
|
395
|
+
/^\d{2}-[A-Za-z]+-\d{4}\s+\d{2}:\d{2}:\d{2}\.\d+\s+(INFORMAΓΓES|INFO)\s+\[main\].*APR capabilities/,
|
|
396
|
+
/^\d{2}-[A-Za-z]+-\d{4}\s+\d{2}:\d{2}:\d{2}\.\d+\s+(INFORMAΓΓES|INFO)\s+\[main\].*APR\/OpenSSL configuration/,
|
|
397
|
+
/^\d{2}-[A-Za-z]+-\d{4}\s+\d{2}:\d{2}:\d{2}\.\d+\s+(INFORMAΓΓES|INFO)\s+\[main\].*OpenSSL successfully initialized/,
|
|
398
|
+
/^\d{2}-[A-Za-z]+-\d{4}\s+\d{2}:\d{2}:\d{2}\.\d+\s+(INFORMAΓΓES|INFO)\s+\[main\].*Server initialization in/,
|
|
399
|
+
/^\d{2}-[A-Za-z]+-\d{4}\s+\d{2}:\d{2}:\d{2}\.\d+\s+(INFORMAΓΓES|INFO)\s+\[main\].*Starting service/,
|
|
400
|
+
/^\d{2}-[A-Za-z]+-\d{4}\s+\d{2}:\d{2}:\d{2}\.\d+\s+(INFORMAΓΓES|INFO)\s+\[main\].*Starting Servlet engine/,
|
|
401
|
+
/^ParallelWebappClassLoader/,
|
|
402
|
+
/^context:/,
|
|
403
|
+
/^delegate:/,
|
|
404
|
+
/^-+> Parent Classloader/,
|
|
405
|
+
/^java\.net\.URLClassLoader/,
|
|
406
|
+
];
|
|
407
|
+
|
|
408
|
+
static isTomcatNoise(line: string): boolean {
|
|
409
|
+
return this.tomcatNoisePatterns.some(p => p.test(line));
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
static formatTomcatLog(line: string): string {
|
|
413
|
+
const cleanLine = line.trim();
|
|
414
|
+
if (!cleanLine) return "";
|
|
415
|
+
|
|
416
|
+
// Silencia noise completamente
|
|
417
|
+
if (this.isTomcatNoise(cleanLine)) return "";
|
|
418
|
+
|
|
419
|
+
// HOTSWAP AGENT: Loading Hotswap agent X.X.X
|
|
420
|
+
if (cleanLine.includes("HOTSWAP AGENT") && cleanLine.includes("Loading Hotswap agent")) {
|
|
421
|
+
const versionMatch = cleanLine.match(/Loading Hotswap agent ([\d.]+)/);
|
|
422
|
+
if (versionMatch) {
|
|
423
|
+
return `${C.gray}β${C.reset} ${C.secondary}β»${C.reset} ${C.dim}HotswapAgent v${versionMatch[1]}${C.reset}`;
|
|
424
|
+
}
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
// HOTSWAP AGENT: Discovered plugins
|
|
428
|
+
if (cleanLine.includes("HOTSWAP AGENT") && cleanLine.includes("Discovered plugins")) {
|
|
429
|
+
return `${C.gray}β${C.reset} ${C.secondary}β»${C.reset} ${C.dim}plugins loaded${C.reset}`;
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
// Server initialization
|
|
433
|
+
const initMatch = cleanLine.match(/Server initialization in \[(\d+)\] milliseconds/);
|
|
434
|
+
if (initMatch) {
|
|
435
|
+
return `${C.gray}β${C.reset} ${C.success}β${C.reset} ${C.dim}initialized in ${initMatch[1]}ms${C.reset}`;
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
// Starting service [Catalina]
|
|
439
|
+
const serviceMatch = cleanLine.match(/Starting service \[(\w+)\]/);
|
|
440
|
+
if (serviceMatch) {
|
|
441
|
+
return `${C.gray}β${C.reset} ${C.primary}βΈ${C.reset} ${C.dim}starting ${serviceMatch[1].toLowerCase()}${C.reset}`;
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
// Starting Servlet engine
|
|
445
|
+
if (cleanLine.includes("Starting Servlet engine")) {
|
|
446
|
+
const versionMatch = cleanLine.match(/Apache Tomcat\/([\d.]+)/);
|
|
447
|
+
if (versionMatch) {
|
|
448
|
+
return `${C.gray}β${C.reset} ${C.primary}βΈ${C.reset} ${C.dim}Tomcat ${versionMatch[1]}${C.reset}`;
|
|
449
|
+
}
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
// Deploy directory
|
|
453
|
+
const deployMatch = cleanLine.match(/deployDirectory.*webapps[\\/]([^'"\]]+)/);
|
|
454
|
+
if (deployMatch) {
|
|
455
|
+
return `${C.gray}β${C.reset} ${C.primary}βΈ${C.reset} ${C.dim}deploying ${deployMatch[1]}${C.reset}`;
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
// Server startup in ( jΓ‘ temos no summarize mas reforΓ§a aqui )
|
|
459
|
+
const startupMatch = cleanLine.match(/Server startup in.*?([\d,]+)\s*ms/);
|
|
460
|
+
if (startupMatch) {
|
|
461
|
+
const time = startupMatch[1].replace(",", "");
|
|
462
|
+
const seconds = (parseInt(time) / 1000).toFixed(1);
|
|
463
|
+
return `${C.gray}β${C.reset} ${C.success}β${C.reset} ${C.dim}ready in ${C.white}${seconds}s${C.reset}`;
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
// Tomcat versΓ£o info (sumarizado)
|
|
467
|
+
const tomcatVersionMatch = cleanLine.match(/Server version number:\s+([\d.]+)/);
|
|
468
|
+
if (tomcatVersionMatch) {
|
|
469
|
+
return `${C.gray}β${C.reset} ${C.dim}Tomcat ${tomcatVersionMatch[1]}${C.reset}`;
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
// JVM info sumarizada
|
|
473
|
+
const jvmMatch = cleanLine.match(/JVM Version:\s+([\d._]+)/);
|
|
474
|
+
if (jvmMatch) {
|
|
475
|
+
return `${C.gray}β${C.reset} ${C.dim}JVM ${jvmMatch[1]}${C.reset}`;
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
// Protocol handler init
|
|
479
|
+
const protocolMatch = cleanLine.match(/Initializing ProtocolHandler \["(.+?)"\]/);
|
|
480
|
+
if (protocolMatch) {
|
|
481
|
+
return `${C.gray}β${C.reset} ${C.primary}βΈ${C.reset} ${C.dim}${protocolMatch[1]}${C.reset}`;
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
// Erros e warnings que passaram pelo filtro
|
|
485
|
+
if (cleanLine.includes("SEVERE") || cleanLine.includes("ERROR")) {
|
|
486
|
+
return `${C.gray}β${C.reset} ${C.error}β${C.reset} ${C.gray}${cleanLine.slice(0, 80)}${C.reset}`;
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
if (cleanLine.includes("WARNING") || cleanLine.includes("ADVERTΓNCIA")) {
|
|
490
|
+
return `${C.gray}β${C.reset} ${C.warning}β ${C.reset} ${C.gray}${cleanLine.slice(0, 80)}${C.reset}`;
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
// Outros logs INFO - mostra resumido
|
|
494
|
+
const infoMatch = cleanLine.match(/(?:INFORMAΓΓES|INFO)\s+\[.*?\]\s+(.+)/);
|
|
495
|
+
if (infoMatch) {
|
|
496
|
+
const msg = infoMatch[1].trim();
|
|
497
|
+
if (msg.length > 0 && !this.isTomcatNoise(msg)) {
|
|
498
|
+
return `${C.gray}β${C.reset} ${C.dim}${msg.slice(0, 70)}${C.reset}`;
|
|
499
|
+
}
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
return "";
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
// ========== Build Log Formatting (Maven/Gradle) ==========
|
|
506
|
+
|
|
507
|
+
private static buildNoisePatterns = [
|
|
508
|
+
/^\[INFO\]\s+Scanning for projects/,
|
|
509
|
+
/^\[INFO\]\s+Using the MultiThreadedBuilder/,
|
|
510
|
+
/^\[INFO\]\s+---\s+.*\s+---$/,
|
|
511
|
+
/^\[INFO\]\s+T+E+\s*$/,
|
|
512
|
+
/^\[INFO\]\s+BUILD\s+SUCCESS/i,
|
|
513
|
+
/^\[INFO\]\s+BUILD\s+FAILURE/i,
|
|
514
|
+
/^\[INFO\]\s+Total time:/,
|
|
515
|
+
/^\[INFO\]\s+Finished at:/,
|
|
516
|
+
/^\[INFO\]\s+Final Memory:/,
|
|
517
|
+
/^\[INFO\]\s+http:\/\/cwiki\.apache\.org/,
|
|
518
|
+
/^\[INFO\]\s+-> \[Help 1\]/,
|
|
519
|
+
/^\[INFO\]\s+Re-run Maven using/,
|
|
520
|
+
/^\[INFO\]\s+To see the full stack trace/,
|
|
521
|
+
/^\[INFO\]\s+For more information about the errors/,
|
|
522
|
+
/^\[ERROR\]\s+To see the full stack trace/,
|
|
523
|
+
/^\[ERROR\]\s+Re-run Maven using/,
|
|
524
|
+
/^\[ERROR\]\s+For more information/,
|
|
525
|
+
/^\[ERROR\]\s+-> \[Help 1\]/,
|
|
526
|
+
/^\[WARNING\]\s+It is highly recommended/,
|
|
527
|
+
/^\[WARNING\]\s+For this reason, future Maven/,
|
|
528
|
+
/^\[WARNING\]\s+\[HELP, sysprop:version/,
|
|
529
|
+
/^\[WARNING\]\s+Some problems were encountered/,
|
|
530
|
+
/^\[WARNING\]\s+'dependencies\.dependency/,
|
|
531
|
+
/Building .*war/,
|
|
532
|
+
/from pom\.xml/,
|
|
533
|
+
/\[ war \]/,
|
|
534
|
+
/^\s*$/,
|
|
535
|
+
];
|
|
536
|
+
|
|
537
|
+
private static gradleNoisePatterns = [
|
|
538
|
+
/^> Task/,
|
|
539
|
+
/^Download/,
|
|
540
|
+
/^Expiring/,
|
|
541
|
+
/^BUILD/,
|
|
542
|
+
/^\d+ actionable task/,
|
|
543
|
+
];
|
|
544
|
+
|
|
545
|
+
static isBuildNoise(line: string): boolean {
|
|
546
|
+
return this.buildNoisePatterns.some(p => p.test(line)) ||
|
|
547
|
+
this.gradleNoisePatterns.some(p => p.test(line));
|
|
548
|
+
}
|
|
549
|
+
|
|
550
|
+
// Acumulador de erro de compilaΓ§Γ£o (para pegar mensagens multi-linha)
|
|
551
|
+
private static pendingError: { file: string; line: string; msg: string } | null = null;
|
|
552
|
+
|
|
553
|
+
static formatBuildLog(line: string, buildTool: 'maven' | 'gradle' = 'maven'): string {
|
|
554
|
+
const cleanLine = line.trim();
|
|
555
|
+
if (!cleanLine) return "";
|
|
556
|
+
|
|
557
|
+
// Maven: [ERROR] /path/file.java:[123,45] error message
|
|
558
|
+
const mavenErrorMatch = cleanLine.match(/^\[ERROR\]\s+(.+\.java):\[(\d+),\d+\]\s*(.+)/);
|
|
559
|
+
if (mavenErrorMatch) {
|
|
560
|
+
const [, file, lineNum, msg] = mavenErrorMatch;
|
|
561
|
+
const shortFile = file.split(/[/\\]/).pop() || file;
|
|
562
|
+
return `${C.gray}β${C.reset} ${C.error}β${C.reset} ${C.white}${shortFile}${C.gray}:${lineNum}${C.reset} ${C.error}${msg.slice(0, 60)}${C.reset}`;
|
|
563
|
+
}
|
|
564
|
+
|
|
565
|
+
// Maven: [ERROR] COMPILATION ERROR / BUILD FAILURE (tΓtulo)
|
|
566
|
+
if (cleanLine.match(/^\[(ERROR|INFO)\]\s+(COMPILATION ERROR|BUILD FAILURE)/)) {
|
|
567
|
+
return `${C.gray}β${C.reset} ${C.error}β COMPILATION FAILED${C.reset}`;
|
|
568
|
+
}
|
|
569
|
+
|
|
570
|
+
// Maven: [WARNING] 'dependencies.dependency...'
|
|
571
|
+
if (cleanLine.match(/^\[WARNING\]\s+'dependencies\.dependency/)) {
|
|
572
|
+
const match = cleanLine.match(/'dependencies\.dependency\.[\w:]+'\s+(.+)/);
|
|
573
|
+
if (match) {
|
|
574
|
+
return `${C.gray}β${C.reset} ${C.warning}β ${C.reset} ${C.gray}${match[1].slice(0, 60)}${C.reset}`;
|
|
575
|
+
}
|
|
576
|
+
}
|
|
577
|
+
|
|
578
|
+
// Maven: [WARNING] The POM for ... is invalid
|
|
579
|
+
const invalidPomMatch = cleanLine.match(/^\[WARNING\]\s+The POM for (.+?) is invalid/);
|
|
580
|
+
if (invalidPomMatch) {
|
|
581
|
+
return `${C.gray}β${C.reset} ${C.warning}β ${C.reset} ${C.gray}Invalid POM: ${invalidPomMatch[1].slice(0, 50)}${C.reset}`;
|
|
582
|
+
}
|
|
583
|
+
|
|
584
|
+
// Maven: [INFO] Compiling N source files
|
|
585
|
+
const compilingMatch = cleanLine.match(/^\[INFO\]\s+Compiling\s+(\d+)\s+source/);
|
|
586
|
+
if (compilingMatch) {
|
|
587
|
+
return `${C.gray}β${C.reset} ${C.primary}βΈ${C.reset} ${C.dim}compiling ${C.white}${compilingMatch[1]}${C.reset} ${C.dim}files${C.reset}`;
|
|
588
|
+
}
|
|
589
|
+
|
|
590
|
+
// Maven: [INFO] /path/file.java: Some input files use or override a deprecated API
|
|
591
|
+
const deprecatedMatch = cleanLine.match(/^\[INFO\]\s+(.+\.java):\s+Some input files use (.+)/);
|
|
592
|
+
if (deprecatedMatch) {
|
|
593
|
+
const shortFile = deprecatedMatch[1].split(/[/\\]/).pop() || deprecatedMatch[1];
|
|
594
|
+
const type = deprecatedMatch[2].includes('removal') ? 'deprecated (removal)' : 'deprecated';
|
|
595
|
+
return `${C.gray}β${C.reset} ${C.warning}β ${C.reset} ${C.gray}${shortFile} uses ${type}${C.reset}`;
|
|
596
|
+
}
|
|
597
|
+
|
|
598
|
+
// Maven: [INFO] Changes detected - recompiling
|
|
599
|
+
if (cleanLine.match(/^\[INFO\]\s+Changes detected/)) {
|
|
600
|
+
return `${C.gray}β${C.reset} ${C.primary}βΈ${C.reset} ${C.dim}changes detected, recompiling${C.reset}`;
|
|
601
|
+
}
|
|
602
|
+
|
|
603
|
+
// Maven: [INFO] Copying N resources
|
|
604
|
+
const resourcesMatch = cleanLine.match(/^\[INFO\]\s+Copying\s+(\d+)\s+resources?/);
|
|
605
|
+
if (resourcesMatch) {
|
|
606
|
+
return `${C.gray}β${C.reset} ${C.dim}copying ${C.white}${resourcesMatch[1]}${C.reset} ${C.dim}resources${C.reset}`;
|
|
607
|
+
}
|
|
608
|
+
|
|
609
|
+
// Maven: [INFO] Deleting ...
|
|
610
|
+
const deletingMatch = cleanLine.match(/^\[INFO\]\s+Deleting\s+(.+)/);
|
|
611
|
+
if (deletingMatch) {
|
|
612
|
+
return `${C.gray}β${C.reset} ${C.dim}cleaning target directory${C.reset}`;
|
|
613
|
+
}
|
|
614
|
+
|
|
615
|
+
// Maven: [INFO] skip non existing resourceDirectory
|
|
616
|
+
if (cleanLine.match(/^\[INFO\]\s+skip non existing/)) {
|
|
617
|
+
return ""; // Silencia
|
|
618
|
+
}
|
|
619
|
+
|
|
620
|
+
// Gradle: > Task :name
|
|
621
|
+
const gradleTaskMatch = cleanLine.match(/^> Task :(.+)/);
|
|
622
|
+
if (gradleTaskMatch) {
|
|
623
|
+
return `${C.gray}β${C.reset} ${C.primary}βΈ${C.reset} ${C.dim}${gradleTaskMatch[1]}${C.reset}`;
|
|
624
|
+
}
|
|
625
|
+
|
|
626
|
+
// Gradle errors
|
|
627
|
+
const gradleErrorMatch = cleanLine.match(/^(.+\.java):(\d+):\s*(error|warning):\s*(.+)/);
|
|
628
|
+
if (gradleErrorMatch) {
|
|
629
|
+
const [, file, lineNum, level, msg] = gradleErrorMatch;
|
|
630
|
+
const shortFile = file.split(/[/\\]/).pop() || file;
|
|
631
|
+
const icon = level === 'error' ? C.error + 'β' + C.reset : C.warning + 'β ' + C.reset;
|
|
632
|
+
return `${C.gray}β${C.reset} ${icon} ${C.white}${shortFile}${C.gray}:${lineNum}${C.reset} ${C.error}${msg.slice(0, 60)}${C.reset}`;
|
|
633
|
+
}
|
|
634
|
+
|
|
635
|
+
// Se nΓ£o for nenhum padrΓ£o conhecido e nΓ£o for noise, retorna formatado como info
|
|
636
|
+
if (!this.isBuildNoise(cleanLine)) {
|
|
637
|
+
// Remove prefixos [INFO], [WARNING], [ERROR] genΓ©ricos
|
|
638
|
+
const clean = cleanLine.replace(/^\[(INFO|WARNING|ERROR)\]\s*/, '');
|
|
639
|
+
if (clean.length > 0 && !this.isBuildNoise(clean)) {
|
|
640
|
+
return `${C.gray}β${C.reset} ${C.dim}${clean.slice(0, 70)}${C.reset}`;
|
|
641
|
+
}
|
|
642
|
+
}
|
|
643
|
+
|
|
644
|
+
return "";
|
|
645
|
+
}
|
|
241
646
|
}
|