pgpm 5.19.1 → 5.20.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/cache.js +1 -1
- package/commands/diff.d.ts +3 -0
- package/commands/diff.js +325 -0
- package/commands/dump.js +3 -3
- package/commands/export.js +24 -5
- package/commands/import.d.ts +3 -0
- package/commands/import.js +198 -0
- package/commands/init/boilerplate.js +1 -1
- package/commands/init/index.js +3 -3
- package/commands/package.js +0 -1
- package/commands/slice.js +1 -1
- package/commands/test-packages.js +1 -1
- package/commands/transform.d.ts +13 -0
- package/commands/transform.js +275 -0
- package/commands/update.js +1 -1
- package/commands.js +7 -0
- package/esm/commands/cache.js +1 -1
- package/esm/commands/diff.js +290 -0
- package/esm/commands/dump.js +3 -3
- package/esm/commands/export.js +22 -3
- package/esm/commands/import.js +163 -0
- package/esm/commands/init/boilerplate.js +1 -1
- package/esm/commands/init/index.js +3 -3
- package/esm/commands/package.js +0 -1
- package/esm/commands/slice.js +1 -1
- package/esm/commands/test-packages.js +1 -1
- package/esm/commands/transform.js +236 -0
- package/esm/commands/update.js +1 -1
- package/esm/commands.js +7 -0
- package/esm/utils/display.js +3 -0
- package/esm/utils/driver.js +1 -1
- package/esm/utils/emit-package.js +1 -0
- package/esm/utils/engine.js +1 -1
- package/esm/utils/module-projections.js +47 -0
- package/esm/utils/scratch-db.js +61 -0
- package/package.json +14 -12
- package/utils/display.d.ts +1 -1
- package/utils/display.js +3 -0
- package/utils/driver.js +1 -1
- package/utils/emit-package.d.ts +9 -0
- package/utils/emit-package.js +7 -0
- package/utils/engine.js +1 -1
- package/utils/module-projections.d.ts +15 -0
- package/utils/module-projections.js +85 -0
- package/utils/scratch-db.d.ts +31 -0
- package/utils/scratch-db.js +66 -0
package/commands/cache.js
CHANGED
package/commands/diff.js
ADDED
|
@@ -0,0 +1,325 @@
|
|
|
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
|
+
const core_1 = require("@pgpmjs/core");
|
|
37
|
+
const diff_1 = require("@pgpmjs/diff");
|
|
38
|
+
const logger_1 = require("@pgpmjs/logger");
|
|
39
|
+
const transform_1 = require("@pgpmjs/transform");
|
|
40
|
+
const child_process_1 = require("child_process");
|
|
41
|
+
const fs = __importStar(require("fs"));
|
|
42
|
+
const inquirerer_1 = require("inquirerer");
|
|
43
|
+
const os = __importStar(require("os"));
|
|
44
|
+
const path = __importStar(require("path"));
|
|
45
|
+
const pg_cache_1 = require("pg-cache");
|
|
46
|
+
const pg_env_1 = require("pg-env");
|
|
47
|
+
const module_projections_1 = require("../utils/module-projections");
|
|
48
|
+
const scratch_db_1 = require("../utils/scratch-db");
|
|
49
|
+
const log = new logger_1.Logger('diff');
|
|
50
|
+
const diffUsageText = `
|
|
51
|
+
Diff Command:
|
|
52
|
+
|
|
53
|
+
pgpm diff <A> <B> [OPTIONS]
|
|
54
|
+
|
|
55
|
+
Identity-keyed semantic diff between two schema sources. Each side is
|
|
56
|
+
normalized to an object set keyed by identity (kind/schema/name), so the
|
|
57
|
+
same schema authored at different granularity, naming, or partitioning
|
|
58
|
+
diffs as empty. Tables are compared column-by-column and
|
|
59
|
+
constraint-by-constraint, so table changes emit ALTER TABLE, not a rebuild.
|
|
60
|
+
|
|
61
|
+
Sides:
|
|
62
|
+
<A> / <B> may each be:
|
|
63
|
+
- a pgpm module directory (flattened in plan order)
|
|
64
|
+
- a raw .sql file
|
|
65
|
+
- a live database: a postgres:// connection string, or db:<name>
|
|
66
|
+
(uses PG* env for host/port/user; schema is read via pg_dump)
|
|
67
|
+
|
|
68
|
+
The delta is one model; every --emit-* flag is a projection of it, so they
|
|
69
|
+
compose (a single run can emit a module, a linear SQL file, and a bundle).
|
|
70
|
+
|
|
71
|
+
Options:
|
|
72
|
+
--help, -h Show this help message
|
|
73
|
+
--emit-migration <dir> Write the delta as a pgpm module (deploy/revert/
|
|
74
|
+
verify per change, spec-derived paths, graph-derived
|
|
75
|
+
requires) into <dir>/<pkg>
|
|
76
|
+
--emit-module <dir> Alias of --emit-migration
|
|
77
|
+
--emit-sql <file|-> Also project the delta to a single consolidated SQL
|
|
78
|
+
file (deparsed in plan order); - writes to stdout
|
|
79
|
+
--emit-bundle <file> Also project the delta to a content-addressed
|
|
80
|
+
.bundle.tar.gz archive
|
|
81
|
+
--pkg <name> Emitted migration package name (default: diff-migration)
|
|
82
|
+
--granularity <level> Granularity for emitted changes: atomic | object |
|
|
83
|
+
consolidated (default: object)
|
|
84
|
+
--naming <style> Change path naming style: directory | flat (default: directory)
|
|
85
|
+
--json Machine-readable output
|
|
86
|
+
--verify Oracle mode: deploy A plus the emitted migration into
|
|
87
|
+
a scratch database and assert catalog equivalence
|
|
88
|
+
with B deployed fresh
|
|
89
|
+
--cwd <directory> Working directory (default: current directory)
|
|
90
|
+
|
|
91
|
+
Exit status: 0 when the sides are identical or the diff succeeds; non-zero on
|
|
92
|
+
error or when --verify finds the migrated catalog differs from B.
|
|
93
|
+
|
|
94
|
+
Examples:
|
|
95
|
+
pgpm diff ./v1-module ./v2-module
|
|
96
|
+
pgpm diff ./v1-module ./schema-v2.sql --json
|
|
97
|
+
pgpm diff db:app_v1 db:app_v2 --emit-migration ./out --verify
|
|
98
|
+
`;
|
|
99
|
+
const NAMING_STYLES = ['directory', 'flat'];
|
|
100
|
+
/** pg_dump a side's schema: `db:<name>` uses PG* env; DSNs pass through. */
|
|
101
|
+
const dumpDatabase = async (spec) => {
|
|
102
|
+
const args = ['--schema-only', '--no-owner'];
|
|
103
|
+
let env = process.env;
|
|
104
|
+
if (spec.startsWith('db:')) {
|
|
105
|
+
const config = (0, pg_env_1.getPgEnvOptions)({ database: spec.slice(3) });
|
|
106
|
+
env = (0, pg_env_1.getSpawnEnvWithPg)(config);
|
|
107
|
+
}
|
|
108
|
+
else {
|
|
109
|
+
args.push('--dbname', spec);
|
|
110
|
+
}
|
|
111
|
+
return new Promise((resolve, reject) => {
|
|
112
|
+
const child = (0, child_process_1.spawn)('pg_dump', args, { env, stdio: ['ignore', 'pipe', 'pipe'] });
|
|
113
|
+
let out = '';
|
|
114
|
+
let err = '';
|
|
115
|
+
child.stdout.on('data', chunk => { out += chunk; });
|
|
116
|
+
child.stderr.on('data', chunk => { err += chunk; });
|
|
117
|
+
child.on('error', (e) => {
|
|
118
|
+
if (e.code === 'ENOENT') {
|
|
119
|
+
reject(new Error('pg_dump not found; ensure PostgreSQL client tools are installed and in PATH'));
|
|
120
|
+
return;
|
|
121
|
+
}
|
|
122
|
+
reject(e);
|
|
123
|
+
});
|
|
124
|
+
child.on('close', code => {
|
|
125
|
+
if (code === 0)
|
|
126
|
+
resolve(out);
|
|
127
|
+
else
|
|
128
|
+
reject(new Error(`pg_dump exited with code ${code}: ${err.trim()}`));
|
|
129
|
+
});
|
|
130
|
+
});
|
|
131
|
+
};
|
|
132
|
+
/** Resolve one side spec into diff input changes. */
|
|
133
|
+
const loadSide = async (spec, cwd) => {
|
|
134
|
+
if (resolveDiffSideKindSafe(spec) === 'database') {
|
|
135
|
+
const label = spec.startsWith('db:') ? spec.slice(3) : spec;
|
|
136
|
+
return {
|
|
137
|
+
kind: 'database',
|
|
138
|
+
label,
|
|
139
|
+
changes: (0, diff_1.sqlToDiffChanges)(await dumpDatabase(spec), label),
|
|
140
|
+
warnings: []
|
|
141
|
+
};
|
|
142
|
+
}
|
|
143
|
+
return (0, diff_1.loadDiffSideFromDisk)(path.resolve(cwd, spec));
|
|
144
|
+
};
|
|
145
|
+
/** `resolveDiffSideKind` without throwing on relative on-disk paths. */
|
|
146
|
+
const resolveDiffSideKindSafe = (spec) => /^postgres(ql)?:\/\//.test(spec) || spec.startsWith('db:') ? 'database' : 'disk';
|
|
147
|
+
/** Apply one diff side into a scratch database (module deploy or raw SQL). */
|
|
148
|
+
const applySide = async (config, side, spec, cwd) => {
|
|
149
|
+
if (side.kind === 'module') {
|
|
150
|
+
const client = new core_1.PgpmMigrate(config);
|
|
151
|
+
const result = await client.deploy({ modulePath: path.resolve(cwd, spec) });
|
|
152
|
+
if (result.failed) {
|
|
153
|
+
throw new Error(`deploy failed at change ${result.failed} (module ${spec})`);
|
|
154
|
+
}
|
|
155
|
+
return;
|
|
156
|
+
}
|
|
157
|
+
const pool = (0, pg_cache_1.getPgPool)(config);
|
|
158
|
+
const sql = side.changes.map(c => c.deploy).filter(Boolean).join('\n\n');
|
|
159
|
+
if (sql.trim())
|
|
160
|
+
await pool.query(sql);
|
|
161
|
+
};
|
|
162
|
+
/**
|
|
163
|
+
* Oracle mode: deploy A into a scratch database, deploy the emitted migration
|
|
164
|
+
* module on top (when there is one), and assert catalog equivalence with B
|
|
165
|
+
* deployed fresh into a second scratch database. Column order is physical — a
|
|
166
|
+
* drop+add migration cannot reproduce a fresh deploy's ordinals — so
|
|
167
|
+
* equivalence is checked order-insensitively via `withoutColumnOrder`.
|
|
168
|
+
*/
|
|
169
|
+
const runVerify = async (sideA, sideB, specA, specB, migrationDir, cwd) => {
|
|
170
|
+
const config = (0, pg_env_1.getPgEnvOptions)();
|
|
171
|
+
const stamp = Date.now();
|
|
172
|
+
const names = [`pgpm_diff_verify_a_${stamp}`, `pgpm_diff_verify_b_${stamp}`];
|
|
173
|
+
return (0, scratch_db_1.withScratchDatabases)(config, names, async ([cfgMigrated, cfgTarget]) => {
|
|
174
|
+
await applySide(cfgMigrated, sideA, specA, cwd);
|
|
175
|
+
if (migrationDir) {
|
|
176
|
+
const result = await new core_1.PgpmMigrate(cfgMigrated).deploy({ modulePath: migrationDir });
|
|
177
|
+
if (result.failed) {
|
|
178
|
+
throw new Error(`migration deploy failed at change ${result.failed}`);
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
await applySide(cfgTarget, sideB, specB, cwd);
|
|
182
|
+
return (0, scratch_db_1.catalogDifferences)(cfgMigrated, cfgTarget, transform_1.withoutColumnOrder);
|
|
183
|
+
});
|
|
184
|
+
};
|
|
185
|
+
const printSummary = (result, labelA, labelB) => {
|
|
186
|
+
if (result.identical) {
|
|
187
|
+
console.log(`No differences: ${labelA} and ${labelB} describe the same objects.`);
|
|
188
|
+
return;
|
|
189
|
+
}
|
|
190
|
+
const byDelta = (delta) => result.objects.filter(o => o.delta === delta);
|
|
191
|
+
const section = (title, objects) => {
|
|
192
|
+
if (objects.length === 0)
|
|
193
|
+
return;
|
|
194
|
+
console.log(`${title} (${objects.length}):`);
|
|
195
|
+
for (const obj of objects) {
|
|
196
|
+
const detail = [];
|
|
197
|
+
if (obj.columnsAdded?.length)
|
|
198
|
+
detail.push(`+cols: ${obj.columnsAdded.join(', ')}`);
|
|
199
|
+
if (obj.columnsRemoved?.length)
|
|
200
|
+
detail.push(`-cols: ${obj.columnsRemoved.join(', ')}`);
|
|
201
|
+
if (obj.columnsModified?.length)
|
|
202
|
+
detail.push(`~cols: ${obj.columnsModified.join(', ')}`);
|
|
203
|
+
console.log(` ${obj.identity.kind} ${obj.path}${detail.length ? ` (${detail.join('; ')})` : ''}`);
|
|
204
|
+
}
|
|
205
|
+
};
|
|
206
|
+
section('Added', byDelta('added'));
|
|
207
|
+
section('Removed', byDelta('removed'));
|
|
208
|
+
section('Changed', byDelta('modified'));
|
|
209
|
+
console.log(`${result.changes.length} migration change(s) derivable.`);
|
|
210
|
+
};
|
|
211
|
+
exports.default = async (argv, prompter, _options) => {
|
|
212
|
+
if (argv.help || argv.h) {
|
|
213
|
+
console.log(diffUsageText);
|
|
214
|
+
process.exit(0);
|
|
215
|
+
}
|
|
216
|
+
const { first: specA, newArgv } = (0, inquirerer_1.extractFirst)(argv);
|
|
217
|
+
const { first: specB, newArgv: restArgv } = (0, inquirerer_1.extractFirst)(newArgv);
|
|
218
|
+
argv = restArgv;
|
|
219
|
+
if (!specA || !specB) {
|
|
220
|
+
await (0, inquirerer_1.cliExitWithError)('pgpm diff requires two sides: pgpm diff <A> <B> (module dir, .sql file, db:<name>, or connection string).');
|
|
221
|
+
}
|
|
222
|
+
const granularityRaw = argv.granularity ?? 'object';
|
|
223
|
+
if (!(0, transform_1.isExportGranularity)(granularityRaw)) {
|
|
224
|
+
await (0, inquirerer_1.cliExitWithError)(`Invalid --granularity "${granularityRaw}". Expected one of: ${transform_1.EXPORT_GRANULARITIES.join(', ')}.`);
|
|
225
|
+
}
|
|
226
|
+
const granularity = granularityRaw;
|
|
227
|
+
const namingRaw = argv.naming ?? 'directory';
|
|
228
|
+
if (!NAMING_STYLES.includes(namingRaw)) {
|
|
229
|
+
await (0, inquirerer_1.cliExitWithError)(`Invalid --naming "${namingRaw}". Expected one of: ${NAMING_STYLES.join(', ')}.`);
|
|
230
|
+
}
|
|
231
|
+
const naming = namingRaw;
|
|
232
|
+
const cwd = argv.cwd || process.cwd();
|
|
233
|
+
const json = Boolean(argv.json);
|
|
234
|
+
const verify = Boolean(argv.verify);
|
|
235
|
+
const emitModuleRaw = argv['emit-migration'] ?? argv.emitMigration ?? argv['emit-module'] ?? argv.emitModule;
|
|
236
|
+
const emitModuleDir = typeof emitModuleRaw === 'string' && emitModuleRaw
|
|
237
|
+
? path.resolve(cwd, emitModuleRaw)
|
|
238
|
+
: undefined;
|
|
239
|
+
const emitSqlRaw = argv['emit-sql'] ?? argv.emitSql;
|
|
240
|
+
const emitSql = typeof emitSqlRaw === 'string' && emitSqlRaw
|
|
241
|
+
? (emitSqlRaw === module_projections_1.STDOUT_TARGET ? module_projections_1.STDOUT_TARGET : path.resolve(cwd, emitSqlRaw))
|
|
242
|
+
: undefined;
|
|
243
|
+
const emitBundleRaw = argv['emit-bundle'] ?? argv.emitBundle;
|
|
244
|
+
const emitBundle = typeof emitBundleRaw === 'string' && emitBundleRaw
|
|
245
|
+
? path.resolve(cwd, emitBundleRaw)
|
|
246
|
+
: undefined;
|
|
247
|
+
const sqlToStdout = emitSql === module_projections_1.STDOUT_TARGET;
|
|
248
|
+
const pkgName = argv.pkg || 'diff-migration';
|
|
249
|
+
await (0, transform_1.loadModule)();
|
|
250
|
+
let sideA;
|
|
251
|
+
let sideB;
|
|
252
|
+
try {
|
|
253
|
+
sideA = await loadSide(specA, cwd);
|
|
254
|
+
sideB = await loadSide(specB, cwd);
|
|
255
|
+
}
|
|
256
|
+
catch (err) {
|
|
257
|
+
await (0, inquirerer_1.cliExitWithError)(err instanceof Error ? err.message : String(err));
|
|
258
|
+
return;
|
|
259
|
+
}
|
|
260
|
+
const result = (0, transform_1.diffChangeSets)(sideA.changes, sideB.changes, { granularity, style: naming });
|
|
261
|
+
const warnings = [
|
|
262
|
+
...sideA.warnings.map(w => `${sideA.label}: ${w}`),
|
|
263
|
+
...sideB.warnings.map(w => `${sideB.label}: ${w}`),
|
|
264
|
+
...result.warnings
|
|
265
|
+
];
|
|
266
|
+
if (json) {
|
|
267
|
+
console.log(JSON.stringify({
|
|
268
|
+
identical: result.identical,
|
|
269
|
+
objects: result.objects,
|
|
270
|
+
changes: result.changes.map(c => ({
|
|
271
|
+
name: c.name,
|
|
272
|
+
dependencies: c.dependencies,
|
|
273
|
+
deploy: c.deploy,
|
|
274
|
+
revert: c.revert,
|
|
275
|
+
verify: c.verify
|
|
276
|
+
})),
|
|
277
|
+
warnings
|
|
278
|
+
}, null, 2));
|
|
279
|
+
}
|
|
280
|
+
else {
|
|
281
|
+
for (const warning of warnings)
|
|
282
|
+
console.warn(`diff: ${warning}`);
|
|
283
|
+
// Keep stdout clean when the SQL projection is piped there.
|
|
284
|
+
if (!sqlToStdout)
|
|
285
|
+
printSummary(result, sideA.label, sideB.label);
|
|
286
|
+
}
|
|
287
|
+
let migrationDir;
|
|
288
|
+
const needModule = Boolean(emitModuleDir || emitSql || emitBundle) || (verify && !result.identical);
|
|
289
|
+
if (needModule) {
|
|
290
|
+
const outBase = emitModuleDir ?? fs.mkdtempSync(path.join(os.tmpdir(), 'pgpm-diff-'));
|
|
291
|
+
const rows = (0, diff_1.deltaChangesToRows)(result.changes);
|
|
292
|
+
if (rows.length === 0) {
|
|
293
|
+
if (!sqlToStdout)
|
|
294
|
+
log.info('no migration changes to emit (sides are identical).');
|
|
295
|
+
}
|
|
296
|
+
else {
|
|
297
|
+
migrationDir = (0, transform_1.writeModule)(outBase, { name: pkgName, requires: [], rows });
|
|
298
|
+
if (emitModuleDir && !sqlToStdout)
|
|
299
|
+
log.success(`wrote ${rows.length} migration change(s) to ${migrationDir}`);
|
|
300
|
+
if (emitSql) {
|
|
301
|
+
await (0, module_projections_1.emitModuleSql)(migrationDir, emitSql);
|
|
302
|
+
if (!sqlToStdout)
|
|
303
|
+
log.success(`wrote linear SQL to ${emitSql}`);
|
|
304
|
+
}
|
|
305
|
+
if (emitBundle) {
|
|
306
|
+
await (0, module_projections_1.emitModuleBundle)(migrationDir, emitBundle);
|
|
307
|
+
if (!sqlToStdout)
|
|
308
|
+
log.success(`wrote bundle to ${emitBundle}`);
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
if (verify) {
|
|
313
|
+
log.info('running --verify: deploying A plus the migration and B into scratch databases...');
|
|
314
|
+
const diffs = await runVerify(sideA, sideB, specA, specB, migrationDir, cwd);
|
|
315
|
+
if (diffs.length) {
|
|
316
|
+
console.error(`--verify failed: catalogs differ (${diffs.length} differences):`);
|
|
317
|
+
for (const diff of diffs)
|
|
318
|
+
console.error(` ${diff}`);
|
|
319
|
+
await (0, inquirerer_1.cliExitWithError)('Migrated catalog does not match the target.');
|
|
320
|
+
}
|
|
321
|
+
log.success('--verify passed: migrated catalog is structurally equivalent to the target.');
|
|
322
|
+
}
|
|
323
|
+
prompter.close();
|
|
324
|
+
return argv;
|
|
325
|
+
};
|
package/commands/dump.js
CHANGED
|
@@ -4,12 +4,12 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
4
4
|
};
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
6
|
const logger_1 = require("@pgpmjs/logger");
|
|
7
|
-
const
|
|
8
|
-
const pg_cache_1 = require("pg-cache");
|
|
7
|
+
const quotes_1 = require("@pgsql/quotes");
|
|
9
8
|
const child_process_1 = require("child_process");
|
|
10
9
|
const fs_1 = __importDefault(require("fs"));
|
|
11
10
|
const path_1 = __importDefault(require("path"));
|
|
12
|
-
const
|
|
11
|
+
const pg_cache_1 = require("pg-cache");
|
|
12
|
+
const pg_env_1 = require("pg-env");
|
|
13
13
|
const utils_1 = require("../utils");
|
|
14
14
|
const log = new logger_1.Logger('dump');
|
|
15
15
|
const dumpUsageText = `
|
package/commands/export.js
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
const core_1 = require("@pgpmjs/core");
|
|
4
|
-
const export_1 = require("@pgpmjs/export");
|
|
5
4
|
const env_1 = require("@pgpmjs/env");
|
|
5
|
+
const export_1 = require("@pgpmjs/export");
|
|
6
|
+
const transform_1 = require("@pgpmjs/transform");
|
|
6
7
|
const types_1 = require("@pgpmjs/types");
|
|
7
8
|
const path_1 = require("path");
|
|
8
9
|
const pg_cache_1 = require("pg-cache");
|
|
@@ -30,12 +31,16 @@ Options:
|
|
|
30
31
|
Change paths are derived from the naming spec and
|
|
31
32
|
requires from the statement graph. When omitted,
|
|
32
33
|
sql_actions rows are exported unchanged.
|
|
34
|
+
--partition <file> Partition config (JSON: rules/defaultPackage/splitRiders)
|
|
35
|
+
splitting the exported database module into multiple
|
|
36
|
+
pgpm packages with derived cross-package requires.
|
|
33
37
|
--cwd <directory> Working directory (default: current directory)
|
|
34
38
|
|
|
35
39
|
Examples:
|
|
36
40
|
pgpm export Export migrations from selected database (SQL mode)
|
|
37
41
|
pgpm export --exclude-categories security,permissions,auth,memberships
|
|
38
42
|
pgpm export --granularity consolidated
|
|
43
|
+
pgpm export --granularity object --partition partition.json
|
|
39
44
|
pgpm export --graphql-endpoint 'http://[::1]:3002/graphql' --migrate-endpoint 'http://[::1]:3000/graphql' --migrate-host db_migrate.localhost:3000
|
|
40
45
|
`;
|
|
41
46
|
exports.default = async (argv, prompter, _options) => {
|
|
@@ -61,12 +66,24 @@ exports.default = async (argv, prompter, _options) => {
|
|
|
61
66
|
? excludeCategoriesRaw
|
|
62
67
|
: undefined;
|
|
63
68
|
const granularityRaw = argv.granularity;
|
|
64
|
-
if (granularityRaw !== undefined && !(0,
|
|
65
|
-
console.error(`Invalid --granularity "${granularityRaw}". Expected one of: ${
|
|
69
|
+
if (granularityRaw !== undefined && !(0, transform_1.isExportGranularity)(granularityRaw)) {
|
|
70
|
+
console.error(`Invalid --granularity "${granularityRaw}". Expected one of: ${transform_1.EXPORT_GRANULARITIES.join(', ')}.`);
|
|
66
71
|
prompter.close();
|
|
67
72
|
return;
|
|
68
73
|
}
|
|
69
74
|
const granularity = granularityRaw;
|
|
75
|
+
const partitionRaw = argv.partition;
|
|
76
|
+
let partition;
|
|
77
|
+
if (typeof partitionRaw === 'string' && partitionRaw) {
|
|
78
|
+
try {
|
|
79
|
+
partition = (0, transform_1.parsePartitionConfig)((0, path_1.resolve)(cwd, partitionRaw));
|
|
80
|
+
}
|
|
81
|
+
catch (err) {
|
|
82
|
+
console.error(err instanceof Error ? err.message : String(err));
|
|
83
|
+
prompter.close();
|
|
84
|
+
return;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
70
87
|
if (graphqlEndpoint) {
|
|
71
88
|
// =========================================================================
|
|
72
89
|
// GraphQL export mode
|
|
@@ -164,7 +181,8 @@ exports.default = async (argv, prompter, _options) => {
|
|
|
164
181
|
argv,
|
|
165
182
|
username,
|
|
166
183
|
excludeCategories,
|
|
167
|
-
granularity
|
|
184
|
+
granularity,
|
|
185
|
+
partition
|
|
168
186
|
});
|
|
169
187
|
}
|
|
170
188
|
else {
|
|
@@ -285,7 +303,8 @@ exports.default = async (argv, prompter, _options) => {
|
|
|
285
303
|
argv,
|
|
286
304
|
username,
|
|
287
305
|
excludeCategories,
|
|
288
|
-
granularity
|
|
306
|
+
granularity,
|
|
307
|
+
partition
|
|
289
308
|
});
|
|
290
309
|
}
|
|
291
310
|
prompter.close();
|
|
@@ -0,0 +1,198 @@
|
|
|
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
|
+
const import_1 = require("@pgpmjs/import");
|
|
37
|
+
const logger_1 = require("@pgpmjs/logger");
|
|
38
|
+
const transform_1 = require("@pgpmjs/transform");
|
|
39
|
+
const inquirerer_1 = require("inquirerer");
|
|
40
|
+
const path = __importStar(require("path"));
|
|
41
|
+
const emit_package_1 = require("../utils/emit-package");
|
|
42
|
+
const log = new logger_1.Logger('import');
|
|
43
|
+
const importUsageText = `
|
|
44
|
+
Import Command:
|
|
45
|
+
|
|
46
|
+
pgpm import <dump.sql | dir-of-sql-files> --pkg <module-name> [OPTIONS]
|
|
47
|
+
|
|
48
|
+
pgpm-itize an arbitrary SQL dump (e.g. pg_dump --schema-only output): parse
|
|
49
|
+
it, classify every statement, and emit a complete deployable pgpm module —
|
|
50
|
+
module dir, pgpm.plan, and deploy/revert/verify trees with spec-derived
|
|
51
|
+
change paths, graph-derived requires, and generated revert/verify scripts.
|
|
52
|
+
|
|
53
|
+
Directory inputs concatenate their .sql files in sorted (lexicographic)
|
|
54
|
+
filename order.
|
|
55
|
+
|
|
56
|
+
Dump handling:
|
|
57
|
+
- pg_dump preamble noise (SET ..., SELECT set_config(...), psql backslash
|
|
58
|
+
commands) is skipped; COMMENT ON and GRANT statements are kept, attached
|
|
59
|
+
to their host object's change.
|
|
60
|
+
- CREATE EXTENSION statements become .control requires (pgpm owns
|
|
61
|
+
extension creation at deploy time).
|
|
62
|
+
- COPY ... FROM stdin blocks and INSERTs are skipped with a warning unless
|
|
63
|
+
--with-data is passed, which emits them as seed fixture changes (COPY
|
|
64
|
+
data is converted to INSERTs).
|
|
65
|
+
- Statements that classify to no object land in a misc/statements change
|
|
66
|
+
with a warning — never dropped silently.
|
|
67
|
+
|
|
68
|
+
Options:
|
|
69
|
+
--help, -h Show this help message
|
|
70
|
+
--pkg <name> Module name for the generated package (required)
|
|
71
|
+
--granularity <level> Granularity dial: atomic | object | consolidated
|
|
72
|
+
(default: object)
|
|
73
|
+
--naming <style> Change path naming style: directory | flat (default: directory)
|
|
74
|
+
--out <dir> Output base directory (default: current directory);
|
|
75
|
+
the module is written to <out>/<pkg>
|
|
76
|
+
--with-data Import COPY/INSERT data as seed fixture changes
|
|
77
|
+
--partition <file> Partition config (JSON: rules/defaultPackage/splitRiders)
|
|
78
|
+
splitting the import into multiple pgpm packages with
|
|
79
|
+
derived cross-package requires
|
|
80
|
+
--write Allow overwriting an existing module directory
|
|
81
|
+
--cwd <directory> Working directory (default: current directory)
|
|
82
|
+
--dry-run Print the resulting plan/paths without writing
|
|
83
|
+
|
|
84
|
+
Examples:
|
|
85
|
+
pgpm import dump.sql --pkg my-app
|
|
86
|
+
pgpm import dump.sql --pkg my-app --granularity consolidated --naming flat
|
|
87
|
+
pgpm import ./sql-files --pkg my-app --out ./packages --with-data
|
|
88
|
+
pgpm import dump.sql --pkg my-app --partition partition.json
|
|
89
|
+
`;
|
|
90
|
+
const NAMING_STYLES = ['directory', 'flat'];
|
|
91
|
+
exports.default = async (argv, prompter, _options) => {
|
|
92
|
+
if (argv.help || argv.h) {
|
|
93
|
+
console.log(importUsageText);
|
|
94
|
+
process.exit(0);
|
|
95
|
+
}
|
|
96
|
+
const cwd = argv.cwd || process.cwd();
|
|
97
|
+
const input = argv._?.filter(Boolean)[0];
|
|
98
|
+
if (!input) {
|
|
99
|
+
await (0, inquirerer_1.cliExitWithError)('Missing input: pgpm import <dump.sql | dir-of-sql-files> --pkg <name>');
|
|
100
|
+
}
|
|
101
|
+
const pkgName = argv.pkg;
|
|
102
|
+
if (!pkgName || typeof pkgName !== 'string') {
|
|
103
|
+
await (0, inquirerer_1.cliExitWithError)('--pkg <module-name> is required.');
|
|
104
|
+
}
|
|
105
|
+
const granularityRaw = argv.granularity ?? 'object';
|
|
106
|
+
if (!(0, transform_1.isExportGranularity)(granularityRaw)) {
|
|
107
|
+
await (0, inquirerer_1.cliExitWithError)(`Invalid --granularity "${granularityRaw}". Expected one of: ${transform_1.EXPORT_GRANULARITIES.join(', ')}.`);
|
|
108
|
+
}
|
|
109
|
+
const granularity = granularityRaw;
|
|
110
|
+
const namingRaw = argv.naming ?? 'directory';
|
|
111
|
+
if (!NAMING_STYLES.includes(namingRaw)) {
|
|
112
|
+
await (0, inquirerer_1.cliExitWithError)(`Invalid --naming "${namingRaw}". Expected one of: ${NAMING_STYLES.join(', ')}.`);
|
|
113
|
+
}
|
|
114
|
+
const naming = namingRaw;
|
|
115
|
+
let partition;
|
|
116
|
+
if (typeof argv.partition === 'string' && argv.partition) {
|
|
117
|
+
try {
|
|
118
|
+
partition = (0, transform_1.parsePartitionConfig)(path.resolve(cwd, argv.partition));
|
|
119
|
+
}
|
|
120
|
+
catch (err) {
|
|
121
|
+
await (0, inquirerer_1.cliExitWithError)(err instanceof Error ? err.message : String(err));
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
const outBase = typeof argv.out === 'string' && argv.out ? path.resolve(cwd, argv.out) : path.resolve(cwd);
|
|
125
|
+
const withData = Boolean(argv['with-data'] ?? argv.withData);
|
|
126
|
+
const write = Boolean(argv.write);
|
|
127
|
+
const dryRun = Boolean(argv['dry-run'] ?? argv.dryRun);
|
|
128
|
+
let source;
|
|
129
|
+
try {
|
|
130
|
+
source = (0, import_1.loadDumpSource)(path.resolve(cwd, input));
|
|
131
|
+
}
|
|
132
|
+
catch (err) {
|
|
133
|
+
await (0, inquirerer_1.cliExitWithError)(err instanceof Error ? err.message : String(err));
|
|
134
|
+
return;
|
|
135
|
+
}
|
|
136
|
+
if (source.files.length > 1) {
|
|
137
|
+
log.info(`concatenated ${source.files.length} .sql files in sorted order`);
|
|
138
|
+
}
|
|
139
|
+
for (const warning of (0, import_1.dumpCompatibilityWarnings)(source)) {
|
|
140
|
+
console.warn(`\nWARNING: ${warning}\n`);
|
|
141
|
+
}
|
|
142
|
+
const result = await (0, import_1.importDumpRows)(source, { granularity, naming, withData });
|
|
143
|
+
let packages;
|
|
144
|
+
if (partition) {
|
|
145
|
+
try {
|
|
146
|
+
const partitioned = await (0, transform_1.partitionExportRows)(result.rows, partition);
|
|
147
|
+
result.warnings.push(...partitioned.warnings.map(w => `partition: ${w}`));
|
|
148
|
+
packages = partitioned.packages.map(pkg => ({
|
|
149
|
+
name: pkg.name,
|
|
150
|
+
requires: [...result.controlRequires, ...pkg.requires],
|
|
151
|
+
rows: (0, import_1.linkTextualDeps)(pkg.rows)
|
|
152
|
+
}));
|
|
153
|
+
}
|
|
154
|
+
catch (err) {
|
|
155
|
+
if (err instanceof transform_1.PartitionCycleError) {
|
|
156
|
+
await (0, inquirerer_1.cliExitWithError)(`Partition failed: ${err.message}`);
|
|
157
|
+
return;
|
|
158
|
+
}
|
|
159
|
+
throw err;
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
else {
|
|
163
|
+
packages = [{ name: pkgName, requires: result.controlRequires, rows: result.rows }];
|
|
164
|
+
}
|
|
165
|
+
for (const warning of result.warnings) {
|
|
166
|
+
console.warn(`import: ${warning}`);
|
|
167
|
+
}
|
|
168
|
+
if (dryRun) {
|
|
169
|
+
for (const pkg of packages) {
|
|
170
|
+
console.log(`package ${pkg.name} -> ${path.join(outBase, pkg.name)}`);
|
|
171
|
+
if (pkg.requires.length) {
|
|
172
|
+
console.log(` requires: ${pkg.requires.join(', ')}`);
|
|
173
|
+
}
|
|
174
|
+
for (const row of pkg.rows) {
|
|
175
|
+
const deps = row.deps?.length ? ` [${row.deps.join(' ')}]` : '';
|
|
176
|
+
console.log(` ${row.deploy}${deps}`);
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
else {
|
|
181
|
+
for (const pkg of packages) {
|
|
182
|
+
const guard = (0, emit_package_1.checkOverwrite)(path.join(outBase, pkg.name), source.files[0], write);
|
|
183
|
+
if (guard) {
|
|
184
|
+
await (0, inquirerer_1.cliExitWithError)(guard);
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
for (const pkg of packages) {
|
|
188
|
+
const dir = (0, emit_package_1.writePackage)(outBase, pkg);
|
|
189
|
+
log.success(`wrote ${pkg.rows.length} changes to ${dir}`);
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
const { summary } = result;
|
|
193
|
+
log.success(`import: ${summary.statements} statement(s) -> ${summary.changes} change(s), ` +
|
|
194
|
+
`${summary.skippedPreamble} preamble skipped, ${summary.skippedData} data skipped, ` +
|
|
195
|
+
`${summary.misc} in misc, ${result.warnings.length} warning(s)`);
|
|
196
|
+
prompter.close();
|
|
197
|
+
return argv;
|
|
198
|
+
};
|
|
@@ -6,9 +6,9 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
6
6
|
exports.resolveInitTemplateRepo = resolveInitTemplateRepo;
|
|
7
7
|
exports.persistBoilerplateSource = persistBoilerplateSource;
|
|
8
8
|
exports.readBoilerplateSource = readBoilerplateSource;
|
|
9
|
+
const core_1 = require("@pgpmjs/core");
|
|
9
10
|
const fs_1 = __importDefault(require("fs"));
|
|
10
11
|
const path_1 = __importDefault(require("path"));
|
|
11
|
-
const core_1 = require("@pgpmjs/core");
|
|
12
12
|
/**
|
|
13
13
|
* Resolve the template repo for an `init` invocation from its flags.
|
|
14
14
|
*
|
package/commands/init/index.js
CHANGED
|
@@ -4,13 +4,13 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
4
4
|
};
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
6
|
exports.createInitUsageText = void 0;
|
|
7
|
-
const child_process_1 = require("child_process");
|
|
8
|
-
const fs_1 = __importDefault(require("fs"));
|
|
9
|
-
const path_1 = __importDefault(require("path"));
|
|
10
7
|
const core_1 = require("@pgpmjs/core");
|
|
11
8
|
const env_1 = require("@pgpmjs/env");
|
|
12
9
|
const types_1 = require("@pgpmjs/types");
|
|
10
|
+
const child_process_1 = require("child_process");
|
|
11
|
+
const fs_1 = __importDefault(require("fs"));
|
|
13
12
|
const inquirerer_1 = require("inquirerer");
|
|
13
|
+
const path_1 = __importDefault(require("path"));
|
|
14
14
|
const utils_1 = require("../../utils");
|
|
15
15
|
const boilerplate_1 = require("./boilerplate");
|
|
16
16
|
const DEFAULT_MOTD = `
|
package/commands/package.js
CHANGED
|
@@ -118,7 +118,6 @@ exports.default = async (argv, prompter, _options) => {
|
|
|
118
118
|
const project = new core_1.PgpmPackage(cwd);
|
|
119
119
|
project.ensureModule();
|
|
120
120
|
const info = project.getModuleInfo();
|
|
121
|
-
info.version;
|
|
122
121
|
await (0, core_1.writePackage)({
|
|
123
122
|
version: info.version,
|
|
124
123
|
extension: true,
|