@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 CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  > Ultra-fast development toolkit for Java Enterprise (Tomcat) on Windows, Linux & macOS
4
4
 
5
- [![Version](https://img.shields.io/badge/version-2.6.0-blue.svg)](https://github.com/leorsousa05/Xavva)
5
+ [![Version](https://img.shields.io/badge/version-3.1.0-blue.svg)](https://github.com/leorsousa05/Xavva)
6
6
  [![License](https://img.shields.io/badge/license-MIT-green.svg)](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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@archznn/xavva",
3
- "version": "2.9.0",
3
+ "version": "3.1.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",
@@ -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
+ }