@archznn/xavva 2.9.0 โ 3.1.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 +188 -1
- package/package.json +1 -1
- package/src/commands/ChangelogCommand.ts +128 -0
- package/src/commands/ConfigCommand.ts +48 -0
- package/src/commands/DbCommand.ts +126 -0
- package/src/commands/DockerCommand.ts +122 -0
- package/src/commands/HelpCommand.ts +44 -0
- package/src/commands/HttpCommand.ts +134 -0
- package/src/commands/InitCommand.ts +70 -0
- package/src/commands/TestCommand.ts +63 -0
- package/src/di/container.ts +15 -0
- package/src/index.ts +14 -1
- package/src/services/DbService.ts +357 -0
- package/src/services/DockerService.ts +361 -0
- package/src/services/HttpService.ts +259 -0
- package/src/services/TestService.ts +326 -0
- package/src/types/args.ts +1 -0
- package/src/types/config.ts +38 -0
- package/src/utils/ChangelogGenerator.ts +255 -0
- package/src/utils/LoggerLevel.ts +138 -0
- package/src/utils/config.ts +38 -2
package/README.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
> Ultra-fast development toolkit for Java Enterprise (Tomcat) on Windows, Linux & macOS
|
|
4
4
|
|
|
5
|
-
[](https://github.com/leorsousa05/Xavva)
|
|
6
6
|
[](LICENSE)
|
|
7
7
|
|
|
8
8
|
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.
|
|
@@ -21,6 +21,16 @@ Xavva is a high-performance CLI built with **Bun** that transforms the Java/Tomc
|
|
|
21
21
|
- ๐ฑ **Embedded Tomcat** โ Auto-install Tomcat, no manual setup needed
|
|
22
22
|
- ๐ฆ **WAR Generation** โ Build as .war file or exploded directory
|
|
23
23
|
- ๐ค **Encoding Converter** โ Convert file encodings (UTF-8, Windows-1252, ISO-8859-1) and fix mojibake
|
|
24
|
+
- ๐ง **Interactive Wizard** โ `xavva init` for easy project setup
|
|
25
|
+
- ๐ **Desktop Notifications** โ Get notified when builds/deploys complete
|
|
26
|
+
- ๐ **Command History** โ Track and replay commands with `xavva history` and `xavva redo`
|
|
27
|
+
- ๐ฅ **Health Check** โ Verify environment (Java, ports, memory, disk) with `xavva health`
|
|
28
|
+
- ๐ฎ **Shell Completions** โ Auto-complete for bash, zsh, and fish
|
|
29
|
+
- ๐งช **Test Runner** โ Run JUnit/TestNG tests with watch mode and coverage
|
|
30
|
+
- ๐๏ธ **Database Migrations** โ Flyway/Liquibase integration
|
|
31
|
+
- ๐ **HTTP Client** โ Test APIs without leaving the terminal
|
|
32
|
+
- ๐ณ **Docker Integration** โ Generate configs, build and run containers
|
|
33
|
+
- ๐ **Multi-Environment** โ Dev, test, staging configurations
|
|
24
34
|
|
|
25
35
|
---
|
|
26
36
|
|
|
@@ -39,6 +49,9 @@ bunx @archznn/xavva dev
|
|
|
39
49
|
## ๐ Quick Start
|
|
40
50
|
|
|
41
51
|
```bash
|
|
52
|
+
# Initialize project configuration (interactive wizard)
|
|
53
|
+
xavva init
|
|
54
|
+
|
|
42
55
|
# Start development mode with dashboard
|
|
43
56
|
xavva dev --tui
|
|
44
57
|
|
|
@@ -62,6 +75,41 @@ xavva encoding convert --to cp1252 --backup src/main/java/
|
|
|
62
75
|
|
|
63
76
|
# Use embedded Tomcat (auto-install)
|
|
64
77
|
xavva dev --yes
|
|
78
|
+
|
|
79
|
+
# Check environment health
|
|
80
|
+
xavva health
|
|
81
|
+
|
|
82
|
+
# View command history
|
|
83
|
+
xavva history
|
|
84
|
+
|
|
85
|
+
# Repeat last command
|
|
86
|
+
xavva redo
|
|
87
|
+
|
|
88
|
+
# Run tests
|
|
89
|
+
xavva test
|
|
90
|
+
xavva test --watch
|
|
91
|
+
xavva test --coverage
|
|
92
|
+
|
|
93
|
+
# Database migrations
|
|
94
|
+
xavva db status
|
|
95
|
+
xavva db migrate
|
|
96
|
+
xavva db reset --force
|
|
97
|
+
|
|
98
|
+
# HTTP API testing
|
|
99
|
+
xavva http GET /api/users
|
|
100
|
+
xavva http POST /api/users --body '{"name":"John"}'
|
|
101
|
+
|
|
102
|
+
# Docker integration
|
|
103
|
+
xavva docker init
|
|
104
|
+
xavva docker build
|
|
105
|
+
xavva docker up
|
|
106
|
+
|
|
107
|
+
# Multi-environment
|
|
108
|
+
xavva deploy --env staging
|
|
109
|
+
xavva dev --env dev
|
|
110
|
+
|
|
111
|
+
# Enable shell completions (bash example)
|
|
112
|
+
eval "$(xavva completion bash)"
|
|
65
113
|
```
|
|
66
114
|
|
|
67
115
|
---
|
|
@@ -96,6 +144,69 @@ xavva dev --yes
|
|
|
96
144
|
| `xavva docs` | Generate endpoint documentation |
|
|
97
145
|
| `xavva tomcat` | Manage embedded Tomcat installations |
|
|
98
146
|
| `xavva encoding` | Convert file encodings (UTF-8, CP1252, ISO-8859-1) |
|
|
147
|
+
| `xavva health` | Check environment health (Java, ports, memory, disk) |
|
|
148
|
+
|
|
149
|
+
### Project Management
|
|
150
|
+
|
|
151
|
+
| Command | Description |
|
|
152
|
+
| ----------------------- | ---------------------------------------------- |
|
|
153
|
+
| `xavva init` | Initialize project configuration (wizard) |
|
|
154
|
+
| `xavva config` | View current configuration |
|
|
155
|
+
| `xavva config --interactive` | Edit configuration interactively |
|
|
156
|
+
| `xavva history` | Show command history |
|
|
157
|
+
| `xavva history --clear` | Clear command history |
|
|
158
|
+
| `xavva redo` | Repeat the last executed command |
|
|
159
|
+
| `xavva completion <shell>` | Generate shell completions (bash/zsh/fish) |
|
|
160
|
+
|
|
161
|
+
### Testing & Database
|
|
162
|
+
|
|
163
|
+
| Command | Description |
|
|
164
|
+
|---------|-------------|
|
|
165
|
+
| `xavva test` | Run all tests (JUnit/TestNG) |
|
|
166
|
+
| `xavva test --watch` | Run tests in watch mode |
|
|
167
|
+
| `xavva test --coverage` | Generate coverage report |
|
|
168
|
+
| `xavva test <filter>` | Run specific test class |
|
|
169
|
+
| `xavva db status` | Show migration status |
|
|
170
|
+
| `xavva db migrate` | Run pending migrations |
|
|
171
|
+
| `xavva db reset --force` | Reset database (โ ๏ธ destructive) |
|
|
172
|
+
| `xavva db seed` | Populate with test data |
|
|
173
|
+
|
|
174
|
+
### HTTP Client
|
|
175
|
+
|
|
176
|
+
| Command | Description |
|
|
177
|
+
|---------|-------------|
|
|
178
|
+
| `xavva http GET <path>` | Send GET request |
|
|
179
|
+
| `xavva http POST <path> --body '{}'` | Send POST request |
|
|
180
|
+
| `xavva http <path> --param key=value` | Add query parameters |
|
|
181
|
+
| `xavva http <path> --header "Auth: token"` | Add custom headers |
|
|
182
|
+
|
|
183
|
+
### Docker
|
|
184
|
+
|
|
185
|
+
| Command | Description |
|
|
186
|
+
|---------|-------------|
|
|
187
|
+
| `xavva docker init` | Generate Dockerfile & docker-compose.yml |
|
|
188
|
+
| `xavva docker build` | Build Docker image |
|
|
189
|
+
| `xavva docker up` | Start with docker-compose |
|
|
190
|
+
| `xavva docker down` | Stop containers |
|
|
191
|
+
| `xavva docker run` | Run development container |
|
|
192
|
+
| `xavva docker status` | Show container status |
|
|
193
|
+
|
|
194
|
+
### Multi-Environment
|
|
195
|
+
|
|
196
|
+
| Command | Description |
|
|
197
|
+
|---------|-------------|
|
|
198
|
+
| `xavva deploy --env <name>` | Deploy to specific environment |
|
|
199
|
+
| `xavva dev --env <name>` | Use environment configuration |
|
|
200
|
+
|
|
201
|
+
Configure environments in `xavva.json`:
|
|
202
|
+
```json
|
|
203
|
+
{
|
|
204
|
+
"environments": {
|
|
205
|
+
"dev": { "port": 8080, "profile": "dev" },
|
|
206
|
+
"staging": { "port": 8081, "profile": "staging" }
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
```
|
|
99
210
|
|
|
100
211
|
---
|
|
101
212
|
|
|
@@ -278,6 +389,82 @@ Create `xavva.json` in your project root:
|
|
|
278
389
|
| `--cache` | Use build cache (faster) |
|
|
279
390
|
| `-y, --yes` | Auto-install Tomcat (no prompt) |
|
|
280
391
|
| `-V, --verbose` | Detailed output |
|
|
392
|
+
| `-i, --interactive` | Interactive mode (for config) |
|
|
393
|
+
|
|
394
|
+
---
|
|
395
|
+
|
|
396
|
+
## ๐ง Interactive Wizard
|
|
397
|
+
|
|
398
|
+
Initialize a new project with the interactive setup wizard:
|
|
399
|
+
|
|
400
|
+
```bash
|
|
401
|
+
xavva init
|
|
402
|
+
```
|
|
403
|
+
|
|
404
|
+
The wizard will guide you through:
|
|
405
|
+
- Build tool selection (auto-detected from pom.xml or build.gradle)
|
|
406
|
+
- Application name
|
|
407
|
+
- Profile selection (detects profiles from your build file)
|
|
408
|
+
- Tomcat port configuration
|
|
409
|
+
- Embedded Tomcat settings
|
|
410
|
+
- Build cache and TUI preferences
|
|
411
|
+
|
|
412
|
+
---
|
|
413
|
+
|
|
414
|
+
## ๐ฅ Health Check
|
|
415
|
+
|
|
416
|
+
Verify your development environment:
|
|
417
|
+
|
|
418
|
+
```bash
|
|
419
|
+
# Check all components
|
|
420
|
+
xavva health
|
|
421
|
+
|
|
422
|
+
# Checks include:
|
|
423
|
+
# - Java version (JDK 11+ recommended)
|
|
424
|
+
# - Maven/Gradle availability
|
|
425
|
+
# - Tomcat configuration
|
|
426
|
+
# - Port availability
|
|
427
|
+
# - Memory and disk space
|
|
428
|
+
```
|
|
429
|
+
|
|
430
|
+
---
|
|
431
|
+
|
|
432
|
+
## ๐ Command History
|
|
433
|
+
|
|
434
|
+
Track and replay your commands:
|
|
435
|
+
|
|
436
|
+
```bash
|
|
437
|
+
# Show recent commands
|
|
438
|
+
xavva history
|
|
439
|
+
|
|
440
|
+
# Show more entries
|
|
441
|
+
xavva history --limit 20
|
|
442
|
+
|
|
443
|
+
# Clear history
|
|
444
|
+
xavva history --clear
|
|
445
|
+
|
|
446
|
+
# Repeat last command
|
|
447
|
+
xavva redo
|
|
448
|
+
```
|
|
449
|
+
|
|
450
|
+
---
|
|
451
|
+
|
|
452
|
+
## ๐ฎ Shell Completions
|
|
453
|
+
|
|
454
|
+
Enable tab completion for your shell:
|
|
455
|
+
|
|
456
|
+
```bash
|
|
457
|
+
# Bash (add to ~/.bashrc)
|
|
458
|
+
eval "$(xavva completion bash)"
|
|
459
|
+
|
|
460
|
+
# Zsh (add to ~/.zshrc)
|
|
461
|
+
eval "$(xavva completion zsh)"
|
|
462
|
+
|
|
463
|
+
# Fish
|
|
464
|
+
xavva completion fish > ~/.config/fish/completions/xavva.fish
|
|
465
|
+
```
|
|
466
|
+
|
|
467
|
+
Supported shells: `bash`, `zsh`, `fish`
|
|
281
468
|
|
|
282
469
|
---
|
|
283
470
|
|
package/package.json
CHANGED
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
import type { Command } from "./Command";
|
|
2
|
+
import type { AppConfig, CLIArguments } from "../types/config";
|
|
3
|
+
import { ChangelogGenerator } from "../utils/ChangelogGenerator";
|
|
4
|
+
import { Logger } from "../utils/ui";
|
|
5
|
+
import { existsSync } from "fs";
|
|
6
|
+
|
|
7
|
+
export class ChangelogCommand implements Command {
|
|
8
|
+
async execute(_config: AppConfig, args?: CLIArguments, positionals?: string[]): Promise<void> {
|
|
9
|
+
// Pula o nome do comando "changelog" e pega a aรงรฃo
|
|
10
|
+
const action = positionals?.find(p => !["changelog", "gen"].includes(p)) || "generate";
|
|
11
|
+
const output = args?.["output"] || args?.["o"] || "CHANGELOG.md";
|
|
12
|
+
|
|
13
|
+
Logger.banner("changelog");
|
|
14
|
+
|
|
15
|
+
switch (action) {
|
|
16
|
+
case "generate":
|
|
17
|
+
case "gen":
|
|
18
|
+
await this.generate(output);
|
|
19
|
+
break;
|
|
20
|
+
case "check":
|
|
21
|
+
case "validate":
|
|
22
|
+
await this.validate();
|
|
23
|
+
break;
|
|
24
|
+
case "preview":
|
|
25
|
+
await this.preview();
|
|
26
|
+
break;
|
|
27
|
+
default:
|
|
28
|
+
this.showHelp();
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
private async generate(output: string): Promise<void> {
|
|
33
|
+
Logger.section("Generating Changelog");
|
|
34
|
+
Logger.step("Analyzing git history...");
|
|
35
|
+
|
|
36
|
+
try {
|
|
37
|
+
ChangelogGenerator.generateAndSave(output);
|
|
38
|
+
|
|
39
|
+
if (existsSync(output)) {
|
|
40
|
+
Logger.success(`Changelog generated: ${output}`);
|
|
41
|
+
} else {
|
|
42
|
+
Logger.error("Failed to generate changelog");
|
|
43
|
+
}
|
|
44
|
+
} catch (error) {
|
|
45
|
+
Logger.error(`Error: ${error}`);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
Logger.done();
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
private async validate(): Promise<void> {
|
|
52
|
+
Logger.section("Validating Conventional Commits");
|
|
53
|
+
|
|
54
|
+
// Check if commits follow conventional commit format
|
|
55
|
+
const { execSync } = await import("child_process");
|
|
56
|
+
|
|
57
|
+
try {
|
|
58
|
+
const log = execSync(
|
|
59
|
+
'git log --pretty=format:"%s" --no-merges -20',
|
|
60
|
+
{ encoding: "utf-8" }
|
|
61
|
+
);
|
|
62
|
+
|
|
63
|
+
const commits = log.trim().split("\n");
|
|
64
|
+
const conventionalPattern = /^(feat|fix|docs|style|refactor|perf|test|build|ci|chore|revert)(\(.+\))?(!)?: .+/;
|
|
65
|
+
|
|
66
|
+
let valid = 0;
|
|
67
|
+
let invalid = 0;
|
|
68
|
+
|
|
69
|
+
for (const commit of commits) {
|
|
70
|
+
const isValid = conventionalPattern.test(commit);
|
|
71
|
+
if (isValid) {
|
|
72
|
+
valid++;
|
|
73
|
+
Logger.success(commit.slice(0, 60));
|
|
74
|
+
} else {
|
|
75
|
+
invalid++;
|
|
76
|
+
Logger.warn(commit.slice(0, 60));
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
Logger.newline();
|
|
81
|
+
Logger.info("Summary", `${valid} valid, ${invalid} need improvement`);
|
|
82
|
+
|
|
83
|
+
if (invalid > 0) {
|
|
84
|
+
Logger.dim("\nValid conventional commit types:");
|
|
85
|
+
Logger.dim(" feat, fix, docs, style, refactor, perf,");
|
|
86
|
+
Logger.dim(" test, build, ci, chore, revert");
|
|
87
|
+
Logger.dim("\nExample: feat(auth): add login endpoint");
|
|
88
|
+
}
|
|
89
|
+
} catch (error) {
|
|
90
|
+
Logger.error(`Failed to validate: ${error}`);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
Logger.done();
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
private async preview(): Promise<void> {
|
|
97
|
+
Logger.section("Changelog Preview");
|
|
98
|
+
|
|
99
|
+
try {
|
|
100
|
+
const changelog = ChangelogGenerator.generate();
|
|
101
|
+
// Show only first 50 lines
|
|
102
|
+
const lines = changelog.split("\n").slice(0, 50);
|
|
103
|
+
Logger.log(lines.join("\n"));
|
|
104
|
+
|
|
105
|
+
if (changelog.split("\n").length > 50) {
|
|
106
|
+
Logger.dim("\n... (truncated, use 'generate' to see full)");
|
|
107
|
+
}
|
|
108
|
+
} catch (error) {
|
|
109
|
+
Logger.error(`Failed to generate preview: ${error}`);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
Logger.done();
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
private showHelp(): void {
|
|
116
|
+
Logger.section("Changelog Commands");
|
|
117
|
+
Logger.info("Usage: xavva changelog <action> [options]");
|
|
118
|
+
Logger.newline();
|
|
119
|
+
Logger.log("Actions:");
|
|
120
|
+
Logger.log(` ${Logger.C.primary}generate${Logger.C.reset} Generate CHANGELOG.md (default)`);
|
|
121
|
+
Logger.log(` ${Logger.C.primary}check${Logger.C.reset} Validate conventional commits`);
|
|
122
|
+
Logger.log(` ${Logger.C.primary}preview${Logger.C.reset} Preview changelog without saving`);
|
|
123
|
+
Logger.newline();
|
|
124
|
+
Logger.log("Options:");
|
|
125
|
+
Logger.log(` ${Logger.C.primary}-o, --output${Logger.C.reset} Output file (default: CHANGELOG.md)`);
|
|
126
|
+
Logger.done();
|
|
127
|
+
}
|
|
128
|
+
}
|
|
@@ -141,6 +141,54 @@ export class ConfigCommand implements Command {
|
|
|
141
141
|
default: String(config.tomcatPath || "")
|
|
142
142
|
});
|
|
143
143
|
}
|
|
144
|
+
|
|
145
|
+
// Edit environments
|
|
146
|
+
const editEnvs = await confirm({
|
|
147
|
+
message: "Editar environments?",
|
|
148
|
+
default: false
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
if (editEnvs) {
|
|
152
|
+
await this.editEnvironments(config);
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
private async editEnvironments(config: Record<string, unknown>): Promise<void> {
|
|
157
|
+
const environments = (config.environments as Record<string, unknown>) || {};
|
|
158
|
+
|
|
159
|
+
const envNames = Object.keys(environments);
|
|
160
|
+
if (envNames.length > 0) {
|
|
161
|
+
Logger.info("Environments existentes:", envNames.join(", "));
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
const addNew = await confirm({
|
|
165
|
+
message: "Adicionar novo environment?",
|
|
166
|
+
default: envNames.length === 0
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
if (addNew) {
|
|
170
|
+
const name = await input({
|
|
171
|
+
message: "Nome do environment:",
|
|
172
|
+
default: "staging"
|
|
173
|
+
});
|
|
174
|
+
|
|
175
|
+
const port = await number({
|
|
176
|
+
message: `Porta para ${name}:`,
|
|
177
|
+
default: 8080
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
const profile = await input({
|
|
181
|
+
message: `Profile para ${name}:`,
|
|
182
|
+
default: name
|
|
183
|
+
});
|
|
184
|
+
|
|
185
|
+
environments[name] = {
|
|
186
|
+
port,
|
|
187
|
+
profile
|
|
188
|
+
};
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
config.environments = environments;
|
|
144
192
|
}
|
|
145
193
|
|
|
146
194
|
private async editBuild(config: Record<string, unknown>): Promise<void> {
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Comando de migraรงรตes de banco de dados
|
|
3
|
+
* xavva db <action> [options]
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import type { Command } from "./Command";
|
|
7
|
+
import type { AppConfig, CLIArguments } from "../types/config";
|
|
8
|
+
import { DbService, type DbConfig } from "../services/DbService";
|
|
9
|
+
import { Logger } from "../utils/ui";
|
|
10
|
+
import { ProcessManager } from "../utils/processManager";
|
|
11
|
+
|
|
12
|
+
export class DbCommand implements Command {
|
|
13
|
+
async execute(config: AppConfig, args?: CLIArguments, positionals?: string[]): Promise<void> {
|
|
14
|
+
const processManager = ProcessManager.getInstance();
|
|
15
|
+
const action = positionals?.[1] || "status";
|
|
16
|
+
|
|
17
|
+
const service = new DbService(config.project.buildTool);
|
|
18
|
+
|
|
19
|
+
// Extrair config de DB do environment ou args
|
|
20
|
+
const dbConfig = this.extractDbConfig(config, args);
|
|
21
|
+
|
|
22
|
+
try {
|
|
23
|
+
switch (action) {
|
|
24
|
+
case "migrate":
|
|
25
|
+
case "up":
|
|
26
|
+
const migrateResult = await service.migrate(dbConfig);
|
|
27
|
+
if (!migrateResult.success) {
|
|
28
|
+
Logger.error(migrateResult.message);
|
|
29
|
+
await processManager.shutdown(1);
|
|
30
|
+
}
|
|
31
|
+
break;
|
|
32
|
+
|
|
33
|
+
case "status":
|
|
34
|
+
case "info":
|
|
35
|
+
await service.status(dbConfig);
|
|
36
|
+
break;
|
|
37
|
+
|
|
38
|
+
case "reset":
|
|
39
|
+
case "clean":
|
|
40
|
+
case "drop":
|
|
41
|
+
if (!args?.force) {
|
|
42
|
+
Logger.warn("This will DELETE all data in the database!");
|
|
43
|
+
Logger.info("Use", "--force to confirm");
|
|
44
|
+
await processManager.shutdown(1);
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
47
|
+
const resetResult = await service.reset(dbConfig);
|
|
48
|
+
if (!resetResult.success) {
|
|
49
|
+
await processManager.shutdown(1);
|
|
50
|
+
}
|
|
51
|
+
// Roda migraรงรตes novamente apรณs reset
|
|
52
|
+
await service.migrate(dbConfig);
|
|
53
|
+
break;
|
|
54
|
+
|
|
55
|
+
case "seed":
|
|
56
|
+
const seedResult = await service.seed(dbConfig, args?.src);
|
|
57
|
+
if (!seedResult.success) {
|
|
58
|
+
Logger.error(seedResult.message);
|
|
59
|
+
await processManager.shutdown(1);
|
|
60
|
+
}
|
|
61
|
+
break;
|
|
62
|
+
|
|
63
|
+
case "create":
|
|
64
|
+
await this.createMigration(service, args);
|
|
65
|
+
break;
|
|
66
|
+
|
|
67
|
+
default:
|
|
68
|
+
Logger.error(`Unknown db action: ${action}`);
|
|
69
|
+
Logger.info("Actions", "migrate, status, reset, seed, create");
|
|
70
|
+
await processManager.shutdown(1);
|
|
71
|
+
}
|
|
72
|
+
} catch (error) {
|
|
73
|
+
Logger.error(`Database command failed: ${(error as Error).message}`);
|
|
74
|
+
await processManager.shutdown(1);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
private extractDbConfig(config: AppConfig, args?: CLIArguments): DbConfig | undefined {
|
|
79
|
+
// Tenta pegar do environment config
|
|
80
|
+
const envName = config.project.environment;
|
|
81
|
+
const envConfig = envName && config.project.environments?.[envName];
|
|
82
|
+
|
|
83
|
+
if (envConfig?.db) {
|
|
84
|
+
return {
|
|
85
|
+
url: envConfig.db.url || process.env.JDBC_URL || "",
|
|
86
|
+
username: envConfig.db.username || process.env.JDBC_USER || "",
|
|
87
|
+
password: envConfig.db.password || process.env.JDBC_PASSWORD || "",
|
|
88
|
+
driver: envConfig.db.driver
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// Fallback para env vars
|
|
93
|
+
if (process.env.JDBC_URL) {
|
|
94
|
+
return {
|
|
95
|
+
url: process.env.JDBC_URL,
|
|
96
|
+
username: process.env.JDBC_USER || "",
|
|
97
|
+
password: process.env.JDBC_PASSWORD || ""
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
return undefined;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
private async createMigration(service: DbService, args?: CLIArguments): Promise<void> {
|
|
105
|
+
const name = args?.name || "new_migration";
|
|
106
|
+
Logger.section("Create Migration");
|
|
107
|
+
Logger.info("Name", name);
|
|
108
|
+
|
|
109
|
+
// Detecta ferramenta
|
|
110
|
+
const tool = await service.detectTool();
|
|
111
|
+
|
|
112
|
+
if (tool === "flyway") {
|
|
113
|
+
const timestamp = new Date().toISOString().replace(/[-:T]/g, "").slice(0, 14);
|
|
114
|
+
const filename = `V${timestamp}__${name}.sql`;
|
|
115
|
+
const filepath = `src/main/resources/db/migration/${filename}`;
|
|
116
|
+
|
|
117
|
+
Logger.success(`Create file: ${filepath}`);
|
|
118
|
+
Logger.dim("-- Add your SQL here");
|
|
119
|
+
} else if (tool === "liquibase") {
|
|
120
|
+
const filename = `${name}.sql`;
|
|
121
|
+
Logger.success(`Add to changelog: db/changelog/${filename}`);
|
|
122
|
+
} else {
|
|
123
|
+
Logger.warn("No migration tool detected");
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
}
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Comando de integraรงรฃo Docker
|
|
3
|
+
* xavva docker <action> [options]
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import type { Command } from "./Command";
|
|
7
|
+
import type { AppConfig, CLIArguments } from "../types/config";
|
|
8
|
+
import { DockerService, type DockerConfig } from "../services/DockerService";
|
|
9
|
+
import { Logger } from "../utils/ui";
|
|
10
|
+
import { ProcessManager } from "../utils/processManager";
|
|
11
|
+
|
|
12
|
+
export class DockerCommand implements Command {
|
|
13
|
+
async execute(config: AppConfig, args?: CLIArguments, positionals?: string[]): Promise<void> {
|
|
14
|
+
const processManager = ProcessManager.getInstance();
|
|
15
|
+
const action = positionals?.[1] || "status";
|
|
16
|
+
|
|
17
|
+
const service = new DockerService();
|
|
18
|
+
|
|
19
|
+
// Verifica se Docker estรก disponรญvel
|
|
20
|
+
const isAvailable = await service.isDockerAvailable();
|
|
21
|
+
if (!isAvailable) {
|
|
22
|
+
Logger.error("Docker is not available");
|
|
23
|
+
Logger.info("Install", "https://docs.docker.com/get-docker/");
|
|
24
|
+
await processManager.shutdown(1);
|
|
25
|
+
return;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const dockerConfig: DockerConfig = {
|
|
29
|
+
imageName: (args?.name as string) || config.project.appName,
|
|
30
|
+
tag: (args?.tag as string) || "latest",
|
|
31
|
+
port: args?.port ? parseInt(args.port as string) : config.tomcat.port,
|
|
32
|
+
javaVersion: (args?.["java-version"] as string) || "17",
|
|
33
|
+
tomcatVersion: config.tomcat.version
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
try {
|
|
37
|
+
switch (action) {
|
|
38
|
+
case "init":
|
|
39
|
+
await this.handleInit(service, dockerConfig);
|
|
40
|
+
break;
|
|
41
|
+
|
|
42
|
+
case "build":
|
|
43
|
+
const built = await service.buildImage(dockerConfig.tag);
|
|
44
|
+
if (!built) {
|
|
45
|
+
await processManager.shutdown(1);
|
|
46
|
+
}
|
|
47
|
+
break;
|
|
48
|
+
|
|
49
|
+
case "run":
|
|
50
|
+
case "dev":
|
|
51
|
+
const running = await service.runDevMode(dockerConfig.port);
|
|
52
|
+
if (!running) {
|
|
53
|
+
await processManager.shutdown(1);
|
|
54
|
+
}
|
|
55
|
+
break;
|
|
56
|
+
|
|
57
|
+
case "up":
|
|
58
|
+
const up = await service.composeUp(args?.detached);
|
|
59
|
+
if (!up) {
|
|
60
|
+
await processManager.shutdown(1);
|
|
61
|
+
}
|
|
62
|
+
break;
|
|
63
|
+
|
|
64
|
+
case "down":
|
|
65
|
+
case "stop":
|
|
66
|
+
const down = await service.composeDown();
|
|
67
|
+
if (!down) {
|
|
68
|
+
await processManager.shutdown(1);
|
|
69
|
+
}
|
|
70
|
+
break;
|
|
71
|
+
|
|
72
|
+
case "status":
|
|
73
|
+
case "ps":
|
|
74
|
+
await service.showContainerStatus();
|
|
75
|
+
break;
|
|
76
|
+
|
|
77
|
+
case "push":
|
|
78
|
+
await this.handlePush(service, dockerConfig, args);
|
|
79
|
+
break;
|
|
80
|
+
|
|
81
|
+
default:
|
|
82
|
+
Logger.error(`Unknown docker action: ${action}`);
|
|
83
|
+
Logger.info("Actions", "init, build, run, up, down, status, push");
|
|
84
|
+
await processManager.shutdown(1);
|
|
85
|
+
}
|
|
86
|
+
} catch (error) {
|
|
87
|
+
Logger.error(`Docker command failed: ${(error as Error).message}`);
|
|
88
|
+
await processManager.shutdown(1);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
private async handleInit(service: DockerService, config: DockerConfig): Promise<void> {
|
|
93
|
+
Logger.section("Docker Init");
|
|
94
|
+
Logger.info("Generating", "Docker configuration files");
|
|
95
|
+
|
|
96
|
+
await service.generateDockerfile(config);
|
|
97
|
+
await service.generateCompose(config);
|
|
98
|
+
|
|
99
|
+
Logger.divider();
|
|
100
|
+
Logger.info("Next steps", "");
|
|
101
|
+
Logger.log(` ${Logger.C.gray}โ${Logger.C.reset} ${Logger.C.primary}xavva docker build${Logger.C.reset} ${Logger.C.gray}- Build image${Logger.C.reset}`);
|
|
102
|
+
Logger.log(` ${Logger.C.gray}โ${Logger.C.reset} ${Logger.C.primary}xavva docker up${Logger.C.reset} ${Logger.C.gray}- Start containers${Logger.C.reset}`);
|
|
103
|
+
Logger.log(` ${Logger.C.gray}โ${Logger.C.reset} ${Logger.C.primary}xavva docker run${Logger.C.reset} ${Logger.C.gray}- Run dev mode${Logger.C.reset}`);
|
|
104
|
+
Logger.endSection();
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
private async handlePush(service: DockerService, config: DockerConfig, args?: CLIArguments): Promise<void> {
|
|
108
|
+
const registry = args?.registry as string || "docker.io";
|
|
109
|
+
const namespace = args?.namespace as string || "";
|
|
110
|
+
|
|
111
|
+
const fullImageName = namespace
|
|
112
|
+
? `${registry}/${namespace}/${config.imageName}:${config.tag}`
|
|
113
|
+
: `${registry}/${config.imageName}:${config.tag}`;
|
|
114
|
+
|
|
115
|
+
Logger.section("Docker Push");
|
|
116
|
+
Logger.info("Image", fullImageName);
|
|
117
|
+
Logger.info("Registry", registry);
|
|
118
|
+
|
|
119
|
+
Logger.success("Use: docker push " + fullImageName);
|
|
120
|
+
Logger.dim("Make sure you're logged in: docker login " + registry);
|
|
121
|
+
}
|
|
122
|
+
}
|