@archznn/xavva 2.6.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.
@@ -0,0 +1,151 @@
1
+ /**
2
+ * Argumentos de linha de comando tipados por contexto
3
+ * Substitui CLIArguments monolítico
4
+ */
5
+
6
+ // ===== Args Base (comuns a todos os comandos) =====
7
+ export interface BaseArgs {
8
+ help?: boolean;
9
+ version?: boolean;
10
+ verbose?: boolean;
11
+ quiet?: boolean;
12
+ }
13
+
14
+ // ===== Args de Configuração de Projeto =====
15
+ export interface ProjectArgs {
16
+ tool?: string;
17
+ name?: string;
18
+ profile?: string;
19
+ encoding?: string;
20
+ "no-build"?: boolean;
21
+ clean?: boolean;
22
+ war?: boolean;
23
+ cache?: boolean;
24
+ }
25
+
26
+ // ===== Args de Configuração do Tomcat =====
27
+ export interface TomcatArgs {
28
+ path?: string;
29
+ port?: string;
30
+ "tomcat-version"?: string;
31
+ yes?: boolean;
32
+ }
33
+
34
+ // ===== Args de Debug/Desenvolvimento =====
35
+ export interface DebugArgs {
36
+ debug?: boolean;
37
+ watch?: boolean;
38
+ tui?: boolean;
39
+ dp?: string;
40
+ }
41
+
42
+ // ===== Args de Análise =====
43
+ export interface AnalysisArgs {
44
+ grep?: string;
45
+ scan?: boolean;
46
+ fix?: boolean;
47
+ output?: string;
48
+ strict?: boolean;
49
+ "update-safe"?: boolean;
50
+ }
51
+
52
+ // ===== Args de Encoding =====
53
+ export interface EncodingArgs {
54
+ from?: string;
55
+ to?: string;
56
+ backup?: boolean;
57
+ "dry-run"?: boolean;
58
+ force?: boolean;
59
+ src?: string;
60
+ }
61
+
62
+ // ===== Args Específicas de Comandos =====
63
+ export interface DeployArgs extends BaseArgs, ProjectArgs, TomcatArgs, DebugArgs {
64
+ incremental?: boolean;
65
+ changedFiles?: string[];
66
+ }
67
+
68
+ export interface BuildArgs extends BaseArgs, ProjectArgs {
69
+ // Herda de ProjectArgs
70
+ }
71
+
72
+ export interface StartArgs extends BaseArgs, TomcatArgs, DebugArgs {
73
+ // Herda de TomcatArgs e DebugArgs
74
+ }
75
+
76
+ export interface RunArgs extends BaseArgs, DebugArgs {
77
+ // Positional: className
78
+ }
79
+
80
+ export interface LogsArgs extends BaseArgs {
81
+ grep?: string;
82
+ }
83
+
84
+ export interface AuditArgs extends BaseArgs, AnalysisArgs {
85
+ // Herda de AnalysisArgs
86
+ }
87
+
88
+ export interface DepsArgs extends BaseArgs, AnalysisArgs {
89
+ // Herda de AnalysisArgs
90
+ }
91
+
92
+ export interface DocsArgs extends BaseArgs {
93
+ output?: string;
94
+ }
95
+
96
+ export interface ProfilesArgs extends BaseArgs {
97
+ // Sem args específicas
98
+ }
99
+
100
+ export interface DoctorArgs extends BaseArgs {
101
+ fix?: boolean;
102
+ }
103
+
104
+ export interface TomcatCommandArgs extends BaseArgs, TomcatArgs {
105
+ "tomcat-action"?: string;
106
+ }
107
+
108
+ export interface EncodingCommandArgs extends BaseArgs, EncodingArgs {
109
+ // Herda de EncodingArgs
110
+ }
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
+
127
+ // ===== CLIArguments Legado (para compatibilidade) =====
128
+ // Será gradualmente removido
129
+ export interface CLIArguments extends
130
+ BaseArgs,
131
+ ProjectArgs,
132
+ TomcatArgs,
133
+ DebugArgs,
134
+ AnalysisArgs,
135
+ EncodingArgs {
136
+ // Campos adicionais para compatibilidade
137
+ [key: string]: unknown;
138
+ }
139
+
140
+ // ===== Type Guards =====
141
+ export function isDeployArgs(args: CLIArguments): args is DeployArgs {
142
+ return "incremental" in args || "watch" in args;
143
+ }
144
+
145
+ export function isBuildArgs(args: CLIArguments): args is BuildArgs {
146
+ return "tool" in args || "clean" in args;
147
+ }
148
+
149
+ export function isEncodingArgs(args: CLIArguments): args is EncodingCommandArgs {
150
+ return "from" in args || "to" in args;
151
+ }
@@ -62,6 +62,12 @@ export interface CLIArguments {
62
62
  war?: boolean;
63
63
  cache?: boolean;
64
64
  changedFiles?: string[];
65
+ from?: string;
66
+ to?: string;
67
+ backup?: boolean;
68
+ "dry-run"?: boolean;
69
+ force?: boolean;
70
+ src?: string;
65
71
  }
