pgpm 5.20.0 → 5.21.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/diff.js +35 -1
- package/commands/import.js +43 -6
- package/commands/transform.js +46 -4
- package/esm/commands/diff.js +36 -2
- package/esm/commands/import.js +44 -7
- package/esm/commands/transform.js +47 -5
- package/esm/utils/module-projections.js +35 -0
- package/esm/utils/scratch-db.js +1 -1
- package/package.json +8 -8
- package/utils/module-projections.d.ts +21 -0
- package/utils/module-projections.js +39 -1
- package/utils/scratch-db.d.ts +0 -15
- package/utils/scratch-db.js +1 -1
package/commands/diff.js
CHANGED
|
@@ -74,6 +74,10 @@ Options:
|
|
|
74
74
|
verify per change, spec-derived paths, graph-derived
|
|
75
75
|
requires) into <dir>/<pkg>
|
|
76
76
|
--emit-module <dir> Alias of --emit-migration
|
|
77
|
+
--append-module <dir> Append the delta into an EXISTING pgpm module at
|
|
78
|
+
<dir> (new changes only; existing changes, scripts,
|
|
79
|
+
and .control are left untouched). Standalone: not
|
|
80
|
+
combinable with --emit-*/--verify.
|
|
77
81
|
--emit-sql <file|-> Also project the delta to a single consolidated SQL
|
|
78
82
|
file (deparsed in plan order); - writes to stdout
|
|
79
83
|
--emit-bundle <file> Also project the delta to a content-addressed
|
|
@@ -81,6 +85,9 @@ Options:
|
|
|
81
85
|
--pkg <name> Emitted migration package name (default: diff-migration)
|
|
82
86
|
--granularity <level> Granularity for emitted changes: atomic | object |
|
|
83
87
|
consolidated (default: object)
|
|
88
|
+
--change-granularity <level>
|
|
89
|
+
Change-level distribution for emitted changes:
|
|
90
|
+
alteration | object | single (default: object)
|
|
84
91
|
--naming <style> Change path naming style: directory | flat (default: directory)
|
|
85
92
|
--json Machine-readable output
|
|
86
93
|
--verify Oracle mode: deploy A plus the emitted migration into
|
|
@@ -224,6 +231,11 @@ exports.default = async (argv, prompter, _options) => {
|
|
|
224
231
|
await (0, inquirerer_1.cliExitWithError)(`Invalid --granularity "${granularityRaw}". Expected one of: ${transform_1.EXPORT_GRANULARITIES.join(', ')}.`);
|
|
225
232
|
}
|
|
226
233
|
const granularity = granularityRaw;
|
|
234
|
+
const changeGranularityRaw = argv['change-granularity'] ?? argv.changeGranularity ?? 'object';
|
|
235
|
+
if (!(0, transform_1.isChangeGranularity)(changeGranularityRaw)) {
|
|
236
|
+
await (0, inquirerer_1.cliExitWithError)(`Invalid --change-granularity "${changeGranularityRaw}". Expected one of: ${transform_1.CHANGE_GRANULARITIES.join(', ')}.`);
|
|
237
|
+
}
|
|
238
|
+
const changeGranularity = changeGranularityRaw;
|
|
227
239
|
const namingRaw = argv.naming ?? 'directory';
|
|
228
240
|
if (!NAMING_STYLES.includes(namingRaw)) {
|
|
229
241
|
await (0, inquirerer_1.cliExitWithError)(`Invalid --naming "${namingRaw}". Expected one of: ${NAMING_STYLES.join(', ')}.`);
|
|
@@ -236,6 +248,10 @@ exports.default = async (argv, prompter, _options) => {
|
|
|
236
248
|
const emitModuleDir = typeof emitModuleRaw === 'string' && emitModuleRaw
|
|
237
249
|
? path.resolve(cwd, emitModuleRaw)
|
|
238
250
|
: undefined;
|
|
251
|
+
const appendModuleRaw = argv['append-module'] ?? argv.appendModule;
|
|
252
|
+
const appendModuleDir = typeof appendModuleRaw === 'string' && appendModuleRaw
|
|
253
|
+
? path.resolve(cwd, appendModuleRaw)
|
|
254
|
+
: undefined;
|
|
239
255
|
const emitSqlRaw = argv['emit-sql'] ?? argv.emitSql;
|
|
240
256
|
const emitSql = typeof emitSqlRaw === 'string' && emitSqlRaw
|
|
241
257
|
? (emitSqlRaw === module_projections_1.STDOUT_TARGET ? module_projections_1.STDOUT_TARGET : path.resolve(cwd, emitSqlRaw))
|
|
@@ -246,6 +262,9 @@ exports.default = async (argv, prompter, _options) => {
|
|
|
246
262
|
: undefined;
|
|
247
263
|
const sqlToStdout = emitSql === module_projections_1.STDOUT_TARGET;
|
|
248
264
|
const pkgName = argv.pkg || 'diff-migration';
|
|
265
|
+
if (appendModuleDir && (emitModuleDir || emitSql || emitBundle || verify)) {
|
|
266
|
+
await (0, inquirerer_1.cliExitWithError)('--append-module is standalone; it cannot be combined with --emit-migration/--emit-module/--emit-sql/--emit-bundle/--verify.');
|
|
267
|
+
}
|
|
249
268
|
await (0, transform_1.loadModule)();
|
|
250
269
|
let sideA;
|
|
251
270
|
let sideB;
|
|
@@ -257,7 +276,7 @@ exports.default = async (argv, prompter, _options) => {
|
|
|
257
276
|
await (0, inquirerer_1.cliExitWithError)(err instanceof Error ? err.message : String(err));
|
|
258
277
|
return;
|
|
259
278
|
}
|
|
260
|
-
const result = (0, transform_1.diffChangeSets)(sideA.changes, sideB.changes, { granularity, style: naming });
|
|
279
|
+
const result = (0, transform_1.diffChangeSets)(sideA.changes, sideB.changes, { granularity, changeGranularity, style: naming });
|
|
261
280
|
const warnings = [
|
|
262
281
|
...sideA.warnings.map(w => `${sideA.label}: ${w}`),
|
|
263
282
|
...sideB.warnings.map(w => `${sideB.label}: ${w}`),
|
|
@@ -284,6 +303,21 @@ exports.default = async (argv, prompter, _options) => {
|
|
|
284
303
|
if (!sqlToStdout)
|
|
285
304
|
printSummary(result, sideA.label, sideB.label);
|
|
286
305
|
}
|
|
306
|
+
if (appendModuleDir) {
|
|
307
|
+
const rows = (0, diff_1.deltaChangesToRows)(result.changes);
|
|
308
|
+
if (rows.length === 0) {
|
|
309
|
+
log.info('no migration changes to append (sides are identical).');
|
|
310
|
+
}
|
|
311
|
+
else {
|
|
312
|
+
const appended = (0, transform_1.appendModule)(appendModuleDir, rows);
|
|
313
|
+
for (const w of appended.warnings)
|
|
314
|
+
console.warn(`diff: ${w}`);
|
|
315
|
+
log.success(`appended ${appended.added.length} change(s) to ${appended.dir}` +
|
|
316
|
+
(appended.skipped.length ? ` (${appended.skipped.length} skipped)` : ''));
|
|
317
|
+
}
|
|
318
|
+
prompter.close();
|
|
319
|
+
return argv;
|
|
320
|
+
}
|
|
287
321
|
let migrationDir;
|
|
288
322
|
const needModule = Boolean(emitModuleDir || emitSql || emitBundle) || (verify && !result.identical);
|
|
289
323
|
if (needModule) {
|
package/commands/import.js
CHANGED
|
@@ -39,6 +39,7 @@ const transform_1 = require("@pgpmjs/transform");
|
|
|
39
39
|
const inquirerer_1 = require("inquirerer");
|
|
40
40
|
const path = __importStar(require("path"));
|
|
41
41
|
const emit_package_1 = require("../utils/emit-package");
|
|
42
|
+
const module_projections_1 = require("../utils/module-projections");
|
|
42
43
|
const log = new logger_1.Logger('import');
|
|
43
44
|
const importUsageText = `
|
|
44
45
|
Import Command:
|
|
@@ -70,6 +71,9 @@ Options:
|
|
|
70
71
|
--pkg <name> Module name for the generated package (required)
|
|
71
72
|
--granularity <level> Granularity dial: atomic | object | consolidated
|
|
72
73
|
(default: object)
|
|
74
|
+
--change-granularity <level>
|
|
75
|
+
Change-level distribution: alteration | object | single
|
|
76
|
+
(default: object)
|
|
73
77
|
--naming <style> Change path naming style: directory | flat (default: directory)
|
|
74
78
|
--out <dir> Output base directory (default: current directory);
|
|
75
79
|
the module is written to <out>/<pkg>
|
|
@@ -78,14 +82,24 @@ Options:
|
|
|
78
82
|
splitting the import into multiple pgpm packages with
|
|
79
83
|
derived cross-package requires
|
|
80
84
|
--write Allow overwriting an existing module directory
|
|
85
|
+
--emit-sql <file|-> Also project the imported module into a single linear
|
|
86
|
+
SQL script (- for stdout). Requires a single output
|
|
87
|
+
package (not supported with --partition).
|
|
88
|
+
--emit-bundle <file> Also project the imported module into a
|
|
89
|
+
content-addressed .bundle.tar.gz. Same single-package
|
|
90
|
+
requirement as --emit-sql.
|
|
81
91
|
--cwd <directory> Working directory (default: current directory)
|
|
82
92
|
--dry-run Print the resulting plan/paths without writing
|
|
83
93
|
|
|
94
|
+
The imported module is the canonical artifact; --emit-sql and --emit-bundle are
|
|
95
|
+
pure projections of it (the same machinery pgpm package and pgpm diff use).
|
|
96
|
+
|
|
84
97
|
Examples:
|
|
85
98
|
pgpm import dump.sql --pkg my-app
|
|
86
99
|
pgpm import dump.sql --pkg my-app --granularity consolidated --naming flat
|
|
87
100
|
pgpm import ./sql-files --pkg my-app --out ./packages --with-data
|
|
88
101
|
pgpm import dump.sql --pkg my-app --partition partition.json
|
|
102
|
+
pgpm import dump.sql --pkg my-app --emit-sql my-app.sql
|
|
89
103
|
`;
|
|
90
104
|
const NAMING_STYLES = ['directory', 'flat'];
|
|
91
105
|
exports.default = async (argv, prompter, _options) => {
|
|
@@ -107,6 +121,11 @@ exports.default = async (argv, prompter, _options) => {
|
|
|
107
121
|
await (0, inquirerer_1.cliExitWithError)(`Invalid --granularity "${granularityRaw}". Expected one of: ${transform_1.EXPORT_GRANULARITIES.join(', ')}.`);
|
|
108
122
|
}
|
|
109
123
|
const granularity = granularityRaw;
|
|
124
|
+
const changeGranularityRaw = argv['change-granularity'] ?? argv.changeGranularity ?? 'object';
|
|
125
|
+
if (!(0, transform_1.isChangeGranularity)(changeGranularityRaw)) {
|
|
126
|
+
await (0, inquirerer_1.cliExitWithError)(`Invalid --change-granularity "${changeGranularityRaw}". Expected one of: ${transform_1.CHANGE_GRANULARITIES.join(', ')}.`);
|
|
127
|
+
}
|
|
128
|
+
const changeGranularity = changeGranularityRaw;
|
|
110
129
|
const namingRaw = argv.naming ?? 'directory';
|
|
111
130
|
if (!NAMING_STYLES.includes(namingRaw)) {
|
|
112
131
|
await (0, inquirerer_1.cliExitWithError)(`Invalid --naming "${namingRaw}". Expected one of: ${NAMING_STYLES.join(', ')}.`);
|
|
@@ -125,6 +144,12 @@ exports.default = async (argv, prompter, _options) => {
|
|
|
125
144
|
const withData = Boolean(argv['with-data'] ?? argv.withData);
|
|
126
145
|
const write = Boolean(argv.write);
|
|
127
146
|
const dryRun = Boolean(argv['dry-run'] ?? argv.dryRun);
|
|
147
|
+
const emit = (0, module_projections_1.parseEmitProjectionTargets)(argv, cwd);
|
|
148
|
+
const emitRequested = (0, module_projections_1.hasEmitProjection)(emit);
|
|
149
|
+
const sqlToStdout = emit.emitSql === module_projections_1.STDOUT_TARGET;
|
|
150
|
+
if (emitRequested && dryRun) {
|
|
151
|
+
await (0, inquirerer_1.cliExitWithError)('--emit-sql/--emit-bundle cannot be combined with --dry-run.');
|
|
152
|
+
}
|
|
128
153
|
let source;
|
|
129
154
|
try {
|
|
130
155
|
source = (0, import_1.loadDumpSource)(path.resolve(cwd, input));
|
|
@@ -133,13 +158,13 @@ exports.default = async (argv, prompter, _options) => {
|
|
|
133
158
|
await (0, inquirerer_1.cliExitWithError)(err instanceof Error ? err.message : String(err));
|
|
134
159
|
return;
|
|
135
160
|
}
|
|
136
|
-
if (source.files.length > 1) {
|
|
161
|
+
if (source.files.length > 1 && !sqlToStdout) {
|
|
137
162
|
log.info(`concatenated ${source.files.length} .sql files in sorted order`);
|
|
138
163
|
}
|
|
139
164
|
for (const warning of (0, import_1.dumpCompatibilityWarnings)(source)) {
|
|
140
165
|
console.warn(`\nWARNING: ${warning}\n`);
|
|
141
166
|
}
|
|
142
|
-
const result = await (0, import_1.importDumpRows)(source, { granularity, naming, withData });
|
|
167
|
+
const result = await (0, import_1.importDumpRows)(source, { granularity, changeGranularity, naming, withData });
|
|
143
168
|
let packages;
|
|
144
169
|
if (partition) {
|
|
145
170
|
try {
|
|
@@ -178,21 +203,33 @@ exports.default = async (argv, prompter, _options) => {
|
|
|
178
203
|
}
|
|
179
204
|
}
|
|
180
205
|
else {
|
|
206
|
+
if (emitRequested && packages.length !== 1) {
|
|
207
|
+
await (0, inquirerer_1.cliExitWithError)('--emit-sql/--emit-bundle require a single output package; a --partition import emits multiple packages.');
|
|
208
|
+
}
|
|
181
209
|
for (const pkg of packages) {
|
|
182
210
|
const guard = (0, emit_package_1.checkOverwrite)(path.join(outBase, pkg.name), source.files[0], write);
|
|
183
211
|
if (guard) {
|
|
184
212
|
await (0, inquirerer_1.cliExitWithError)(guard);
|
|
185
213
|
}
|
|
186
214
|
}
|
|
215
|
+
let firstDir;
|
|
187
216
|
for (const pkg of packages) {
|
|
188
217
|
const dir = (0, emit_package_1.writePackage)(outBase, pkg);
|
|
189
|
-
|
|
218
|
+
firstDir ??= dir;
|
|
219
|
+
if (!sqlToStdout) {
|
|
220
|
+
log.success(`wrote ${pkg.rows.length} changes to ${dir}`);
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
if (emitRequested && firstDir) {
|
|
224
|
+
await (0, module_projections_1.projectModule)(firstDir, emit, sqlToStdout ? undefined : msg => log.success(msg));
|
|
190
225
|
}
|
|
191
226
|
}
|
|
192
227
|
const { summary } = result;
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
228
|
+
if (!sqlToStdout) {
|
|
229
|
+
log.success(`import: ${summary.statements} statement(s) -> ${summary.changes} change(s), ` +
|
|
230
|
+
`${summary.skippedPreamble} preamble skipped, ${summary.skippedData} data skipped, ` +
|
|
231
|
+
`${summary.misc} in misc, ${result.warnings.length} warning(s)`);
|
|
232
|
+
}
|
|
196
233
|
prompter.close();
|
|
197
234
|
return argv;
|
|
198
235
|
};
|
package/commands/transform.js
CHANGED
|
@@ -42,6 +42,7 @@ const inquirerer_1 = require("inquirerer");
|
|
|
42
42
|
const path = __importStar(require("path"));
|
|
43
43
|
const pg_env_1 = require("pg-env");
|
|
44
44
|
const emit_package_1 = require("../utils/emit-package");
|
|
45
|
+
const module_projections_1 = require("../utils/module-projections");
|
|
45
46
|
const scratch_db_1 = require("../utils/scratch-db");
|
|
46
47
|
var emit_package_2 = require("../utils/emit-package");
|
|
47
48
|
Object.defineProperty(exports, "checkOverwrite", { enumerable: true, get: function () { return emit_package_2.checkOverwrite; } });
|
|
@@ -59,6 +60,12 @@ Transform Command:
|
|
|
59
60
|
Options:
|
|
60
61
|
--help, -h Show this help message
|
|
61
62
|
--granularity <level> Target granularity: atomic | object | consolidated (required)
|
|
63
|
+
--change-granularity <level>
|
|
64
|
+
Change-level distribution: alteration | object | single
|
|
65
|
+
(default: object). With alteration, every ADD COLUMN /
|
|
66
|
+
ADD CONSTRAINT becomes its own change with its own
|
|
67
|
+
deploy/revert/verify and requires. With single, the
|
|
68
|
+
whole module becomes one change.
|
|
62
69
|
--partition <file> Partition config (JSON: rules/defaultPackage/splitRiders)
|
|
63
70
|
splitting the module into multiple pgpm packages with
|
|
64
71
|
derived cross-package requires.
|
|
@@ -66,14 +73,25 @@ Options:
|
|
|
66
73
|
--cwd <directory> Module or workspace directory (default: current directory)
|
|
67
74
|
--out <dir> Output directory (default: sibling <module>-<granularity>)
|
|
68
75
|
--write Allow writing over an existing/module directory
|
|
76
|
+
--emit-sql <file|-> Also project the transformed module into a single
|
|
77
|
+
linear SQL script (- for stdout). Requires a single
|
|
78
|
+
output package (no workspace/partition fan-out).
|
|
79
|
+
--emit-bundle <file> Also project the transformed module into a
|
|
80
|
+
content-addressed .bundle.tar.gz. Same single-package
|
|
81
|
+
requirement as --emit-sql.
|
|
69
82
|
--check Deploy original and transformed output into scratch
|
|
70
83
|
databases and assert the catalogs are equivalent
|
|
71
84
|
--dry-run Print the resulting plan/paths without writing
|
|
72
85
|
|
|
86
|
+
The transformed module is the canonical artifact; --emit-sql and --emit-bundle
|
|
87
|
+
are pure projections of it (the same machinery pgpm package and pgpm diff use).
|
|
88
|
+
|
|
73
89
|
Examples:
|
|
74
90
|
pgpm transform --granularity object
|
|
75
91
|
pgpm transform --granularity atomic --naming flat --out ./out
|
|
76
92
|
pgpm transform --granularity object --partition partition.json --check
|
|
93
|
+
pgpm transform --granularity consolidated --emit-sql migration.sql
|
|
94
|
+
pgpm transform --granularity atomic --change-granularity alteration
|
|
77
95
|
`;
|
|
78
96
|
const NAMING_STYLES = ['directory', 'flat'];
|
|
79
97
|
/**
|
|
@@ -141,7 +159,7 @@ const runCheck = async (transformed) => {
|
|
|
141
159
|
return (0, scratch_db_1.catalogDifferences)(cfgOriginal, cfgTransformed);
|
|
142
160
|
});
|
|
143
161
|
};
|
|
144
|
-
const transformModule = async (modulePath, granularity, naming, partition, out) => {
|
|
162
|
+
const transformModule = async (modulePath, granularity, changeGranularity, naming, partition, out) => {
|
|
145
163
|
const source = (0, transform_1.loadModuleSource)(modulePath);
|
|
146
164
|
const warnings = [...source.warnings];
|
|
147
165
|
const rows = source.changes.map(change => ({
|
|
@@ -150,7 +168,7 @@ const transformModule = async (modulePath, granularity, naming, partition, out)
|
|
|
150
168
|
deps: change.dependencies,
|
|
151
169
|
content: change.deploy
|
|
152
170
|
}));
|
|
153
|
-
const restructured = await (0, transform_1.restructureExportRows)(rows, granularity, { naming });
|
|
171
|
+
const restructured = await (0, transform_1.restructureExportRows)(rows, granularity, { naming, changeGranularity });
|
|
154
172
|
warnings.push(...restructured.warnings.map(w => `restructure (${granularity}): ${w}`));
|
|
155
173
|
const outBase = (0, exports.resolveOutBase)(modulePath, out);
|
|
156
174
|
let packages;
|
|
@@ -189,6 +207,11 @@ exports.default = async (argv, prompter, _options) => {
|
|
|
189
207
|
await (0, inquirerer_1.cliExitWithError)(`Invalid --granularity "${granularityRaw}". Expected one of: ${transform_1.EXPORT_GRANULARITIES.join(', ')}.`);
|
|
190
208
|
}
|
|
191
209
|
const granularity = granularityRaw;
|
|
210
|
+
const changeGranularityRaw = argv['change-granularity'] ?? argv.changeGranularity ?? 'object';
|
|
211
|
+
if (!(0, transform_1.isChangeGranularity)(changeGranularityRaw)) {
|
|
212
|
+
await (0, inquirerer_1.cliExitWithError)(`Invalid --change-granularity "${changeGranularityRaw}". Expected one of: ${transform_1.CHANGE_GRANULARITIES.join(', ')}.`);
|
|
213
|
+
}
|
|
214
|
+
const changeGranularity = changeGranularityRaw;
|
|
192
215
|
const namingRaw = argv.naming ?? 'directory';
|
|
193
216
|
if (!NAMING_STYLES.includes(namingRaw)) {
|
|
194
217
|
await (0, inquirerer_1.cliExitWithError)(`Invalid --naming "${namingRaw}". Expected one of: ${NAMING_STYLES.join(', ')}.`);
|
|
@@ -208,6 +231,9 @@ exports.default = async (argv, prompter, _options) => {
|
|
|
208
231
|
const write = Boolean(argv.write);
|
|
209
232
|
const check = Boolean(argv.check);
|
|
210
233
|
const dryRun = Boolean(argv['dry-run'] ?? argv.dryRun);
|
|
234
|
+
const emit = (0, module_projections_1.parseEmitProjectionTargets)(argv, cwd);
|
|
235
|
+
const emitRequested = (0, module_projections_1.hasEmitProjection)(emit);
|
|
236
|
+
const sqlToStdout = emit.emitSql === module_projections_1.STDOUT_TARGET;
|
|
211
237
|
const pkg = new core_1.PgpmPackage(path.resolve(cwd));
|
|
212
238
|
let modulePaths;
|
|
213
239
|
if (pkg.isInModule()) {
|
|
@@ -227,10 +253,16 @@ exports.default = async (argv, prompter, _options) => {
|
|
|
227
253
|
await (0, inquirerer_1.cliExitWithError)('Not inside a pgpm module or workspace (pass --cwd <dir>).');
|
|
228
254
|
return;
|
|
229
255
|
}
|
|
256
|
+
if (emitRequested && modulePaths.length > 1) {
|
|
257
|
+
await (0, inquirerer_1.cliExitWithError)('--emit-sql/--emit-bundle target a single module; they are not supported when transforming a multi-module workspace.');
|
|
258
|
+
}
|
|
259
|
+
if (emitRequested && dryRun) {
|
|
260
|
+
await (0, inquirerer_1.cliExitWithError)('--emit-sql/--emit-bundle cannot be combined with --dry-run.');
|
|
261
|
+
}
|
|
230
262
|
for (const modulePath of modulePaths) {
|
|
231
263
|
let transformed;
|
|
232
264
|
try {
|
|
233
|
-
transformed = await transformModule(modulePath, granularity, naming, partition, out);
|
|
265
|
+
transformed = await transformModule(modulePath, granularity, changeGranularity, naming, partition, out);
|
|
234
266
|
}
|
|
235
267
|
catch (err) {
|
|
236
268
|
if (err instanceof transform_1.PartitionCycleError) {
|
|
@@ -253,10 +285,20 @@ exports.default = async (argv, prompter, _options) => {
|
|
|
253
285
|
await (0, inquirerer_1.cliExitWithError)(guard);
|
|
254
286
|
}
|
|
255
287
|
}
|
|
288
|
+
if (emitRequested && transformed.packages.length !== 1) {
|
|
289
|
+
await (0, inquirerer_1.cliExitWithError)('--emit-sql/--emit-bundle require a single output package; a --partition transform emits multiple packages.');
|
|
290
|
+
}
|
|
256
291
|
const sourceRequires = readControlRequires(modulePath, transformed.name);
|
|
292
|
+
let firstDir;
|
|
257
293
|
for (const pkgRows of transformed.packages) {
|
|
258
294
|
const dir = (0, emit_package_1.writePackage)(transformed.outBase, pkgRows, sourceRequires);
|
|
259
|
-
|
|
295
|
+
firstDir ??= dir;
|
|
296
|
+
if (!sqlToStdout) {
|
|
297
|
+
log.success(`wrote ${pkgRows.rows.length} changes to ${dir}`);
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
if (emitRequested && firstDir) {
|
|
301
|
+
await (0, module_projections_1.projectModule)(firstDir, emit, sqlToStdout ? undefined : msg => log.success(msg));
|
|
260
302
|
}
|
|
261
303
|
if (check) {
|
|
262
304
|
log.info('running --check: deploying original and transformed output to scratch databases...');
|
package/esm/commands/diff.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { PgpmMigrate } from '@pgpmjs/core';
|
|
2
2
|
import { deltaChangesToRows, loadDiffSideFromDisk, sqlToDiffChanges } from '@pgpmjs/diff';
|
|
3
3
|
import { Logger } from '@pgpmjs/logger';
|
|
4
|
-
import { diffChangeSets, EXPORT_GRANULARITIES, isExportGranularity, loadModule, withoutColumnOrder, writeModule } from '@pgpmjs/transform';
|
|
4
|
+
import { appendModule, CHANGE_GRANULARITIES, diffChangeSets, EXPORT_GRANULARITIES, isChangeGranularity, isExportGranularity, loadModule, withoutColumnOrder, writeModule } from '@pgpmjs/transform';
|
|
5
5
|
import { spawn } from 'child_process';
|
|
6
6
|
import * as fs from 'fs';
|
|
7
7
|
import { cliExitWithError, extractFirst } from 'inquirerer';
|
|
@@ -39,6 +39,10 @@ Options:
|
|
|
39
39
|
verify per change, spec-derived paths, graph-derived
|
|
40
40
|
requires) into <dir>/<pkg>
|
|
41
41
|
--emit-module <dir> Alias of --emit-migration
|
|
42
|
+
--append-module <dir> Append the delta into an EXISTING pgpm module at
|
|
43
|
+
<dir> (new changes only; existing changes, scripts,
|
|
44
|
+
and .control are left untouched). Standalone: not
|
|
45
|
+
combinable with --emit-*/--verify.
|
|
42
46
|
--emit-sql <file|-> Also project the delta to a single consolidated SQL
|
|
43
47
|
file (deparsed in plan order); - writes to stdout
|
|
44
48
|
--emit-bundle <file> Also project the delta to a content-addressed
|
|
@@ -46,6 +50,9 @@ Options:
|
|
|
46
50
|
--pkg <name> Emitted migration package name (default: diff-migration)
|
|
47
51
|
--granularity <level> Granularity for emitted changes: atomic | object |
|
|
48
52
|
consolidated (default: object)
|
|
53
|
+
--change-granularity <level>
|
|
54
|
+
Change-level distribution for emitted changes:
|
|
55
|
+
alteration | object | single (default: object)
|
|
49
56
|
--naming <style> Change path naming style: directory | flat (default: directory)
|
|
50
57
|
--json Machine-readable output
|
|
51
58
|
--verify Oracle mode: deploy A plus the emitted migration into
|
|
@@ -189,6 +196,11 @@ export default async (argv, prompter, _options) => {
|
|
|
189
196
|
await cliExitWithError(`Invalid --granularity "${granularityRaw}". Expected one of: ${EXPORT_GRANULARITIES.join(', ')}.`);
|
|
190
197
|
}
|
|
191
198
|
const granularity = granularityRaw;
|
|
199
|
+
const changeGranularityRaw = argv['change-granularity'] ?? argv.changeGranularity ?? 'object';
|
|
200
|
+
if (!isChangeGranularity(changeGranularityRaw)) {
|
|
201
|
+
await cliExitWithError(`Invalid --change-granularity "${changeGranularityRaw}". Expected one of: ${CHANGE_GRANULARITIES.join(', ')}.`);
|
|
202
|
+
}
|
|
203
|
+
const changeGranularity = changeGranularityRaw;
|
|
192
204
|
const namingRaw = argv.naming ?? 'directory';
|
|
193
205
|
if (!NAMING_STYLES.includes(namingRaw)) {
|
|
194
206
|
await cliExitWithError(`Invalid --naming "${namingRaw}". Expected one of: ${NAMING_STYLES.join(', ')}.`);
|
|
@@ -201,6 +213,10 @@ export default async (argv, prompter, _options) => {
|
|
|
201
213
|
const emitModuleDir = typeof emitModuleRaw === 'string' && emitModuleRaw
|
|
202
214
|
? path.resolve(cwd, emitModuleRaw)
|
|
203
215
|
: undefined;
|
|
216
|
+
const appendModuleRaw = argv['append-module'] ?? argv.appendModule;
|
|
217
|
+
const appendModuleDir = typeof appendModuleRaw === 'string' && appendModuleRaw
|
|
218
|
+
? path.resolve(cwd, appendModuleRaw)
|
|
219
|
+
: undefined;
|
|
204
220
|
const emitSqlRaw = argv['emit-sql'] ?? argv.emitSql;
|
|
205
221
|
const emitSql = typeof emitSqlRaw === 'string' && emitSqlRaw
|
|
206
222
|
? (emitSqlRaw === STDOUT_TARGET ? STDOUT_TARGET : path.resolve(cwd, emitSqlRaw))
|
|
@@ -211,6 +227,9 @@ export default async (argv, prompter, _options) => {
|
|
|
211
227
|
: undefined;
|
|
212
228
|
const sqlToStdout = emitSql === STDOUT_TARGET;
|
|
213
229
|
const pkgName = argv.pkg || 'diff-migration';
|
|
230
|
+
if (appendModuleDir && (emitModuleDir || emitSql || emitBundle || verify)) {
|
|
231
|
+
await cliExitWithError('--append-module is standalone; it cannot be combined with --emit-migration/--emit-module/--emit-sql/--emit-bundle/--verify.');
|
|
232
|
+
}
|
|
214
233
|
await loadModule();
|
|
215
234
|
let sideA;
|
|
216
235
|
let sideB;
|
|
@@ -222,7 +241,7 @@ export default async (argv, prompter, _options) => {
|
|
|
222
241
|
await cliExitWithError(err instanceof Error ? err.message : String(err));
|
|
223
242
|
return;
|
|
224
243
|
}
|
|
225
|
-
const result = diffChangeSets(sideA.changes, sideB.changes, { granularity, style: naming });
|
|
244
|
+
const result = diffChangeSets(sideA.changes, sideB.changes, { granularity, changeGranularity, style: naming });
|
|
226
245
|
const warnings = [
|
|
227
246
|
...sideA.warnings.map(w => `${sideA.label}: ${w}`),
|
|
228
247
|
...sideB.warnings.map(w => `${sideB.label}: ${w}`),
|
|
@@ -249,6 +268,21 @@ export default async (argv, prompter, _options) => {
|
|
|
249
268
|
if (!sqlToStdout)
|
|
250
269
|
printSummary(result, sideA.label, sideB.label);
|
|
251
270
|
}
|
|
271
|
+
if (appendModuleDir) {
|
|
272
|
+
const rows = deltaChangesToRows(result.changes);
|
|
273
|
+
if (rows.length === 0) {
|
|
274
|
+
log.info('no migration changes to append (sides are identical).');
|
|
275
|
+
}
|
|
276
|
+
else {
|
|
277
|
+
const appended = appendModule(appendModuleDir, rows);
|
|
278
|
+
for (const w of appended.warnings)
|
|
279
|
+
console.warn(`diff: ${w}`);
|
|
280
|
+
log.success(`appended ${appended.added.length} change(s) to ${appended.dir}` +
|
|
281
|
+
(appended.skipped.length ? ` (${appended.skipped.length} skipped)` : ''));
|
|
282
|
+
}
|
|
283
|
+
prompter.close();
|
|
284
|
+
return argv;
|
|
285
|
+
}
|
|
252
286
|
let migrationDir;
|
|
253
287
|
const needModule = Boolean(emitModuleDir || emitSql || emitBundle) || (verify && !result.identical);
|
|
254
288
|
if (needModule) {
|
package/esm/commands/import.js
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import { dumpCompatibilityWarnings, importDumpRows, linkTextualDeps, loadDumpSource } from '@pgpmjs/import';
|
|
2
2
|
import { Logger } from '@pgpmjs/logger';
|
|
3
|
-
import { EXPORT_GRANULARITIES, isExportGranularity, parsePartitionConfig, PartitionCycleError, partitionExportRows } from '@pgpmjs/transform';
|
|
3
|
+
import { CHANGE_GRANULARITIES, EXPORT_GRANULARITIES, isChangeGranularity, isExportGranularity, parsePartitionConfig, PartitionCycleError, partitionExportRows } from '@pgpmjs/transform';
|
|
4
4
|
import { cliExitWithError } from 'inquirerer';
|
|
5
5
|
import * as path from 'path';
|
|
6
6
|
import { checkOverwrite, writePackage } from '../utils/emit-package';
|
|
7
|
+
import { hasEmitProjection, parseEmitProjectionTargets, projectModule, STDOUT_TARGET } from '../utils/module-projections';
|
|
7
8
|
const log = new Logger('import');
|
|
8
9
|
const importUsageText = `
|
|
9
10
|
Import Command:
|
|
@@ -35,6 +36,9 @@ Options:
|
|
|
35
36
|
--pkg <name> Module name for the generated package (required)
|
|
36
37
|
--granularity <level> Granularity dial: atomic | object | consolidated
|
|
37
38
|
(default: object)
|
|
39
|
+
--change-granularity <level>
|
|
40
|
+
Change-level distribution: alteration | object | single
|
|
41
|
+
(default: object)
|
|
38
42
|
--naming <style> Change path naming style: directory | flat (default: directory)
|
|
39
43
|
--out <dir> Output base directory (default: current directory);
|
|
40
44
|
the module is written to <out>/<pkg>
|
|
@@ -43,14 +47,24 @@ Options:
|
|
|
43
47
|
splitting the import into multiple pgpm packages with
|
|
44
48
|
derived cross-package requires
|
|
45
49
|
--write Allow overwriting an existing module directory
|
|
50
|
+
--emit-sql <file|-> Also project the imported module into a single linear
|
|
51
|
+
SQL script (- for stdout). Requires a single output
|
|
52
|
+
package (not supported with --partition).
|
|
53
|
+
--emit-bundle <file> Also project the imported module into a
|
|
54
|
+
content-addressed .bundle.tar.gz. Same single-package
|
|
55
|
+
requirement as --emit-sql.
|
|
46
56
|
--cwd <directory> Working directory (default: current directory)
|
|
47
57
|
--dry-run Print the resulting plan/paths without writing
|
|
48
58
|
|
|
59
|
+
The imported module is the canonical artifact; --emit-sql and --emit-bundle are
|
|
60
|
+
pure projections of it (the same machinery pgpm package and pgpm diff use).
|
|
61
|
+
|
|
49
62
|
Examples:
|
|
50
63
|
pgpm import dump.sql --pkg my-app
|
|
51
64
|
pgpm import dump.sql --pkg my-app --granularity consolidated --naming flat
|
|
52
65
|
pgpm import ./sql-files --pkg my-app --out ./packages --with-data
|
|
53
66
|
pgpm import dump.sql --pkg my-app --partition partition.json
|
|
67
|
+
pgpm import dump.sql --pkg my-app --emit-sql my-app.sql
|
|
54
68
|
`;
|
|
55
69
|
const NAMING_STYLES = ['directory', 'flat'];
|
|
56
70
|
export default async (argv, prompter, _options) => {
|
|
@@ -72,6 +86,11 @@ export default async (argv, prompter, _options) => {
|
|
|
72
86
|
await cliExitWithError(`Invalid --granularity "${granularityRaw}". Expected one of: ${EXPORT_GRANULARITIES.join(', ')}.`);
|
|
73
87
|
}
|
|
74
88
|
const granularity = granularityRaw;
|
|
89
|
+
const changeGranularityRaw = argv['change-granularity'] ?? argv.changeGranularity ?? 'object';
|
|
90
|
+
if (!isChangeGranularity(changeGranularityRaw)) {
|
|
91
|
+
await cliExitWithError(`Invalid --change-granularity "${changeGranularityRaw}". Expected one of: ${CHANGE_GRANULARITIES.join(', ')}.`);
|
|
92
|
+
}
|
|
93
|
+
const changeGranularity = changeGranularityRaw;
|
|
75
94
|
const namingRaw = argv.naming ?? 'directory';
|
|
76
95
|
if (!NAMING_STYLES.includes(namingRaw)) {
|
|
77
96
|
await cliExitWithError(`Invalid --naming "${namingRaw}". Expected one of: ${NAMING_STYLES.join(', ')}.`);
|
|
@@ -90,6 +109,12 @@ export default async (argv, prompter, _options) => {
|
|
|
90
109
|
const withData = Boolean(argv['with-data'] ?? argv.withData);
|
|
91
110
|
const write = Boolean(argv.write);
|
|
92
111
|
const dryRun = Boolean(argv['dry-run'] ?? argv.dryRun);
|
|
112
|
+
const emit = parseEmitProjectionTargets(argv, cwd);
|
|
113
|
+
const emitRequested = hasEmitProjection(emit);
|
|
114
|
+
const sqlToStdout = emit.emitSql === STDOUT_TARGET;
|
|
115
|
+
if (emitRequested && dryRun) {
|
|
116
|
+
await cliExitWithError('--emit-sql/--emit-bundle cannot be combined with --dry-run.');
|
|
117
|
+
}
|
|
93
118
|
let source;
|
|
94
119
|
try {
|
|
95
120
|
source = loadDumpSource(path.resolve(cwd, input));
|
|
@@ -98,13 +123,13 @@ export default async (argv, prompter, _options) => {
|
|
|
98
123
|
await cliExitWithError(err instanceof Error ? err.message : String(err));
|
|
99
124
|
return;
|
|
100
125
|
}
|
|
101
|
-
if (source.files.length > 1) {
|
|
126
|
+
if (source.files.length > 1 && !sqlToStdout) {
|
|
102
127
|
log.info(`concatenated ${source.files.length} .sql files in sorted order`);
|
|
103
128
|
}
|
|
104
129
|
for (const warning of dumpCompatibilityWarnings(source)) {
|
|
105
130
|
console.warn(`\nWARNING: ${warning}\n`);
|
|
106
131
|
}
|
|
107
|
-
const result = await importDumpRows(source, { granularity, naming, withData });
|
|
132
|
+
const result = await importDumpRows(source, { granularity, changeGranularity, naming, withData });
|
|
108
133
|
let packages;
|
|
109
134
|
if (partition) {
|
|
110
135
|
try {
|
|
@@ -143,21 +168,33 @@ export default async (argv, prompter, _options) => {
|
|
|
143
168
|
}
|
|
144
169
|
}
|
|
145
170
|
else {
|
|
171
|
+
if (emitRequested && packages.length !== 1) {
|
|
172
|
+
await cliExitWithError('--emit-sql/--emit-bundle require a single output package; a --partition import emits multiple packages.');
|
|
173
|
+
}
|
|
146
174
|
for (const pkg of packages) {
|
|
147
175
|
const guard = checkOverwrite(path.join(outBase, pkg.name), source.files[0], write);
|
|
148
176
|
if (guard) {
|
|
149
177
|
await cliExitWithError(guard);
|
|
150
178
|
}
|
|
151
179
|
}
|
|
180
|
+
let firstDir;
|
|
152
181
|
for (const pkg of packages) {
|
|
153
182
|
const dir = writePackage(outBase, pkg);
|
|
154
|
-
|
|
183
|
+
firstDir ??= dir;
|
|
184
|
+
if (!sqlToStdout) {
|
|
185
|
+
log.success(`wrote ${pkg.rows.length} changes to ${dir}`);
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
if (emitRequested && firstDir) {
|
|
189
|
+
await projectModule(firstDir, emit, sqlToStdout ? undefined : msg => log.success(msg));
|
|
155
190
|
}
|
|
156
191
|
}
|
|
157
192
|
const { summary } = result;
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
193
|
+
if (!sqlToStdout) {
|
|
194
|
+
log.success(`import: ${summary.statements} statement(s) -> ${summary.changes} change(s), ` +
|
|
195
|
+
`${summary.skippedPreamble} preamble skipped, ${summary.skippedData} data skipped, ` +
|
|
196
|
+
`${summary.misc} in misc, ${result.warnings.length} warning(s)`);
|
|
197
|
+
}
|
|
161
198
|
prompter.close();
|
|
162
199
|
return argv;
|
|
163
200
|
};
|
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
import { PgpmMigrate, PgpmPackage } from '@pgpmjs/core';
|
|
2
2
|
import { Logger } from '@pgpmjs/logger';
|
|
3
|
-
import { EXPORT_GRANULARITIES, isExportGranularity, loadModuleSource, parsePartitionConfig, PartitionCycleError, partitionExportRows, restructureExportRows } from '@pgpmjs/transform';
|
|
3
|
+
import { CHANGE_GRANULARITIES, EXPORT_GRANULARITIES, isChangeGranularity, isExportGranularity, loadModuleSource, parsePartitionConfig, PartitionCycleError, partitionExportRows, restructureExportRows } from '@pgpmjs/transform';
|
|
4
4
|
import * as fs from 'fs';
|
|
5
5
|
import { cliExitWithError } from 'inquirerer';
|
|
6
6
|
import * as path from 'path';
|
|
7
7
|
import { getPgEnvOptions } from 'pg-env';
|
|
8
8
|
import { checkOverwrite, writePackage } from '../utils/emit-package';
|
|
9
|
+
import { hasEmitProjection, parseEmitProjectionTargets, projectModule, STDOUT_TARGET } from '../utils/module-projections';
|
|
9
10
|
import { catalogDifferences, withScratchDatabases } from '../utils/scratch-db';
|
|
10
11
|
export { checkOverwrite } from '../utils/emit-package';
|
|
11
12
|
const log = new Logger('transform');
|
|
@@ -22,6 +23,12 @@ Transform Command:
|
|
|
22
23
|
Options:
|
|
23
24
|
--help, -h Show this help message
|
|
24
25
|
--granularity <level> Target granularity: atomic | object | consolidated (required)
|
|
26
|
+
--change-granularity <level>
|
|
27
|
+
Change-level distribution: alteration | object | single
|
|
28
|
+
(default: object). With alteration, every ADD COLUMN /
|
|
29
|
+
ADD CONSTRAINT becomes its own change with its own
|
|
30
|
+
deploy/revert/verify and requires. With single, the
|
|
31
|
+
whole module becomes one change.
|
|
25
32
|
--partition <file> Partition config (JSON: rules/defaultPackage/splitRiders)
|
|
26
33
|
splitting the module into multiple pgpm packages with
|
|
27
34
|
derived cross-package requires.
|
|
@@ -29,14 +36,25 @@ Options:
|
|
|
29
36
|
--cwd <directory> Module or workspace directory (default: current directory)
|
|
30
37
|
--out <dir> Output directory (default: sibling <module>-<granularity>)
|
|
31
38
|
--write Allow writing over an existing/module directory
|
|
39
|
+
--emit-sql <file|-> Also project the transformed module into a single
|
|
40
|
+
linear SQL script (- for stdout). Requires a single
|
|
41
|
+
output package (no workspace/partition fan-out).
|
|
42
|
+
--emit-bundle <file> Also project the transformed module into a
|
|
43
|
+
content-addressed .bundle.tar.gz. Same single-package
|
|
44
|
+
requirement as --emit-sql.
|
|
32
45
|
--check Deploy original and transformed output into scratch
|
|
33
46
|
databases and assert the catalogs are equivalent
|
|
34
47
|
--dry-run Print the resulting plan/paths without writing
|
|
35
48
|
|
|
49
|
+
The transformed module is the canonical artifact; --emit-sql and --emit-bundle
|
|
50
|
+
are pure projections of it (the same machinery pgpm package and pgpm diff use).
|
|
51
|
+
|
|
36
52
|
Examples:
|
|
37
53
|
pgpm transform --granularity object
|
|
38
54
|
pgpm transform --granularity atomic --naming flat --out ./out
|
|
39
55
|
pgpm transform --granularity object --partition partition.json --check
|
|
56
|
+
pgpm transform --granularity consolidated --emit-sql migration.sql
|
|
57
|
+
pgpm transform --granularity atomic --change-granularity alteration
|
|
40
58
|
`;
|
|
41
59
|
const NAMING_STYLES = ['directory', 'flat'];
|
|
42
60
|
/**
|
|
@@ -102,7 +120,7 @@ const runCheck = async (transformed) => {
|
|
|
102
120
|
return catalogDifferences(cfgOriginal, cfgTransformed);
|
|
103
121
|
});
|
|
104
122
|
};
|
|
105
|
-
const transformModule = async (modulePath, granularity, naming, partition, out) => {
|
|
123
|
+
const transformModule = async (modulePath, granularity, changeGranularity, naming, partition, out) => {
|
|
106
124
|
const source = loadModuleSource(modulePath);
|
|
107
125
|
const warnings = [...source.warnings];
|
|
108
126
|
const rows = source.changes.map(change => ({
|
|
@@ -111,7 +129,7 @@ const transformModule = async (modulePath, granularity, naming, partition, out)
|
|
|
111
129
|
deps: change.dependencies,
|
|
112
130
|
content: change.deploy
|
|
113
131
|
}));
|
|
114
|
-
const restructured = await restructureExportRows(rows, granularity, { naming });
|
|
132
|
+
const restructured = await restructureExportRows(rows, granularity, { naming, changeGranularity });
|
|
115
133
|
warnings.push(...restructured.warnings.map(w => `restructure (${granularity}): ${w}`));
|
|
116
134
|
const outBase = resolveOutBase(modulePath, out);
|
|
117
135
|
let packages;
|
|
@@ -150,6 +168,11 @@ export default async (argv, prompter, _options) => {
|
|
|
150
168
|
await cliExitWithError(`Invalid --granularity "${granularityRaw}". Expected one of: ${EXPORT_GRANULARITIES.join(', ')}.`);
|
|
151
169
|
}
|
|
152
170
|
const granularity = granularityRaw;
|
|
171
|
+
const changeGranularityRaw = argv['change-granularity'] ?? argv.changeGranularity ?? 'object';
|
|
172
|
+
if (!isChangeGranularity(changeGranularityRaw)) {
|
|
173
|
+
await cliExitWithError(`Invalid --change-granularity "${changeGranularityRaw}". Expected one of: ${CHANGE_GRANULARITIES.join(', ')}.`);
|
|
174
|
+
}
|
|
175
|
+
const changeGranularity = changeGranularityRaw;
|
|
153
176
|
const namingRaw = argv.naming ?? 'directory';
|
|
154
177
|
if (!NAMING_STYLES.includes(namingRaw)) {
|
|
155
178
|
await cliExitWithError(`Invalid --naming "${namingRaw}". Expected one of: ${NAMING_STYLES.join(', ')}.`);
|
|
@@ -169,6 +192,9 @@ export default async (argv, prompter, _options) => {
|
|
|
169
192
|
const write = Boolean(argv.write);
|
|
170
193
|
const check = Boolean(argv.check);
|
|
171
194
|
const dryRun = Boolean(argv['dry-run'] ?? argv.dryRun);
|
|
195
|
+
const emit = parseEmitProjectionTargets(argv, cwd);
|
|
196
|
+
const emitRequested = hasEmitProjection(emit);
|
|
197
|
+
const sqlToStdout = emit.emitSql === STDOUT_TARGET;
|
|
172
198
|
const pkg = new PgpmPackage(path.resolve(cwd));
|
|
173
199
|
let modulePaths;
|
|
174
200
|
if (pkg.isInModule()) {
|
|
@@ -188,10 +214,16 @@ export default async (argv, prompter, _options) => {
|
|
|
188
214
|
await cliExitWithError('Not inside a pgpm module or workspace (pass --cwd <dir>).');
|
|
189
215
|
return;
|
|
190
216
|
}
|
|
217
|
+
if (emitRequested && modulePaths.length > 1) {
|
|
218
|
+
await cliExitWithError('--emit-sql/--emit-bundle target a single module; they are not supported when transforming a multi-module workspace.');
|
|
219
|
+
}
|
|
220
|
+
if (emitRequested && dryRun) {
|
|
221
|
+
await cliExitWithError('--emit-sql/--emit-bundle cannot be combined with --dry-run.');
|
|
222
|
+
}
|
|
191
223
|
for (const modulePath of modulePaths) {
|
|
192
224
|
let transformed;
|
|
193
225
|
try {
|
|
194
|
-
transformed = await transformModule(modulePath, granularity, naming, partition, out);
|
|
226
|
+
transformed = await transformModule(modulePath, granularity, changeGranularity, naming, partition, out);
|
|
195
227
|
}
|
|
196
228
|
catch (err) {
|
|
197
229
|
if (err instanceof PartitionCycleError) {
|
|
@@ -214,10 +246,20 @@ export default async (argv, prompter, _options) => {
|
|
|
214
246
|
await cliExitWithError(guard);
|
|
215
247
|
}
|
|
216
248
|
}
|
|
249
|
+
if (emitRequested && transformed.packages.length !== 1) {
|
|
250
|
+
await cliExitWithError('--emit-sql/--emit-bundle require a single output package; a --partition transform emits multiple packages.');
|
|
251
|
+
}
|
|
217
252
|
const sourceRequires = readControlRequires(modulePath, transformed.name);
|
|
253
|
+
let firstDir;
|
|
218
254
|
for (const pkgRows of transformed.packages) {
|
|
219
255
|
const dir = writePackage(transformed.outBase, pkgRows, sourceRequires);
|
|
220
|
-
|
|
256
|
+
firstDir ??= dir;
|
|
257
|
+
if (!sqlToStdout) {
|
|
258
|
+
log.success(`wrote ${pkgRows.rows.length} changes to ${dir}`);
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
if (emitRequested && firstDir) {
|
|
262
|
+
await projectModule(firstDir, emit, sqlToStdout ? undefined : msg => log.success(msg));
|
|
221
263
|
}
|
|
222
264
|
if (check) {
|
|
223
265
|
log.info('running --check: deploying original and transformed output to scratch databases...');
|
|
@@ -45,3 +45,38 @@ export const emitModuleBundle = async (moduleDir, target) => {
|
|
|
45
45
|
fs.mkdirSync(path.dirname(resolved), { recursive: true });
|
|
46
46
|
writeBundleArchiveFile(bundle, resolved);
|
|
47
47
|
};
|
|
48
|
+
/**
|
|
49
|
+
* Parse the shared `--emit-sql` / `--emit-bundle` projection flags off a parsed
|
|
50
|
+
* argv, resolving file targets against `cwd` (the `-` stdout sentinel for SQL is
|
|
51
|
+
* preserved). Keeps every command's projection flags identical.
|
|
52
|
+
*/
|
|
53
|
+
export const parseEmitProjectionTargets = (argv, cwd) => {
|
|
54
|
+
const sqlRaw = argv['emit-sql'] ?? argv.emitSql;
|
|
55
|
+
const emitSql = typeof sqlRaw === 'string' && sqlRaw
|
|
56
|
+
? sqlRaw === STDOUT_TARGET
|
|
57
|
+
? STDOUT_TARGET
|
|
58
|
+
: path.resolve(cwd, sqlRaw)
|
|
59
|
+
: undefined;
|
|
60
|
+
const bundleRaw = argv['emit-bundle'] ?? argv.emitBundle;
|
|
61
|
+
const emitBundle = typeof bundleRaw === 'string' && bundleRaw ? path.resolve(cwd, bundleRaw) : undefined;
|
|
62
|
+
return { emitSql, emitBundle };
|
|
63
|
+
};
|
|
64
|
+
/** Whether any projection target was requested. */
|
|
65
|
+
export const hasEmitProjection = (targets) => Boolean(targets.emitSql || targets.emitBundle);
|
|
66
|
+
/**
|
|
67
|
+
* Run the requested SQL/bundle projections against a written module directory.
|
|
68
|
+
* `onSuccess` (when provided) is invoked with a human-readable line per emitted
|
|
69
|
+
* artifact; it is skipped for stdout SQL so the stream stays valid SQL.
|
|
70
|
+
*/
|
|
71
|
+
export const projectModule = async (moduleDir, targets, onSuccess) => {
|
|
72
|
+
if (targets.emitSql) {
|
|
73
|
+
await emitModuleSql(moduleDir, targets.emitSql);
|
|
74
|
+
if (targets.emitSql !== STDOUT_TARGET) {
|
|
75
|
+
onSuccess?.(`wrote linear SQL to ${targets.emitSql}`);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
if (targets.emitBundle) {
|
|
79
|
+
await emitModuleBundle(moduleDir, targets.emitBundle);
|
|
80
|
+
onSuccess?.(`wrote bundle to ${targets.emitBundle}`);
|
|
81
|
+
}
|
|
82
|
+
};
|
package/esm/utils/scratch-db.js
CHANGED
|
@@ -13,8 +13,8 @@
|
|
|
13
13
|
* `PgpmMigrate` from `@pgpmjs/core`, and `core` already depends on `transform`
|
|
14
14
|
* — hosting the oracle in `transform` would close that cycle.
|
|
15
15
|
*/
|
|
16
|
-
import { diffCatalogSnapshots, snapshotCatalog } from '@pgpmjs/transform';
|
|
17
16
|
import { Logger } from '@pgpmjs/logger';
|
|
17
|
+
import { diffCatalogSnapshots, snapshotCatalog } from '@pgpmjs/transform';
|
|
18
18
|
import { getPgPool } from 'pg-cache';
|
|
19
19
|
const log = new Logger('scratch-db');
|
|
20
20
|
const createScratchDb = async (config, dbName) => {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pgpm",
|
|
3
|
-
"version": "5.
|
|
3
|
+
"version": "5.21.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,14 +46,14 @@
|
|
|
46
46
|
},
|
|
47
47
|
"dependencies": {
|
|
48
48
|
"@inquirerer/utils": "^3.3.9",
|
|
49
|
-
"@pgpmjs/core": "^7.21.
|
|
50
|
-
"@pgpmjs/diff": "^0.1.
|
|
49
|
+
"@pgpmjs/core": "^7.21.4",
|
|
50
|
+
"@pgpmjs/diff": "^0.1.2",
|
|
51
51
|
"@pgpmjs/env": "^2.40.3",
|
|
52
|
-
"@pgpmjs/export": "^1.15.
|
|
53
|
-
"@pgpmjs/import": "^0.
|
|
52
|
+
"@pgpmjs/export": "^1.15.1",
|
|
53
|
+
"@pgpmjs/import": "^0.2.0",
|
|
54
54
|
"@pgpmjs/logger": "^2.23.3",
|
|
55
|
-
"@pgpmjs/slice": "^0.13.
|
|
56
|
-
"@pgpmjs/transform": "^0.
|
|
55
|
+
"@pgpmjs/slice": "^0.13.9",
|
|
56
|
+
"@pgpmjs/transform": "^0.20.0",
|
|
57
57
|
"@pgpmjs/types": "^2.49.3",
|
|
58
58
|
"@pgsql/quotes": "^18.2.0",
|
|
59
59
|
"appstash": "^0.7.0",
|
|
@@ -80,5 +80,5 @@
|
|
|
80
80
|
"pg",
|
|
81
81
|
"pgsql"
|
|
82
82
|
],
|
|
83
|
-
"gitHead": "
|
|
83
|
+
"gitHead": "e6a7a57f88c9deb2a4acecaba2451320d1be8203"
|
|
84
84
|
}
|
|
@@ -13,3 +13,24 @@ export declare const emitModuleSql: (moduleDir: string, target: string) => Promi
|
|
|
13
13
|
* stores beside the packaged SQL, but written to an arbitrary path.
|
|
14
14
|
*/
|
|
15
15
|
export declare const emitModuleBundle: (moduleDir: string, target: string) => Promise<void>;
|
|
16
|
+
/** Resolved `--emit-sql` / `--emit-bundle` projection targets for a command. */
|
|
17
|
+
export interface EmitProjectionTargets {
|
|
18
|
+
/** Linear SQL target (absolute path, or `-` for stdout). */
|
|
19
|
+
emitSql?: string;
|
|
20
|
+
/** Bundle archive target (absolute path). */
|
|
21
|
+
emitBundle?: string;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Parse the shared `--emit-sql` / `--emit-bundle` projection flags off a parsed
|
|
25
|
+
* argv, resolving file targets against `cwd` (the `-` stdout sentinel for SQL is
|
|
26
|
+
* preserved). Keeps every command's projection flags identical.
|
|
27
|
+
*/
|
|
28
|
+
export declare const parseEmitProjectionTargets: (argv: Record<string, unknown>, cwd: string) => EmitProjectionTargets;
|
|
29
|
+
/** Whether any projection target was requested. */
|
|
30
|
+
export declare const hasEmitProjection: (targets: EmitProjectionTargets) => boolean;
|
|
31
|
+
/**
|
|
32
|
+
* Run the requested SQL/bundle projections against a written module directory.
|
|
33
|
+
* `onSuccess` (when provided) is invoked with a human-readable line per emitted
|
|
34
|
+
* artifact; it is skipped for stdout SQL so the stream stays valid SQL.
|
|
35
|
+
*/
|
|
36
|
+
export declare const projectModule: (moduleDir: string, targets: EmitProjectionTargets, onSuccess?: (message: string) => void) => Promise<void>;
|
|
@@ -33,7 +33,7 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
33
33
|
};
|
|
34
34
|
})();
|
|
35
35
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
-
exports.emitModuleBundle = exports.emitModuleSql = exports.STDOUT_TARGET = void 0;
|
|
36
|
+
exports.projectModule = exports.hasEmitProjection = exports.parseEmitProjectionTargets = exports.emitModuleBundle = exports.emitModuleSql = exports.STDOUT_TARGET = void 0;
|
|
37
37
|
/**
|
|
38
38
|
* Projections of an emitted pgpm module directory.
|
|
39
39
|
*
|
|
@@ -83,3 +83,41 @@ const emitModuleBundle = async (moduleDir, target) => {
|
|
|
83
83
|
(0, core_1.writeBundleArchiveFile)(bundle, resolved);
|
|
84
84
|
};
|
|
85
85
|
exports.emitModuleBundle = emitModuleBundle;
|
|
86
|
+
/**
|
|
87
|
+
* Parse the shared `--emit-sql` / `--emit-bundle` projection flags off a parsed
|
|
88
|
+
* argv, resolving file targets against `cwd` (the `-` stdout sentinel for SQL is
|
|
89
|
+
* preserved). Keeps every command's projection flags identical.
|
|
90
|
+
*/
|
|
91
|
+
const parseEmitProjectionTargets = (argv, cwd) => {
|
|
92
|
+
const sqlRaw = argv['emit-sql'] ?? argv.emitSql;
|
|
93
|
+
const emitSql = typeof sqlRaw === 'string' && sqlRaw
|
|
94
|
+
? sqlRaw === exports.STDOUT_TARGET
|
|
95
|
+
? exports.STDOUT_TARGET
|
|
96
|
+
: path.resolve(cwd, sqlRaw)
|
|
97
|
+
: undefined;
|
|
98
|
+
const bundleRaw = argv['emit-bundle'] ?? argv.emitBundle;
|
|
99
|
+
const emitBundle = typeof bundleRaw === 'string' && bundleRaw ? path.resolve(cwd, bundleRaw) : undefined;
|
|
100
|
+
return { emitSql, emitBundle };
|
|
101
|
+
};
|
|
102
|
+
exports.parseEmitProjectionTargets = parseEmitProjectionTargets;
|
|
103
|
+
/** Whether any projection target was requested. */
|
|
104
|
+
const hasEmitProjection = (targets) => Boolean(targets.emitSql || targets.emitBundle);
|
|
105
|
+
exports.hasEmitProjection = hasEmitProjection;
|
|
106
|
+
/**
|
|
107
|
+
* Run the requested SQL/bundle projections against a written module directory.
|
|
108
|
+
* `onSuccess` (when provided) is invoked with a human-readable line per emitted
|
|
109
|
+
* artifact; it is skipped for stdout SQL so the stream stays valid SQL.
|
|
110
|
+
*/
|
|
111
|
+
const projectModule = async (moduleDir, targets, onSuccess) => {
|
|
112
|
+
if (targets.emitSql) {
|
|
113
|
+
await (0, exports.emitModuleSql)(moduleDir, targets.emitSql);
|
|
114
|
+
if (targets.emitSql !== exports.STDOUT_TARGET) {
|
|
115
|
+
onSuccess?.(`wrote linear SQL to ${targets.emitSql}`);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
if (targets.emitBundle) {
|
|
119
|
+
await (0, exports.emitModuleBundle)(moduleDir, targets.emitBundle);
|
|
120
|
+
onSuccess?.(`wrote bundle to ${targets.emitBundle}`);
|
|
121
|
+
}
|
|
122
|
+
};
|
|
123
|
+
exports.projectModule = projectModule;
|
package/utils/scratch-db.d.ts
CHANGED
|
@@ -1,18 +1,3 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Scratch-database catalog oracle shared by the dials commands that prove
|
|
3
|
-
* structural equivalence (`pgpm transform --check`, `pgpm diff --verify`).
|
|
4
|
-
*
|
|
5
|
-
* Both commands do the same dance: create throwaway databases, deploy a
|
|
6
|
-
* schema into each, snapshot the catalogs, and diff them — always dropping the
|
|
7
|
-
* scratch databases afterwards even on failure. This centralizes the lifecycle
|
|
8
|
-
* (`withScratchDatabases`) and the snapshot/compare step (`catalogDifferences`)
|
|
9
|
-
* so each command only supplies what is genuinely command-specific: how it
|
|
10
|
-
* deploys a side.
|
|
11
|
-
*
|
|
12
|
-
* It lives in the CLI (not `@pgpmjs/transform`) because deploying touches
|
|
13
|
-
* `PgpmMigrate` from `@pgpmjs/core`, and `core` already depends on `transform`
|
|
14
|
-
* — hosting the oracle in `transform` would close that cycle.
|
|
15
|
-
*/
|
|
16
1
|
import { CatalogSnapshot } from '@pgpmjs/transform';
|
|
17
2
|
import type { PgConfig } from 'pg-env';
|
|
18
3
|
/**
|
package/utils/scratch-db.js
CHANGED
|
@@ -16,8 +16,8 @@ exports.catalogDifferences = exports.withScratchDatabases = void 0;
|
|
|
16
16
|
* `PgpmMigrate` from `@pgpmjs/core`, and `core` already depends on `transform`
|
|
17
17
|
* — hosting the oracle in `transform` would close that cycle.
|
|
18
18
|
*/
|
|
19
|
-
const transform_1 = require("@pgpmjs/transform");
|
|
20
19
|
const logger_1 = require("@pgpmjs/logger");
|
|
20
|
+
const transform_1 = require("@pgpmjs/transform");
|
|
21
21
|
const pg_cache_1 = require("pg-cache");
|
|
22
22
|
const log = new logger_1.Logger('scratch-db');
|
|
23
23
|
const createScratchDb = async (config, dbName) => {
|