@archznn/xavva 3.0.0 → 3.1.1
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 +79 -1
- package/package.json +1 -1
- package/src/commands/ConfigCommand.ts +48 -0
- package/src/commands/DbCommand.ts +126 -0
- package/src/commands/DockerCommand.ts +133 -0
- package/src/commands/HelpCommand.ts +32 -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 +12 -0
- package/src/index.ts +6 -1
- package/src/services/DbService.ts +357 -0
- package/src/services/DockerService.ts +372 -0
- package/src/services/HttpService.ts +259 -0
- package/src/services/TestService.ts +326 -0
- package/src/types/config.ts +38 -0
- package/src/utils/config.ts +38 -2
|
@@ -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
|
@@ -32,6 +32,10 @@ import { HealthCommand } from "../commands/HealthCommand";
|
|
|
32
32
|
import { CompletionCommand } from "../commands/CompletionCommand";
|
|
33
33
|
import { ChangelogCommand } from "../commands/ChangelogCommand";
|
|
34
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";
|
|
35
39
|
import { NotificationService } from "../services/NotificationService";
|
|
36
40
|
import type { Command } from "../commands/Command";
|
|
37
41
|
import { Logger } from "../utils/ui";
|
|
@@ -70,6 +74,10 @@ export interface Commands {
|
|
|
70
74
|
health: HealthCommand;
|
|
71
75
|
completion: CompletionCommand;
|
|
72
76
|
changelog: ChangelogCommand;
|
|
77
|
+
test: TestCommand;
|
|
78
|
+
db: DbCommand;
|
|
79
|
+
http: HttpCommand;
|
|
80
|
+
docker: DockerCommand;
|
|
73
81
|
}
|
|
74
82
|
|
|
75
83
|
export class DIContainer {
|
|
@@ -166,6 +174,10 @@ export class DIContainer {
|
|
|
166
174
|
health: new HealthCommand(),
|
|
167
175
|
completion: new CompletionCommand(),
|
|
168
176
|
changelog: new ChangelogCommand(),
|
|
177
|
+
test: new TestCommand(),
|
|
178
|
+
db: new DbCommand(),
|
|
179
|
+
http: new HttpCommand(),
|
|
180
|
+
docker: new DockerCommand(),
|
|
169
181
|
};
|
|
170
182
|
}
|
|
171
183
|
|
package/src/index.ts
CHANGED
|
@@ -31,7 +31,8 @@ async function main() {
|
|
|
31
31
|
"deploy", "build", "start", "dev", "doctor", "run",
|
|
32
32
|
"debug", "logs", "docs", "audit", "profiles",
|
|
33
33
|
"deps", "tomcat", "encoding", "init", "config",
|
|
34
|
-
"history", "redo", "health", "completion", "changelog",
|
|
34
|
+
"history", "redo", "health", "completion", "changelog",
|
|
35
|
+
"test", "db", "http", "docker", "help"
|
|
35
36
|
];
|
|
36
37
|
const commandName = positionals.find(p => commandNames.includes(p)) || "deploy";
|
|
37
38
|
|
|
@@ -111,6 +112,10 @@ async function main() {
|
|
|
111
112
|
registry.register("health", commands.health);
|
|
112
113
|
registry.register("completion", commands.completion);
|
|
113
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);
|
|
114
119
|
|
|
115
120
|
// Configura flags específicas
|
|
116
121
|
if (commandName === "debug") values.debug = true;
|
|
@@ -0,0 +1,357 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Serviço de migrações de banco de dados
|
|
3
|
+
* Suporta Flyway e Liquibase
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { Logger } from "../utils/ui";
|
|
7
|
+
import { spawn } from "child_process";
|
|
8
|
+
import fs from "fs";
|
|
9
|
+
import path from "path";
|
|
10
|
+
|
|
11
|
+
export type MigrationTool = "flyway" | "liquibase" | "auto";
|
|
12
|
+
|
|
13
|
+
export interface DbConfig {
|
|
14
|
+
url: string;
|
|
15
|
+
username: string;
|
|
16
|
+
password: string;
|
|
17
|
+
driver?: string;
|
|
18
|
+
migrationsPath?: string;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export interface MigrationStatus {
|
|
22
|
+
version: string;
|
|
23
|
+
description: string;
|
|
24
|
+
state: "pending" | "applied" | "failed";
|
|
25
|
+
installedOn?: Date;
|
|
26
|
+
executionTime?: number;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export interface MigrationResult {
|
|
30
|
+
success: boolean;
|
|
31
|
+
message: string;
|
|
32
|
+
migrationsApplied: number;
|
|
33
|
+
errors: string[];
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export class DbService {
|
|
37
|
+
private buildTool: "maven" | "gradle";
|
|
38
|
+
private projectPath: string;
|
|
39
|
+
|
|
40
|
+
constructor(buildTool: "maven" | "gradle", projectPath: string = process.cwd()) {
|
|
41
|
+
this.buildTool = buildTool;
|
|
42
|
+
this.projectPath = projectPath;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Detecta qual ferramenta de migração está configurada no projeto
|
|
47
|
+
*/
|
|
48
|
+
async detectTool(): Promise<MigrationTool> {
|
|
49
|
+
const pomPath = path.join(this.projectPath, "pom.xml");
|
|
50
|
+
const buildGradlePath = path.join(this.projectPath, "build.gradle");
|
|
51
|
+
const buildGradleKtsPath = path.join(this.projectPath, "build.gradle.kts");
|
|
52
|
+
|
|
53
|
+
let content = "";
|
|
54
|
+
if (fs.existsSync(pomPath)) {
|
|
55
|
+
content = fs.readFileSync(pomPath, "utf-8");
|
|
56
|
+
} else if (fs.existsSync(buildGradlePath)) {
|
|
57
|
+
content = fs.readFileSync(buildGradlePath, "utf-8");
|
|
58
|
+
} else if (fs.existsSync(buildGradleKtsPath)) {
|
|
59
|
+
content = fs.readFileSync(buildGradleKtsPath, "utf-8");
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
if (content.includes("flyway") || content.includes("flyway-core")) {
|
|
63
|
+
return "flyway";
|
|
64
|
+
}
|
|
65
|
+
if (content.includes("liquibase") || content.includes("liquibase-core")) {
|
|
66
|
+
return "liquibase";
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// Verificar arquivos de configuração
|
|
70
|
+
if (fs.existsSync(path.join(this.projectPath, "src", "main", "resources", "db", "migration"))) {
|
|
71
|
+
return "flyway";
|
|
72
|
+
}
|
|
73
|
+
if (fs.existsSync(path.join(this.projectPath, "src", "main", "resources", "db", "changelog"))) {
|
|
74
|
+
return "liquibase";
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
return "auto";
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Executa migrações pendentes
|
|
82
|
+
*/
|
|
83
|
+
async migrate(config?: DbConfig): Promise<MigrationResult> {
|
|
84
|
+
const tool = await this.detectTool();
|
|
85
|
+
Logger.section("Database Migration");
|
|
86
|
+
Logger.info("Tool", tool === "auto" ? "auto-detect" : tool);
|
|
87
|
+
|
|
88
|
+
if (tool === "auto") {
|
|
89
|
+
return {
|
|
90
|
+
success: false,
|
|
91
|
+
message: "No migration tool detected. Please add Flyway or Liquibase to your project.",
|
|
92
|
+
migrationsApplied: 0,
|
|
93
|
+
errors: ["No migration tool found"]
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
const result = tool === "flyway"
|
|
98
|
+
? await this.runFlywayMigrate(config)
|
|
99
|
+
: await this.runLiquibaseUpdate(config);
|
|
100
|
+
|
|
101
|
+
Logger.endSection();
|
|
102
|
+
return result;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Mostra status das migrações
|
|
107
|
+
*/
|
|
108
|
+
async status(config?: DbConfig): Promise<MigrationStatus[]> {
|
|
109
|
+
const tool = await this.detectTool();
|
|
110
|
+
Logger.section("Migration Status");
|
|
111
|
+
Logger.info("Tool", tool);
|
|
112
|
+
|
|
113
|
+
if (tool === "auto") {
|
|
114
|
+
Logger.warn("No migration tool detected");
|
|
115
|
+
Logger.endSection();
|
|
116
|
+
return [];
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
const statuses = tool === "flyway"
|
|
120
|
+
? await this.runFlywayInfo(config)
|
|
121
|
+
: await this.runLiquibaseStatus(config);
|
|
122
|
+
|
|
123
|
+
// Print status table
|
|
124
|
+
if (statuses.length > 0) {
|
|
125
|
+
Logger.divider();
|
|
126
|
+
for (const status of statuses) {
|
|
127
|
+
const stateColor = status.state === "applied" ? Logger.C.success
|
|
128
|
+
: status.state === "failed" ? Logger.C.error
|
|
129
|
+
: Logger.C.warning;
|
|
130
|
+
Logger.info(status.version, `${stateColor}${status.state}${Logger.C.reset} - ${status.description}`);
|
|
131
|
+
}
|
|
132
|
+
} else {
|
|
133
|
+
Logger.info("Status", "No migrations found");
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
Logger.endSection();
|
|
137
|
+
return statuses;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* Reseta o banco (drop all + migrate)
|
|
142
|
+
*/
|
|
143
|
+
async reset(config?: DbConfig): Promise<MigrationResult> {
|
|
144
|
+
Logger.section("Database Reset");
|
|
145
|
+
Logger.warn("This will DROP ALL DATA in the database!");
|
|
146
|
+
|
|
147
|
+
const tool = await this.detectTool();
|
|
148
|
+
|
|
149
|
+
if (tool === "flyway") {
|
|
150
|
+
return await this.runFlywayClean(config);
|
|
151
|
+
} else if (tool === "liquibase") {
|
|
152
|
+
return await this.runLiquibaseDropAll(config);
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
Logger.endSection();
|
|
156
|
+
return {
|
|
157
|
+
success: false,
|
|
158
|
+
message: "No migration tool detected",
|
|
159
|
+
migrationsApplied: 0,
|
|
160
|
+
errors: []
|
|
161
|
+
};
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/**
|
|
165
|
+
* Popula dados de teste/seed
|
|
166
|
+
*/
|
|
167
|
+
async seed(config?: DbConfig, seedFile?: string): Promise<MigrationResult> {
|
|
168
|
+
Logger.section("Database Seed");
|
|
169
|
+
|
|
170
|
+
// Procurar arquivos de seed
|
|
171
|
+
const seedPaths = [
|
|
172
|
+
path.join(this.projectPath, "src", "test", "resources", "seed.sql"),
|
|
173
|
+
path.join(this.projectPath, "src", "main", "resources", "seed.sql"),
|
|
174
|
+
path.join(this.projectPath, "seed.sql"),
|
|
175
|
+
];
|
|
176
|
+
|
|
177
|
+
const seedPath = seedFile || seedPaths.find(p => fs.existsSync(p));
|
|
178
|
+
|
|
179
|
+
if (!seedPath) {
|
|
180
|
+
return {
|
|
181
|
+
success: false,
|
|
182
|
+
message: "No seed file found. Create seed.sql in src/test/resources/ or project root.",
|
|
183
|
+
migrationsApplied: 0,
|
|
184
|
+
errors: ["Seed file not found"]
|
|
185
|
+
};
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
Logger.info("Seed file", seedPath);
|
|
189
|
+
|
|
190
|
+
// Executar seed via JDBC ou comando SQL
|
|
191
|
+
const result = await this.executeSeed(seedPath, config);
|
|
192
|
+
|
|
193
|
+
Logger.endSection();
|
|
194
|
+
return result;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
// ===== Flyway Commands =====
|
|
198
|
+
|
|
199
|
+
private async runFlywayMigrate(config?: DbConfig): Promise<MigrationResult> {
|
|
200
|
+
return this.runMavenOrGradle("flyway:migrate", "flywayMigrate", config);
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
private async runFlywayInfo(config?: DbConfig): Promise<MigrationStatus[]> {
|
|
204
|
+
const output = await this.runMavenOrGradleOutput("flyway:info", "flywayInfo", config);
|
|
205
|
+
return this.parseFlywayInfo(output);
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
private async runFlywayClean(config?: DbConfig): Promise<MigrationResult> {
|
|
209
|
+
return this.runMavenOrGradle("flyway:clean", "flywayClean", config);
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
// ===== Liquibase Commands =====
|
|
213
|
+
|
|
214
|
+
private async runLiquibaseUpdate(config?: DbConfig): Promise<MigrationResult> {
|
|
215
|
+
return this.runMavenOrGradle("liquibase:update", "liquibaseUpdate", config);
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
private async runLiquibaseStatus(config?: DbConfig): Promise<MigrationStatus[]> {
|
|
219
|
+
const output = await this.runMavenOrGradleOutput("liquibase:status", "liquibaseStatus", config);
|
|
220
|
+
return this.parseLiquibaseStatus(output);
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
private async runLiquibaseDropAll(config?: DbConfig): Promise<MigrationResult> {
|
|
224
|
+
return this.runMavenOrGradle("liquibase:dropAll", "liquibaseDropAll", config);
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
// ===== Generic Execution =====
|
|
228
|
+
|
|
229
|
+
private async runMavenOrGradle(
|
|
230
|
+
mavenGoal: string,
|
|
231
|
+
gradleTask: string,
|
|
232
|
+
config?: DbConfig
|
|
233
|
+
): Promise<MigrationResult> {
|
|
234
|
+
return new Promise((resolve) => {
|
|
235
|
+
const [cmd, ...args] = this.buildTool === "maven"
|
|
236
|
+
? [process.platform === "win32" ? "mvn.cmd" : "mvn", mavenGoal, "-q"]
|
|
237
|
+
: [process.platform === "win32" ? "gradle.bat" : "gradle", gradleTask, "-q"];
|
|
238
|
+
|
|
239
|
+
const env = config ? this.buildEnv(config) : process.env;
|
|
240
|
+
const spinner = Logger.spinner("Running migrations");
|
|
241
|
+
|
|
242
|
+
const child = spawn(cmd, args, {
|
|
243
|
+
cwd: this.projectPath,
|
|
244
|
+
env: { ...process.env, ...env },
|
|
245
|
+
shell: process.platform === "win32"
|
|
246
|
+
});
|
|
247
|
+
|
|
248
|
+
let stdout = "";
|
|
249
|
+
let stderr = "";
|
|
250
|
+
|
|
251
|
+
child.stdout?.on("data", (data) => stdout += data.toString());
|
|
252
|
+
child.stderr?.on("data", (data) => stderr += data.toString());
|
|
253
|
+
|
|
254
|
+
child.on("close", (code) => {
|
|
255
|
+
spinner(code === 0);
|
|
256
|
+
|
|
257
|
+
if (code === 0) {
|
|
258
|
+
Logger.success("Migrations completed successfully");
|
|
259
|
+
} else {
|
|
260
|
+
Logger.error("Migration failed");
|
|
261
|
+
if (stderr) Logger.dim(stderr.slice(0, 500));
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
resolve({
|
|
265
|
+
success: code === 0,
|
|
266
|
+
message: code === 0 ? "Success" : stderr || "Failed",
|
|
267
|
+
migrationsApplied: this.countMigrations(stdout),
|
|
268
|
+
errors: code !== 0 ? [stderr] : []
|
|
269
|
+
});
|
|
270
|
+
});
|
|
271
|
+
});
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
private async runMavenOrGradleOutput(
|
|
275
|
+
mavenGoal: string,
|
|
276
|
+
gradleTask: string,
|
|
277
|
+
config?: DbConfig
|
|
278
|
+
): Promise<string> {
|
|
279
|
+
return new Promise((resolve) => {
|
|
280
|
+
const [cmd, ...args] = this.buildTool === "maven"
|
|
281
|
+
? [process.platform === "win32" ? "mvn.cmd" : "mvn", mavenGoal]
|
|
282
|
+
: [process.platform === "win32" ? "gradle.bat" : "gradle", gradleTask];
|
|
283
|
+
|
|
284
|
+
const env = config ? this.buildEnv(config) : process.env;
|
|
285
|
+
let output = "";
|
|
286
|
+
|
|
287
|
+
const child = spawn(cmd, args, {
|
|
288
|
+
cwd: this.projectPath,
|
|
289
|
+
env: { ...process.env, ...env },
|
|
290
|
+
shell: process.platform === "win32"
|
|
291
|
+
});
|
|
292
|
+
|
|
293
|
+
child.stdout?.on("data", (data) => output += data.toString());
|
|
294
|
+
child.stderr?.on("data", (data) => output += data.toString());
|
|
295
|
+
|
|
296
|
+
child.on("close", () => resolve(output));
|
|
297
|
+
});
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
private async executeSeed(seedPath: string, config?: DbConfig): Promise<MigrationResult> {
|
|
301
|
+
// Implementação básica - executa via JDBC se possível
|
|
302
|
+
// Ou gera comando SQL para execução manual
|
|
303
|
+
|
|
304
|
+
const sql = fs.readFileSync(seedPath, "utf-8");
|
|
305
|
+
const statements = sql.split(";").filter(s => s.trim());
|
|
306
|
+
|
|
307
|
+
Logger.info("Statements", statements.length);
|
|
308
|
+
Logger.success("Seed file ready for execution");
|
|
309
|
+
Logger.dim("Use your database client to execute the seed file");
|
|
310
|
+
|
|
311
|
+
return {
|
|
312
|
+
success: true,
|
|
313
|
+
message: `Seed file prepared: ${seedPath}`,
|
|
314
|
+
migrationsApplied: 0,
|
|
315
|
+
errors: []
|
|
316
|
+
};
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
private buildEnv(config: DbConfig): Record<string, string> {
|
|
320
|
+
return {
|
|
321
|
+
JDBC_URL: config.url,
|
|
322
|
+
JDBC_USER: config.username,
|
|
323
|
+
JDBC_PASSWORD: config.password,
|
|
324
|
+
...(config.driver && { JDBC_DRIVER: config.driver })
|
|
325
|
+
};
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
private countMigrations(output: string): number {
|
|
329
|
+
const match = output.match(/Successfully applied (\d+) migration/);
|
|
330
|
+
return match ? parseInt(match[1]) : 0;
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
private parseFlywayInfo(output: string): MigrationStatus[] {
|
|
334
|
+
const statuses: MigrationStatus[] = [];
|
|
335
|
+
const lines = output.split("\n");
|
|
336
|
+
|
|
337
|
+
for (const line of lines) {
|
|
338
|
+
// Parse Flyway info table
|
|
339
|
+
const match = line.match(/\|\s*(\S+)\s*\|\s*(\S+)\s*\|\s*(\S+)\s*\|\s*(.+?)\s*\|/);
|
|
340
|
+
if (match && !line.includes("Version")) {
|
|
341
|
+
statuses.push({
|
|
342
|
+
version: match[1],
|
|
343
|
+
description: match[4].trim(),
|
|
344
|
+
state: match[3].toLowerCase() as MigrationStatus["state"]
|
|
345
|
+
});
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
return statuses;
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
private parseLiquibaseStatus(output: string): MigrationStatus[] {
|
|
353
|
+
const statuses: MigrationStatus[] = [];
|
|
354
|
+
// Simplified parsing - Liquibase output varies
|
|
355
|
+
return statuses;
|
|
356
|
+
}
|
|
357
|
+
}
|