66
72
 
67
73
  export interface CommandContext {
@@ -0,0 +1,7 @@
1
+ /**
2
+ * Barrel export para todos os tipos
3
+ */
4
+
5
+ export * from "./config";
6
+ export * from "./endpoint";
7
+ export * from "./args";
@@ -0,0 +1,221 @@
1
+ /**
2
+ * Utilitários para manipulação de paths
3
+ * Centraliza lógica comum de normalização e resolução de caminhos
4
+ */
5
+
6
+ import path from "path";
7
+
8
+ export class PathUtils {
9
+ /**
10
+ * Normaliza separadores de path para forward slash
11
+ */
12
+ static normalizeSeparators(filePath: string): string {
13
+ return filePath.replace(/\\/g, "/");
14
+ }
15
+
16
+ /**
17
+ * Converte caminho relativo para absoluto a partir do cwd
18
+ */
19
+ static resolveFromCwd(relativePath: string): string {
20
+ return path.resolve(process.cwd(), relativePath);
21
+ }
22
+
23
+ /**
24
+ * Obtém caminho relativo a partir do diretório raiz do projeto
25
+ */
26
+ static relativeToProject(absolutePath: string): string {
27
+ return path.relative(process.cwd(), absolutePath);
28
+ }
29
+
30
+ /**
31
+ * Verifica se caminho está dentro do diretório src/main/java (Maven) ou equivalente
32
+ */
33
+ static isSourceFile(filePath: string): boolean {
34
+ const normalized = this.normalizeSeparators(filePath);
35
+ return /src\/(main|test)\/(java|resources)/.test(normalized);
36
+ }
37
+
38
+ /**
39
+ * Extrai package de um arquivo Java baseado no caminho
40
+ */
41
+ static extractPackageFromPath(javaFilePath: string): string | null {
42
+ const normalized = this.normalizeSeparators(javaFilePath);
43
+
44
+ // Procura por src/main/java ou src/test/java
45
+ const match = normalized.match(/src\/(?:main|test)\/java\/(.*)\/[^/]+\.java$/);
46
+ if (match) {
47
+ return match[1].replace(/\//g, ".");
48
+ }
49
+
50
+ return null;
51
+ }
52
+
53
+ /**
54
+ * Converte caminho de .java para .class
55
+ */
56
+ static javaToClassPath(javaPath: string): string {
57
+ return javaPath.replace(/\.java$/i, ".class");
58
+ }
59
+
60
+ /**
61
+ * Converte caminho relativo do source para caminho no diretório de classes
62
+ */
63
+ static sourceToClassesPath(
64
+ sourcePath: string,
65
+ classesDir: string,
66
+ sourceRoots: string[] = ["src/main/java", "src/test/java", "src"]
67
+ ): string {
68
+ const normalized = this.normalizeSeparators(sourcePath);
69
+
70
+ for (const root of sourceRoots) {
71
+ const normalizedRoot = this.normalizeSeparators(root);
72
+ const index = normalized.indexOf(normalizedRoot);
73
+ if (index !== -1) {
74
+ const relativePart = normalized.slice(index + normalizedRoot.length + 1);
75
+ const classFile = this.javaToClassPath(relativePart);
76
+ return path.join(classesDir, classFile);
77
+ }
78
+ }
79
+
80
+ // Fallback: assume que é relativo ao classesDir
81
+ return path.join(classesDir, path.basename(this.javaToClassPath(sourcePath)));
82
+ }
83
+
84
+ /**
85
+ * Encontra o webapp root a partir de um caminho de recurso
86
+ */
87
+ static findWebappRoot(resourcePath: string): string | null {
88
+ const normalized = this.normalizeSeparators(resourcePath);
89
+ const parts = normalized.split("/");
90
+
91
+ const webappIndex = parts.indexOf("webapp");
92
+ const webContentIndex = parts.indexOf("WebContent");
93
+ const rootIndex = webappIndex !== -1 ? webappIndex : webContentIndex;
94
+
95
+ if (rootIndex !== -1) {
96
+ return parts.slice(0, rootIndex + 1).join("/");
97
+ }
98
+
99
+ return null;
100
+ }
101
+
102
+ /**
103
+ * Obtém caminho relativo dentro do webapp
104
+ */
105
+ static getWebappRelativePath(resourcePath: string): string | null {
106
+ const normalized = this.normalizeSeparators(resourcePath);
107
+ const parts = normalized.split("/");
108
+
109
+ const webappIndex = parts.indexOf("webapp");
110
+ const webContentIndex = parts.indexOf("WebContent");
111
+ const rootIndex = webappIndex !== -1 ? webappIndex : webContentIndex;
112
+
113
+ if (rootIndex !== -1 && rootIndex < parts.length - 1) {
114
+ return parts.slice(rootIndex + 1).join("/");
115
+ }
116
+
117
+ return null;
118
+ }
119
+
120
+ /**
121
+ * Verifica se é arquivo de recurso web (JSP, HTML, etc)
122
+ */
123
+ static isWebResource(filePath: string): boolean {
124
+ return /\.(jsp|html|htm|css|js|xml|properties|json|png|jpg|jpeg|gif|svg|ico)$/i.test(filePath);
125
+ }
126
+
127
+ /**
128
+ * Verifica se é arquivo Java
129
+ */
130
+ static isJavaFile(filePath: string): boolean {
131
+ return /\.java$/i.test(filePath);
132
+ }
133
+
134
+ /**
135
+ * Verifica se é arquivo de build/configuração
136
+ */
137
+ static isBuildConfig(filePath: string): boolean {
138
+ return /(pom\.xml|build\.gradle|build\.gradle\.kts|settings\.gradle)$/i.test(filePath);
139
+ }
140
+
141
+ /**
142
+ * Combina múltiplos paths de forma segura
143
+ */
144
+ static combinePaths(...parts: string[]): string {
145
+ return path.join(...parts);
146
+ }
147
+
148
+ /**
149
+ * Obtém diretório do arquivo
150
+ */
151
+ static getDir(filePath: string): string {
152
+ return path.dirname(filePath);
153
+ }
154
+
155
+ /**
156
+ * Obtém nome do arquivo
157
+ */
158
+ static getFilename(filePath: string): string {
159
+ return path.basename(filePath);
160
+ }
161
+
162
+ /**
163
+ * Obtém extensão do arquivo
164
+ */
165
+ static getExtension(filePath: string): string {
166
+ return path.extname(filePath).toLowerCase();
167
+ }
168
+
169
+ /**
170
+ * Remove extensão do arquivo
171
+ */
172
+ static removeExtension(filePath: string): string {
173
+ const ext = path.extname(filePath);
174
+ return ext ? filePath.slice(0, -ext.length) : filePath;
175
+ }
176
+
177
+ /**
178
+ * Verifica se caminho está dentro de diretório ignorado
179
+ */
180
+ static isIgnoredPath(filePath: string, ignoredDirs: string[] = []): boolean {
181
+ const normalized = this.normalizeSeparators(filePath);
182
+ const defaultIgnored = ["target", "build", "node_modules", ".git", ".xavva"];
183
+ const allIgnored = [...defaultIgnored, ...ignoredDirs];
184
+
185
+ return allIgnored.some(dir => {
186
+ // Match exato ou como diretório
187
+ const pattern = new RegExp(`(^|/)${dir}($|/)`);
188
+ return pattern.test(normalized);
189
+ });
190
+ }
191
+
192
+ /**
193
+ * Resolve caminho de contexto WAR
194
+ */
195
+ static resolveContextPath(appName: string): string {
196
+ return appName.replace(/\.war$/i, "");
197
+ }
198
+ }
199
+
200
+ // Exporta funções standalone para conveniência
201
+ export const {
202
+ normalizeSeparators,
203
+ resolveFromCwd,
204
+ relativeToProject,
205
+ isSourceFile,
206
+ extractPackageFromPath,
207
+ javaToClassPath,
208
+ sourceToClassesPath,
209
+ findWebappRoot,
210
+ getWebappRelativePath,
211
+ isWebResource,
212
+ isJavaFile,
213
+ isBuildConfig,
214
+ combinePaths,
215
+ getDir,
216
+ getFilename,
217
+ getExtension,
218
+ removeExtension,
219
+ isIgnoredPath,
220
+ resolveContextPath,
221
+ } = PathUtils;
@@ -0,0 +1,182 @@
1
+ import { Logger } from "./ui";
2
+
3
+ export interface ProgressBarOptions {
4
+ title: string;
5
+ total: number;
6
+ width?: number;
7
+ showPercentage?: boolean;
8
+ showEta?: boolean;
9
+ }
10
+
11
+ export class ProgressBar {
12
+ private current = 0;
13
+ private startTime: number;
14
+ private options: Required<ProgressBarOptions>;
15
+ private isComplete = false;
16
+
17
+ constructor(options: ProgressBarOptions) {
18
+ this.options = {
19
+ width: 30,
20
+ showPercentage: true,
21
+ showEta: true,
22
+ ...options
23
+ };
24
+ this.startTime = Date.now();
25
+ }
26
+
27
+ update(current: number): void {
28
+ if (this.isComplete) return;
29
+ this.current = Math.min(current, this.options.total);
30
+ this.render();
31
+ }
32
+
33
+ increment(amount = 1): void {
34
+ this.update(this.current + amount);
35
+ }
36
+
37
+ complete(): void {
38
+ if (this.isComplete) return;
39
+ this.current = this.options.total;
40
+ this.isComplete = true;
41
+ this.render();
42
+ process.stdout.write("\n");
43
+ }
44
+
45
+ private render(): void {
46
+ const { title, total, width, showPercentage, showEta } = this.options;
47
+ const ratio = this.current / total;
48
+ const filled = Math.floor(ratio * width);
49
+ const empty = width - filled;
50
+
51
+ const bar = "█".repeat(filled) + "░".repeat(empty);
52
+ const percentage = Math.floor(ratio * 100);
53
+
54
+ let line = `${Logger.C.gray}│${Logger.C.reset} ${Logger.C.primary}▸${Logger.C.reset} ${Logger.C.dim}${title}${Logger.C.reset} `;
55
+ line += `${Logger.C.primary}[${bar}]${Logger.C.reset}`;
56
+
57
+ if (showPercentage) {
58
+ line += ` ${Logger.C.white}${percentage}%${Logger.C.reset}`;
59
+ }
60
+
61
+ if (showEta && this.current > 0) {
62
+ const elapsed = Date.now() - this.startTime;
63
+ const eta = Math.ceil((elapsed / this.current) * (total - this.current) / 1000);
64
+ line += ` ${Logger.C.gray}(ETA: ${eta}s)${Logger.C.reset}`;
65
+ }
66
+
67
+ // Clear line and write new content
68
+ process.stdout.write(`\r${line}`);
69
+ }
70
+ }
71
+
72
+ // Spinner especializado para diferentes operações
73
+ export class ThemedSpinner {
74
+ private static frames = {
75
+ default: ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"],
76
+ dots: ["⣾", "⣽", "⣻", "⢿", "⡿", "⣟", "⣯", "⣷"],
77
+ line: ["-", "\\", "|", "/"],
78
+ arrow: ["←", "↖", "↑", "↗", "→", "↘", "↓", "↙"],
79
+ pulse: ["█", "▉", "▊", "▋", "▌", "▍", "▎", "▏"],
80
+ moon: ["🌑", "🌒", "🌓", "🌔", "🌕", "🌖", "🌗", "🌘"]
81
+ };
82
+
83
+ private static colors = {
84
+ default: Logger.C.primary,
85
+ build: Logger.C.warning,
86
+ download: Logger.C.info,
87
+ deploy: Logger.C.success,
88
+ watch: Logger.C.secondary
89
+ };
90
+
91
+ private timer?: Timer;
92
+ private frameIndex = 0;
93
+
94
+ start(
95
+ message: string,
96
+ theme: keyof typeof ThemedSpinner.frames = "default",
97
+ color: keyof typeof ThemedSpinner.colors = "default"
98
+ ): { stop: (success?: boolean) => void } {
99
+ const frames = ThemedSpinner.frames[theme];
100
+ const colorCode = ThemedSpinner.colors[color];
101
+
102
+ process.stdout.write(`${Logger.C.gray}│${Logger.C.reset} `);
103
+ process.stdout.write("\x1B[?25l");
104
+
105
+ this.timer = setInterval(() => {
106
+ const frame = frames[this.frameIndex];
107
+ process.stdout.write(`\r${Logger.C.gray}│${Logger.C.reset} ${colorCode}${frame}${Logger.C.reset} ${Logger.C.dim}${message}${Logger.C.reset}`);
108
+ this.frameIndex = (this.frameIndex + 1) % frames.length;
109
+ }, 80);
110
+
111
+ return {
112
+ stop: (success = true) => {
113
+ if (this.timer) {
114
+ clearInterval(this.timer);
115
+ }
116
+ process.stdout.write("\x1B[?25h");
117
+ const icon = success ? `${Logger.C.success}✓${Logger.C.reset}` : `${Logger.C.error}✗${Logger.C.reset}`;
118
+ console.log(`\r${Logger.C.gray}│${Logger.C.reset} ${icon} ${message}`);
119
+ }
120
+ };
121
+ }
122
+ }
123
+
124
+ // Função auxiliar para downloads com progresso
125
+ export async function downloadWithProgress(
126
+ url: string,
127
+ destination: string,
128
+ title: string
129
+ ): Promise<void> {
130
+ const response = await fetch(url);
131
+ if (!response.ok) {
132
+ throw new Error(`Failed to download: ${response.statusText}`);
133
+ }
134
+
135
+ const totalSize = parseInt(response.headers.get("content-length") || "0");
136
+ if (!totalSize) {
137
+ // Sem content-length, usar spinner simples
138
+ const spinner = new ThemedSpinner();
139
+ const stop = spinner.start(title, "dots", "download");
140
+
141
+ const data = await response.arrayBuffer();
142
+ await Bun.write(destination, data);
143
+
144
+ stop(true);
145
+ return;
146
+ }
147
+
148
+ const progress = new ProgressBar({
149
+ title,
150
+ total: totalSize,
151
+ width: 25
152
+ });
153
+
154
+ const reader = response.body?.getReader();
155
+ if (!reader) {
156
+ throw new Error("No response body");
157
+ }
158
+
159
+ const chunks: Uint8Array[] = [];
160
+ let received = 0;
161
+
162
+ while (true) {
163
+ const { done, value } = await reader.read();
164
+ if (done) break;
165
+
166
+ chunks.push(value);
167
+ received += value.length;
168
+ progress.update(received);
169
+ }
170
+
171
+ progress.complete();
172
+
173
+ // Concatena chunks e salva
174
+ const allChunks = new Uint8Array(received);
175
+ let position = 0;
176
+ for (const chunk of chunks) {
177
+ allChunks.set(chunk, position);
178
+ position += chunk.length;
179
+ }
180
+
181
+ await Bun.write(destination, allChunks);
182
+ }
@@ -38,6 +38,12 @@ export class ConfigManager {
38
38
  yes: { type: "boolean", short: "y" },
39
39
  war: { type: "boolean", short: "W" },
40
40
  cache: { type: "boolean" },
41
+ from: { type: "string" },
42
+ to: { type: "string" },
43
+ backup: { type: "boolean" },
44
+ "dry-run": { type: "boolean" },
45
+ force: { type: "boolean" },
46
+ src: { type: "string" },
41
47
  },
42
48
  strict: false,
43
49
  allowPositionals: true,