frg-data-diff 2.0.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 +628 -0
- package/dist/apply/apply-diff.d.ts +24 -0
- package/dist/apply/apply-diff.js +205 -0
- package/dist/apply/conflict.d.ts +20 -0
- package/dist/apply/conflict.js +14 -0
- package/dist/apply/plan.d.ts +13 -0
- package/dist/apply/plan.js +37 -0
- package/dist/cli/apply.d.ts +8 -0
- package/dist/cli/apply.js +270 -0
- package/dist/cli/generator.d.ts +9 -0
- package/dist/cli/generator.js +804 -0
- package/dist/cli/pg-triggers.d.ts +2 -0
- package/dist/cli/pg-triggers.js +185 -0
- package/dist/cli/root.d.ts +8 -0
- package/dist/cli/root.js +231 -0
- package/dist/cli/sql.d.ts +9 -0
- package/dist/cli/sql.js +158 -0
- package/dist/config/config-schema.d.ts +380 -0
- package/dist/config/config-schema.js +95 -0
- package/dist/config/load-config.d.ts +12 -0
- package/dist/config/load-config.js +87 -0
- package/dist/config/resolve-options.d.ts +95 -0
- package/dist/config/resolve-options.js +183 -0
- package/dist/config/write-config.d.ts +18 -0
- package/dist/config/write-config.js +103 -0
- package/dist/db/connection.d.ts +28 -0
- package/dist/db/connection.js +34 -0
- package/dist/db/metadata.d.ts +70 -0
- package/dist/db/metadata.js +238 -0
- package/dist/db/pg-triggers.d.ts +27 -0
- package/dist/db/pg-triggers.js +59 -0
- package/dist/db/sql.d.ts +72 -0
- package/dist/db/sql.js +250 -0
- package/dist/diff/diff-schema.d.ts +380 -0
- package/dist/diff/diff-schema.js +67 -0
- package/dist/diff/generate-diff.d.ts +20 -0
- package/dist/diff/generate-diff.js +224 -0
- package/dist/diff/pg-triggers-diff.d.ts +11 -0
- package/dist/diff/pg-triggers-diff.js +161 -0
- package/dist/diff/serialize-value.d.ts +35 -0
- package/dist/diff/serialize-value.js +242 -0
- package/dist/diff/write-diff-yaml.d.ts +3 -0
- package/dist/diff/write-diff-yaml.js +48 -0
- package/dist/schema-diff/generate-schema-diff.d.ts +12 -0
- package/dist/schema-diff/generate-schema-diff.js +355 -0
- package/dist/schema-diff/generate-schema-sql.d.ts +20 -0
- package/dist/schema-diff/generate-schema-sql.js +187 -0
- package/dist/schema-diff/schema-diff-schema.d.ts +1088 -0
- package/dist/schema-diff/schema-diff-schema.js +70 -0
- package/dist/shared/env-values.d.ts +11 -0
- package/dist/shared/env-values.js +100 -0
- package/dist/shared/generator-wizard.d.ts +10 -0
- package/dist/shared/generator-wizard.js +337 -0
- package/dist/shared/identifiers.d.ts +24 -0
- package/dist/shared/identifiers.js +45 -0
- package/dist/shared/prompts.d.ts +26 -0
- package/dist/shared/prompts.js +104 -0
- package/dist/shared/summary.d.ts +33 -0
- package/dist/shared/summary.js +41 -0
- package/dist/sql/generate-sql.d.ts +19 -0
- package/dist/sql/generate-sql.js +223 -0
- package/package.json +39 -0
|
@@ -0,0 +1,804 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
"use strict";
|
|
3
|
+
/**
|
|
4
|
+
* frg-data-diff generate
|
|
5
|
+
*
|
|
6
|
+
* Compares a source PostgreSQL database against a destination PostgreSQL database
|
|
7
|
+
* and writes a JSON diff file.
|
|
8
|
+
*/
|
|
9
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
12
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
13
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
14
|
+
}
|
|
15
|
+
Object.defineProperty(o, k2, desc);
|
|
16
|
+
}) : (function(o, m, k, k2) {
|
|
17
|
+
if (k2 === undefined) k2 = k;
|
|
18
|
+
o[k2] = m[k];
|
|
19
|
+
}));
|
|
20
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
21
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
22
|
+
}) : function(o, v) {
|
|
23
|
+
o["default"] = v;
|
|
24
|
+
});
|
|
25
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
26
|
+
var ownKeys = function(o) {
|
|
27
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
28
|
+
var ar = [];
|
|
29
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
30
|
+
return ar;
|
|
31
|
+
};
|
|
32
|
+
return ownKeys(o);
|
|
33
|
+
};
|
|
34
|
+
return function (mod) {
|
|
35
|
+
if (mod && mod.__esModule) return mod;
|
|
36
|
+
var result = {};
|
|
37
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
38
|
+
__setModuleDefault(result, mod);
|
|
39
|
+
return result;
|
|
40
|
+
};
|
|
41
|
+
})();
|
|
42
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
43
|
+
const commander_1 = require("commander");
|
|
44
|
+
const child_process_1 = require("child_process");
|
|
45
|
+
const fs = __importStar(require("fs"));
|
|
46
|
+
const path = __importStar(require("path"));
|
|
47
|
+
const load_config_1 = require("../config/load-config");
|
|
48
|
+
const resolve_options_1 = require("../config/resolve-options");
|
|
49
|
+
const write_config_1 = require("../config/write-config");
|
|
50
|
+
const connection_1 = require("../db/connection");
|
|
51
|
+
const metadata_1 = require("../db/metadata");
|
|
52
|
+
const generate_diff_1 = require("../diff/generate-diff");
|
|
53
|
+
const write_diff_yaml_1 = require("../diff/write-diff-yaml");
|
|
54
|
+
const prompts_1 = require("../shared/prompts");
|
|
55
|
+
const env_values_1 = require("../shared/env-values");
|
|
56
|
+
const generator_wizard_1 = require("../shared/generator-wizard");
|
|
57
|
+
const generate_sql_1 = require("../sql/generate-sql");
|
|
58
|
+
const generate_schema_sql_1 = require("../schema-diff/generate-schema-sql");
|
|
59
|
+
const generate_schema_diff_1 = require("../schema-diff/generate-schema-diff");
|
|
60
|
+
const program = new commander_1.Command();
|
|
61
|
+
program
|
|
62
|
+
.name("frg-data-diff generate")
|
|
63
|
+
.description("Compares source and destination PostgreSQL databases and writes JSON, YAML, and optional SQL diff files.")
|
|
64
|
+
.option("--source-pg-host <host>", "Source database host")
|
|
65
|
+
.option("--source-pg-port <port>", "Source database port", (v) => parseInt(v, 10))
|
|
66
|
+
.option("--source-pg-database <db>", "Source database name")
|
|
67
|
+
.option("--source-pg-user <user>", "Source database user")
|
|
68
|
+
.option("--source-pg-password-env <value>", "Source DB password or $ENV_VAR reference")
|
|
69
|
+
.option("--source-pg-ssl", "Use SSL for source database connection")
|
|
70
|
+
.option("--no-source-pg-ssl", "Do not use SSL for source database connection")
|
|
71
|
+
.option("--dest-pg-host <host>", "Destination database host")
|
|
72
|
+
.option("--dest-pg-port <port>", "Destination database port", (v) => parseInt(v, 10))
|
|
73
|
+
.option("--dest-pg-database <db>", "Destination database name")
|
|
74
|
+
.option("--dest-pg-user <user>", "Destination database user")
|
|
75
|
+
.option("--dest-pg-password-env <value>", "Destination DB password or $ENV_VAR reference")
|
|
76
|
+
.option("--dest-pg-ssl", "Use SSL for destination database connection")
|
|
77
|
+
.option("--no-dest-pg-ssl", "Do not use SSL for destination database connection")
|
|
78
|
+
.option("--schema <schema>", "PostgreSQL schema to compare")
|
|
79
|
+
.option("--table <table...>", "Table(s) to include")
|
|
80
|
+
.option("--exclude-table <table...>", "Table(s) to exclude")
|
|
81
|
+
.option("--schema-diff-table <table...>", "Table(s) to include for schema diff")
|
|
82
|
+
.option("--schema-diff-exclude-table <table...>", "Table(s) to exclude from schema diff")
|
|
83
|
+
.option("--pg-triggers-table <table...>", "Table(s) to include for PostgreSQL triggers diff")
|
|
84
|
+
.option("--pg-triggers-exclude-table <table...>", "Table(s) to exclude from PostgreSQL triggers diff")
|
|
85
|
+
.option("--ignore-column <column...>", "Column(s) to ignore during comparison")
|
|
86
|
+
.option("--include-deletes", "Include deletes in the diff (rows in dest not in source)")
|
|
87
|
+
.option("--skip-missing-pk", "Skip tables that have no primary key instead of failing")
|
|
88
|
+
.option("--output <file>", "Output diff file path")
|
|
89
|
+
.option("--schema-diff-output <file>", "Output schema diff file path")
|
|
90
|
+
.option("--pg-triggers-output <file>", "Output PostgreSQL triggers diff file path")
|
|
91
|
+
.option("--pretty", "Pretty-print the output JSON")
|
|
92
|
+
.option("--generate-pg-triggers", "Generate a PostgreSQL triggers and functions diff")
|
|
93
|
+
.option("--no-generate-pg-triggers", "Do not generate a PostgreSQL triggers and functions diff")
|
|
94
|
+
.option("--verbose", "Enable verbose logging")
|
|
95
|
+
.option("--config <file>", "Path to config file", load_config_1.DEFAULT_CONFIG_FILENAME)
|
|
96
|
+
.option("--wizard <value>", "Force the interactive wizard (use true or 1)")
|
|
97
|
+
.option("--yes", "Skip interactive confirmation (for CI/CD)");
|
|
98
|
+
program.parse(process.argv);
|
|
99
|
+
const opts = program.opts();
|
|
100
|
+
async function main() {
|
|
101
|
+
console.log("frg-data-diff: generate");
|
|
102
|
+
console.log("Loading configuration and resolving options...");
|
|
103
|
+
const configFilePath = path.resolve(opts["config"] || load_config_1.DEFAULT_CONFIG_FILENAME);
|
|
104
|
+
const configExists = fs.existsSync(configFilePath);
|
|
105
|
+
const hasCliArgs = hasAnyGeneratorArgs(process.argv.slice(2));
|
|
106
|
+
const wizardRequested = isWizardRequested(opts["wizard"]);
|
|
107
|
+
let loadedConfig;
|
|
108
|
+
let generatorConfig;
|
|
109
|
+
let configWritten = configExists;
|
|
110
|
+
let configCreatedThisRun = false;
|
|
111
|
+
let envrcWrittenDuringWizard = false;
|
|
112
|
+
let configUpdatedThisRun = false;
|
|
113
|
+
if (configExists) {
|
|
114
|
+
loadedConfig = (0, load_config_1.loadConfig)(configFilePath);
|
|
115
|
+
generatorConfig = loadedConfig.generator;
|
|
116
|
+
}
|
|
117
|
+
let wizardRan = false;
|
|
118
|
+
// Build resolved options from CLI args + config
|
|
119
|
+
const cliArgs = {
|
|
120
|
+
sourcePgHost: opts["sourcePgHost"],
|
|
121
|
+
sourcePgPort: opts["sourcePgPort"],
|
|
122
|
+
sourcePgDatabase: opts["sourcePgDatabase"],
|
|
123
|
+
sourcePgUser: opts["sourcePgUser"],
|
|
124
|
+
sourcePgPassword: opts["sourcePgPasswordEnv"],
|
|
125
|
+
sourcePgSsl: normalizeOptionalBoolean(opts["sourcePgSsl"]),
|
|
126
|
+
destPgHost: opts["destPgHost"],
|
|
127
|
+
destPgPort: opts["destPgPort"],
|
|
128
|
+
destPgDatabase: opts["destPgDatabase"],
|
|
129
|
+
destPgUser: opts["destPgUser"],
|
|
130
|
+
destPgPassword: opts["destPgPasswordEnv"],
|
|
131
|
+
destPgSsl: normalizeOptionalBoolean(opts["destPgSsl"]),
|
|
132
|
+
schema: opts["schema"],
|
|
133
|
+
tables: opts["table"],
|
|
134
|
+
excludeTables: opts["excludeTable"],
|
|
135
|
+
schemaDiffTables: opts["schemaDiffTable"],
|
|
136
|
+
schemaDiffExcludeTables: opts["schemaDiffExcludeTable"],
|
|
137
|
+
pgTriggersTables: opts["pgTriggersTable"],
|
|
138
|
+
pgTriggersExcludeTables: opts["pgTriggersExcludeTable"],
|
|
139
|
+
ignoreColumns: opts["ignoreColumn"],
|
|
140
|
+
includeDeletes: opts["includeDeletes"] ? true : undefined,
|
|
141
|
+
skipMissingPk: opts["skipMissingPk"] ? true : undefined,
|
|
142
|
+
output: opts["output"],
|
|
143
|
+
schemaDiffOutput: opts["schemaDiffOutput"],
|
|
144
|
+
pgTriggersOutput: opts["pgTriggersOutput"],
|
|
145
|
+
pretty: opts["pretty"] ? true : undefined,
|
|
146
|
+
generatePgTriggers: normalizeOptionalBoolean(opts["generatePgTriggers"]),
|
|
147
|
+
verbose: opts["verbose"] ? true : undefined,
|
|
148
|
+
};
|
|
149
|
+
// Remove undefined values
|
|
150
|
+
let cleanCliArgs = Object.fromEntries(Object.entries(cliArgs).filter(([, v]) => v !== undefined));
|
|
151
|
+
if ((!configExists && !hasCliArgs && !opts["yes"]) ||
|
|
152
|
+
(configExists && wizardRequested)) {
|
|
153
|
+
const wizardDefaults = (0, resolve_options_1.resolveGeneratorOptions)(generatorConfig, cleanCliArgs);
|
|
154
|
+
const wizardArgs = await (0, generator_wizard_1.promptForGeneratorOptions)(wizardDefaults, undefined, {
|
|
155
|
+
onEnvrcWrite: () => {
|
|
156
|
+
envrcWrittenDuringWizard = true;
|
|
157
|
+
},
|
|
158
|
+
});
|
|
159
|
+
wizardRan = true;
|
|
160
|
+
cleanCliArgs = {
|
|
161
|
+
...cleanCliArgs,
|
|
162
|
+
...wizardArgs,
|
|
163
|
+
};
|
|
164
|
+
}
|
|
165
|
+
const resolved = (0, resolve_options_1.resolveGeneratorOptions)(generatorConfig, cleanCliArgs);
|
|
166
|
+
const requestedTables = [...resolved.tables];
|
|
167
|
+
const requestedExcludeTables = cleanCliArgs.excludeTables === null ? null : [...resolved.excludeTables];
|
|
168
|
+
const requestedSchemaDiffTables = [...resolved.schemaDiffTables];
|
|
169
|
+
const requestedSchemaDiffExcludeTables = cleanCliArgs.schemaDiffExcludeTables === null
|
|
170
|
+
? null
|
|
171
|
+
: [...resolved.schemaDiffExcludeTables];
|
|
172
|
+
const requestedPgTriggersTables = [...resolved.pgTriggersTables];
|
|
173
|
+
const requestedPgTriggersExcludeTables = cleanCliArgs.pgTriggersExcludeTables === null
|
|
174
|
+
? null
|
|
175
|
+
: [...resolved.pgTriggersExcludeTables];
|
|
176
|
+
const requestedIgnoreColumns = cleanCliArgs.ignoreColumns === null ? null : [...resolved.ignoreColumns];
|
|
177
|
+
const runtimeBaseResolved = (0, resolve_options_1.resolveRuntimeGeneratorOptions)({
|
|
178
|
+
...resolved,
|
|
179
|
+
tables: requestedTables,
|
|
180
|
+
excludeTables: resolved.excludeTables,
|
|
181
|
+
schemaDiffTables: requestedSchemaDiffTables,
|
|
182
|
+
schemaDiffExcludeTables: resolved.schemaDiffExcludeTables,
|
|
183
|
+
pgTriggersTables: requestedPgTriggersTables,
|
|
184
|
+
pgTriggersExcludeTables: resolved.pgTriggersExcludeTables,
|
|
185
|
+
ignoreColumns: resolved.ignoreColumns,
|
|
186
|
+
});
|
|
187
|
+
// Validate required values
|
|
188
|
+
if (!resolved.sourcePgHost ||
|
|
189
|
+
!resolved.sourcePgDatabase ||
|
|
190
|
+
!resolved.sourcePgUser ||
|
|
191
|
+
!resolved.sourcePgPassword) {
|
|
192
|
+
console.error("Error: Missing required source database connection options.");
|
|
193
|
+
console.error("Provide --source-pg-host, --source-pg-database, --source-pg-user, --source-pg-password-env");
|
|
194
|
+
console.error("or configure them in .frg-data-diff.config.json");
|
|
195
|
+
process.exit(1);
|
|
196
|
+
}
|
|
197
|
+
if (!resolved.destPgHost ||
|
|
198
|
+
!resolved.destPgDatabase ||
|
|
199
|
+
!resolved.destPgUser ||
|
|
200
|
+
!resolved.destPgPassword) {
|
|
201
|
+
console.error("Error: Missing required destination database connection options.");
|
|
202
|
+
console.error("Provide --dest-pg-host, --dest-pg-database, --dest-pg-user, --dest-pg-password-env");
|
|
203
|
+
console.error("or configure them in .frg-data-diff.config.json");
|
|
204
|
+
process.exit(1);
|
|
205
|
+
}
|
|
206
|
+
if (!resolved.tables || resolved.tables.length === 0) {
|
|
207
|
+
console.error("Error: No tables specified. Use --table <table> or configure tables in .frg-data-diff.config.json");
|
|
208
|
+
process.exit(1);
|
|
209
|
+
}
|
|
210
|
+
const configToWrite = (0, write_config_1.buildConfigFile)({
|
|
211
|
+
...resolved,
|
|
212
|
+
tables: requestedTables,
|
|
213
|
+
excludeTables: requestedExcludeTables,
|
|
214
|
+
schemaDiffTables: requestedSchemaDiffTables,
|
|
215
|
+
schemaDiffExcludeTables: requestedSchemaDiffExcludeTables,
|
|
216
|
+
pgTriggersTables: requestedPgTriggersTables,
|
|
217
|
+
pgTriggersExcludeTables: requestedPgTriggersExcludeTables,
|
|
218
|
+
ignoreColumns: requestedIgnoreColumns,
|
|
219
|
+
}, (0, resolve_options_1.resolveApplyOptions)(loadedConfig?.apply, {
|
|
220
|
+
destPgHost: resolved.destPgHost,
|
|
221
|
+
destPgPort: resolved.destPgPort,
|
|
222
|
+
destPgDatabase: resolved.destPgDatabase,
|
|
223
|
+
destPgUser: resolved.destPgUser,
|
|
224
|
+
destPgPassword: resolved.destPgPassword,
|
|
225
|
+
destPgSsl: resolved.destPgSsl,
|
|
226
|
+
}));
|
|
227
|
+
if ((wizardRan || (!configExists && hasCliArgs)) && !opts["yes"]) {
|
|
228
|
+
if (configExists) {
|
|
229
|
+
const shouldUpdate = await (0, prompts_1.confirmUpdateConfig)();
|
|
230
|
+
if (shouldUpdate) {
|
|
231
|
+
(0, write_config_1.writeConfig)(configFilePath, configToWrite);
|
|
232
|
+
configWritten = true;
|
|
233
|
+
configUpdatedThisRun = true;
|
|
234
|
+
console.log(`Config updated: ${configFilePath}`);
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
else {
|
|
238
|
+
const shouldCreate = await (0, prompts_1.confirmCreateConfig)();
|
|
239
|
+
if (shouldCreate) {
|
|
240
|
+
(0, write_config_1.writeConfig)(configFilePath, configToWrite);
|
|
241
|
+
configWritten = true;
|
|
242
|
+
configCreatedThisRun = true;
|
|
243
|
+
console.log(`Config written to: ${configFilePath}`);
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
console.log("Continuing with the requested operation...");
|
|
247
|
+
}
|
|
248
|
+
if (!opts["yes"] &&
|
|
249
|
+
(envrcWrittenDuringWizard || configCreatedThisRun || configUpdatedThisRun)) {
|
|
250
|
+
await maybeRunDirenvAllow();
|
|
251
|
+
}
|
|
252
|
+
if (wizardRan && !opts["yes"]) {
|
|
253
|
+
const shouldRunDiff = await (0, prompts_1.confirmProceed)("\nGenerate the diff now? [yes]: ", true);
|
|
254
|
+
if (!shouldRunDiff) {
|
|
255
|
+
console.log("Stopped after wizard setup.");
|
|
256
|
+
process.exit(0);
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
if (!wizardRan && !configExists && !opts["yes"]) {
|
|
260
|
+
const proceed = await (0, prompts_1.confirmProceed)('\nProceed with generating the diff now? Type "yes" to continue: ');
|
|
261
|
+
if (!proceed) {
|
|
262
|
+
console.log("Aborted.");
|
|
263
|
+
process.exit(0);
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
const sourceConnection = (0, connection_1.resolveConnectionParams)({
|
|
267
|
+
host: resolved.sourcePgHost,
|
|
268
|
+
port: resolved.sourcePgPort,
|
|
269
|
+
database: resolved.sourcePgDatabase,
|
|
270
|
+
user: resolved.sourcePgUser,
|
|
271
|
+
password: resolved.sourcePgPassword,
|
|
272
|
+
ssl: runtimeBaseResolved.sourcePgSsl,
|
|
273
|
+
}, {
|
|
274
|
+
host: "source host",
|
|
275
|
+
port: "source port",
|
|
276
|
+
database: "source database",
|
|
277
|
+
user: "source user",
|
|
278
|
+
password: "source password",
|
|
279
|
+
});
|
|
280
|
+
const destConnection = (0, connection_1.resolveConnectionParams)({
|
|
281
|
+
host: resolved.destPgHost,
|
|
282
|
+
port: resolved.destPgPort,
|
|
283
|
+
database: resolved.destPgDatabase,
|
|
284
|
+
user: resolved.destPgUser,
|
|
285
|
+
password: resolved.destPgPassword,
|
|
286
|
+
ssl: runtimeBaseResolved.destPgSsl,
|
|
287
|
+
}, {
|
|
288
|
+
host: "destination host",
|
|
289
|
+
port: "destination port",
|
|
290
|
+
database: "destination database",
|
|
291
|
+
user: "destination user",
|
|
292
|
+
password: "destination password",
|
|
293
|
+
});
|
|
294
|
+
const sourcePool = (0, connection_1.createPool)(sourceConnection);
|
|
295
|
+
const destPool = (0, connection_1.createPool)(destConnection);
|
|
296
|
+
attachPoolErrorLogger(sourcePool, "source");
|
|
297
|
+
attachPoolErrorLogger(destPool, "destination");
|
|
298
|
+
try {
|
|
299
|
+
console.log("Preparing database connection pools...");
|
|
300
|
+
console.log("Resolving requested table patterns across both databases...");
|
|
301
|
+
const resolvedTablePatterns = await resolveGeneratorTablePatterns(sourcePool, destPool, runtimeBaseResolved, (message) => {
|
|
302
|
+
console.log(message);
|
|
303
|
+
});
|
|
304
|
+
const expandedTables = resolvedTablePatterns.data;
|
|
305
|
+
const expandedSchemaDiffTables = resolvedTablePatterns.schema;
|
|
306
|
+
const expandedPgTriggersTables = resolvedTablePatterns.pgTriggers;
|
|
307
|
+
console.log(`Resolved ${expandedTables.tables.length} data table(s), ${expandedSchemaDiffTables.tables.length} schema table(s), and ${expandedPgTriggersTables.tables.length} PostgreSQL trigger table(s).`);
|
|
308
|
+
const runtimeResolved = {
|
|
309
|
+
...runtimeBaseResolved,
|
|
310
|
+
tables: expandedTables.tables,
|
|
311
|
+
excludeTables: expandedTables.excludedTables,
|
|
312
|
+
schemaDiffTables: expandedSchemaDiffTables.tables,
|
|
313
|
+
schemaDiffExcludeTables: expandedSchemaDiffTables.excludedTables,
|
|
314
|
+
pgTriggersTables: expandedPgTriggersTables.tables,
|
|
315
|
+
pgTriggersExcludeTables: expandedPgTriggersTables.excludedTables,
|
|
316
|
+
};
|
|
317
|
+
const logProgress = (message) => {
|
|
318
|
+
console.log(message);
|
|
319
|
+
};
|
|
320
|
+
const logVerboseProgress = runtimeResolved.verbose
|
|
321
|
+
? (message) => {
|
|
322
|
+
console.log(message);
|
|
323
|
+
}
|
|
324
|
+
: undefined;
|
|
325
|
+
// Print resolved plan
|
|
326
|
+
printResolvedPlan(resolved, runtimeResolved.tables, runtimeResolved.excludeTables, runtimeResolved.schemaDiffTables, runtimeResolved.schemaDiffExcludeTables, runtimeResolved.pgTriggersTables, runtimeResolved.pgTriggersExcludeTables, sourceConnection, destConnection);
|
|
327
|
+
console.log("Connecting to databases...");
|
|
328
|
+
console.log(`Comparing ${runtimeResolved.tables.length} data table(s), ${runtimeResolved.schemaDiffTables.length} schema table(s), and ${runtimeResolved.pgTriggersTables.length} PostgreSQL trigger table(s)...`);
|
|
329
|
+
console.log("Starting data and schema diff generation...");
|
|
330
|
+
const [diff, schemaDiff] = await Promise.all([
|
|
331
|
+
(0, generate_diff_1.generateDiff)(sourcePool, destPool, {
|
|
332
|
+
schema: runtimeResolved.schema,
|
|
333
|
+
tables: runtimeResolved.tables,
|
|
334
|
+
excludeTables: runtimeResolved.excludeTables,
|
|
335
|
+
ignoreColumns: runtimeResolved.ignoreColumns,
|
|
336
|
+
tablesWhereDataFilters: runtimeResolved.tablesWhereDataFilters,
|
|
337
|
+
includeDeletes: runtimeResolved.includeDeletes,
|
|
338
|
+
skipMissingPk: runtimeResolved.skipMissingPk,
|
|
339
|
+
verbose: runtimeResolved.verbose,
|
|
340
|
+
onProgress: logProgress,
|
|
341
|
+
onVerboseProgress: logVerboseProgress,
|
|
342
|
+
}),
|
|
343
|
+
(0, generate_schema_diff_1.generateSchemaDiff)(sourcePool, destPool, {
|
|
344
|
+
schema: runtimeResolved.schema,
|
|
345
|
+
tables: runtimeResolved.schemaDiffTables,
|
|
346
|
+
excludeTables: runtimeResolved.schemaDiffExcludeTables,
|
|
347
|
+
verbose: runtimeResolved.verbose,
|
|
348
|
+
onProgress: logProgress,
|
|
349
|
+
onVerboseProgress: logVerboseProgress,
|
|
350
|
+
}),
|
|
351
|
+
]);
|
|
352
|
+
const outputPath = path.resolve(runtimeResolved.output);
|
|
353
|
+
const yamlOutputPath = path.resolve((0, write_diff_yaml_1.buildYamlOutputPath)(runtimeResolved.output));
|
|
354
|
+
const sqlOutputPath = path.resolve((0, generate_sql_1.buildSqlOutputPath)(runtimeResolved.output));
|
|
355
|
+
const schemaDiffOutputPath = path.resolve(runtimeResolved.schemaDiffOutput);
|
|
356
|
+
const schemaDiffYamlOutputPath = path.resolve((0, write_diff_yaml_1.buildYamlOutputPath)(runtimeResolved.schemaDiffOutput));
|
|
357
|
+
const schemaDiffSqlOutputPath = path.resolve((0, generate_schema_sql_1.buildSchemaSqlOutputPath)(runtimeResolved.schemaDiffOutput));
|
|
358
|
+
const pgTriggersOutputPath = path.resolve(runtimeResolved.pgTriggersOutput);
|
|
359
|
+
console.log("Writing JSON and YAML diff artifacts...");
|
|
360
|
+
const dataContent = runtimeResolved.pretty
|
|
361
|
+
? JSON.stringify(diff, null, 2) + "\n"
|
|
362
|
+
: JSON.stringify(diff) + "\n";
|
|
363
|
+
const schemaContent = runtimeResolved.pretty
|
|
364
|
+
? JSON.stringify(schemaDiff, null, 2) + "\n"
|
|
365
|
+
: JSON.stringify(schemaDiff) + "\n";
|
|
366
|
+
fs.writeFileSync(outputPath, dataContent, "utf-8");
|
|
367
|
+
(0, write_diff_yaml_1.writeYamlFile)(yamlOutputPath, diff);
|
|
368
|
+
fs.writeFileSync(schemaDiffOutputPath, schemaContent, "utf-8");
|
|
369
|
+
(0, write_diff_yaml_1.writeYamlFile)(schemaDiffYamlOutputPath, schemaDiff);
|
|
370
|
+
console.log("Generating schema SQL script...");
|
|
371
|
+
const schemaSqlResult = (0, generate_schema_sql_1.generateSchemaSqlScript)(schemaDiff, {
|
|
372
|
+
transaction: true,
|
|
373
|
+
includeDrops: true,
|
|
374
|
+
onProgress: logProgress,
|
|
375
|
+
});
|
|
376
|
+
fs.writeFileSync(schemaDiffSqlOutputPath, schemaSqlResult.sql, "utf-8");
|
|
377
|
+
let sqlGenerated = false;
|
|
378
|
+
let pgTriggersSqlGenerated = false;
|
|
379
|
+
console.log("\nData diff summary:");
|
|
380
|
+
console.log(` Tables compared: ${diff.summary.tablesCompared}`);
|
|
381
|
+
console.log(` Inserts: ${diff.summary.inserts}`);
|
|
382
|
+
console.log(` Updates: ${diff.summary.updates}`);
|
|
383
|
+
console.log(` Deletes: ${diff.summary.deletes}`);
|
|
384
|
+
if (diff.summary.skippedTables.length > 0) {
|
|
385
|
+
console.log(` Skipped tables: ${diff.summary.skippedTables.join(", ")}`);
|
|
386
|
+
}
|
|
387
|
+
console.log("\nSchema diff summary:");
|
|
388
|
+
console.log(` Tables compared: ${schemaDiff.summary.tablesCompared}`);
|
|
389
|
+
console.log(` Tables to create: ${schemaDiff.summary.tablesToCreate}`);
|
|
390
|
+
console.log(` Tables to drop: ${schemaDiff.summary.tablesToDrop}`);
|
|
391
|
+
console.log(` Columns to add: ${schemaDiff.summary.columnsToAdd}`);
|
|
392
|
+
console.log(` Columns to alter: ${schemaDiff.summary.columnsToAlter}`);
|
|
393
|
+
console.log(` Columns to drop: ${schemaDiff.summary.columnsToDrop}`);
|
|
394
|
+
console.log(` Primary keys to change: ${schemaDiff.summary.primaryKeysToChange}`);
|
|
395
|
+
let shouldGenerateSql = false;
|
|
396
|
+
if (runtimeResolved.generateSql !== undefined) {
|
|
397
|
+
shouldGenerateSql = runtimeResolved.generateSql;
|
|
398
|
+
}
|
|
399
|
+
else if (shouldAskInteractiveQuestion(opts["yes"])) {
|
|
400
|
+
shouldGenerateSql = await (0, prompts_1.confirmProceed)("\nGenerate a SQL script from this diff now? [yes]: ", true);
|
|
401
|
+
}
|
|
402
|
+
if (shouldGenerateSql) {
|
|
403
|
+
console.log("Generating data SQL script...");
|
|
404
|
+
const sqlResult = (0, generate_sql_1.generateSqlScript)(diff, {
|
|
405
|
+
applyInserts: true,
|
|
406
|
+
applyUpdates: true,
|
|
407
|
+
applyDeletes: runtimeResolved.includeDeletes,
|
|
408
|
+
transaction: true,
|
|
409
|
+
onProgress: logProgress,
|
|
410
|
+
});
|
|
411
|
+
fs.writeFileSync(sqlOutputPath, sqlResult.sql, "utf-8");
|
|
412
|
+
sqlGenerated = true;
|
|
413
|
+
}
|
|
414
|
+
let shouldGeneratePgTriggers = false;
|
|
415
|
+
if (runtimeResolved.generatePgTriggers !== undefined) {
|
|
416
|
+
shouldGeneratePgTriggers = runtimeResolved.generatePgTriggers;
|
|
417
|
+
}
|
|
418
|
+
else if (shouldAskInteractiveQuestion(opts["yes"])) {
|
|
419
|
+
shouldGeneratePgTriggers = await (0, prompts_1.confirmProceed)("\nGenerate a PostgreSQL triggers and functions diff? (SQL script) [yes]: ", true);
|
|
420
|
+
}
|
|
421
|
+
if (shouldGeneratePgTriggers) {
|
|
422
|
+
console.log("\nGenerating PostgreSQL triggers and functions diff...");
|
|
423
|
+
const pgTriggersArgs = [];
|
|
424
|
+
if (configWritten) {
|
|
425
|
+
pgTriggersArgs.push("--config", path.relative(process.cwd(), configFilePath) ||
|
|
426
|
+
load_config_1.DEFAULT_CONFIG_FILENAME);
|
|
427
|
+
}
|
|
428
|
+
else {
|
|
429
|
+
// Pass all resolved args down manually if no config is available.
|
|
430
|
+
// This is a rare edge case, usually we write the config.
|
|
431
|
+
pgTriggersArgs.push("--source-pg-host", resolved.sourcePgHost);
|
|
432
|
+
pgTriggersArgs.push("--source-pg-port", String(resolved.sourcePgPort));
|
|
433
|
+
pgTriggersArgs.push("--source-pg-database", resolved.sourcePgDatabase);
|
|
434
|
+
pgTriggersArgs.push("--source-pg-user", resolved.sourcePgUser);
|
|
435
|
+
if ((0, env_values_1.isEnvReference)(resolved.sourcePgPassword)) {
|
|
436
|
+
pgTriggersArgs.push("--source-pg-password-env", resolved.sourcePgPassword);
|
|
437
|
+
}
|
|
438
|
+
if (resolved.sourcePgSsl)
|
|
439
|
+
pgTriggersArgs.push("--source-pg-ssl");
|
|
440
|
+
else
|
|
441
|
+
pgTriggersArgs.push("--no-source-pg-ssl");
|
|
442
|
+
pgTriggersArgs.push("--dest-pg-host", resolved.destPgHost);
|
|
443
|
+
pgTriggersArgs.push("--dest-pg-port", String(resolved.destPgPort));
|
|
444
|
+
pgTriggersArgs.push("--dest-pg-database", resolved.destPgDatabase);
|
|
445
|
+
pgTriggersArgs.push("--dest-pg-user", resolved.destPgUser);
|
|
446
|
+
if ((0, env_values_1.isEnvReference)(resolved.destPgPassword)) {
|
|
447
|
+
pgTriggersArgs.push("--dest-pg-password-env", resolved.destPgPassword);
|
|
448
|
+
}
|
|
449
|
+
if (resolved.destPgSsl)
|
|
450
|
+
pgTriggersArgs.push("--dest-pg-ssl");
|
|
451
|
+
else
|
|
452
|
+
pgTriggersArgs.push("--no-dest-pg-ssl");
|
|
453
|
+
pgTriggersArgs.push("--schema", resolved.schema);
|
|
454
|
+
for (const t of runtimeResolved.pgTriggersTables)
|
|
455
|
+
pgTriggersArgs.push("--table", t);
|
|
456
|
+
for (const t of runtimeResolved.pgTriggersExcludeTables)
|
|
457
|
+
pgTriggersArgs.push("--exclude-table", t);
|
|
458
|
+
pgTriggersArgs.push("--output", runtimeResolved.pgTriggersOutput);
|
|
459
|
+
}
|
|
460
|
+
if (runtimeResolved.verbose) {
|
|
461
|
+
pgTriggersArgs.push("--verbose");
|
|
462
|
+
}
|
|
463
|
+
const pgTriggersCommandPath = path.join(__dirname, "pg-triggers.js");
|
|
464
|
+
const res = (0, child_process_1.spawnSync)(process.execPath, [pgTriggersCommandPath, ...pgTriggersArgs], {
|
|
465
|
+
stdio: "inherit",
|
|
466
|
+
});
|
|
467
|
+
if (res.status !== 0) {
|
|
468
|
+
console.error("Warning: pg-triggers diff generation failed.");
|
|
469
|
+
}
|
|
470
|
+
else {
|
|
471
|
+
pgTriggersSqlGenerated = true;
|
|
472
|
+
}
|
|
473
|
+
}
|
|
474
|
+
console.log("\nReview the data diff and schema SQL carefully before applying anything.");
|
|
475
|
+
if (!sqlGenerated) {
|
|
476
|
+
console.log("\nGenerate SQL Diff:");
|
|
477
|
+
console.log(buildSqlCommand(runtimeResolved.output, configFilePath, configWritten, runtimeResolved.includeDeletes));
|
|
478
|
+
}
|
|
479
|
+
console.log("\nApply command:");
|
|
480
|
+
console.log(buildApplyCommand(runtimeResolved, configFilePath, configWritten));
|
|
481
|
+
console.log("");
|
|
482
|
+
console.log(`Data diff written to: ${outputPath}`);
|
|
483
|
+
console.log(`Data YAML written to: ${yamlOutputPath}`);
|
|
484
|
+
if (sqlGenerated) {
|
|
485
|
+
console.log(`Data SQL written to: ${sqlOutputPath}`);
|
|
486
|
+
}
|
|
487
|
+
console.log(`Schema diff written to: ${schemaDiffOutputPath}`);
|
|
488
|
+
console.log(`Schema YAML written to: ${schemaDiffYamlOutputPath}`);
|
|
489
|
+
console.log(`Schema SQL written to: ${schemaDiffSqlOutputPath}`);
|
|
490
|
+
if (pgTriggersSqlGenerated) {
|
|
491
|
+
console.log(`PostgreSQL triggers SQL written to: ${pgTriggersOutputPath}`);
|
|
492
|
+
}
|
|
493
|
+
console.log("");
|
|
494
|
+
}
|
|
495
|
+
finally {
|
|
496
|
+
await sourcePool.end();
|
|
497
|
+
await destPool.end();
|
|
498
|
+
}
|
|
499
|
+
}
|
|
500
|
+
function buildSqlCommand(output, configFilePath, configAvailable, applyDeletes) {
|
|
501
|
+
if (configAvailable) {
|
|
502
|
+
return [
|
|
503
|
+
"npx frg-data-diff sql",
|
|
504
|
+
`--config ${shellEscapeArg(path.relative(process.cwd(), configFilePath) || load_config_1.DEFAULT_CONFIG_FILENAME)}`,
|
|
505
|
+
`--input ${shellEscapeArg(output)}`,
|
|
506
|
+
applyDeletes ? "--apply-deletes" : "--no-apply-deletes",
|
|
507
|
+
"--yes",
|
|
508
|
+
].join(" \\\n ");
|
|
509
|
+
}
|
|
510
|
+
return [
|
|
511
|
+
"npx frg-data-diff sql",
|
|
512
|
+
`--input ${shellEscapeArg(output)}`,
|
|
513
|
+
applyDeletes ? "--apply-deletes" : "--no-apply-deletes",
|
|
514
|
+
"--yes",
|
|
515
|
+
].join(" \\\n ");
|
|
516
|
+
}
|
|
517
|
+
async function maybeRunDirenvAllow() {
|
|
518
|
+
if (!isDirenvAvailable())
|
|
519
|
+
return;
|
|
520
|
+
const shouldAllow = await (0, prompts_1.confirmDirenvAllow)();
|
|
521
|
+
if (!shouldAllow)
|
|
522
|
+
return;
|
|
523
|
+
try {
|
|
524
|
+
(0, child_process_1.execFileSync)("direnv", ["allow"], { stdio: "inherit" });
|
|
525
|
+
}
|
|
526
|
+
catch (err) {
|
|
527
|
+
console.warn('Warning: failed to run "direnv allow".');
|
|
528
|
+
if (err instanceof Error && err.message) {
|
|
529
|
+
console.warn(err.message);
|
|
530
|
+
}
|
|
531
|
+
}
|
|
532
|
+
}
|
|
533
|
+
function isDirenvAvailable() {
|
|
534
|
+
const result = (0, child_process_1.spawnSync)("direnv", ["version"], { stdio: "ignore" });
|
|
535
|
+
return result.status === 0;
|
|
536
|
+
}
|
|
537
|
+
function isWizardRequested(value) {
|
|
538
|
+
return value === "true" || value === "1";
|
|
539
|
+
}
|
|
540
|
+
function buildApplyCommand(resolved, configFilePath, configAvailable) {
|
|
541
|
+
if (configAvailable) {
|
|
542
|
+
return [
|
|
543
|
+
"npx frg-data-diff apply",
|
|
544
|
+
`--config ${shellEscapeArg(path.relative(process.cwd(), configFilePath) || load_config_1.DEFAULT_CONFIG_FILENAME)}`,
|
|
545
|
+
`--input ${shellEscapeArg(resolved.output)}`,
|
|
546
|
+
"--execute",
|
|
547
|
+
].join(" \\\n ");
|
|
548
|
+
}
|
|
549
|
+
const parts = [
|
|
550
|
+
"npx frg-data-diff apply",
|
|
551
|
+
`--dest-pg-host ${shellEscapeArg(resolved.destPgHost)}`,
|
|
552
|
+
`--dest-pg-port ${resolved.destPgPort}`,
|
|
553
|
+
`--dest-pg-database ${shellEscapeArg(resolved.destPgDatabase)}`,
|
|
554
|
+
`--dest-pg-user ${shellEscapeArg(resolved.destPgUser)}`,
|
|
555
|
+
resolved.destPgSsl ? "--dest-pg-ssl" : "--no-dest-pg-ssl",
|
|
556
|
+
`--input ${shellEscapeArg(resolved.output)}`,
|
|
557
|
+
"--execute",
|
|
558
|
+
];
|
|
559
|
+
if ((0, env_values_1.isEnvReference)(resolved.destPgPassword)) {
|
|
560
|
+
parts.splice(5, 0, `--dest-pg-password-env ${shellEscapeArg(resolved.destPgPassword)}`);
|
|
561
|
+
}
|
|
562
|
+
return parts.join(" \\\n ");
|
|
563
|
+
}
|
|
564
|
+
async function resolveGeneratorTablePatterns(sourcePool, destPool, resolved, logProgress) {
|
|
565
|
+
let sourceClient;
|
|
566
|
+
let destClient;
|
|
567
|
+
try {
|
|
568
|
+
sourceClient = await runLoggedAsyncStep("connecting to source database for table resolution", () => sourcePool.connect(), logProgress);
|
|
569
|
+
destClient = await runLoggedAsyncStep("connecting to destination database for table resolution", () => destPool.connect(), logProgress);
|
|
570
|
+
const sourceTables = await listTablesWithProgress(sourceClient, resolved.schema, "source", logProgress);
|
|
571
|
+
const destTables = await listTablesWithProgress(destClient, resolved.schema, "destination", logProgress);
|
|
572
|
+
const data = resolveTablePatternGroup("data", sourceTables, destTables, resolved.tables, resolved.excludeTables, "common", logProgress);
|
|
573
|
+
const schema = resolveTablePatternGroup("schema diff", sourceTables, destTables, resolved.schemaDiffTables, resolved.schemaDiffExcludeTables, "either", logProgress);
|
|
574
|
+
const pgTriggers = resolveTablePatternGroup("PostgreSQL trigger", sourceTables, destTables, resolved.pgTriggersTables, resolved.pgTriggersExcludeTables, "common", logProgress);
|
|
575
|
+
return { data, schema, pgTriggers };
|
|
576
|
+
}
|
|
577
|
+
finally {
|
|
578
|
+
if (destClient !== undefined) {
|
|
579
|
+
destClient.release();
|
|
580
|
+
logProgress("[table resolution] released destination metadata connection.");
|
|
581
|
+
}
|
|
582
|
+
if (sourceClient !== undefined) {
|
|
583
|
+
sourceClient.release();
|
|
584
|
+
logProgress("[table resolution] released source metadata connection.");
|
|
585
|
+
}
|
|
586
|
+
}
|
|
587
|
+
}
|
|
588
|
+
async function listTablesWithProgress(client, schema, databaseLabel, logProgress) {
|
|
589
|
+
const tables = await runLoggedAsyncStep(`scanning ${databaseLabel} tables in schema "${schema}"`, () => (0, metadata_1.listTables)(client, schema), logProgress);
|
|
590
|
+
logProgress(`[table resolution] ${databaseLabel} database returned ${tables.length} base table(s).`);
|
|
591
|
+
logProgress(`[table resolution] ${databaseLabel} table preview: ${formatStringList(tables)}`);
|
|
592
|
+
return tables;
|
|
593
|
+
}
|
|
594
|
+
function resolveTablePatternGroup(label, sourceTables, destTables, includePatterns, excludePatterns, availability, logProgress) {
|
|
595
|
+
const startedAt = Date.now();
|
|
596
|
+
logProgress(`[table resolution] resolving ${label} patterns against ${formatAvailability(availability)}.`);
|
|
597
|
+
logProgress(`[table resolution] ${label} include patterns: ${formatStringList(includePatterns)}`);
|
|
598
|
+
logProgress(`[table resolution] ${label} exclude patterns: ${formatStringList(excludePatterns)}`);
|
|
599
|
+
try {
|
|
600
|
+
let result;
|
|
601
|
+
if (availability === "either") {
|
|
602
|
+
result = (0, metadata_1.resolveTablePatternsFromTableLists)(sourceTables, destTables, includePatterns, excludePatterns, { availability: "either" });
|
|
603
|
+
}
|
|
604
|
+
else {
|
|
605
|
+
result = (0, metadata_1.resolveTablePatternsFromTableLists)(sourceTables, destTables, includePatterns, excludePatterns);
|
|
606
|
+
}
|
|
607
|
+
logProgress(`[table resolution] resolved ${label} patterns in ${formatDuration(Date.now() - startedAt)}.`);
|
|
608
|
+
logProgress(`[table resolution] ${label} tables: ${formatStringList(result.tables)}`);
|
|
609
|
+
if (result.excludedTables.length > 0) {
|
|
610
|
+
logProgress(`[table resolution] ${label} excluded tables: ${formatStringList(result.excludedTables)}`);
|
|
611
|
+
}
|
|
612
|
+
return result;
|
|
613
|
+
}
|
|
614
|
+
catch (error) {
|
|
615
|
+
console.error(`[table resolution] failed to resolve ${label} patterns after ${formatDuration(Date.now() - startedAt)}.`);
|
|
616
|
+
throw error;
|
|
617
|
+
}
|
|
618
|
+
}
|
|
619
|
+
async function runLoggedAsyncStep(label, action, logProgress) {
|
|
620
|
+
const startedAt = Date.now();
|
|
621
|
+
logProgress(`[table resolution] ${label}...`);
|
|
622
|
+
try {
|
|
623
|
+
const result = await action();
|
|
624
|
+
logProgress(`[table resolution] ${label} completed in ${formatDuration(Date.now() - startedAt)}.`);
|
|
625
|
+
return result;
|
|
626
|
+
}
|
|
627
|
+
catch (error) {
|
|
628
|
+
console.error(`[table resolution] ${label} failed after ${formatDuration(Date.now() - startedAt)}.`);
|
|
629
|
+
throw error;
|
|
630
|
+
}
|
|
631
|
+
}
|
|
632
|
+
function formatAvailability(availability) {
|
|
633
|
+
if (availability === "either") {
|
|
634
|
+
return "either database";
|
|
635
|
+
}
|
|
636
|
+
return "both databases";
|
|
637
|
+
}
|
|
638
|
+
function formatStringList(values, maxItems = 25) {
|
|
639
|
+
if (values.length === 0) {
|
|
640
|
+
return "(none)";
|
|
641
|
+
}
|
|
642
|
+
const visibleValues = values.slice(0, maxItems);
|
|
643
|
+
const visible = visibleValues.join(", ");
|
|
644
|
+
if (values.length > maxItems) {
|
|
645
|
+
const remaining = values.length - maxItems;
|
|
646
|
+
return `${visible}, ... (${remaining} more)`;
|
|
647
|
+
}
|
|
648
|
+
return visible;
|
|
649
|
+
}
|
|
650
|
+
function formatDuration(durationMs) {
|
|
651
|
+
if (durationMs < 1000) {
|
|
652
|
+
return `${durationMs}ms`;
|
|
653
|
+
}
|
|
654
|
+
return `${(durationMs / 1000).toFixed(1)}s`;
|
|
655
|
+
}
|
|
656
|
+
function attachPoolErrorLogger(pool, label) {
|
|
657
|
+
pool.on("error", (error) => {
|
|
658
|
+
console.error(`[${label} pool] idle PostgreSQL client error:`);
|
|
659
|
+
logErrorDetails(error);
|
|
660
|
+
});
|
|
661
|
+
}
|
|
662
|
+
function logErrorDetails(error) {
|
|
663
|
+
if (error instanceof Error) {
|
|
664
|
+
if (error.stack) {
|
|
665
|
+
console.error(error.stack);
|
|
666
|
+
return;
|
|
667
|
+
}
|
|
668
|
+
console.error(error.message);
|
|
669
|
+
return;
|
|
670
|
+
}
|
|
671
|
+
console.error(String(error));
|
|
672
|
+
}
|
|
673
|
+
function shellEscapeArg(value) {
|
|
674
|
+
if (/^[A-Za-z0-9_./:-]+$/.test(value)) {
|
|
675
|
+
return value;
|
|
676
|
+
}
|
|
677
|
+
return `'${value.replace(/'/g, `'\\''`)}'`;
|
|
678
|
+
}
|
|
679
|
+
function printResolvedPlan(resolved, expandedTables, expandedExcludeTables, expandedSchemaDiffTables, expandedSchemaDiffExcludeTables, expandedPgTriggersTables, expandedPgTriggersExcludeTables, sourceConnection, destConnection) {
|
|
680
|
+
console.log("\ntool:");
|
|
681
|
+
console.log(" frg-data-diff generate");
|
|
682
|
+
console.log("\nsource:");
|
|
683
|
+
console.log(` host: ${(0, env_values_1.formatVisibleValue)(resolved.sourcePgHost, sourceConnection.host)}`);
|
|
684
|
+
console.log(` port: ${typeof resolved.sourcePgPort === "string" ? `${resolved.sourcePgPort} -> ${sourceConnection.port}` : sourceConnection.port}`);
|
|
685
|
+
console.log(` database: ${(0, env_values_1.formatVisibleValue)(resolved.sourcePgDatabase, sourceConnection.database)}`);
|
|
686
|
+
console.log(` user: ${(0, env_values_1.formatVisibleValue)(resolved.sourcePgUser, sourceConnection.user)}`);
|
|
687
|
+
console.log(` password: ${(0, env_values_1.formatSecretValue)(String(resolved.sourcePgPassword))}`);
|
|
688
|
+
console.log(` ssl: ${resolved.sourcePgSsl}`);
|
|
689
|
+
console.log("\ndest:");
|
|
690
|
+
console.log(` host: ${(0, env_values_1.formatVisibleValue)(resolved.destPgHost, destConnection.host)}`);
|
|
691
|
+
console.log(` port: ${typeof resolved.destPgPort === "string" ? `${resolved.destPgPort} -> ${destConnection.port}` : destConnection.port}`);
|
|
692
|
+
console.log(` database: ${(0, env_values_1.formatVisibleValue)(resolved.destPgDatabase, destConnection.database)}`);
|
|
693
|
+
console.log(` user: ${(0, env_values_1.formatVisibleValue)(resolved.destPgUser, destConnection.user)}`);
|
|
694
|
+
console.log(` password: ${(0, env_values_1.formatSecretValue)(String(resolved.destPgPassword))}`);
|
|
695
|
+
console.log(` ssl: ${resolved.destPgSsl}`);
|
|
696
|
+
console.log("\nschema: " + resolved.schema);
|
|
697
|
+
console.log("tables: " + resolved.tables.join(", "));
|
|
698
|
+
if (expandedTables.join(",") !== resolved.tables.join(",")) {
|
|
699
|
+
console.log("expanded tables: " + expandedTables.join(", "));
|
|
700
|
+
}
|
|
701
|
+
if (resolved.excludeTables.length > 0) {
|
|
702
|
+
console.log("exclude tables: " + resolved.excludeTables.join(", "));
|
|
703
|
+
}
|
|
704
|
+
if (expandedExcludeTables.join(",") !== resolved.excludeTables.join(",")) {
|
|
705
|
+
console.log("expanded exclude tables: " + expandedExcludeTables.join(", "));
|
|
706
|
+
}
|
|
707
|
+
if (resolved.ignoreColumns.length > 0) {
|
|
708
|
+
console.log("ignored columns: " + resolved.ignoreColumns.join(", "));
|
|
709
|
+
}
|
|
710
|
+
console.log("output: " + resolved.output);
|
|
711
|
+
console.log("yaml output: " + (0, write_diff_yaml_1.buildYamlOutputPath)(resolved.output));
|
|
712
|
+
console.log("schema diff tables: " + resolved.schemaDiffTables.join(", "));
|
|
713
|
+
if (expandedSchemaDiffTables.join(",") !== resolved.schemaDiffTables.join(",")) {
|
|
714
|
+
console.log("expanded schema diff tables: " + expandedSchemaDiffTables.join(", "));
|
|
715
|
+
}
|
|
716
|
+
if (resolved.schemaDiffExcludeTables.length > 0) {
|
|
717
|
+
console.log("schema diff exclude tables: " +
|
|
718
|
+
resolved.schemaDiffExcludeTables.join(", "));
|
|
719
|
+
}
|
|
720
|
+
if (expandedSchemaDiffExcludeTables.join(",") !==
|
|
721
|
+
resolved.schemaDiffExcludeTables.join(",")) {
|
|
722
|
+
console.log("expanded schema diff exclude tables: " +
|
|
723
|
+
expandedSchemaDiffExcludeTables.join(", "));
|
|
724
|
+
}
|
|
725
|
+
console.log("schema diff output: " + resolved.schemaDiffOutput);
|
|
726
|
+
console.log("schema diff yaml output: " +
|
|
727
|
+
(0, write_diff_yaml_1.buildYamlOutputPath)(resolved.schemaDiffOutput));
|
|
728
|
+
console.log("schema diff sql output: " +
|
|
729
|
+
(0, generate_schema_sql_1.buildSchemaSqlOutputPath)(resolved.schemaDiffOutput));
|
|
730
|
+
console.log("pg triggers output: " + resolved.pgTriggersOutput);
|
|
731
|
+
console.log("pg triggers tables: " + resolved.pgTriggersTables.join(", "));
|
|
732
|
+
if (expandedPgTriggersTables.join(",") !== resolved.pgTriggersTables.join(",")) {
|
|
733
|
+
console.log("expanded pg triggers tables: " + expandedPgTriggersTables.join(", "));
|
|
734
|
+
}
|
|
735
|
+
if (resolved.pgTriggersExcludeTables.length > 0) {
|
|
736
|
+
console.log("pg triggers exclude tables: " +
|
|
737
|
+
resolved.pgTriggersExcludeTables.join(", "));
|
|
738
|
+
}
|
|
739
|
+
if (expandedPgTriggersExcludeTables.join(",") !==
|
|
740
|
+
resolved.pgTriggersExcludeTables.join(",")) {
|
|
741
|
+
console.log("expanded pg triggers exclude tables: " +
|
|
742
|
+
expandedPgTriggersExcludeTables.join(", "));
|
|
743
|
+
}
|
|
744
|
+
console.log("include deletes: " + resolved.includeDeletes);
|
|
745
|
+
console.log("pretty: " + resolved.pretty);
|
|
746
|
+
console.log("generate sql: " + (resolved.generateSql ?? "interactive"));
|
|
747
|
+
console.log("generate pg triggers: " + (resolved.generatePgTriggers ?? "interactive"));
|
|
748
|
+
}
|
|
749
|
+
function hasAnyGeneratorArgs(argv) {
|
|
750
|
+
const argKeys = [
|
|
751
|
+
"--source-pg-host",
|
|
752
|
+
"--source-pg-port",
|
|
753
|
+
"--source-pg-database",
|
|
754
|
+
"--source-pg-user",
|
|
755
|
+
"--source-pg-password-env",
|
|
756
|
+
"--source-pg-ssl",
|
|
757
|
+
"--no-source-pg-ssl",
|
|
758
|
+
"--dest-pg-host",
|
|
759
|
+
"--dest-pg-port",
|
|
760
|
+
"--dest-pg-database",
|
|
761
|
+
"--dest-pg-user",
|
|
762
|
+
"--dest-pg-password-env",
|
|
763
|
+
"--dest-pg-ssl",
|
|
764
|
+
"--no-dest-pg-ssl",
|
|
765
|
+
"--schema",
|
|
766
|
+
"--table",
|
|
767
|
+
"--exclude-table",
|
|
768
|
+
"--ignore-column",
|
|
769
|
+
"--schema-diff-table",
|
|
770
|
+
"--schema-diff-exclude-table",
|
|
771
|
+
"--pg-triggers-table",
|
|
772
|
+
"--pg-triggers-exclude-table",
|
|
773
|
+
"--include-deletes",
|
|
774
|
+
"--skip-missing-pk",
|
|
775
|
+
"--output",
|
|
776
|
+
"--schema-diff-output",
|
|
777
|
+
"--pg-triggers-output",
|
|
778
|
+
"--pretty",
|
|
779
|
+
"--generate-pg-triggers",
|
|
780
|
+
"--no-generate-pg-triggers",
|
|
781
|
+
"--verbose",
|
|
782
|
+
"--config",
|
|
783
|
+
"--wizard",
|
|
784
|
+
];
|
|
785
|
+
return argv.some((arg) => argKeys.includes(arg) || argKeys.some((key) => arg.startsWith(`${key}=`)));
|
|
786
|
+
}
|
|
787
|
+
function shouldAskInteractiveQuestion(skipPrompts) {
|
|
788
|
+
return !skipPrompts && process.stdin.isTTY === true;
|
|
789
|
+
}
|
|
790
|
+
function normalizeOptionalBoolean(value) {
|
|
791
|
+
if (value === true) {
|
|
792
|
+
return true;
|
|
793
|
+
}
|
|
794
|
+
if (value === false) {
|
|
795
|
+
return false;
|
|
796
|
+
}
|
|
797
|
+
return undefined;
|
|
798
|
+
}
|
|
799
|
+
main().catch((err) => {
|
|
800
|
+
console.error("Fatal error:");
|
|
801
|
+
logErrorDetails(err);
|
|
802
|
+
process.exit(1);
|
|
803
|
+
});
|
|
804
|
+
//# sourceMappingURL=generator.js.map
|