@archznn/xavva 3.0.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.9.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.
@@ -26,6 +26,11 @@ Xavva is a high-performance CLI built with **Bun** that transforms the Java/Tomc
26
26
  - ๐Ÿ“œ **Command History** โ€” Track and replay commands with `xavva history` and `xavva redo`
27
27
  - ๐Ÿฅ **Health Check** โ€” Verify environment (Java, ports, memory, disk) with `xavva health`
28
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
29
34
 
30
35
  ---
31
36
 
@@ -80,6 +85,29 @@ xavva history
80
85
  # Repeat last command
81
86
  xavva redo
82
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
+
83
111
  # Enable shell completions (bash example)
84
112
  eval "$(xavva completion bash)"
85
113
  ```
@@ -130,6 +158,56 @@ eval "$(xavva completion bash)"
130
158
  | `xavva redo` | Repeat the last executed command |
131
159
  | `xavva completion <shell>` | Generate shell completions (bash/zsh/fish) |
132
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
+ ```
210
+
133
211
  ---
134
212
 
135
213
  ## ๐Ÿฑ Embedded Tomcat
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@archznn/xavva",
3
- "version": "3.0.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",
@@ -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
+ }
@@ -40,6 +40,11 @@ export class HelpCommand implements Command {
40
40
  ${this.c("cyan", "completion")} Generate shell completions (bash/zsh/fish)
41
41
  ${this.c("cyan", "changelog")} Generate changelog from conventional commits
42
42
 
43
+ ${this.c("magenta", "test")} Run tests (JUnit/TestNG)
44
+ ${this.c("magenta", "db")} Database migrations (Flyway/Liquibase)
45
+ ${this.c("magenta", "http")} HTTP client for API testing
46
+ ${this.c("magenta", "docker")} Docker integration (build, run, compose)
47
+
43
48
  ${this.c("yellow", "GENERAL OPTIONS")}
44
49
  ${this.c("cyan", "-p, --path")} <path> Tomcat installation path
45
50
  ${this.c("cyan", "-t, --tool")} <tool> Build tool: maven | gradle
@@ -151,6 +156,33 @@ export class HelpCommand implements Command {
151
156
  xavva deploy --debug-level trace # Trace all operations
152
157
  xavva deploy --debug-level silly # Everything including config
153
158
 
159
+ ${this.c("dim", "# Multi-environment")}
160
+ xavva deploy --env staging # Deploy to staging environment
161
+ xavva dev --env dev # Use dev environment config
162
+
163
+ ${this.c("dim", "# Test runner")}
164
+ xavva test # Run all tests
165
+ xavva test --watch # Watch mode
166
+ xavva test --coverage # Generate coverage report
167
+ xavva test UserServiceTest # Run specific test class
168
+
169
+ ${this.c("dim", "# Database migrations")}
170
+ xavva db status # Show migration status
171
+ xavva db migrate # Run pending migrations
172
+ xavva db reset --force # Reset database (drops all!)
173
+ xavva db seed # Populate with test data
174
+
175
+ ${this.c("dim", "# HTTP Client")}
176
+ xavva http GET /api/users # Test endpoint
177
+ xavva http POST /api/users --body '{"name":"John"}'
178
+ xavva http GET /api/users --param page=1 --param size=10
179
+
180
+ ${this.c("dim", "# Docker")}
181
+ xavva docker init # Generate Dockerfile & compose
182
+ xavva docker build # Build image
183
+ xavva docker up # Start with docker-compose
184
+ xavva docker run # Run dev container
185
+
154
186
  ${this.c("yellow", "CONFIGURATION")}
155
187
  Settings are loaded from ${this.c("cyan", "xavva.json")} in the project root:
156
188
 
@@ -0,0 +1,134 @@
1
+ /**
2
+ * Comando de HTTP Client
3
+ * xavva http <method> <url> [options]
4
+ * xavva http --interactive
5
+ */
6
+
7
+ import type { Command } from "./Command";
8
+ import type { AppConfig, CLIArguments } from "../types/config";
9
+ import { HttpService, type HttpRequest } from "../services/HttpService";
10
+ import { EndpointService } from "../services/EndpointService";
11
+ import { Logger } from "../utils/ui";
12
+ import { ProcessManager } from "../utils/processManager";
13
+ import fs from "fs";
14
+ import path from "path";
15
+
16
+ export class HttpCommand implements Command {
17
+ async execute(config: AppConfig, args?: CLIArguments, positionals?: string[]): Promise<void> {
18
+ const processManager = ProcessManager.getInstance();
19
+
20
+ // Modo interativo
21
+ if (args?.interactive || positionals?.length === 1) {
22
+ const baseUrl = `http://localhost:${config.tomcat.port}`;
23
+ const service = new HttpService(baseUrl);
24
+ await service.interactive();
25
+ return;
26
+ }
27
+
28
+ // Extrai mรฉtodo e URL
29
+ const method = (positionals?.[1] || "GET").toUpperCase() as HttpRequest["method"];
30
+ let url = positionals?.[2] || "/";
31
+
32
+ // Se URL nรฃo comeรงar com http, adiciona base
33
+ const baseUrl = args?.["base-url"] || `http://localhost:${config.tomcat.port}`;
34
+ if (!url.startsWith("http")) {
35
+ url = `${baseUrl}${url.startsWith("/") ? url : `/${url}`}`;
36
+ }
37
+
38
+ // Parse headers
39
+ const headers = this.parseHeaders(args);
40
+
41
+ // Parse body
42
+ let body: string | object | undefined;
43
+ if (args?.body) {
44
+ body = this.parseBody(args.body as string);
45
+ } else if (args?.file) {
46
+ const filePath = args.file as string;
47
+ if (fs.existsSync(filePath)) {
48
+ body = fs.readFileSync(filePath, "utf-8");
49
+ } else {
50
+ Logger.error(`File not found: ${filePath}`);
51
+ await processManager.shutdown(1);
52
+ return;
53
+ }
54
+ }
55
+
56
+ // Parse query params
57
+ const params = this.parseParams(args);
58
+
59
+ // Cria request
60
+ const request: HttpRequest = {
61
+ method,
62
+ url,
63
+ headers,
64
+ body,
65
+ params,
66
+ timeout: args?.timeout ? parseInt(args.timeout as string) : 30000
67
+ };
68
+
69
+ // Executa
70
+ const service = new HttpService();
71
+
72
+ try {
73
+ await service.request(request);
74
+ } catch (error) {
75
+ await processManager.shutdown(1);
76
+ }
77
+ }
78
+
79
+ private parseHeaders(args?: CLIArguments): Record<string, string> | undefined {
80
+ const headers: Record<string, string> = {};
81
+
82
+ // Formato: --header "Authorization: Bearer token"
83
+ const headerArgs = args?.header;
84
+ if (headerArgs) {
85
+ const headerList = Array.isArray(headerArgs) ? headerArgs : [headerArgs];
86
+ for (const h of headerList) {
87
+ const match = h.match(/^([^:]+):\s*(.+)$/);
88
+ if (match) {
89
+ headers[match[1]] = match[2];
90
+ }
91
+ }
92
+ }
93
+
94
+ // Content-Type shorthand
95
+ if (args?.["content-type"]) {
96
+ headers["Content-Type"] = args["content-type"] as string;
97
+ }
98
+
99
+ // Accept shorthand
100
+ if (args?.accept) {
101
+ headers["Accept"] = args.accept as string;
102
+ }
103
+
104
+ return Object.keys(headers).length > 0 ? headers : undefined;
105
+ }
106
+
107
+ private parseBody(bodyStr: string): string | object {
108
+ // Tenta parsear como JSON
109
+ try {
110
+ return JSON.parse(bodyStr);
111
+ } catch {
112
+ // Retorna como string
113
+ return bodyStr;
114
+ }
115
+ }
116
+
117
+ private parseParams(args?: CLIArguments): Record<string, string> | undefined {
118
+ const params: Record<string, string> = {};
119
+
120
+ // Formato: --param "key=value"
121
+ const paramArgs = args?.param;
122
+ if (paramArgs) {
123
+ const paramList = Array.isArray(paramArgs) ? paramArgs : [paramArgs];
124
+ for (const p of paramList) {
125
+ const [key, value] = p.split("=");
126
+ if (key && value !== undefined) {
127
+ params[key] = value;
128
+ }
129
+ }
130
+ }
131
+
132
+ return Object.keys(params).length > 0 ? params : undefined;
133
+ }
134
+ }
@@ -104,6 +104,12 @@ export class InitCommand implements Command {
104
104
  default: "UTF-8"
105
105
  });
106
106
 
107
+ // Multi-environment setup
108
+ const enableMultiEnv = await confirm({
109
+ message: "Configure multiple environments?",
110
+ default: false
111
+ });
112
+
107
113
  // Build config object
108
114
  const config: Record<string, unknown> = {
109
115
  appName,
@@ -142,6 +148,70 @@ export class InitCommand implements Command {
142
148
  config.tomcatPath = tomcatPath;
143
149
  }
144
150
 
151
+ // Add environments if enabled
152
+ if (enableMultiEnv) {
153
+ Logger.newline();
154
+ Logger.dim("Environment Configuration:");
155
+
156
+ const environments: Record<string, unknown> = {};
157
+
158
+ // Dev environment
159
+ const devPort = await number({
160
+ message: "Dev environment port:",
161
+ default: port
162
+ }) || port;
163
+ environments.dev = {
164
+ port: devPort,
165
+ profile: "dev"
166
+ };
167
+
168
+ // Test environment
169
+ const testPort = await number({
170
+ message: "Test environment port:",
171
+ default: port + 1
172
+ }) || port + 1;
173
+ environments.test = {
174
+ port: testPort,
175
+ profile: "test"
176
+ };
177
+
178
+ // Staging environment
179
+ const hasStaging = await confirm({
180
+ message: "Add staging environment?",
181
+ default: true
182
+ });
183
+
184
+ if (hasStaging) {
185
+ const stagingPort = await number({
186
+ message: "Staging environment port:",
187
+ default: port + 2
188
+ }) || port + 2;
189
+ environments.staging = {
190
+ port: stagingPort,
191
+ profile: "staging"
192
+ };
193
+ }
194
+
195
+ config.environments = environments;
196
+
197
+ // Add DB config example
198
+ const addDbExample = await confirm({
199
+ message: "Add database configuration example?",
200
+ default: true
201
+ });
202
+
203
+ if (addDbExample) {
204
+ environments.dev = {
205
+ ...environments.dev,
206
+ db: {
207
+ url: "jdbc:h2:mem:devdb",
208
+ username: "sa",
209
+ password: ""
210
+ }
211
+ };
212
+ }
213
+ }
214
+
145
215
  // Save file
146
216
  Logger.newline();
147
217
  Logger.step("Saving configuration...");