@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
|
@@ -38,6 +38,12 @@ export class HelpCommand implements Command {
|
|
|
38
38
|
${this.c("cyan", "redo")} Repeat last command
|
|
39
39
|
${this.c("cyan", "health")} Check environment health
|
|
40
40
|
${this.c("cyan", "completion")} Generate shell completions (bash/zsh/fish)
|
|
41
|
+
${this.c("cyan", "changelog")} Generate changelog from conventional commits
|
|
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)
|
|
41
47
|
|
|
42
48
|
${this.c("yellow", "GENERAL OPTIONS")}
|
|
43
49
|
${this.c("cyan", "-p, --path")} <path> Tomcat installation path
|
|
@@ -56,6 +62,7 @@ export class HelpCommand implements Command {
|
|
|
56
62
|
${this.c("cyan", "-s, --no-build")} Skip initial build
|
|
57
63
|
${this.c("cyan", "-q, --quiet")} Minimal output
|
|
58
64
|
${this.c("cyan", "-V, --verbose")} Detailed output
|
|
65
|
+
${this.c("cyan", "--debug-level")} <lvl> Debug level: error|warn|info|verbose|trace|silly
|
|
59
66
|
${this.c("cyan", "-h, --help")} Show this help
|
|
60
67
|
${this.c("cyan", "-v, --version")} Show version
|
|
61
68
|
|
|
@@ -139,6 +146,43 @@ export class HelpCommand implements Command {
|
|
|
139
146
|
xavva completion zsh # Generate zsh completions
|
|
140
147
|
eval "$(xavva completion bash)" # Enable in current shell
|
|
141
148
|
|
|
149
|
+
${this.c("dim", "# Changelog")}
|
|
150
|
+
xavva changelog generate # Generate CHANGELOG.md
|
|
151
|
+
xavva changelog check # Validate conventional commits
|
|
152
|
+
xavva changelog preview # Preview without saving
|
|
153
|
+
|
|
154
|
+
${this.c("dim", "# Debug levels")}
|
|
155
|
+
xavva deploy --debug-level verbose # Verbose logging
|
|
156
|
+
xavva deploy --debug-level trace # Trace all operations
|
|
157
|
+
xavva deploy --debug-level silly # Everything including config
|
|
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
|
+
|
|
142
186
|
${this.c("yellow", "CONFIGURATION")}
|
|
143
187
|
Settings are loaded from ${this.c("cyan", "xavva.json")} in the project root:
|
|
144
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...");
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Comando de execução de testes
|
|
3
|
+
* xavva test [options] [filter]
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import type { Command } from "./Command";
|
|
7
|
+
import type { AppConfig, CLIArguments } from "../types/config";
|
|
8
|
+
import { TestService } from "../services/TestService";
|
|
9
|
+
import { Logger } from "../utils/ui";
|
|
10
|
+
import { ProcessManager } from "../utils/processManager";
|
|
11
|
+
|
|
12
|
+
export class TestCommand implements Command {
|
|
13
|
+
private service: TestService | null = null;
|
|
14
|
+
|
|
15
|
+
async execute(config: AppConfig, args?: CLIArguments, positionals?: string[]): Promise<void> {
|
|
16
|
+
const processManager = ProcessManager.getInstance();
|
|
17
|
+
|
|
18
|
+
// Extrai filtros de teste dos positionals (após o comando "test")
|
|
19
|
+
const filter = positionals?.slice(1).join(" ") || undefined;
|
|
20
|
+
|
|
21
|
+
// Opções
|
|
22
|
+
const watch = args?.watch || false;
|
|
23
|
+
const coverage = args?.coverage || false;
|
|
24
|
+
const verbose = args?.verbose || false;
|
|
25
|
+
const failFast = args?.["fail-fast"] || false;
|
|
26
|
+
const parallel = args?.parallel || false;
|
|
27
|
+
|
|
28
|
+
this.service = new TestService(config.project.buildTool);
|
|
29
|
+
|
|
30
|
+
try {
|
|
31
|
+
if (watch) {
|
|
32
|
+
this.service.startWatch({
|
|
33
|
+
coverage,
|
|
34
|
+
filter,
|
|
35
|
+
verbose,
|
|
36
|
+
failFast,
|
|
37
|
+
parallel
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
// Mantém processo rodando
|
|
41
|
+
process.on("SIGINT", () => {
|
|
42
|
+
this.service?.stopWatch();
|
|
43
|
+
processManager.shutdown(0);
|
|
44
|
+
});
|
|
45
|
+
} else {
|
|
46
|
+
const result = await this.service.runTests({
|
|
47
|
+
coverage,
|
|
48
|
+
filter,
|
|
49
|
+
verbose,
|
|
50
|
+
failFast,
|
|
51
|
+
parallel
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
if (!result.success) {
|
|
55
|
+
await processManager.shutdown(1);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
} catch (error) {
|
|
59
|
+
Logger.error(`Test execution failed: ${(error as Error).message}`);
|
|
60
|
+
await processManager.shutdown(1);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
}
|
package/src/di/container.ts
CHANGED
|
@@ -30,7 +30,12 @@ import { HistoryCommand } from "../commands/HistoryCommand";
|
|
|
30
30
|
import { RedoCommand } from "../commands/RedoCommand";
|
|
31
31
|
import { HealthCommand } from "../commands/HealthCommand";
|
|
32
32
|
import { CompletionCommand } from "../commands/CompletionCommand";
|
|
33
|
+
import { ChangelogCommand } from "../commands/ChangelogCommand";
|
|
33
34
|
import { HistoryService } from "../services/HistoryService";
|
|
35
|
+
import { TestCommand } from "../commands/TestCommand";
|
|
36
|
+
import { DbCommand } from "../commands/DbCommand";
|
|
37
|
+
import { HttpCommand } from "../commands/HttpCommand";
|
|
38
|
+
import { DockerCommand } from "../commands/DockerCommand";
|
|
34
39
|
import { NotificationService } from "../services/NotificationService";
|
|
35
40
|
import type { Command } from "../commands/Command";
|
|
36
41
|
import { Logger } from "../utils/ui";
|
|
@@ -68,6 +73,11 @@ export interface Commands {
|
|
|
68
73
|
redo: RedoCommand;
|
|
69
74
|
health: HealthCommand;
|
|
70
75
|
completion: CompletionCommand;
|
|
76
|
+
changelog: ChangelogCommand;
|
|
77
|
+
test: TestCommand;
|
|
78
|
+
db: DbCommand;
|
|
79
|
+
http: HttpCommand;
|
|
80
|
+
docker: DockerCommand;
|
|
71
81
|
}
|
|
72
82
|
|
|
73
83
|
export class DIContainer {
|
|
@@ -163,6 +173,11 @@ export class DIContainer {
|
|
|
163
173
|
redo: new RedoCommand(),
|
|
164
174
|
health: new HealthCommand(),
|
|
165
175
|
completion: new CompletionCommand(),
|
|
176
|
+
changelog: new ChangelogCommand(),
|
|
177
|
+
test: new TestCommand(),
|
|
178
|
+
db: new DbCommand(),
|
|
179
|
+
http: new HttpCommand(),
|
|
180
|
+
docker: new DockerCommand(),
|
|
166
181
|
};
|
|
167
182
|
}
|
|
168
183
|
|
package/src/index.ts
CHANGED
|
@@ -5,6 +5,7 @@ import { createContainer, type DIContainer } from "./di/container";
|
|
|
5
5
|
import { DeployWatcher } from "./services/DeployWatcher";
|
|
6
6
|
import { ErrorHandler } from "./errors/ErrorHandler";
|
|
7
7
|
import { ProcessManager } from "./utils/processManager";
|
|
8
|
+
import { LoggerLevel } from "./utils/LoggerLevel";
|
|
8
9
|
import pkg from "../package.json";
|
|
9
10
|
import { Logger } from "./utils/ui";
|
|
10
11
|
import type { CLIArguments } from "./types/args";
|
|
@@ -19,12 +20,19 @@ async function main() {
|
|
|
19
20
|
await processManager.shutdown(0);
|
|
20
21
|
}
|
|
21
22
|
|
|
23
|
+
// Configura debug level
|
|
24
|
+
if (values["debug-level"]) {
|
|
25
|
+
LoggerLevel.setLevel(values["debug-level"]);
|
|
26
|
+
LoggerLevel.verbose(`Debug level set to: ${values["debug-level"]}`, {});
|
|
27
|
+
}
|
|
28
|
+
|
|
22
29
|
// Identifica comando
|
|
23
30
|
const commandNames = [
|
|
24
31
|
"deploy", "build", "start", "dev", "doctor", "run",
|
|
25
32
|
"debug", "logs", "docs", "audit", "profiles",
|
|
26
33
|
"deps", "tomcat", "encoding", "init", "config",
|
|
27
|
-
"history", "redo", "health", "completion", "
|
|
34
|
+
"history", "redo", "health", "completion", "changelog",
|
|
35
|
+
"test", "db", "http", "docker", "help"
|
|
28
36
|
];
|
|
29
37
|
const commandName = positionals.find(p => commandNames.includes(p)) || "deploy";
|
|
30
38
|
|
|
@@ -103,6 +111,11 @@ async function main() {
|
|
|
103
111
|
registry.register("redo", commands.redo);
|
|
104
112
|
registry.register("health", commands.health);
|
|
105
113
|
registry.register("completion", commands.completion);
|
|
114
|
+
registry.register("changelog", commands.changelog);
|
|
115
|
+
registry.register("test", commands.test);
|
|
116
|
+
registry.register("db", commands.db);
|
|
117
|
+
registry.register("http", commands.http);
|
|
118
|
+
registry.register("docker", commands.docker);
|
|
106
119
|
|
|
107
120
|
// Configura flags específicas
|
|
108
121
|
if (commandName === "debug") values.debug = true;
|