@archznn/xavva 2.7.0 → 2.8.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/package.json +4 -2
- package/src/commands/CompletionCommand.ts +212 -0
- package/src/commands/ConfigCommand.ts +184 -0
- package/src/commands/HealthCommand.ts +302 -0
- package/src/commands/HelpCommand.ts +27 -0
- package/src/commands/HistoryCommand.ts +49 -0
- package/src/commands/InitCommand.ts +148 -0
- package/src/commands/RedoCommand.ts +36 -0
- package/src/di/container.ts +23 -0
- package/src/index.ts +39 -1
- package/src/services/EmbeddedTomcatService.ts +62 -18
- package/src/services/HistoryService.ts +73 -0
- package/src/services/NotificationService.ts +145 -0
- package/src/services/TomcatService.ts +52 -23
- package/src/types/args.ts +15 -0
- package/src/utils/ProgressBar.ts +182 -0
package/src/index.ts
CHANGED
|
@@ -23,7 +23,8 @@ async function main() {
|
|
|
23
23
|
const commandNames = [
|
|
24
24
|
"deploy", "build", "start", "dev", "doctor", "run",
|
|
25
25
|
"debug", "logs", "docs", "audit", "profiles",
|
|
26
|
-
"deps", "tomcat", "encoding"
|
|
26
|
+
"deps", "tomcat", "encoding", "init", "config",
|
|
27
|
+
"history", "redo", "health", "completion", "help"
|
|
27
28
|
];
|
|
28
29
|
const commandName = positionals.find(p => commandNames.includes(p)) || "deploy";
|
|
29
30
|
|
|
@@ -96,15 +97,52 @@ async function main() {
|
|
|
96
97
|
registry.register("encoding", commands.encoding);
|
|
97
98
|
registry.register("deploy", commands.deploy);
|
|
98
99
|
registry.register("dev", commands.dev);
|
|
100
|
+
registry.register("init", commands.init);
|
|
101
|
+
registry.register("config", commands.config);
|
|
102
|
+
registry.register("history", commands.history);
|
|
103
|
+
registry.register("redo", commands.redo);
|
|
104
|
+
registry.register("health", commands.health);
|
|
105
|
+
registry.register("completion", commands.completion);
|
|
99
106
|
|
|
100
107
|
// Configura flags específicas
|
|
101
108
|
if (commandName === "debug") values.debug = true;
|
|
102
109
|
if (commandName === "run") values.debug = false;
|
|
103
110
|
|
|
111
|
+
// Registra comando no histórico antes da execução
|
|
112
|
+
const startTime = Date.now();
|
|
113
|
+
let success = true;
|
|
114
|
+
|
|
104
115
|
try {
|
|
105
116
|
await registry.execute(commandName, config, values as CLIArguments, positionals);
|
|
106
117
|
} catch (error) {
|
|
118
|
+
success = false;
|
|
107
119
|
await ErrorHandler.getInstance().handle(error, { phase: "command-execution", command: commandName });
|
|
120
|
+
} finally {
|
|
121
|
+
// Salva no histórico
|
|
122
|
+
const duration = (Date.now() - startTime) / 1000;
|
|
123
|
+
const filteredPositionals = positionals.filter(p => p !== commandName && !commandNames.includes(p));
|
|
124
|
+
services.historyService.add({
|
|
125
|
+
command: commandName,
|
|
126
|
+
args: [...filteredPositionals, ...Object.entries(values)
|
|
127
|
+
.filter(([, v]) => v !== undefined && typeof v !== "object")
|
|
128
|
+
.flatMap(([k, v]) => [`--${k}`, String(v)])],
|
|
129
|
+
success,
|
|
130
|
+
duration
|
|
131
|
+
}).catch(() => { /* ignore history errors */ });
|
|
132
|
+
|
|
133
|
+
// Envia notificação para comandos longos
|
|
134
|
+
if (duration > 5 && commandName !== "logs" && commandName !== "history") {
|
|
135
|
+
const { NotificationService } = await import("./services/NotificationService");
|
|
136
|
+
if (success) {
|
|
137
|
+
if (commandName === "build" || commandName === "deploy") {
|
|
138
|
+
NotificationService.buildSuccess(duration);
|
|
139
|
+
} else if (commandName === "start") {
|
|
140
|
+
NotificationService.deployComplete(config.project.appName);
|
|
141
|
+
}
|
|
142
|
+
} else {
|
|
143
|
+
NotificationService.buildFailed(`Comando ${commandName} falhou`);
|
|
144
|
+
}
|
|
145
|
+
}
|
|
108
146
|
}
|
|
109
147
|
}
|
|
110
148
|
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { Logger } from "../utils/ui";
|
|
2
|
+
import { ProgressBar, ThemedSpinner } from "../utils/ProgressBar";
|
|
2
3
|
import { VERSIONS, getAvailableTomcatVersions, isSupportedTomcatVersion } from "../config/versions";
|
|
3
4
|
import {
|
|
4
5
|
existsSync,
|
|
@@ -347,27 +348,69 @@ export class EmbeddedTomcatService {
|
|
|
347
348
|
* Download com progresso
|
|
348
349
|
*/
|
|
349
350
|
private async downloadFile(url: string, destPath: string): Promise<void> {
|
|
350
|
-
const
|
|
351
|
+
const response = await fetch(url);
|
|
351
352
|
|
|
352
|
-
|
|
353
|
-
|
|
353
|
+
if (!response.ok) {
|
|
354
|
+
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
const totalSize = parseInt(response.headers.get("content-length") || "0");
|
|
358
|
+
|
|
359
|
+
// Se temos content-length, usar progress bar
|
|
360
|
+
if (totalSize > 0) {
|
|
361
|
+
const progress = new ProgressBar({
|
|
362
|
+
title: `Baixando Tomcat ${this.version}`,
|
|
363
|
+
total: totalSize,
|
|
364
|
+
width: 25
|
|
365
|
+
});
|
|
354
366
|
|
|
355
|
-
|
|
356
|
-
|
|
367
|
+
const reader = response.body?.getReader();
|
|
368
|
+
if (!reader) {
|
|
369
|
+
throw new Error("Response body não disponível");
|
|
357
370
|
}
|
|
358
371
|
|
|
359
|
-
const
|
|
360
|
-
|
|
372
|
+
const chunks: Uint8Array[] = [];
|
|
373
|
+
let received = 0;
|
|
361
374
|
|
|
362
|
-
|
|
375
|
+
while (true) {
|
|
376
|
+
const { done, value } = await reader.read();
|
|
377
|
+
if (done) break;
|
|
378
|
+
|
|
379
|
+
chunks.push(value);
|
|
380
|
+
received += value.length;
|
|
381
|
+
progress.update(received);
|
|
382
|
+
}
|
|
363
383
|
|
|
364
|
-
|
|
384
|
+
progress.complete();
|
|
365
385
|
|
|
366
|
-
|
|
386
|
+
// Concatena chunks e salva
|
|
387
|
+
const allChunks = new Uint8Array(received);
|
|
388
|
+
let position = 0;
|
|
389
|
+
for (const chunk of chunks) {
|
|
390
|
+
allChunks.set(chunk, position);
|
|
391
|
+
position += chunk.length;
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
await fsPromises.writeFile(destPath, allChunks);
|
|
395
|
+
|
|
396
|
+
const sizeMB = (received / 1024 / 1024).toFixed(1);
|
|
367
397
|
Logger.info("Download", `${sizeMB} MB baixados`);
|
|
368
|
-
}
|
|
369
|
-
spinner
|
|
370
|
-
|
|
398
|
+
} else {
|
|
399
|
+
// Sem content-length, usar spinner temático
|
|
400
|
+
const spinner = new ThemedSpinner();
|
|
401
|
+
const stop = spinner.start(`Baixando Tomcat ${this.version}`, "dots", "download");
|
|
402
|
+
|
|
403
|
+
try {
|
|
404
|
+
const buffer = await response.arrayBuffer();
|
|
405
|
+
await fsPromises.writeFile(destPath, Buffer.from(buffer));
|
|
406
|
+
stop(true);
|
|
407
|
+
|
|
408
|
+
const sizeMB = (buffer.byteLength / 1024 / 1024).toFixed(1);
|
|
409
|
+
Logger.info("Download", `${sizeMB} MB baixados`);
|
|
410
|
+
} catch (error) {
|
|
411
|
+
stop(false);
|
|
412
|
+
throw error;
|
|
413
|
+
}
|
|
371
414
|
}
|
|
372
415
|
}
|
|
373
416
|
|
|
@@ -375,13 +418,14 @@ export class EmbeddedTomcatService {
|
|
|
375
418
|
* Extrai arquivo de arquivos (ZIP ou tar.gz)
|
|
376
419
|
*/
|
|
377
420
|
private async extractZip(zipPath: string, destDir: string): Promise<void> {
|
|
378
|
-
const spinner =
|
|
421
|
+
const spinner = new ThemedSpinner();
|
|
422
|
+
const stop = spinner.start("Extraindo arquivos", "pulse", "build");
|
|
379
423
|
|
|
380
424
|
return new Promise((resolve, reject) => {
|
|
381
425
|
const cmd = getExtractCommand(zipPath, destDir);
|
|
382
426
|
|
|
383
427
|
if (!cmd) {
|
|
384
|
-
|
|
428
|
+
stop(false);
|
|
385
429
|
reject(new Error(`Formato de arquivo não suportado: ${path.extname(zipPath)}`));
|
|
386
430
|
return;
|
|
387
431
|
}
|
|
@@ -390,16 +434,16 @@ export class EmbeddedTomcatService {
|
|
|
390
434
|
|
|
391
435
|
extractProcess.on("close", (code) => {
|
|
392
436
|
if (code === 0) {
|
|
393
|
-
|
|
437
|
+
stop(true);
|
|
394
438
|
resolve();
|
|
395
439
|
} else {
|
|
396
|
-
|
|
440
|
+
stop(false);
|
|
397
441
|
reject(new Error(`Falha ao extrair (código ${code})`));
|
|
398
442
|
}
|
|
399
443
|
});
|
|
400
444
|
|
|
401
445
|
extractProcess.on("error", (err) => {
|
|
402
|
-
|
|
446
|
+
stop(false);
|
|
403
447
|
reject(err);
|
|
404
448
|
});
|
|
405
449
|
});
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import { mkdir, readFile, writeFile } from "fs/promises";
|
|
2
|
+
import { existsSync } from "fs";
|
|
3
|
+
import { join } from "path";
|
|
4
|
+
import { homedir } from "os";
|
|
5
|
+
|
|
6
|
+
export interface HistoryEntry {
|
|
7
|
+
command: string;
|
|
8
|
+
args: string[];
|
|
9
|
+
timestamp: string;
|
|
10
|
+
success: boolean;
|
|
11
|
+
duration?: number;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
const HISTORY_FILE = join(homedir(), ".xavva", "history.json");
|
|
15
|
+
const MAX_HISTORY_SIZE = 100;
|
|
16
|
+
|
|
17
|
+
export class HistoryService {
|
|
18
|
+
private async ensureDir(): Promise<void> {
|
|
19
|
+
const dir = join(homedir(), ".xavva");
|
|
20
|
+
if (!existsSync(dir)) {
|
|
21
|
+
await mkdir(dir, { recursive: true });
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
private async load(): Promise<HistoryEntry[]> {
|
|
26
|
+
try {
|
|
27
|
+
await this.ensureDir();
|
|
28
|
+
const data = await readFile(HISTORY_FILE, "utf-8");
|
|
29
|
+
return JSON.parse(data);
|
|
30
|
+
} catch {
|
|
31
|
+
return [];
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
private async save(entries: HistoryEntry[]): Promise<void> {
|
|
36
|
+
await this.ensureDir();
|
|
37
|
+
// Keep only last MAX_HISTORY_SIZE entries
|
|
38
|
+
const trimmed = entries.slice(-MAX_HISTORY_SIZE);
|
|
39
|
+
await writeFile(HISTORY_FILE, JSON.stringify(trimmed, null, 2));
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
async add(entry: Omit<HistoryEntry, "timestamp">): Promise<void> {
|
|
43
|
+
const entries = await this.load();
|
|
44
|
+
entries.push({
|
|
45
|
+
...entry,
|
|
46
|
+
timestamp: new Date().toISOString()
|
|
47
|
+
});
|
|
48
|
+
await this.save(entries);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
async getRecent(limit: number = 10): Promise<HistoryEntry[]> {
|
|
52
|
+
const entries = await this.load();
|
|
53
|
+
return entries.slice(-limit).reverse();
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
async getLast(): Promise<HistoryEntry | null> {
|
|
57
|
+
const entries = await this.load();
|
|
58
|
+
return entries.length > 0 ? entries[entries.length - 1] : null;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
async clear(): Promise<void> {
|
|
62
|
+
await this.save([]);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
async getStats(): Promise<{ total: number; successful: number; failed: number }> {
|
|
66
|
+
const entries = await this.load();
|
|
67
|
+
return {
|
|
68
|
+
total: entries.length,
|
|
69
|
+
successful: entries.filter(e => e.success).length,
|
|
70
|
+
failed: entries.filter(e => !e.success).length
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
}
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
import { exec } from "child_process";
|
|
2
|
+
import { promisify } from "util";
|
|
3
|
+
import { platform } from "os";
|
|
4
|
+
|
|
5
|
+
const execAsync = promisify(exec);
|
|
6
|
+
|
|
7
|
+
export interface NotificationOptions {
|
|
8
|
+
title: string;
|
|
9
|
+
message: string;
|
|
10
|
+
type?: "info" | "success" | "warning" | "error";
|
|
11
|
+
sound?: boolean;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export class NotificationService {
|
|
15
|
+
private static enabled = true;
|
|
16
|
+
|
|
17
|
+
static disable(): void {
|
|
18
|
+
this.enabled = false;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
static enable(): void {
|
|
22
|
+
this.enabled = true;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
static async notify(options: NotificationOptions): Promise<void> {
|
|
26
|
+
if (!this.enabled) return;
|
|
27
|
+
|
|
28
|
+
const { title, message, type = "info", sound = false } = options;
|
|
29
|
+
|
|
30
|
+
try {
|
|
31
|
+
if (platform() === "win32") {
|
|
32
|
+
await this.notifyWindows(title, message, type, sound);
|
|
33
|
+
} else if (platform() === "darwin") {
|
|
34
|
+
await this.notifyMacOS(title, message, type, sound);
|
|
35
|
+
} else {
|
|
36
|
+
await this.notifyLinux(title, message, type, sound);
|
|
37
|
+
}
|
|
38
|
+
} catch {
|
|
39
|
+
// Silently fail - notifications are not critical
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
private static async notifyWindows(
|
|
44
|
+
title: string,
|
|
45
|
+
message: string,
|
|
46
|
+
type: string,
|
|
47
|
+
sound: boolean
|
|
48
|
+
): Promise<void> {
|
|
49
|
+
// Use PowerShell notification
|
|
50
|
+
const iconMap: Record<string, string> = {
|
|
51
|
+
info: "Information",
|
|
52
|
+
success: "Information",
|
|
53
|
+
warning: "Warning",
|
|
54
|
+
error: "Error"
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
const psScript = `
|
|
58
|
+
Add-Type -AssemblyName System.Windows.Forms
|
|
59
|
+
$notify = New-Object System.Windows.Forms.NotifyIcon
|
|
60
|
+
$notify.Icon = [System.Drawing.SystemIcons]::${iconMap[type]}
|
|
61
|
+
$notify.BalloonTipTitle = "${title.replace(/"/g, '""')}"
|
|
62
|
+
$notify.BalloonTipText = "${message.replace(/"/g, '""')}"
|
|
63
|
+
$notify.BalloonTipIcon = "${iconMap[type]}"
|
|
64
|
+
$notify.Visible = $true
|
|
65
|
+
$notify.ShowBalloonTip(5000)
|
|
66
|
+
`;
|
|
67
|
+
|
|
68
|
+
await execAsync(`powershell -Command "${psScript}"`);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
private static async notifyMacOS(
|
|
72
|
+
title: string,
|
|
73
|
+
message: string,
|
|
74
|
+
_type: string,
|
|
75
|
+
sound: boolean
|
|
76
|
+
): Promise<void> {
|
|
77
|
+
const soundFlag = sound ? '"\\"\\""' : "";
|
|
78
|
+
await execAsync(`osascript -e 'display notification "${message.replace(/"/g, '\\"')}" with title "${title.replace(/"/g, '\\"')}"${soundFlag}'`);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
private static async notifyLinux(
|
|
82
|
+
title: string,
|
|
83
|
+
message: string,
|
|
84
|
+
type: string,
|
|
85
|
+
_sound: boolean
|
|
86
|
+
): Promise<void> {
|
|
87
|
+
const urgencyMap: Record<string, string> = {
|
|
88
|
+
info: "normal",
|
|
89
|
+
success: "normal",
|
|
90
|
+
warning: "normal",
|
|
91
|
+
error: "critical"
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
const iconMap: Record<string, string> = {
|
|
95
|
+
info: "dialog-information",
|
|
96
|
+
success: "dialog-information",
|
|
97
|
+
warning: "dialog-warning",
|
|
98
|
+
error: "dialog-error"
|
|
99
|
+
};
|
|
100
|
+
|
|
101
|
+
try {
|
|
102
|
+
await execAsync(`notify-send -u ${urgencyMap[type]} -i ${iconMap[type]} "${title}" "${message}"`);
|
|
103
|
+
} catch {
|
|
104
|
+
// Fallback: try zenity
|
|
105
|
+
try {
|
|
106
|
+
await execAsync(`zenity --info --title="${title}" --text="${message}" --timeout=5`);
|
|
107
|
+
} catch {
|
|
108
|
+
// No notification available
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// Convenience methods
|
|
114
|
+
static buildSuccess(duration: number): Promise<void> {
|
|
115
|
+
return this.notify({
|
|
116
|
+
title: "Xavva - Build Completo",
|
|
117
|
+
message: `Build finalizado com sucesso em ${duration.toFixed(1)}s`,
|
|
118
|
+
type: "success"
|
|
119
|
+
});
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
static buildFailed(error: string): Promise<void> {
|
|
123
|
+
return this.notify({
|
|
124
|
+
title: "Xavva - Build Falhou",
|
|
125
|
+
message: error.slice(0, 100),
|
|
126
|
+
type: "error"
|
|
127
|
+
});
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
static deployComplete(appName: string): Promise<void> {
|
|
131
|
+
return this.notify({
|
|
132
|
+
title: "Xavva - Deploy Completo",
|
|
133
|
+
message: `${appName} implantado com sucesso`,
|
|
134
|
+
type: "success"
|
|
135
|
+
});
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
static watchReady(): Promise<void> {
|
|
139
|
+
return this.notify({
|
|
140
|
+
title: "Xavva - Watch Mode",
|
|
141
|
+
message: "Monitorando alterações nos arquivos...",
|
|
142
|
+
type: "info"
|
|
143
|
+
});
|
|
144
|
+
}
|
|
145
|
+
}
|
|
@@ -2,6 +2,7 @@ import type { TomcatConfig, AppConfig } from "../types";
|
|
|
2
2
|
import { getHotswapAgentUrl, VERSIONS } from "../config/versions";
|
|
3
3
|
import { NetworkError } from "../errors/XavvaError";
|
|
4
4
|
import { Logger } from "../utils/ui";
|
|
5
|
+
import { ProgressBar, ThemedSpinner } from "../utils/ProgressBar";
|
|
5
6
|
import type { Subprocess } from "bun";
|
|
6
7
|
import { ProjectService } from "./ProjectService";
|
|
7
8
|
import { existsSync, mkdirSync, writeFileSync, statSync, promises as fs } from "fs";
|
|
@@ -146,47 +147,75 @@ export class TomcatService {
|
|
|
146
147
|
try {
|
|
147
148
|
if (!existsSync(agentDir)) mkdirSync(agentDir, { recursive: true });
|
|
148
149
|
|
|
149
|
-
|
|
150
|
-
const
|
|
151
|
-
|
|
152
|
-
Logger.debug(`URL: ${url}`);
|
|
153
|
-
Logger.debug(`Destino: ${agentPath}`);
|
|
154
|
-
|
|
155
|
-
const response = await fetch(url, {
|
|
156
|
-
redirect: "follow",
|
|
157
|
-
});
|
|
150
|
+
const url = getHotswapAgentUrl();
|
|
151
|
+
const response = await fetch(url, { redirect: "follow" });
|
|
158
152
|
|
|
159
153
|
if (!response.ok) {
|
|
160
154
|
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
|
|
161
155
|
}
|
|
162
156
|
|
|
163
|
-
const
|
|
164
|
-
Logger.debug(`Content-Length: ${contentLength || "unknown"}`);
|
|
157
|
+
const totalSize = parseInt(response.headers.get("content-length") || "0");
|
|
165
158
|
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
159
|
+
if (totalSize > 0) {
|
|
160
|
+
// Usar progress bar
|
|
161
|
+
const progress = new ProgressBar({
|
|
162
|
+
title: `Baixando HotswapAgent v${VERSIONS.HOTSWAP_AGENT.VERSION}`,
|
|
163
|
+
total: totalSize,
|
|
164
|
+
width: 25
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
const reader = response.body?.getReader();
|
|
168
|
+
if (!reader) throw new Error("Response body não disponível");
|
|
169
|
+
|
|
170
|
+
const chunks: Uint8Array[] = [];
|
|
171
|
+
let received = 0;
|
|
172
|
+
|
|
173
|
+
while (true) {
|
|
174
|
+
const { done, value } = await reader.read();
|
|
175
|
+
if (done) break;
|
|
176
|
+
|
|
177
|
+
chunks.push(value);
|
|
178
|
+
received += value.length;
|
|
179
|
+
progress.update(received);
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
progress.complete();
|
|
183
|
+
|
|
184
|
+
// Concatena e salva
|
|
185
|
+
const allChunks = new Uint8Array(received);
|
|
186
|
+
let position = 0;
|
|
187
|
+
for (const chunk of chunks) {
|
|
188
|
+
allChunks.set(chunk, position);
|
|
189
|
+
position += chunk.length;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
await fs.writeFile(agentPath, allChunks);
|
|
193
|
+
} else {
|
|
194
|
+
// Sem content-length, usar spinner
|
|
195
|
+
const spinner = new ThemedSpinner();
|
|
196
|
+
const stop = spinner.start(`Baixando HotswapAgent v${VERSIONS.HOTSWAP_AGENT.VERSION}`, "dots", "download");
|
|
197
|
+
|
|
198
|
+
const buffer = await response.arrayBuffer();
|
|
199
|
+
writeFileSync(agentPath, Buffer.from(buffer));
|
|
200
|
+
|
|
201
|
+
stop(true);
|
|
171
202
|
}
|
|
172
203
|
|
|
173
|
-
writeFileSync(agentPath, Buffer.from(buffer));
|
|
174
|
-
|
|
175
204
|
// Verifica se foi escrito corretamente
|
|
176
205
|
const stats = statSync(agentPath);
|
|
177
|
-
|
|
206
|
+
if (stats.size < 1000) {
|
|
207
|
+
throw new Error(`Arquivo muito pequeno (${stats.size} bytes)`);
|
|
208
|
+
}
|
|
178
209
|
|
|
179
|
-
Logger.success(`HotswapAgent v${VERSIONS.HOTSWAP_AGENT.VERSION}
|
|
210
|
+
Logger.success(`HotswapAgent v${VERSIONS.HOTSWAP_AGENT.VERSION} instalado!`);
|
|
180
211
|
return agentPath;
|
|
181
212
|
} catch (e: any) {
|
|
182
|
-
|
|
183
|
-
Logger.warn("Usando hot swap padrão da JVM.");
|
|
213
|
+
Logger.warn("Falha ao baixar HotswapAgent. Usando hot swap padrão da JVM.");
|
|
184
214
|
|
|
185
215
|
// Limpa arquivo parcial se existir
|
|
186
216
|
if (existsSync(agentPath)) {
|
|
187
217
|
try {
|
|
188
218
|
await fs.unlink(agentPath);
|
|
189
|
-
Logger.debug("Arquivo parcial removido");
|
|
190
219
|
} catch {}
|
|
191
220
|
}
|
|
192
221
|
|
package/src/types/args.ts
CHANGED
|
@@ -109,6 +109,21 @@ export interface EncodingCommandArgs extends BaseArgs, EncodingArgs {
|
|
|
109
109
|
// Herda de EncodingArgs
|
|
110
110
|
}
|
|
111
111
|
|
|
112
|
+
// ===== Args dos Novos Comandos UX =====
|
|
113
|
+
export interface ConfigArgs extends BaseArgs {
|
|
114
|
+
interactive?: boolean;
|
|
115
|
+
i?: boolean;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
export interface HistoryArgs extends BaseArgs {
|
|
119
|
+
clear?: boolean;
|
|
120
|
+
limit?: string;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
export interface CompletionArgs extends BaseArgs {
|
|
124
|
+
shell?: string;
|
|
125
|
+
}
|
|
126
|
+
|
|
112
127
|
// ===== CLIArguments Legado (para compatibilidade) =====
|
|
113
128
|
// Será gradualmente removido
|
|
114
129
|
export interface CLIArguments extends
|