pgpm 5.21.2 → 5.23.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/commands/deploy.js +2 -1
- package/commands/diff.js +98 -50
- package/commands/dump.js +8 -33
- package/commands/import.js +23 -0
- package/commands/migrate/baseline.d.ts +4 -0
- package/commands/migrate/baseline.js +141 -0
- package/commands/migrate.js +6 -2
- package/esm/commands/deploy.js +4 -3
- package/esm/commands/diff.js +101 -53
- package/esm/commands/dump.js +9 -34
- package/esm/commands/import.js +23 -0
- package/esm/commands/migrate/baseline.js +105 -0
- package/esm/commands/migrate.js +6 -2
- package/esm/utils/doctor.js +3 -1
- package/package.json +15 -15
- package/utils/doctor.js +3 -1
package/commands/deploy.js
CHANGED
|
@@ -127,7 +127,8 @@ exports.default = async (argv, prompter, _options) => {
|
|
|
127
127
|
if (createdb) {
|
|
128
128
|
if (capabilities.createdb) {
|
|
129
129
|
log.info(`Creating database ${database}...`);
|
|
130
|
-
(0,
|
|
130
|
+
const [cmd, ...prefixArgs] = (0, pg_env_1.getPgClientCommand)('createdb');
|
|
131
|
+
(0, child_process_1.execFileSync)(cmd, [...prefixArgs, database], {
|
|
131
132
|
env: (0, pg_env_1.getSpawnEnvWithPg)(pgEnv)
|
|
132
133
|
});
|
|
133
134
|
}
|
package/commands/diff.js
CHANGED
|
@@ -37,7 +37,6 @@ const core_1 = require("@pgpmjs/core");
|
|
|
37
37
|
const diff_1 = require("@pgpmjs/diff");
|
|
38
38
|
const logger_1 = require("@pgpmjs/logger");
|
|
39
39
|
const transform_1 = require("@pgpmjs/transform");
|
|
40
|
-
const child_process_1 = require("child_process");
|
|
41
40
|
const fs = __importStar(require("fs"));
|
|
42
41
|
const inquirerer_1 = require("inquirerer");
|
|
43
42
|
const os = __importStar(require("os"));
|
|
@@ -61,6 +60,8 @@ Diff Command:
|
|
|
61
60
|
Sides:
|
|
62
61
|
<A> / <B> may each be:
|
|
63
62
|
- a pgpm module directory (flattened in plan order)
|
|
63
|
+
- a pgpm workspace directory (every local module, in dependency order,
|
|
64
|
+
with package-qualified change names)
|
|
64
65
|
- a raw .sql file
|
|
65
66
|
- a live database: a postgres:// connection string, or db:<name>
|
|
66
67
|
(uses PG* env for host/port/user; schema is read via pg_dump)
|
|
@@ -82,6 +83,16 @@ Options:
|
|
|
82
83
|
file (deparsed in plan order); - writes to stdout
|
|
83
84
|
--emit-bundle <file> Also project the delta to a content-addressed
|
|
84
85
|
.bundle.tar.gz archive
|
|
86
|
+
--ledger <db> Read a database's pgpm_migrate ledger (db:<name> or a
|
|
87
|
+
connection string) as the deployment cursor for side
|
|
88
|
+
B, and report which of B's plan entries that database
|
|
89
|
+
already has (identical / drifted / pending / orphaned
|
|
90
|
+
/ out-of-order). Combines with --emit-ledger.
|
|
91
|
+
--emit-ledger <file> Write an idempotent pgpm_migrate backfill script that
|
|
92
|
+
records B's already-satisfied changes without
|
|
93
|
+
executing them. Satisfaction is semantic (identity-
|
|
94
|
+
keyed against side A), so regenerated or reordered
|
|
95
|
+
plans still classify as satisfied.
|
|
85
96
|
--pkg <name> Emitted migration package name (default: diff-migration)
|
|
86
97
|
--granularity <level> Granularity for emitted changes: atomic | object |
|
|
87
98
|
consolidated (default: object)
|
|
@@ -102,55 +113,22 @@ Examples:
|
|
|
102
113
|
pgpm diff ./v1-module ./v2-module
|
|
103
114
|
pgpm diff ./v1-module ./schema-v2.sql --json
|
|
104
115
|
pgpm diff db:app_v1 db:app_v2 --emit-migration ./out --verify
|
|
116
|
+
pgpm diff db:prod ./workspace-v2 --ledger db:prod --emit-ledger ./backfill.sql \
|
|
117
|
+
--emit-migration ./out --pkg upgrade
|
|
105
118
|
`;
|
|
106
119
|
const NAMING_STYLES = ['directory', 'flat'];
|
|
107
|
-
/**
|
|
108
|
-
const
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
const
|
|
113
|
-
|
|
120
|
+
/** A plan-bearing on-disk side (workspace or module directory). */
|
|
121
|
+
const isPlanSide = (spec, cwd) => {
|
|
122
|
+
if ((0, core_1.isDatabaseSpec)(spec))
|
|
123
|
+
return false;
|
|
124
|
+
try {
|
|
125
|
+
const kind = (0, diff_1.resolveDiffSideKind)(path.resolve(cwd, spec));
|
|
126
|
+
return kind === 'workspace' || kind === 'module';
|
|
114
127
|
}
|
|
115
|
-
|
|
116
|
-
|
|
128
|
+
catch {
|
|
129
|
+
return false;
|
|
117
130
|
}
|
|
118
|
-
return new Promise((resolve, reject) => {
|
|
119
|
-
const child = (0, child_process_1.spawn)('pg_dump', args, { env, stdio: ['ignore', 'pipe', 'pipe'] });
|
|
120
|
-
let out = '';
|
|
121
|
-
let err = '';
|
|
122
|
-
child.stdout.on('data', chunk => { out += chunk; });
|
|
123
|
-
child.stderr.on('data', chunk => { err += chunk; });
|
|
124
|
-
child.on('error', (e) => {
|
|
125
|
-
if (e.code === 'ENOENT') {
|
|
126
|
-
reject(new Error('pg_dump not found; ensure PostgreSQL client tools are installed and in PATH'));
|
|
127
|
-
return;
|
|
128
|
-
}
|
|
129
|
-
reject(e);
|
|
130
|
-
});
|
|
131
|
-
child.on('close', code => {
|
|
132
|
-
if (code === 0)
|
|
133
|
-
resolve(out);
|
|
134
|
-
else
|
|
135
|
-
reject(new Error(`pg_dump exited with code ${code}: ${err.trim()}`));
|
|
136
|
-
});
|
|
137
|
-
});
|
|
138
131
|
};
|
|
139
|
-
/** Resolve one side spec into diff input changes. */
|
|
140
|
-
const loadSide = async (spec, cwd) => {
|
|
141
|
-
if (resolveDiffSideKindSafe(spec) === 'database') {
|
|
142
|
-
const label = spec.startsWith('db:') ? spec.slice(3) : spec;
|
|
143
|
-
return {
|
|
144
|
-
kind: 'database',
|
|
145
|
-
label,
|
|
146
|
-
changes: (0, diff_1.sqlToDiffChanges)(await dumpDatabase(spec), label),
|
|
147
|
-
warnings: []
|
|
148
|
-
};
|
|
149
|
-
}
|
|
150
|
-
return (0, diff_1.loadDiffSideFromDisk)(path.resolve(cwd, spec));
|
|
151
|
-
};
|
|
152
|
-
/** `resolveDiffSideKind` without throwing on relative on-disk paths. */
|
|
153
|
-
const resolveDiffSideKindSafe = (spec) => /^postgres(ql)?:\/\//.test(spec) || spec.startsWith('db:') ? 'database' : 'disk';
|
|
154
132
|
/** Apply one diff side into a scratch database (module deploy or raw SQL). */
|
|
155
133
|
const applySide = async (config, side, spec, cwd) => {
|
|
156
134
|
if (side.kind === 'module') {
|
|
@@ -189,6 +167,29 @@ const runVerify = async (sideA, sideB, specA, specB, migrationDir, cwd) => {
|
|
|
189
167
|
return (0, scratch_db_1.catalogDifferences)(cfgMigrated, cfgTarget, transform_1.withoutColumnOrder);
|
|
190
168
|
});
|
|
191
169
|
};
|
|
170
|
+
const printLedgerSummary = (report, ledgerSpec) => {
|
|
171
|
+
const counts = new Map();
|
|
172
|
+
for (const entry of report.classification.entries) {
|
|
173
|
+
counts.set(entry.status, (counts.get(entry.status) ?? 0) + 1);
|
|
174
|
+
}
|
|
175
|
+
const parts = [...counts.entries()].map(([status, n]) => `${n} ${status}`);
|
|
176
|
+
console.log(`Ledger (${ledgerSpec}): ${parts.join(', ') || 'empty plan'}.`);
|
|
177
|
+
if (report.classification.orphaned.length) {
|
|
178
|
+
console.log(` Orphaned ledger entries (${report.classification.orphaned.length}):`);
|
|
179
|
+
for (const row of report.classification.orphaned) {
|
|
180
|
+
console.log(` ${row.package}:${row.changeName}`);
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
if (report.classification.outOfOrder.length) {
|
|
184
|
+
console.log(` Deployed out of plan order: ${report.classification.outOfOrder.join(', ')}`);
|
|
185
|
+
}
|
|
186
|
+
if (report.backfilled.length) {
|
|
187
|
+
console.log(` Satisfied but unrecorded (backfillable): ${report.backfilled.length}`);
|
|
188
|
+
}
|
|
189
|
+
if (report.unprovable.length) {
|
|
190
|
+
console.log(` Not provably satisfied (inert/partial; will deploy normally): ${report.unprovable.join(', ')}`);
|
|
191
|
+
}
|
|
192
|
+
};
|
|
192
193
|
const printSummary = (result, labelA, labelB) => {
|
|
193
194
|
if (result.identical) {
|
|
194
195
|
console.log(`No differences: ${labelA} and ${labelB} describe the same objects.`);
|
|
@@ -224,7 +225,7 @@ exports.default = async (argv, prompter, _options) => {
|
|
|
224
225
|
const { first: specB, newArgv: restArgv } = (0, inquirerer_1.extractFirst)(newArgv);
|
|
225
226
|
argv = restArgv;
|
|
226
227
|
if (!specA || !specB) {
|
|
227
|
-
await (0, inquirerer_1.cliExitWithError)('pgpm diff requires two sides: pgpm diff <A> <B> (module dir, .sql file, db:<name>, or connection string).');
|
|
228
|
+
await (0, inquirerer_1.cliExitWithError)('pgpm diff requires two sides: pgpm diff <A> <B> (module dir, workspace dir, .sql file, db:<name>, or connection string).');
|
|
228
229
|
}
|
|
229
230
|
const granularityRaw = argv.granularity ?? 'object';
|
|
230
231
|
if (!(0, transform_1.isExportGranularity)(granularityRaw)) {
|
|
@@ -262,6 +263,17 @@ exports.default = async (argv, prompter, _options) => {
|
|
|
262
263
|
: undefined;
|
|
263
264
|
const sqlToStdout = emitSql === module_projections_1.STDOUT_TARGET;
|
|
264
265
|
const pkgName = argv.pkg || 'diff-migration';
|
|
266
|
+
const ledgerSpec = typeof argv.ledger === 'string' && argv.ledger ? argv.ledger : undefined;
|
|
267
|
+
const emitLedgerRaw = argv['emit-ledger'] ?? argv.emitLedger;
|
|
268
|
+
const emitLedgerTarget = typeof emitLedgerRaw === 'string' && emitLedgerRaw
|
|
269
|
+
? path.resolve(cwd, emitLedgerRaw)
|
|
270
|
+
: undefined;
|
|
271
|
+
if (emitLedgerTarget && !ledgerSpec) {
|
|
272
|
+
await (0, inquirerer_1.cliExitWithError)('--emit-ledger requires --ledger <db> (the database whose pgpm_migrate ledger is backfilled).');
|
|
273
|
+
}
|
|
274
|
+
if (ledgerSpec && !isPlanSide(specB, cwd)) {
|
|
275
|
+
await (0, inquirerer_1.cliExitWithError)('--ledger requires side B to be a pgpm workspace or module directory (it needs a plan to classify).');
|
|
276
|
+
}
|
|
265
277
|
if (appendModuleDir && (emitModuleDir || emitSql || emitBundle || verify)) {
|
|
266
278
|
await (0, inquirerer_1.cliExitWithError)('--append-module is standalone; it cannot be combined with --emit-migration/--emit-module/--emit-sql/--emit-bundle/--verify.');
|
|
267
279
|
}
|
|
@@ -269,8 +281,8 @@ exports.default = async (argv, prompter, _options) => {
|
|
|
269
281
|
let sideA;
|
|
270
282
|
let sideB;
|
|
271
283
|
try {
|
|
272
|
-
sideA = await
|
|
273
|
-
sideB = await
|
|
284
|
+
sideA = await (0, core_1.loadDiffSide)(specA, cwd);
|
|
285
|
+
sideB = await (0, core_1.loadDiffSide)(specB, cwd);
|
|
274
286
|
}
|
|
275
287
|
catch (err) {
|
|
276
288
|
await (0, inquirerer_1.cliExitWithError)(err instanceof Error ? err.message : String(err));
|
|
@@ -282,6 +294,27 @@ exports.default = async (argv, prompter, _options) => {
|
|
|
282
294
|
...sideB.warnings.map(w => `${sideB.label}: ${w}`),
|
|
283
295
|
...result.warnings
|
|
284
296
|
];
|
|
297
|
+
let ledgerReport;
|
|
298
|
+
if (ledgerSpec) {
|
|
299
|
+
ledgerReport = await (0, core_1.buildLedgerReport)({
|
|
300
|
+
config: (0, core_1.resolveDatabaseSpec)(ledgerSpec),
|
|
301
|
+
spec: specB,
|
|
302
|
+
cwd,
|
|
303
|
+
changes: sideB.changes,
|
|
304
|
+
diff: result
|
|
305
|
+
});
|
|
306
|
+
if (emitLedgerTarget) {
|
|
307
|
+
if (ledgerReport.backfillSql) {
|
|
308
|
+
fs.mkdirSync(path.dirname(emitLedgerTarget), { recursive: true });
|
|
309
|
+
fs.writeFileSync(emitLedgerTarget, ledgerReport.backfillSql);
|
|
310
|
+
if (!sqlToStdout && !json)
|
|
311
|
+
log.success(`wrote ledger backfill (${ledgerReport.backfilled.length} change(s)) to ${emitLedgerTarget}`);
|
|
312
|
+
}
|
|
313
|
+
else if (!sqlToStdout && !json) {
|
|
314
|
+
log.info('no ledger backfill to emit (nothing satisfied-but-unrecorded).');
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
}
|
|
285
318
|
if (json) {
|
|
286
319
|
console.log(JSON.stringify({
|
|
287
320
|
identical: result.identical,
|
|
@@ -293,15 +326,30 @@ exports.default = async (argv, prompter, _options) => {
|
|
|
293
326
|
revert: c.revert,
|
|
294
327
|
verify: c.verify
|
|
295
328
|
})),
|
|
296
|
-
warnings
|
|
329
|
+
warnings,
|
|
330
|
+
...(ledgerReport
|
|
331
|
+
? {
|
|
332
|
+
ledger: {
|
|
333
|
+
entries: ledgerReport.classification.entries,
|
|
334
|
+
orphaned: ledgerReport.classification.orphaned,
|
|
335
|
+
outOfOrder: ledgerReport.classification.outOfOrder,
|
|
336
|
+
coverage: ledgerReport.coverage,
|
|
337
|
+
backfilled: ledgerReport.backfilled,
|
|
338
|
+
unprovable: ledgerReport.unprovable
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
: {})
|
|
297
342
|
}, null, 2));
|
|
298
343
|
}
|
|
299
344
|
else {
|
|
300
345
|
for (const warning of warnings)
|
|
301
346
|
console.warn(`diff: ${warning}`);
|
|
302
347
|
// Keep stdout clean when the SQL projection is piped there.
|
|
303
|
-
if (!sqlToStdout)
|
|
348
|
+
if (!sqlToStdout) {
|
|
304
349
|
printSummary(result, sideA.label, sideB.label);
|
|
350
|
+
if (ledgerReport)
|
|
351
|
+
printLedgerSummary(ledgerReport, ledgerSpec);
|
|
352
|
+
}
|
|
305
353
|
}
|
|
306
354
|
if (appendModuleDir) {
|
|
307
355
|
const rows = (0, diff_1.deltaChangesToRows)(result.changes);
|
package/commands/dump.js
CHANGED
|
@@ -3,9 +3,9 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
3
3
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
4
|
};
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
const core_1 = require("@pgpmjs/core");
|
|
6
7
|
const logger_1 = require("@pgpmjs/logger");
|
|
7
8
|
const quotes_1 = require("@pgsql/quotes");
|
|
8
|
-
const child_process_1 = require("child_process");
|
|
9
9
|
const fs_1 = __importDefault(require("fs"));
|
|
10
10
|
const path_1 = __importDefault(require("path"));
|
|
11
11
|
const pg_cache_1 = require("pg-cache");
|
|
@@ -37,28 +37,6 @@ function nowStamp() {
|
|
|
37
37
|
const pad = (n) => String(n).padStart(2, '0');
|
|
38
38
|
return `${d.getFullYear()}${pad(d.getMonth() + 1)}${pad(d.getDate())}-${pad(d.getHours())}${pad(d.getMinutes())}${pad(d.getSeconds())}`;
|
|
39
39
|
}
|
|
40
|
-
async function runPgDump(args, env) {
|
|
41
|
-
await new Promise((resolve, reject) => {
|
|
42
|
-
const child = (0, child_process_1.spawn)('pg_dump', args, {
|
|
43
|
-
env,
|
|
44
|
-
stdio: 'inherit',
|
|
45
|
-
shell: false
|
|
46
|
-
});
|
|
47
|
-
child.on('error', (err) => {
|
|
48
|
-
if (err.code === 'ENOENT') {
|
|
49
|
-
log.error('pg_dump not found; ensure PostgreSQL client tools are installed and in PATH');
|
|
50
|
-
}
|
|
51
|
-
reject(err);
|
|
52
|
-
});
|
|
53
|
-
child.on('close', (code) => {
|
|
54
|
-
if (code === 0) {
|
|
55
|
-
resolve();
|
|
56
|
-
return;
|
|
57
|
-
}
|
|
58
|
-
reject(new Error(`pg_dump exited with code ${code ?? 1}`));
|
|
59
|
-
});
|
|
60
|
-
});
|
|
61
|
-
}
|
|
62
40
|
async function resolveDatabaseId(dbname, databaseIdRaw) {
|
|
63
41
|
const pool = (0, pg_cache_1.getPgPool)((0, pg_env_1.getPgEnvOptions)({ database: dbname }));
|
|
64
42
|
const res = await pool.query(`select id, name from metaschema_public.database order by name`);
|
|
@@ -145,16 +123,13 @@ exports.default = async (argv, prompter, _options) => {
|
|
|
145
123
|
log.info(`database id ${databaseIdInfo.id}`);
|
|
146
124
|
}
|
|
147
125
|
const pgEnv = (0, pg_env_1.getPgEnvOptions)({ database: dbname });
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
'
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
dbname
|
|
156
|
-
];
|
|
157
|
-
await runPgDump(args, spawnEnv);
|
|
126
|
+
await (0, core_1.pgDump)({
|
|
127
|
+
config: pgEnv,
|
|
128
|
+
format: 'plain',
|
|
129
|
+
noOwner: true,
|
|
130
|
+
noPrivileges: true,
|
|
131
|
+
file: outPath
|
|
132
|
+
});
|
|
158
133
|
if (databaseIdInfo) {
|
|
159
134
|
const pruneSql = await buildPruneSql(dbname, databaseIdInfo.id);
|
|
160
135
|
// Use writeFileSync with 'a' flag for explicit append as requested
|
package/commands/import.js
CHANGED
|
@@ -33,9 +33,11 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
33
33
|
};
|
|
34
34
|
})();
|
|
35
35
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
const core_1 = require("@pgpmjs/core");
|
|
36
37
|
const import_1 = require("@pgpmjs/import");
|
|
37
38
|
const logger_1 = require("@pgpmjs/logger");
|
|
38
39
|
const transform_1 = require("@pgpmjs/transform");
|
|
40
|
+
const fs_1 = require("fs");
|
|
39
41
|
const inquirerer_1 = require("inquirerer");
|
|
40
42
|
const path = __importStar(require("path"));
|
|
41
43
|
const emit_package_1 = require("../utils/emit-package");
|
|
@@ -88,6 +90,12 @@ Options:
|
|
|
88
90
|
--emit-bundle <file> Also project the imported module into a
|
|
89
91
|
content-addressed .bundle.tar.gz. Same single-package
|
|
90
92
|
requirement as --emit-sql.
|
|
93
|
+
--baseline After writing each package, also write a
|
|
94
|
+
<pkg>/baseline.sql: an idempotent, log-only
|
|
95
|
+
pgpm_migrate backfill recording every generated
|
|
96
|
+
change as already deployed. Adopts the source
|
|
97
|
+
database — apply the script (after 'pgpm migrate
|
|
98
|
+
init'), then 'pgpm deploy' runs only real changes.
|
|
91
99
|
--cwd <directory> Working directory (default: current directory)
|
|
92
100
|
--dry-run Print the resulting plan/paths without writing
|
|
93
101
|
|
|
@@ -100,6 +108,7 @@ Examples:
|
|
|
100
108
|
pgpm import ./sql-files --pkg my-app --out ./packages --with-data
|
|
101
109
|
pgpm import dump.sql --pkg my-app --partition partition.json
|
|
102
110
|
pgpm import dump.sql --pkg my-app --emit-sql my-app.sql
|
|
111
|
+
pgpm import dump.sql --pkg my-app --baseline
|
|
103
112
|
`;
|
|
104
113
|
const NAMING_STYLES = ['directory', 'flat'];
|
|
105
114
|
exports.default = async (argv, prompter, _options) => {
|
|
@@ -147,9 +156,13 @@ exports.default = async (argv, prompter, _options) => {
|
|
|
147
156
|
const emit = (0, module_projections_1.parseEmitProjectionTargets)(argv, cwd);
|
|
148
157
|
const emitRequested = (0, module_projections_1.hasEmitProjection)(emit);
|
|
149
158
|
const sqlToStdout = emit.emitSql === module_projections_1.STDOUT_TARGET;
|
|
159
|
+
const baseline = Boolean(argv.baseline);
|
|
150
160
|
if (emitRequested && dryRun) {
|
|
151
161
|
await (0, inquirerer_1.cliExitWithError)('--emit-sql/--emit-bundle cannot be combined with --dry-run.');
|
|
152
162
|
}
|
|
163
|
+
if (baseline && dryRun) {
|
|
164
|
+
await (0, inquirerer_1.cliExitWithError)('--baseline cannot be combined with --dry-run.');
|
|
165
|
+
}
|
|
153
166
|
let source;
|
|
154
167
|
try {
|
|
155
168
|
source = (0, import_1.loadDumpSource)(path.resolve(cwd, input));
|
|
@@ -213,9 +226,11 @@ exports.default = async (argv, prompter, _options) => {
|
|
|
213
226
|
}
|
|
214
227
|
}
|
|
215
228
|
let firstDir;
|
|
229
|
+
const writtenDirs = [];
|
|
216
230
|
for (const pkg of packages) {
|
|
217
231
|
const dir = (0, emit_package_1.writePackage)(outBase, pkg);
|
|
218
232
|
firstDir ??= dir;
|
|
233
|
+
writtenDirs.push(dir);
|
|
219
234
|
if (!sqlToStdout) {
|
|
220
235
|
log.success(`wrote ${pkg.rows.length} changes to ${dir}`);
|
|
221
236
|
}
|
|
@@ -223,6 +238,14 @@ exports.default = async (argv, prompter, _options) => {
|
|
|
223
238
|
if (emitRequested && firstDir) {
|
|
224
239
|
await (0, module_projections_1.projectModule)(firstDir, emit, sqlToStdout ? undefined : msg => log.success(msg));
|
|
225
240
|
}
|
|
241
|
+
if (baseline) {
|
|
242
|
+
for (const dir of writtenDirs) {
|
|
243
|
+
const { entries, backfillSql } = await (0, core_1.buildBaselineBackfill)(dir);
|
|
244
|
+
const baselinePath = path.join(dir, 'baseline.sql');
|
|
245
|
+
(0, fs_1.writeFileSync)(baselinePath, backfillSql);
|
|
246
|
+
log.success(`wrote baseline backfill for ${entries.length} change(s) to ${baselinePath}`);
|
|
247
|
+
}
|
|
248
|
+
}
|
|
226
249
|
}
|
|
227
250
|
const { summary } = result;
|
|
228
251
|
if (!sqlToStdout) {
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import { CLIOptions, Inquirerer, ParsedArgs } from 'inquirerer';
|
|
2
|
+
export declare const baselineUsageText = "\nMigrate Baseline Command:\n\n pgpm migrate baseline [<workspace|module>] [OPTIONS]\n\n Record a plan's changes as already deployed WITHOUT executing any DDL, so a\n database that already carries the schema (imported from a dump, restored, or\n otherwise adopted from outside pgpm) is brought to the plan's head in the\n pgpm_migrate ledger. A subsequent 'pgpm deploy' then runs only genuinely new\n changes.\n\n Each change is recorded through the same pgpm_migrate.deploy procedure a real\n deploy uses, with log-only mode (empty script, p_log_only => TRUE), in plan\n order.\n\nOptions:\n --help, -h Show this help message\n --emit-ledger <file|-> Write the log-only backfill SQL instead of applying\n it (- for stdout). Offline; no database is touched.\n The target must already have the pgpm_migrate schema\n (run 'pgpm migrate init' first).\n --database <name> Database to apply the baseline to (skips the prompt)\n -y, --yes Skip the confirmation prompt when applying\n --no-tx Apply without wrapping each module in a transaction\n --cwd <directory> Working directory (default: current directory)\n\nExamples:\n pgpm migrate baseline ./my-app --emit-ledger baseline.sql\n pgpm migrate baseline --database mydb\n pgpm migrate baseline ./ws --emit-ledger -\n";
|
|
3
|
+
declare const _default: (argv: Partial<ParsedArgs>, prompter: Inquirerer, _options: CLIOptions) => Promise<Partial<ParsedArgs>>;
|
|
4
|
+
export default _default;
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.baselineUsageText = void 0;
|
|
37
|
+
const core_1 = require("@pgpmjs/core");
|
|
38
|
+
const diff_1 = require("@pgpmjs/diff");
|
|
39
|
+
const logger_1 = require("@pgpmjs/logger");
|
|
40
|
+
const fs_1 = require("fs");
|
|
41
|
+
const path = __importStar(require("path"));
|
|
42
|
+
const pg_env_1 = require("pg-env");
|
|
43
|
+
const database_1 = require("../../utils/database");
|
|
44
|
+
const log = new logger_1.Logger('migrate-baseline');
|
|
45
|
+
const STDOUT_TARGET = '-';
|
|
46
|
+
exports.baselineUsageText = `
|
|
47
|
+
Migrate Baseline Command:
|
|
48
|
+
|
|
49
|
+
pgpm migrate baseline [<workspace|module>] [OPTIONS]
|
|
50
|
+
|
|
51
|
+
Record a plan's changes as already deployed WITHOUT executing any DDL, so a
|
|
52
|
+
database that already carries the schema (imported from a dump, restored, or
|
|
53
|
+
otherwise adopted from outside pgpm) is brought to the plan's head in the
|
|
54
|
+
pgpm_migrate ledger. A subsequent 'pgpm deploy' then runs only genuinely new
|
|
55
|
+
changes.
|
|
56
|
+
|
|
57
|
+
Each change is recorded through the same pgpm_migrate.deploy procedure a real
|
|
58
|
+
deploy uses, with log-only mode (empty script, p_log_only => TRUE), in plan
|
|
59
|
+
order.
|
|
60
|
+
|
|
61
|
+
Options:
|
|
62
|
+
--help, -h Show this help message
|
|
63
|
+
--emit-ledger <file|-> Write the log-only backfill SQL instead of applying
|
|
64
|
+
it (- for stdout). Offline; no database is touched.
|
|
65
|
+
The target must already have the pgpm_migrate schema
|
|
66
|
+
(run 'pgpm migrate init' first).
|
|
67
|
+
--database <name> Database to apply the baseline to (skips the prompt)
|
|
68
|
+
-y, --yes Skip the confirmation prompt when applying
|
|
69
|
+
--no-tx Apply without wrapping each module in a transaction
|
|
70
|
+
--cwd <directory> Working directory (default: current directory)
|
|
71
|
+
|
|
72
|
+
Examples:
|
|
73
|
+
pgpm migrate baseline ./my-app --emit-ledger baseline.sql
|
|
74
|
+
pgpm migrate baseline --database mydb
|
|
75
|
+
pgpm migrate baseline ./ws --emit-ledger -
|
|
76
|
+
`;
|
|
77
|
+
exports.default = async (argv, prompter, _options) => {
|
|
78
|
+
if (argv.help || argv.h) {
|
|
79
|
+
console.log(exports.baselineUsageText);
|
|
80
|
+
process.exit(0);
|
|
81
|
+
}
|
|
82
|
+
const cwd = argv.cwd || process.cwd();
|
|
83
|
+
const spec = argv._?.filter(Boolean)[0] ?? cwd;
|
|
84
|
+
const modules = await (0, core_1.loadPlanSideModules)(spec, cwd);
|
|
85
|
+
const entries = await (0, core_1.baselineBackfillEntries)(modules);
|
|
86
|
+
if (entries.length === 0) {
|
|
87
|
+
log.warn('No plan changes with deploy scripts found; nothing to baseline.');
|
|
88
|
+
return argv;
|
|
89
|
+
}
|
|
90
|
+
const emitTarget = argv['emit-ledger'] ?? argv.emitLedger;
|
|
91
|
+
// Offline emit: write the log-only backfill SQL, touch no database.
|
|
92
|
+
if (typeof emitTarget === 'string' && emitTarget) {
|
|
93
|
+
const sql = (0, diff_1.emitLedgerBackfill)(entries);
|
|
94
|
+
if (emitTarget === STDOUT_TARGET) {
|
|
95
|
+
process.stdout.write(sql);
|
|
96
|
+
}
|
|
97
|
+
else {
|
|
98
|
+
const outPath = path.resolve(cwd, emitTarget);
|
|
99
|
+
(0, fs_1.writeFileSync)(outPath, sql);
|
|
100
|
+
log.success(`Wrote baseline backfill for ${entries.length} change(s) to ${outPath}`);
|
|
101
|
+
}
|
|
102
|
+
return argv;
|
|
103
|
+
}
|
|
104
|
+
// Apply: record every change log-only against the target database.
|
|
105
|
+
const database = await (0, database_1.getTargetDatabase)(argv, prompter, {
|
|
106
|
+
message: 'Select database to baseline'
|
|
107
|
+
});
|
|
108
|
+
const { yes } = await prompter.prompt(argv, [
|
|
109
|
+
{
|
|
110
|
+
name: 'yes',
|
|
111
|
+
type: 'confirm',
|
|
112
|
+
message: `Record ${entries.length} change(s) as deployed (log-only) in "${database}"?`,
|
|
113
|
+
required: true
|
|
114
|
+
}
|
|
115
|
+
]);
|
|
116
|
+
if (!yes) {
|
|
117
|
+
log.info('Operation cancelled.');
|
|
118
|
+
return argv;
|
|
119
|
+
}
|
|
120
|
+
const config = (0, pg_env_1.getPgEnvOptions)({ database });
|
|
121
|
+
const useTransaction = argv.tx !== false;
|
|
122
|
+
const client = new core_1.PgpmMigrate(config);
|
|
123
|
+
try {
|
|
124
|
+
let recorded = 0;
|
|
125
|
+
let skipped = 0;
|
|
126
|
+
for (const mod of modules) {
|
|
127
|
+
const result = await client.deploy({
|
|
128
|
+
modulePath: mod.modulePath,
|
|
129
|
+
logOnly: true,
|
|
130
|
+
useTransaction
|
|
131
|
+
});
|
|
132
|
+
recorded += result.deployed.length;
|
|
133
|
+
skipped += result.skipped.length;
|
|
134
|
+
}
|
|
135
|
+
log.success(`Baseline complete in "${database}": ${recorded} change(s) recorded, ${skipped} already present.`);
|
|
136
|
+
}
|
|
137
|
+
finally {
|
|
138
|
+
await client.close();
|
|
139
|
+
}
|
|
140
|
+
return argv;
|
|
141
|
+
};
|
package/commands/migrate.js
CHANGED
|
@@ -4,6 +4,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
4
4
|
};
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
6
|
const inquirerer_1 = require("inquirerer");
|
|
7
|
+
const baseline_1 = __importDefault(require("./migrate/baseline"));
|
|
7
8
|
const deps_1 = __importDefault(require("./migrate/deps"));
|
|
8
9
|
// Migrate subcommands
|
|
9
10
|
const init_1 = __importDefault(require("./migrate/init"));
|
|
@@ -13,7 +14,8 @@ const subcommandMap = {
|
|
|
13
14
|
init: init_1.default,
|
|
14
15
|
status: status_1.default,
|
|
15
16
|
list: list_1.default,
|
|
16
|
-
deps: deps_1.default
|
|
17
|
+
deps: deps_1.default,
|
|
18
|
+
baseline: baseline_1.default
|
|
17
19
|
};
|
|
18
20
|
const migrateUsageText = `
|
|
19
21
|
Migrate Commands:
|
|
@@ -22,6 +24,7 @@ Migrate Commands:
|
|
|
22
24
|
pgpm migrate status Show current migration status
|
|
23
25
|
pgpm migrate list List all changes (deployed and pending)
|
|
24
26
|
pgpm migrate deps Show change dependencies
|
|
27
|
+
pgpm migrate baseline Record a plan as already deployed (log-only), adopting an existing database
|
|
25
28
|
|
|
26
29
|
Options:
|
|
27
30
|
--help, -h Show this help message
|
|
@@ -63,7 +66,8 @@ function getSubcommandDescription(cmd) {
|
|
|
63
66
|
init: 'Initialize migration tracking in database',
|
|
64
67
|
status: 'Show current migration status',
|
|
65
68
|
list: 'List all changes (deployed and pending)',
|
|
66
|
-
deps: 'Show change dependencies'
|
|
69
|
+
deps: 'Show change dependencies',
|
|
70
|
+
baseline: 'Record a plan as already deployed (log-only), adopting an existing database'
|
|
67
71
|
};
|
|
68
72
|
return descriptions[cmd] || '';
|
|
69
73
|
}
|
package/esm/commands/deploy.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { PgpmPackage } from '@pgpmjs/core';
|
|
2
2
|
import { getEnvOptions } from '@pgpmjs/env';
|
|
3
3
|
import { Logger } from '@pgpmjs/logger';
|
|
4
|
-
import {
|
|
5
|
-
import { getPgEnvOptions, getSpawnEnvWithPg, } from 'pg-env';
|
|
4
|
+
import { execFileSync } from 'child_process';
|
|
5
|
+
import { getPgClientCommand, getPgEnvOptions, getSpawnEnvWithPg, } from 'pg-env';
|
|
6
6
|
import { getActiveEngine, getTargetDatabase, resolvePackageAlias } from '../utils';
|
|
7
7
|
import { selectPackage } from '../utils/module-utils';
|
|
8
8
|
const deployUsageText = `
|
|
@@ -125,7 +125,8 @@ export default async (argv, prompter, _options) => {
|
|
|
125
125
|
if (createdb) {
|
|
126
126
|
if (capabilities.createdb) {
|
|
127
127
|
log.info(`Creating database ${database}...`);
|
|
128
|
-
|
|
128
|
+
const [cmd, ...prefixArgs] = getPgClientCommand('createdb');
|
|
129
|
+
execFileSync(cmd, [...prefixArgs, database], {
|
|
129
130
|
env: getSpawnEnvWithPg(pgEnv)
|
|
130
131
|
});
|
|
131
132
|
}
|
package/esm/commands/diff.js
CHANGED
|
@@ -1,14 +1,13 @@
|
|
|
1
|
-
import { PgpmMigrate } from '@pgpmjs/core';
|
|
2
|
-
import { deltaChangesToRows,
|
|
1
|
+
import { buildLedgerReport, isDatabaseSpec, loadDiffSide, PgpmMigrate, resolveDatabaseSpec } from '@pgpmjs/core';
|
|
2
|
+
import { deltaChangesToRows, resolveDiffSideKind } from '@pgpmjs/diff';
|
|
3
3
|
import { Logger } from '@pgpmjs/logger';
|
|
4
4
|
import { appendModule, CHANGE_GRANULARITIES, diffChangeSets, EXPORT_GRANULARITIES, isChangeGranularity, isExportGranularity, loadModule, withoutColumnOrder, writeModule } from '@pgpmjs/transform';
|
|
5
|
-
import { spawn } from 'child_process';
|
|
6
5
|
import * as fs from 'fs';
|
|
7
6
|
import { cliExitWithError, extractFirst } from 'inquirerer';
|
|
8
7
|
import * as os from 'os';
|
|
9
8
|
import * as path from 'path';
|
|
10
9
|
import { getPgPool } from 'pg-cache';
|
|
11
|
-
import { getPgEnvOptions
|
|
10
|
+
import { getPgEnvOptions } from 'pg-env';
|
|
12
11
|
import { emitModuleBundle, emitModuleSql, STDOUT_TARGET } from '../utils/module-projections';
|
|
13
12
|
import { catalogDifferences, withScratchDatabases } from '../utils/scratch-db';
|
|
14
13
|
const log = new Logger('diff');
|
|
@@ -26,6 +25,8 @@ Diff Command:
|
|
|
26
25
|
Sides:
|
|
27
26
|
<A> / <B> may each be:
|
|
28
27
|
- a pgpm module directory (flattened in plan order)
|
|
28
|
+
- a pgpm workspace directory (every local module, in dependency order,
|
|
29
|
+
with package-qualified change names)
|
|
29
30
|
- a raw .sql file
|
|
30
31
|
- a live database: a postgres:// connection string, or db:<name>
|
|
31
32
|
(uses PG* env for host/port/user; schema is read via pg_dump)
|
|
@@ -47,6 +48,16 @@ Options:
|
|
|
47
48
|
file (deparsed in plan order); - writes to stdout
|
|
48
49
|
--emit-bundle <file> Also project the delta to a content-addressed
|
|
49
50
|
.bundle.tar.gz archive
|
|
51
|
+
--ledger <db> Read a database's pgpm_migrate ledger (db:<name> or a
|
|
52
|
+
connection string) as the deployment cursor for side
|
|
53
|
+
B, and report which of B's plan entries that database
|
|
54
|
+
already has (identical / drifted / pending / orphaned
|
|
55
|
+
/ out-of-order). Combines with --emit-ledger.
|
|
56
|
+
--emit-ledger <file> Write an idempotent pgpm_migrate backfill script that
|
|
57
|
+
records B's already-satisfied changes without
|
|
58
|
+
executing them. Satisfaction is semantic (identity-
|
|
59
|
+
keyed against side A), so regenerated or reordered
|
|
60
|
+
plans still classify as satisfied.
|
|
50
61
|
--pkg <name> Emitted migration package name (default: diff-migration)
|
|
51
62
|
--granularity <level> Granularity for emitted changes: atomic | object |
|
|
52
63
|
consolidated (default: object)
|
|
@@ -67,55 +78,22 @@ Examples:
|
|
|
67
78
|
pgpm diff ./v1-module ./v2-module
|
|
68
79
|
pgpm diff ./v1-module ./schema-v2.sql --json
|
|
69
80
|
pgpm diff db:app_v1 db:app_v2 --emit-migration ./out --verify
|
|
81
|
+
pgpm diff db:prod ./workspace-v2 --ledger db:prod --emit-ledger ./backfill.sql \
|
|
82
|
+
--emit-migration ./out --pkg upgrade
|
|
70
83
|
`;
|
|
71
84
|
const NAMING_STYLES = ['directory', 'flat'];
|
|
72
|
-
/**
|
|
73
|
-
const
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
const
|
|
78
|
-
|
|
79
|
-
}
|
|
80
|
-
else {
|
|
81
|
-
args.push('--dbname', spec);
|
|
85
|
+
/** A plan-bearing on-disk side (workspace or module directory). */
|
|
86
|
+
const isPlanSide = (spec, cwd) => {
|
|
87
|
+
if (isDatabaseSpec(spec))
|
|
88
|
+
return false;
|
|
89
|
+
try {
|
|
90
|
+
const kind = resolveDiffSideKind(path.resolve(cwd, spec));
|
|
91
|
+
return kind === 'workspace' || kind === 'module';
|
|
82
92
|
}
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
let out = '';
|
|
86
|
-
let err = '';
|
|
87
|
-
child.stdout.on('data', chunk => { out += chunk; });
|
|
88
|
-
child.stderr.on('data', chunk => { err += chunk; });
|
|
89
|
-
child.on('error', (e) => {
|
|
90
|
-
if (e.code === 'ENOENT') {
|
|
91
|
-
reject(new Error('pg_dump not found; ensure PostgreSQL client tools are installed and in PATH'));
|
|
92
|
-
return;
|
|
93
|
-
}
|
|
94
|
-
reject(e);
|
|
95
|
-
});
|
|
96
|
-
child.on('close', code => {
|
|
97
|
-
if (code === 0)
|
|
98
|
-
resolve(out);
|
|
99
|
-
else
|
|
100
|
-
reject(new Error(`pg_dump exited with code ${code}: ${err.trim()}`));
|
|
101
|
-
});
|
|
102
|
-
});
|
|
103
|
-
};
|
|
104
|
-
/** Resolve one side spec into diff input changes. */
|
|
105
|
-
const loadSide = async (spec, cwd) => {
|
|
106
|
-
if (resolveDiffSideKindSafe(spec) === 'database') {
|
|
107
|
-
const label = spec.startsWith('db:') ? spec.slice(3) : spec;
|
|
108
|
-
return {
|
|
109
|
-
kind: 'database',
|
|
110
|
-
label,
|
|
111
|
-
changes: sqlToDiffChanges(await dumpDatabase(spec), label),
|
|
112
|
-
warnings: []
|
|
113
|
-
};
|
|
93
|
+
catch {
|
|
94
|
+
return false;
|
|
114
95
|
}
|
|
115
|
-
return loadDiffSideFromDisk(path.resolve(cwd, spec));
|
|
116
96
|
};
|
|
117
|
-
/** `resolveDiffSideKind` without throwing on relative on-disk paths. */
|
|
118
|
-
const resolveDiffSideKindSafe = (spec) => /^postgres(ql)?:\/\//.test(spec) || spec.startsWith('db:') ? 'database' : 'disk';
|
|
119
97
|
/** Apply one diff side into a scratch database (module deploy or raw SQL). */
|
|
120
98
|
const applySide = async (config, side, spec, cwd) => {
|
|
121
99
|
if (side.kind === 'module') {
|
|
@@ -154,6 +132,29 @@ const runVerify = async (sideA, sideB, specA, specB, migrationDir, cwd) => {
|
|
|
154
132
|
return catalogDifferences(cfgMigrated, cfgTarget, withoutColumnOrder);
|
|
155
133
|
});
|
|
156
134
|
};
|
|
135
|
+
const printLedgerSummary = (report, ledgerSpec) => {
|
|
136
|
+
const counts = new Map();
|
|
137
|
+
for (const entry of report.classification.entries) {
|
|
138
|
+
counts.set(entry.status, (counts.get(entry.status) ?? 0) + 1);
|
|
139
|
+
}
|
|
140
|
+
const parts = [...counts.entries()].map(([status, n]) => `${n} ${status}`);
|
|
141
|
+
console.log(`Ledger (${ledgerSpec}): ${parts.join(', ') || 'empty plan'}.`);
|
|
142
|
+
if (report.classification.orphaned.length) {
|
|
143
|
+
console.log(` Orphaned ledger entries (${report.classification.orphaned.length}):`);
|
|
144
|
+
for (const row of report.classification.orphaned) {
|
|
145
|
+
console.log(` ${row.package}:${row.changeName}`);
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
if (report.classification.outOfOrder.length) {
|
|
149
|
+
console.log(` Deployed out of plan order: ${report.classification.outOfOrder.join(', ')}`);
|
|
150
|
+
}
|
|
151
|
+
if (report.backfilled.length) {
|
|
152
|
+
console.log(` Satisfied but unrecorded (backfillable): ${report.backfilled.length}`);
|
|
153
|
+
}
|
|
154
|
+
if (report.unprovable.length) {
|
|
155
|
+
console.log(` Not provably satisfied (inert/partial; will deploy normally): ${report.unprovable.join(', ')}`);
|
|
156
|
+
}
|
|
157
|
+
};
|
|
157
158
|
const printSummary = (result, labelA, labelB) => {
|
|
158
159
|
if (result.identical) {
|
|
159
160
|
console.log(`No differences: ${labelA} and ${labelB} describe the same objects.`);
|
|
@@ -189,7 +190,7 @@ export default async (argv, prompter, _options) => {
|
|
|
189
190
|
const { first: specB, newArgv: restArgv } = extractFirst(newArgv);
|
|
190
191
|
argv = restArgv;
|
|
191
192
|
if (!specA || !specB) {
|
|
192
|
-
await cliExitWithError('pgpm diff requires two sides: pgpm diff <A> <B> (module dir, .sql file, db:<name>, or connection string).');
|
|
193
|
+
await cliExitWithError('pgpm diff requires two sides: pgpm diff <A> <B> (module dir, workspace dir, .sql file, db:<name>, or connection string).');
|
|
193
194
|
}
|
|
194
195
|
const granularityRaw = argv.granularity ?? 'object';
|
|
195
196
|
if (!isExportGranularity(granularityRaw)) {
|
|
@@ -227,6 +228,17 @@ export default async (argv, prompter, _options) => {
|
|
|
227
228
|
: undefined;
|
|
228
229
|
const sqlToStdout = emitSql === STDOUT_TARGET;
|
|
229
230
|
const pkgName = argv.pkg || 'diff-migration';
|
|
231
|
+
const ledgerSpec = typeof argv.ledger === 'string' && argv.ledger ? argv.ledger : undefined;
|
|
232
|
+
const emitLedgerRaw = argv['emit-ledger'] ?? argv.emitLedger;
|
|
233
|
+
const emitLedgerTarget = typeof emitLedgerRaw === 'string' && emitLedgerRaw
|
|
234
|
+
? path.resolve(cwd, emitLedgerRaw)
|
|
235
|
+
: undefined;
|
|
236
|
+
if (emitLedgerTarget && !ledgerSpec) {
|
|
237
|
+
await cliExitWithError('--emit-ledger requires --ledger <db> (the database whose pgpm_migrate ledger is backfilled).');
|
|
238
|
+
}
|
|
239
|
+
if (ledgerSpec && !isPlanSide(specB, cwd)) {
|
|
240
|
+
await cliExitWithError('--ledger requires side B to be a pgpm workspace or module directory (it needs a plan to classify).');
|
|
241
|
+
}
|
|
230
242
|
if (appendModuleDir && (emitModuleDir || emitSql || emitBundle || verify)) {
|
|
231
243
|
await cliExitWithError('--append-module is standalone; it cannot be combined with --emit-migration/--emit-module/--emit-sql/--emit-bundle/--verify.');
|
|
232
244
|
}
|
|
@@ -234,8 +246,8 @@ export default async (argv, prompter, _options) => {
|
|
|
234
246
|
let sideA;
|
|
235
247
|
let sideB;
|
|
236
248
|
try {
|
|
237
|
-
sideA = await
|
|
238
|
-
sideB = await
|
|
249
|
+
sideA = await loadDiffSide(specA, cwd);
|
|
250
|
+
sideB = await loadDiffSide(specB, cwd);
|
|
239
251
|
}
|
|
240
252
|
catch (err) {
|
|
241
253
|
await cliExitWithError(err instanceof Error ? err.message : String(err));
|
|
@@ -247,6 +259,27 @@ export default async (argv, prompter, _options) => {
|
|
|
247
259
|
...sideB.warnings.map(w => `${sideB.label}: ${w}`),
|
|
248
260
|
...result.warnings
|
|
249
261
|
];
|
|
262
|
+
let ledgerReport;
|
|
263
|
+
if (ledgerSpec) {
|
|
264
|
+
ledgerReport = await buildLedgerReport({
|
|
265
|
+
config: resolveDatabaseSpec(ledgerSpec),
|
|
266
|
+
spec: specB,
|
|
267
|
+
cwd,
|
|
268
|
+
changes: sideB.changes,
|
|
269
|
+
diff: result
|
|
270
|
+
});
|
|
271
|
+
if (emitLedgerTarget) {
|
|
272
|
+
if (ledgerReport.backfillSql) {
|
|
273
|
+
fs.mkdirSync(path.dirname(emitLedgerTarget), { recursive: true });
|
|
274
|
+
fs.writeFileSync(emitLedgerTarget, ledgerReport.backfillSql);
|
|
275
|
+
if (!sqlToStdout && !json)
|
|
276
|
+
log.success(`wrote ledger backfill (${ledgerReport.backfilled.length} change(s)) to ${emitLedgerTarget}`);
|
|
277
|
+
}
|
|
278
|
+
else if (!sqlToStdout && !json) {
|
|
279
|
+
log.info('no ledger backfill to emit (nothing satisfied-but-unrecorded).');
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
}
|
|
250
283
|
if (json) {
|
|
251
284
|
console.log(JSON.stringify({
|
|
252
285
|
identical: result.identical,
|
|
@@ -258,15 +291,30 @@ export default async (argv, prompter, _options) => {
|
|
|
258
291
|
revert: c.revert,
|
|
259
292
|
verify: c.verify
|
|
260
293
|
})),
|
|
261
|
-
warnings
|
|
294
|
+
warnings,
|
|
295
|
+
...(ledgerReport
|
|
296
|
+
? {
|
|
297
|
+
ledger: {
|
|
298
|
+
entries: ledgerReport.classification.entries,
|
|
299
|
+
orphaned: ledgerReport.classification.orphaned,
|
|
300
|
+
outOfOrder: ledgerReport.classification.outOfOrder,
|
|
301
|
+
coverage: ledgerReport.coverage,
|
|
302
|
+
backfilled: ledgerReport.backfilled,
|
|
303
|
+
unprovable: ledgerReport.unprovable
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
: {})
|
|
262
307
|
}, null, 2));
|
|
263
308
|
}
|
|
264
309
|
else {
|
|
265
310
|
for (const warning of warnings)
|
|
266
311
|
console.warn(`diff: ${warning}`);
|
|
267
312
|
// Keep stdout clean when the SQL projection is piped there.
|
|
268
|
-
if (!sqlToStdout)
|
|
313
|
+
if (!sqlToStdout) {
|
|
269
314
|
printSummary(result, sideA.label, sideB.label);
|
|
315
|
+
if (ledgerReport)
|
|
316
|
+
printLedgerSummary(ledgerReport, ledgerSpec);
|
|
317
|
+
}
|
|
270
318
|
}
|
|
271
319
|
if (appendModuleDir) {
|
|
272
320
|
const rows = deltaChangesToRows(result.changes);
|
package/esm/commands/dump.js
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
|
+
import { pgDump } from '@pgpmjs/core';
|
|
1
2
|
import { Logger } from '@pgpmjs/logger';
|
|
2
3
|
import { QuoteUtils } from '@pgsql/quotes';
|
|
3
|
-
import { spawn } from 'child_process';
|
|
4
4
|
import fs from 'fs';
|
|
5
5
|
import path from 'path';
|
|
6
6
|
import { getPgPool } from 'pg-cache';
|
|
7
|
-
import { getPgEnvOptions
|
|
7
|
+
import { getPgEnvOptions } from 'pg-env';
|
|
8
8
|
import { getTargetDatabase } from '../utils';
|
|
9
9
|
const log = new Logger('dump');
|
|
10
10
|
const dumpUsageText = `
|
|
@@ -32,28 +32,6 @@ function nowStamp() {
|
|
|
32
32
|
const pad = (n) => String(n).padStart(2, '0');
|
|
33
33
|
return `${d.getFullYear()}${pad(d.getMonth() + 1)}${pad(d.getDate())}-${pad(d.getHours())}${pad(d.getMinutes())}${pad(d.getSeconds())}`;
|
|
34
34
|
}
|
|
35
|
-
async function runPgDump(args, env) {
|
|
36
|
-
await new Promise((resolve, reject) => {
|
|
37
|
-
const child = spawn('pg_dump', args, {
|
|
38
|
-
env,
|
|
39
|
-
stdio: 'inherit',
|
|
40
|
-
shell: false
|
|
41
|
-
});
|
|
42
|
-
child.on('error', (err) => {
|
|
43
|
-
if (err.code === 'ENOENT') {
|
|
44
|
-
log.error('pg_dump not found; ensure PostgreSQL client tools are installed and in PATH');
|
|
45
|
-
}
|
|
46
|
-
reject(err);
|
|
47
|
-
});
|
|
48
|
-
child.on('close', (code) => {
|
|
49
|
-
if (code === 0) {
|
|
50
|
-
resolve();
|
|
51
|
-
return;
|
|
52
|
-
}
|
|
53
|
-
reject(new Error(`pg_dump exited with code ${code ?? 1}`));
|
|
54
|
-
});
|
|
55
|
-
});
|
|
56
|
-
}
|
|
57
35
|
async function resolveDatabaseId(dbname, databaseIdRaw) {
|
|
58
36
|
const pool = getPgPool(getPgEnvOptions({ database: dbname }));
|
|
59
37
|
const res = await pool.query(`select id, name from metaschema_public.database order by name`);
|
|
@@ -140,16 +118,13 @@ export default async (argv, prompter, _options) => {
|
|
|
140
118
|
log.info(`database id ${databaseIdInfo.id}`);
|
|
141
119
|
}
|
|
142
120
|
const pgEnv = getPgEnvOptions({ database: dbname });
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
'
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
dbname
|
|
151
|
-
];
|
|
152
|
-
await runPgDump(args, spawnEnv);
|
|
121
|
+
await pgDump({
|
|
122
|
+
config: pgEnv,
|
|
123
|
+
format: 'plain',
|
|
124
|
+
noOwner: true,
|
|
125
|
+
noPrivileges: true,
|
|
126
|
+
file: outPath
|
|
127
|
+
});
|
|
153
128
|
if (databaseIdInfo) {
|
|
154
129
|
const pruneSql = await buildPruneSql(dbname, databaseIdInfo.id);
|
|
155
130
|
// Use writeFileSync with 'a' flag for explicit append as requested
|
package/esm/commands/import.js
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
|
+
import { buildBaselineBackfill } from '@pgpmjs/core';
|
|
1
2
|
import { dumpCompatibilityWarnings, importDumpRows, linkTextualDeps, loadDumpSource } from '@pgpmjs/import';
|
|
2
3
|
import { Logger } from '@pgpmjs/logger';
|
|
3
4
|
import { CHANGE_GRANULARITIES, EXPORT_GRANULARITIES, isChangeGranularity, isExportGranularity, parsePartitionConfig, PartitionCycleError, partitionExportRows } from '@pgpmjs/transform';
|
|
5
|
+
import { writeFileSync } from 'fs';
|
|
4
6
|
import { cliExitWithError } from 'inquirerer';
|
|
5
7
|
import * as path from 'path';
|
|
6
8
|
import { checkOverwrite, writePackage } from '../utils/emit-package';
|
|
@@ -53,6 +55,12 @@ Options:
|
|
|
53
55
|
--emit-bundle <file> Also project the imported module into a
|
|
54
56
|
content-addressed .bundle.tar.gz. Same single-package
|
|
55
57
|
requirement as --emit-sql.
|
|
58
|
+
--baseline After writing each package, also write a
|
|
59
|
+
<pkg>/baseline.sql: an idempotent, log-only
|
|
60
|
+
pgpm_migrate backfill recording every generated
|
|
61
|
+
change as already deployed. Adopts the source
|
|
62
|
+
database — apply the script (after 'pgpm migrate
|
|
63
|
+
init'), then 'pgpm deploy' runs only real changes.
|
|
56
64
|
--cwd <directory> Working directory (default: current directory)
|
|
57
65
|
--dry-run Print the resulting plan/paths without writing
|
|
58
66
|
|
|
@@ -65,6 +73,7 @@ Examples:
|
|
|
65
73
|
pgpm import ./sql-files --pkg my-app --out ./packages --with-data
|
|
66
74
|
pgpm import dump.sql --pkg my-app --partition partition.json
|
|
67
75
|
pgpm import dump.sql --pkg my-app --emit-sql my-app.sql
|
|
76
|
+
pgpm import dump.sql --pkg my-app --baseline
|
|
68
77
|
`;
|
|
69
78
|
const NAMING_STYLES = ['directory', 'flat'];
|
|
70
79
|
export default async (argv, prompter, _options) => {
|
|
@@ -112,9 +121,13 @@ export default async (argv, prompter, _options) => {
|
|
|
112
121
|
const emit = parseEmitProjectionTargets(argv, cwd);
|
|
113
122
|
const emitRequested = hasEmitProjection(emit);
|
|
114
123
|
const sqlToStdout = emit.emitSql === STDOUT_TARGET;
|
|
124
|
+
const baseline = Boolean(argv.baseline);
|
|
115
125
|
if (emitRequested && dryRun) {
|
|
116
126
|
await cliExitWithError('--emit-sql/--emit-bundle cannot be combined with --dry-run.');
|
|
117
127
|
}
|
|
128
|
+
if (baseline && dryRun) {
|
|
129
|
+
await cliExitWithError('--baseline cannot be combined with --dry-run.');
|
|
130
|
+
}
|
|
118
131
|
let source;
|
|
119
132
|
try {
|
|
120
133
|
source = loadDumpSource(path.resolve(cwd, input));
|
|
@@ -178,9 +191,11 @@ export default async (argv, prompter, _options) => {
|
|
|
178
191
|
}
|
|
179
192
|
}
|
|
180
193
|
let firstDir;
|
|
194
|
+
const writtenDirs = [];
|
|
181
195
|
for (const pkg of packages) {
|
|
182
196
|
const dir = writePackage(outBase, pkg);
|
|
183
197
|
firstDir ??= dir;
|
|
198
|
+
writtenDirs.push(dir);
|
|
184
199
|
if (!sqlToStdout) {
|
|
185
200
|
log.success(`wrote ${pkg.rows.length} changes to ${dir}`);
|
|
186
201
|
}
|
|
@@ -188,6 +203,14 @@ export default async (argv, prompter, _options) => {
|
|
|
188
203
|
if (emitRequested && firstDir) {
|
|
189
204
|
await projectModule(firstDir, emit, sqlToStdout ? undefined : msg => log.success(msg));
|
|
190
205
|
}
|
|
206
|
+
if (baseline) {
|
|
207
|
+
for (const dir of writtenDirs) {
|
|
208
|
+
const { entries, backfillSql } = await buildBaselineBackfill(dir);
|
|
209
|
+
const baselinePath = path.join(dir, 'baseline.sql');
|
|
210
|
+
writeFileSync(baselinePath, backfillSql);
|
|
211
|
+
log.success(`wrote baseline backfill for ${entries.length} change(s) to ${baselinePath}`);
|
|
212
|
+
}
|
|
213
|
+
}
|
|
191
214
|
}
|
|
192
215
|
const { summary } = result;
|
|
193
216
|
if (!sqlToStdout) {
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import { baselineBackfillEntries, loadPlanSideModules, PgpmMigrate } from '@pgpmjs/core';
|
|
2
|
+
import { emitLedgerBackfill } from '@pgpmjs/diff';
|
|
3
|
+
import { Logger } from '@pgpmjs/logger';
|
|
4
|
+
import { writeFileSync } from 'fs';
|
|
5
|
+
import * as path from 'path';
|
|
6
|
+
import { getPgEnvOptions } from 'pg-env';
|
|
7
|
+
import { getTargetDatabase } from '../../utils/database';
|
|
8
|
+
const log = new Logger('migrate-baseline');
|
|
9
|
+
const STDOUT_TARGET = '-';
|
|
10
|
+
export const baselineUsageText = `
|
|
11
|
+
Migrate Baseline Command:
|
|
12
|
+
|
|
13
|
+
pgpm migrate baseline [<workspace|module>] [OPTIONS]
|
|
14
|
+
|
|
15
|
+
Record a plan's changes as already deployed WITHOUT executing any DDL, so a
|
|
16
|
+
database that already carries the schema (imported from a dump, restored, or
|
|
17
|
+
otherwise adopted from outside pgpm) is brought to the plan's head in the
|
|
18
|
+
pgpm_migrate ledger. A subsequent 'pgpm deploy' then runs only genuinely new
|
|
19
|
+
changes.
|
|
20
|
+
|
|
21
|
+
Each change is recorded through the same pgpm_migrate.deploy procedure a real
|
|
22
|
+
deploy uses, with log-only mode (empty script, p_log_only => TRUE), in plan
|
|
23
|
+
order.
|
|
24
|
+
|
|
25
|
+
Options:
|
|
26
|
+
--help, -h Show this help message
|
|
27
|
+
--emit-ledger <file|-> Write the log-only backfill SQL instead of applying
|
|
28
|
+
it (- for stdout). Offline; no database is touched.
|
|
29
|
+
The target must already have the pgpm_migrate schema
|
|
30
|
+
(run 'pgpm migrate init' first).
|
|
31
|
+
--database <name> Database to apply the baseline to (skips the prompt)
|
|
32
|
+
-y, --yes Skip the confirmation prompt when applying
|
|
33
|
+
--no-tx Apply without wrapping each module in a transaction
|
|
34
|
+
--cwd <directory> Working directory (default: current directory)
|
|
35
|
+
|
|
36
|
+
Examples:
|
|
37
|
+
pgpm migrate baseline ./my-app --emit-ledger baseline.sql
|
|
38
|
+
pgpm migrate baseline --database mydb
|
|
39
|
+
pgpm migrate baseline ./ws --emit-ledger -
|
|
40
|
+
`;
|
|
41
|
+
export default async (argv, prompter, _options) => {
|
|
42
|
+
if (argv.help || argv.h) {
|
|
43
|
+
console.log(baselineUsageText);
|
|
44
|
+
process.exit(0);
|
|
45
|
+
}
|
|
46
|
+
const cwd = argv.cwd || process.cwd();
|
|
47
|
+
const spec = argv._?.filter(Boolean)[0] ?? cwd;
|
|
48
|
+
const modules = await loadPlanSideModules(spec, cwd);
|
|
49
|
+
const entries = await baselineBackfillEntries(modules);
|
|
50
|
+
if (entries.length === 0) {
|
|
51
|
+
log.warn('No plan changes with deploy scripts found; nothing to baseline.');
|
|
52
|
+
return argv;
|
|
53
|
+
}
|
|
54
|
+
const emitTarget = argv['emit-ledger'] ?? argv.emitLedger;
|
|
55
|
+
// Offline emit: write the log-only backfill SQL, touch no database.
|
|
56
|
+
if (typeof emitTarget === 'string' && emitTarget) {
|
|
57
|
+
const sql = emitLedgerBackfill(entries);
|
|
58
|
+
if (emitTarget === STDOUT_TARGET) {
|
|
59
|
+
process.stdout.write(sql);
|
|
60
|
+
}
|
|
61
|
+
else {
|
|
62
|
+
const outPath = path.resolve(cwd, emitTarget);
|
|
63
|
+
writeFileSync(outPath, sql);
|
|
64
|
+
log.success(`Wrote baseline backfill for ${entries.length} change(s) to ${outPath}`);
|
|
65
|
+
}
|
|
66
|
+
return argv;
|
|
67
|
+
}
|
|
68
|
+
// Apply: record every change log-only against the target database.
|
|
69
|
+
const database = await getTargetDatabase(argv, prompter, {
|
|
70
|
+
message: 'Select database to baseline'
|
|
71
|
+
});
|
|
72
|
+
const { yes } = await prompter.prompt(argv, [
|
|
73
|
+
{
|
|
74
|
+
name: 'yes',
|
|
75
|
+
type: 'confirm',
|
|
76
|
+
message: `Record ${entries.length} change(s) as deployed (log-only) in "${database}"?`,
|
|
77
|
+
required: true
|
|
78
|
+
}
|
|
79
|
+
]);
|
|
80
|
+
if (!yes) {
|
|
81
|
+
log.info('Operation cancelled.');
|
|
82
|
+
return argv;
|
|
83
|
+
}
|
|
84
|
+
const config = getPgEnvOptions({ database });
|
|
85
|
+
const useTransaction = argv.tx !== false;
|
|
86
|
+
const client = new PgpmMigrate(config);
|
|
87
|
+
try {
|
|
88
|
+
let recorded = 0;
|
|
89
|
+
let skipped = 0;
|
|
90
|
+
for (const mod of modules) {
|
|
91
|
+
const result = await client.deploy({
|
|
92
|
+
modulePath: mod.modulePath,
|
|
93
|
+
logOnly: true,
|
|
94
|
+
useTransaction
|
|
95
|
+
});
|
|
96
|
+
recorded += result.deployed.length;
|
|
97
|
+
skipped += result.skipped.length;
|
|
98
|
+
}
|
|
99
|
+
log.success(`Baseline complete in "${database}": ${recorded} change(s) recorded, ${skipped} already present.`);
|
|
100
|
+
}
|
|
101
|
+
finally {
|
|
102
|
+
await client.close();
|
|
103
|
+
}
|
|
104
|
+
return argv;
|
|
105
|
+
};
|
package/esm/commands/migrate.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { extractFirst } from 'inquirerer';
|
|
2
|
+
import baseline from './migrate/baseline';
|
|
2
3
|
import deps from './migrate/deps';
|
|
3
4
|
// Migrate subcommands
|
|
4
5
|
import init from './migrate/init';
|
|
@@ -8,7 +9,8 @@ const subcommandMap = {
|
|
|
8
9
|
init,
|
|
9
10
|
status,
|
|
10
11
|
list,
|
|
11
|
-
deps
|
|
12
|
+
deps,
|
|
13
|
+
baseline
|
|
12
14
|
};
|
|
13
15
|
const migrateUsageText = `
|
|
14
16
|
Migrate Commands:
|
|
@@ -17,6 +19,7 @@ Migrate Commands:
|
|
|
17
19
|
pgpm migrate status Show current migration status
|
|
18
20
|
pgpm migrate list List all changes (deployed and pending)
|
|
19
21
|
pgpm migrate deps Show change dependencies
|
|
22
|
+
pgpm migrate baseline Record a plan as already deployed (log-only), adopting an existing database
|
|
20
23
|
|
|
21
24
|
Options:
|
|
22
25
|
--help, -h Show this help message
|
|
@@ -58,7 +61,8 @@ function getSubcommandDescription(cmd) {
|
|
|
58
61
|
init: 'Initialize migration tracking in database',
|
|
59
62
|
status: 'Show current migration status',
|
|
60
63
|
list: 'List all changes (deployed and pending)',
|
|
61
|
-
deps: 'Show change dependencies'
|
|
64
|
+
deps: 'Show change dependencies',
|
|
65
|
+
baseline: 'Record a plan as already deployed (log-only), adopting an existing database'
|
|
62
66
|
};
|
|
63
67
|
return descriptions[cmd] || '';
|
|
64
68
|
}
|
package/esm/utils/doctor.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { spawn } from 'child_process';
|
|
2
2
|
import { readFileSync } from 'fs';
|
|
3
|
+
import { getPgClientCommand } from 'pg-env';
|
|
3
4
|
export const runCommand = (command, args) => {
|
|
4
5
|
return new Promise((resolve) => {
|
|
5
6
|
const child = spawn(command, args, { stdio: 'pipe' });
|
|
@@ -183,7 +184,8 @@ export function parsePsqlMajor(versionOutput) {
|
|
|
183
184
|
return Number.isNaN(major) ? null : major;
|
|
184
185
|
}
|
|
185
186
|
export async function checkPsql(info, exec = runCommand) {
|
|
186
|
-
const
|
|
187
|
+
const [cmd, ...prefixArgs] = getPgClientCommand('psql');
|
|
188
|
+
const result = await exec(cmd, [...prefixArgs, '--version']);
|
|
187
189
|
if (result.code !== 0) {
|
|
188
190
|
return {
|
|
189
191
|
name: 'psql',
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pgpm",
|
|
3
|
-
"version": "5.
|
|
3
|
+
"version": "5.23.0",
|
|
4
4
|
"author": "Constructive <developers@constructive.io>",
|
|
5
5
|
"description": "PostgreSQL Package Manager - Database migration and package management CLI",
|
|
6
6
|
"main": "index.js",
|
|
@@ -46,24 +46,24 @@
|
|
|
46
46
|
},
|
|
47
47
|
"dependencies": {
|
|
48
48
|
"@inquirerer/utils": "^3.3.9",
|
|
49
|
-
"@pgpmjs/core": "^7.
|
|
50
|
-
"@pgpmjs/diff": "^0.
|
|
51
|
-
"@pgpmjs/env": "^2.
|
|
52
|
-
"@pgpmjs/export": "^1.
|
|
53
|
-
"@pgpmjs/import": "^0.
|
|
54
|
-
"@pgpmjs/logger": "^2.
|
|
55
|
-
"@pgpmjs/slice": "^0.
|
|
56
|
-
"@pgpmjs/transform": "^0.
|
|
57
|
-
"@pgpmjs/types": "^2.
|
|
58
|
-
"@pgsql/quotes": "^18.2.
|
|
49
|
+
"@pgpmjs/core": "^7.23.0",
|
|
50
|
+
"@pgpmjs/diff": "^0.3.0",
|
|
51
|
+
"@pgpmjs/env": "^2.41.1",
|
|
52
|
+
"@pgpmjs/export": "^1.16.1",
|
|
53
|
+
"@pgpmjs/import": "^0.3.1",
|
|
54
|
+
"@pgpmjs/logger": "^2.24.0",
|
|
55
|
+
"@pgpmjs/slice": "^0.14.1",
|
|
56
|
+
"@pgpmjs/transform": "^0.22.0",
|
|
57
|
+
"@pgpmjs/types": "^2.50.1",
|
|
58
|
+
"@pgsql/quotes": "^18.2.1",
|
|
59
59
|
"appstash": "^0.7.0",
|
|
60
60
|
"find-and-require-package-json": "^0.9.1",
|
|
61
61
|
"genomic": "^5.6.2",
|
|
62
62
|
"inquirerer": "^4.9.1",
|
|
63
63
|
"js-yaml": "^4.1.0",
|
|
64
|
-
"pg-cache": "^3.
|
|
65
|
-
"pg-env": "^1.
|
|
66
|
-
"pgsql-deparser": "^18.3.
|
|
64
|
+
"pg-cache": "^3.25.1",
|
|
65
|
+
"pg-env": "^1.29.0",
|
|
66
|
+
"pgsql-deparser": "^18.3.3",
|
|
67
67
|
"semver": "^7.8.1",
|
|
68
68
|
"shelljs": "^0.10.0",
|
|
69
69
|
"yanse": "^0.2.1"
|
|
@@ -80,5 +80,5 @@
|
|
|
80
80
|
"pg",
|
|
81
81
|
"pgsql"
|
|
82
82
|
],
|
|
83
|
-
"gitHead": "
|
|
83
|
+
"gitHead": "6da0705f8aa49d62dc2defb8b135b71674611483"
|
|
84
84
|
}
|
package/utils/doctor.js
CHANGED
|
@@ -16,6 +16,7 @@ exports.checkNode = checkNode;
|
|
|
16
16
|
exports.summarizeChecks = summarizeChecks;
|
|
17
17
|
const child_process_1 = require("child_process");
|
|
18
18
|
const fs_1 = require("fs");
|
|
19
|
+
const pg_env_1 = require("pg-env");
|
|
19
20
|
const runCommand = (command, args) => {
|
|
20
21
|
return new Promise((resolve) => {
|
|
21
22
|
const child = (0, child_process_1.spawn)(command, args, { stdio: 'pipe' });
|
|
@@ -200,7 +201,8 @@ function parsePsqlMajor(versionOutput) {
|
|
|
200
201
|
return Number.isNaN(major) ? null : major;
|
|
201
202
|
}
|
|
202
203
|
async function checkPsql(info, exec = exports.runCommand) {
|
|
203
|
-
const
|
|
204
|
+
const [cmd, ...prefixArgs] = (0, pg_env_1.getPgClientCommand)('psql');
|
|
205
|
+
const result = await exec(cmd, [...prefixArgs, '--version']);
|
|
204
206
|
if (result.code !== 0) {
|
|
205
207
|
return {
|
|
206
208
|
name: 'psql',
|