@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.
package/README.md CHANGED
@@ -20,6 +20,7 @@ Xavva is a high-performance CLI built with **Bun** that transforms the Java/Tomc
20
20
  - 🔧 **Auto-Healing** — Automatic diagnosis and repair of common issues
21
21
  - 🐱 **Embedded Tomcat** — Auto-install Tomcat, no manual setup needed
22
22
  - 📦 **WAR Generation** — Build as .war file or exploded directory
23
+ - 🔤 **Encoding Converter** — Convert file encodings (UTF-8, Windows-1252, ISO-8859-1) and fix mojibake
23
24
 
24
25
  ---
25
26
 
@@ -56,6 +57,9 @@ xavva deps --update-safe
56
57
  # Check for security vulnerabilities
57
58
  xavva audit
58
59
 
60
+ # Convert file encoding (UTF-8 → Windows-1252)
61
+ xavva encoding convert --to cp1252 --backup src/main/java/
62
+
59
63
  # Use embedded Tomcat (auto-install)
60
64
  xavva dev --yes
61
65
  ```
@@ -91,6 +95,7 @@ xavva dev --yes
91
95
  | `xavva profiles` | List available Maven/Gradle profiles |
92
96
  | `xavva docs` | Generate endpoint documentation |
93
97
  | `xavva tomcat` | Manage embedded Tomcat installations |
98
+ | `xavva encoding` | Convert file encodings (UTF-8, CP1252, ISO-8859-1) |
94
99
 
95
100
  ---
96
101
 
@@ -126,6 +131,56 @@ xavva dev --tomcat-version 9.0.115
126
131
 
127
132
  ---
128
133
 
134
+ ## 🔤 File Encoding
135
+
136
+ The `xavva encoding` command helps you convert file encodings and fix mojibake (corrupted characters):
137
+
138
+ ```bash
139
+ # Detect encoding of a file
140
+ xavva encoding detect src/main/java/MyClass.java
141
+
142
+ # Convert from UTF-8 to Windows-1252 (with backup)
143
+ xavva encoding convert --from utf-8 --to cp1252 --backup src/main/java/
144
+
145
+ # Convert a single file
146
+ xavva encoding convert --to cp1252 --backup src/main/java/MyClass.java
147
+
148
+ # Fix mojibake (e.g., "A��o" → "Ação")
149
+ xavva encoding fix src/main/java/MyClass.java
150
+
151
+ # List encodings of all files in src/
152
+ xavva encoding list
153
+
154
+ # Simulate conversion without modifying files
155
+ xavva encoding convert --from utf-8 --to cp1252 --dry-run src/
156
+ ```
157
+
158
+ ### Supported Encodings
159
+
160
+ - **utf-8** / **utf8** — UTF-8 (default)
161
+ - **windows-1252** / **cp1252** — Windows CP1252 (ANSI)
162
+ - **iso-8859-1** / **latin1** — ISO-8859-1 (Latin-1)
163
+
164
+ ### Configuration
165
+
166
+ Set default encoding in `xavva.json`:
167
+
168
+ ```json
169
+ {
170
+ "project": {
171
+ "encoding": "cp1252"
172
+ }
173
+ }
174
+ ```
175
+
176
+ Then use without `--to`:
177
+
178
+ ```bash
179
+ xavva encoding convert --backup src/main/java/
180
+ ```
181
+
182
+ ---
183
+
129
184
  ## 🔍 Dependency Analysis
130
185
 
131
186
  The `xavva deps` command provides comprehensive dependency analysis:
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@archznn/xavva",
3
- "version": "2.6.0",
3
+ "version": "2.8.0",
4
4
  "description": "Ultra-fast CLI tool for Java/Tomcat development with Hot-Reload and Zero Config. Supports Windows, Linux and macOS.",
5
5
  "module": "src/index.ts",
6
6
  "type": "module",
@@ -41,6 +41,8 @@
41
41
  "typescript": "^5"
42
42
  },
43
43
  "dependencies": {
44
- "glob": "^13.0.6"
44
+ "@inquirer/prompts": "^8.3.2",
45
+ "glob": "^13.0.6",
46
+ "node-notifier": "^10.0.1"
45
47
  }
46
48
  }
@@ -0,0 +1,212 @@
1
+ import type { Command } from "./Command";
2
+ import type { AppConfig, CLIArguments } from "../types/config";
3
+ import { Logger } from "../utils/ui";
4
+
5
+ export class CompletionCommand implements Command {
6
+ private readonly commands = [
7
+ "init", "config", "build", "deploy", "start", "stop", "restart",
8
+ "logs", "run", "audit", "deps", "docs", "doctor", "profiles",
9
+ "tomcat", "encoding", "history", "redo", "health", "completion", "help"
10
+ ];
11
+
12
+ private readonly flags: Record<string, string[]> = {
13
+ "*": ["--help", "--version", "-v", "--verbose", "--quiet", "-q"],
14
+ "init": [],
15
+ "config": ["--interactive", "-i"],
16
+ "build": ["--clean", "-c", "--profile", "-p", "--no-build", "--cache"],
17
+ "deploy": ["--watch", "-w", "--clean", "-c", "--profile", "-p", "--debug", "--tui"],
18
+ "start": ["--debug", "--tui"],
19
+ "stop": [],
20
+ "restart": ["--debug"],
21
+ "logs": ["--grep", "-g", "--follow", "-f"],
22
+ "run": [],
23
+ "audit": ["--fix", "--strict"],
24
+ "deps": ["--output", "-o"],
25
+ "docs": [],
26
+ "doctor": [],
27
+ "profiles": [],
28
+ "tomcat": ["--tomcat-version", "--tomcat-action"],
29
+ "encoding": ["--from", "--to", "--backup"],
30
+ "history": ["--clear", "--limit"],
31
+ "redo": [],
32
+ "health": [],
33
+ "completion": ["bash", "zsh", "fish"]
34
+ };
35
+
36
+ async execute(_config: AppConfig, args?: CLIArguments, positionals?: string[]): Promise<void> {
37
+ const shell = positionals?.[0] || "bash";
38
+
39
+ switch (shell) {
40
+ case "bash":
41
+ console.log(this.generateBash());
42
+ break;
43
+ case "zsh":
44
+ console.log(this.generateZsh());
45
+ break;
46
+ case "fish":
47
+ console.log(this.generateFish());
48
+ break;
49
+ default:
50
+ Logger.banner("completion");
51
+ Logger.section("Shell Completion");
52
+ Logger.info("Uso: xavva completion <shell>");
53
+ Logger.newline();
54
+ Logger.log(`${Logger.C.gray}│${Logger.C.reset} ${Logger.C.primary}xavva completion bash${Logger.C.reset} ${Logger.C.gray}# Bash${Logger.C.reset}`);
55
+ Logger.log(`${Logger.C.gray}│${Logger.C.reset} ${Logger.C.primary}xavva completion zsh${Logger.C.reset} ${Logger.C.gray}# Zsh${Logger.C.reset}`);
56
+ Logger.log(`${Logger.C.gray}│${Logger.C.reset} ${Logger.C.primary}xavva completion fish${Logger.C.reset} ${Logger.C.gray}# Fish${Logger.C.reset}`);
57
+ Logger.endSection();
58
+ Logger.dim("Adicione ao seu shell:");
59
+ Logger.log(` ${Logger.C.gray}# Bash: echo 'eval "$(xavva completion bash)"' >> ~/.bashrc${Logger.C.reset}`);
60
+ Logger.log(` ${Logger.C.gray}# Zsh: echo 'eval "$(xavva completion zsh)"' >> ~/.zshrc${Logger.C.reset}`);
61
+ Logger.log(` ${Logger.C.gray}# Fish: xavva completion fish > ~/.config/fish/completions/xavva.fish${Logger.C.reset}`);
62
+ }
63
+ }
64
+
65
+ private generateBash(): string {
66
+ const cmds = this.commands.join(" ");
67
+ const flagCases = Object.entries(this.flags)
68
+ .filter(([cmd]) => cmd !== "*")
69
+ .map(([cmd, flags]) => `
70
+ ${cmd})
71
+ opts="${flags.join(" ")}"
72
+ ;;`)
73
+ .join("");
74
+
75
+ return `# xavva completion for bash
76
+ _xavva_completion() {
77
+ local cur prev opts
78
+ COMPREPLY=()
79
+ cur="\${COMP_WORDS[COMP_CWORD]}"
80
+ prev="\${COMP_WORDS[COMP_CWORD-1]}"
81
+
82
+ # Comandos principais
83
+ local commands="${cmds}"
84
+ local global_flags="${this.flags["*"].join(" ")}"
85
+
86
+ # Primeiro argumento: comandos
87
+ if [ \${COMP_CWORD} -eq 1 ]; then
88
+ COMPREPLY=( $(compgen -W "\${commands}" -- \${cur}) )
89
+ return 0
90
+ fi
91
+
92
+ # Flags específicas por comando
93
+ local cmd="\${COMP_WORDS[1]}"
94
+ case \${cmd} in${flagCases}
95
+ *)
96
+ opts="\${global_flags}"
97
+ ;;
98
+ esac
99
+
100
+ if [[ \${cur} == -* ]]; then
101
+ COMPREPLY=( $(compgen -W "\${opts}" -- \${cur}) )
102
+ return 0
103
+ fi
104
+ }
105
+
106
+ complete -F _xavva_completion xavva
107
+ `;
108
+ }
109
+
110
+ private generateZsh(): string {
111
+ const cmds = this.commands.map(c => `"${c}"`).join(" ");
112
+
113
+ return `#compdef xavva
114
+
115
+ # xavva completion for zsh
116
+
117
+ _xavva() {
118
+ local curcontext="$curcontext" state line
119
+ typeset -A opt_args
120
+
121
+ _arguments -C \\
122
+ '1: :_xavva_commands' \\
123
+ '*:: :->args'
124
+
125
+ case $line[1] in
126
+ config)
127
+ _arguments \\
128
+ '(-i --interactive)'{-i,--interactive}'[Modo interativo]'
129
+ ;;
130
+ build)
131
+ _arguments \\
132
+ '(-c --clean)'{-c,--clean}'[Clean build]' \\
133
+ '(-p --profile)'{-p,--profile}'[Profile]:profile:(dev test prod)' \\
134
+ '--cache[Enable cache]'
135
+ ;;
136
+ deploy)
137
+ _arguments \\
138
+ '(-w --watch)'{-w,--watch}'[Watch mode]' \\
139
+ '(-c --clean)'{-c,--clean}'[Clean build]' \\
140
+ '(-p --profile)'{-p,--profile}'[Profile]:profile:(dev test prod)' \\
141
+ '--debug[Debug mode]' \\
142
+ '--tui[Enable TUI]'
143
+ ;;
144
+ logs)
145
+ _arguments \\
146
+ '(-g --grep)'{-g,--grep}'[Filter pattern]:pattern:' \\
147
+ '(-f --follow)'{-f,--follow}'[Follow mode]'
148
+ ;;
149
+ history)
150
+ _arguments \\
151
+ '--clear[Clear history]' \\
152
+ '--limit[Limit entries]:number:'
153
+ ;;
154
+ completion)
155
+ _arguments \\
156
+ '1:shell:(bash zsh fish)'
157
+ ;;
158
+ esac
159
+ }
160
+
161
+ _xavva_commands() {
162
+ local commands=(${cmds})
163
+ _describe -t commands 'xavva commands' commands
164
+ }
165
+
166
+ compdef _xavva xavva
167
+ `;
168
+ }
169
+
170
+ private generateFish(): string {
171
+ const lines: string[] = [
172
+ "# xavva completion for fish",
173
+ "",
174
+ `# Comandos principais`,
175
+ `complete -c xavva -n "not __fish_seen_subcommand_from ${this.commands.join(" ")}" -a "${this.commands.join(" ")}"`,
176
+ "",
177
+ `# Flags globais`,
178
+ ];
179
+
180
+ // Global flags
181
+ for (const flag of this.flags["*"]) {
182
+ const short = flag.startsWith("-") && flag.length === 2 ? flag : "";
183
+ const long = flag.startsWith("--") ? flag : "";
184
+ if (short && long) {
185
+ lines.push(`complete -c xavva -s ${short[1]} -l ${long.slice(2)}`);
186
+ } else if (short) {
187
+ lines.push(`complete -c xavva -s ${short[1]}`);
188
+ } else if (long) {
189
+ lines.push(`complete -c xavva -l ${long.slice(2)}`);
190
+ }
191
+ }
192
+
193
+ // Command-specific flags
194
+ for (const [cmd, flags] of Object.entries(this.flags)) {
195
+ if (cmd === "*") continue;
196
+
197
+ lines.push("");
198
+ lines.push(`# ${cmd}`);
199
+ for (const flag of flags) {
200
+ if (flag.startsWith("--")) {
201
+ lines.push(`complete -c xavva -n "__fish_seen_subcommand_from ${cmd}" -l ${flag.slice(2)}`);
202
+ } else if (flag.startsWith("-") && flag.length === 2) {
203
+ lines.push(`complete -c xavva -n "__fish_seen_subcommand_from ${cmd}" -s ${flag[1]}`);
204
+ } else {
205
+ lines.push(`complete -c xavva -n "__fish_seen_subcommand_from ${cmd}" -a "${flag}"`);
206
+ }
207
+ }
208
+ }
209
+
210
+ return lines.join("\n");
211
+ }
212
+ }
@@ -0,0 +1,184 @@
1
+ import { input, select, confirm, number, editor } from "@inquirer/prompts";
2
+ import { readFile, writeFile } from "fs/promises";
3
+ import { existsSync } from "fs";
4
+ import { join } from "path";
5
+ import type { Command } from "./Command";
6
+ import type { AppConfig, CLIArguments } from "../types/config";
7
+ import { Logger } from "../utils/ui";
8
+
9
+ export class ConfigCommand implements Command {
10
+ async execute(config: AppConfig, args?: CLIArguments): Promise<void> {
11
+ const interactive = args?.["interactive"] || args?.["i"] || false;
12
+ const configPath = join(process.cwd(), "xavva.json");
13
+
14
+ if (!interactive) {
15
+ // Modo view: mostrar configuração atual
16
+ Logger.banner("config");
17
+ Logger.section("Configuração Atual");
18
+
19
+ Logger.config("App Name", config.project.appName);
20
+ Logger.config("Build Tool", config.project.buildTool);
21
+ Logger.config("Profile", config.project.profile);
22
+ Logger.config("Port", config.tomcat.port);
23
+ Logger.config("Cache", config.project.cache ?? true);
24
+ Logger.config("TUI", config.project.tui);
25
+ Logger.config("Encoding", config.project.encoding || "UTF-8");
26
+
27
+ if (config.tomcat.embedded) {
28
+ Logger.config("Tomcat", "embedded");
29
+ Logger.config("Version", config.tomcat.version || "10.1.52");
30
+ } else {
31
+ Logger.config("Tomcat Path", config.tomcat.path);
32
+ }
33
+
34
+ Logger.endSection();
35
+ Logger.dim("Use --interactive ou -i para editar");
36
+ return;
37
+ }
38
+
39
+ // Modo interativo
40
+ Logger.banner("config --interactive");
41
+ Logger.section("Editor Interativo");
42
+
43
+ if (!existsSync(configPath)) {
44
+ Logger.warn("xavva.json não encontrado. Execute 'xavva init' primeiro.");
45
+ return;
46
+ }
47
+
48
+ // Carregar config atual
49
+ let currentConfig: Record<string, unknown>;
50
+ try {
51
+ const content = await readFile(configPath, "utf-8");
52
+ currentConfig = JSON.parse(content);
53
+ } catch {
54
+ Logger.error("Erro ao ler xavva.json");
55
+ return;
56
+ }
57
+
58
+ // Menu de opções
59
+ const action = await select({
60
+ message: "O que deseja editar?",
61
+ choices: [
62
+ { name: "Informações básicas (nome, profile)", value: "basic" },
63
+ { name: "Configurações do Tomcat (porta, path)", value: "tomcat" },
64
+ { name: "Opções de build (cache, encoding)", value: "build" },
65
+ { name: "Editor de texto (JSON completo)", value: "json" }
66
+ ]
67
+ });
68
+
69
+ switch (action) {
70
+ case "basic":
71
+ await this.editBasic(currentConfig);
72
+ break;
73
+ case "tomcat":
74
+ await this.editTomcat(currentConfig);
75
+ break;
76
+ case "build":
77
+ await this.editBuild(currentConfig);
78
+ break;
79
+ case "json":
80
+ await this.editJson(currentConfig, configPath);
81
+ return;
82
+ }
83
+
84
+ // Salvar
85
+ await writeFile(configPath, JSON.stringify(currentConfig, null, 2));
86
+ Logger.success("Configuração salva!");
87
+ }
88
+
89
+ private async editBasic(config: Record<string, unknown>): Promise<void> {
90
+ config.appName = await input({
91
+ message: "Nome da aplicação:",
92
+ default: String(config.appName || "")
93
+ });
94
+
95
+ config.profile = await select({
96
+ message: "Profile:",
97
+ choices: [
98
+ { name: "dev", value: "dev" },
99
+ { name: "test", value: "test" },
100
+ { name: "prod", value: "prod" },
101
+ { name: "custom", value: "custom" }
102
+ ],
103
+ default: String(config.profile || "dev")
104
+ });
105
+
106
+ if (config.profile === "custom") {
107
+ config.profile = await input({
108
+ message: "Nome do profile:",
109
+ default: "local"
110
+ });
111
+ }
112
+ }
113
+
114
+ private async editTomcat(config: Record<string, unknown>): Promise<void> {
115
+ config.port = await number({
116
+ message: "Porta:",
117
+ default: Number(config.port || 8080)
118
+ });
119
+
120
+ const useEmbedded = await confirm({
121
+ message: "Usar Tomcat embutido?",
122
+ default: Boolean(config.embedded ?? true)
123
+ });
124
+
125
+ config.embedded = useEmbedded;
126
+
127
+ if (useEmbedded) {
128
+ config.tomcatVersion = await select({
129
+ message: "Versão do Tomcat:",
130
+ choices: [
131
+ { name: "10.1.52 (recomendada)", value: "10.1.52" },
132
+ { name: "9.0.115", value: "9.0.115" },
133
+ { name: "11.0.18", value: "11.0.18" }
134
+ ],
135
+ default: String(config.tomcatVersion || "10.1.52")
136
+ });
137
+ delete config.tomcatPath;
138
+ } else {
139
+ config.tomcatPath = await input({
140
+ message: "Caminho do Tomcat:",
141
+ default: String(config.tomcatPath || "")
142
+ });
143
+ }
144
+ }
145
+
146
+ private async editBuild(config: Record<string, unknown>): Promise<void> {
147
+ config.cache = await confirm({
148
+ message: "Habilitar cache de build?",
149
+ default: Boolean(config.cache ?? true)
150
+ });
151
+
152
+ config.tui = await confirm({
153
+ message: "Habilitar dashboard TUI?",
154
+ default: Boolean(config.tui ?? true)
155
+ });
156
+
157
+ config.encoding = await select({
158
+ message: "Encoding:",
159
+ choices: [
160
+ { name: "UTF-8", value: "UTF-8" },
161
+ { name: "ISO-8859-1", value: "ISO-8859-1" },
162
+ { name: "Windows-1252", value: "Windows-1252" }
163
+ ],
164
+ default: String(config.encoding || "UTF-8")
165
+ });
166
+ }
167
+
168
+ private async editJson(config: Record<string, unknown>, path: string): Promise<void> {
169
+ const current = JSON.stringify(config, null, 2);
170
+ const edited = await editor({
171
+ message: "Edite o JSON:",
172
+ default: current,
173
+ postfix: ".json"
174
+ });
175
+
176
+ try {
177
+ const parsed = JSON.parse(edited);
178
+ await writeFile(path, JSON.stringify(parsed, null, 2));
179
+ Logger.success("Configuração salva!");
180
+ } catch {
181
+ Logger.error("JSON inválido!");
182
+ }
183
+ }
184
+ }
@@ -260,10 +260,11 @@ export class DeployCommand implements Command {
260
260
  if (!fs.existsSync(targetDir)) fs.mkdirSync(targetDir, { recursive: true });
261
261
 
262
262
  fs.copyFileSync(filename, targetPath);
263
- if (!config.project.quiet) Logger.success(`Synced ${path.basename(filename)} directly to Tomcat!`);
263
+ Logger.success(`${path.basename(filename)} updated Tomcat`);
264
264
 
265
265
  const appUrl = `http://localhost:${config.tomcat.port}/${appFolder}`;
266
266
  await BrowserService.reload(appUrl);
267
+ Logger.ready(`Browser reloaded`);
267
268
  } catch (e) {
268
269
  Logger.error(`Failed to sync resource: ${filename}`);
269
270
  }