pgpm 5.19.1 → 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.
Files changed (46) hide show
  1. package/commands/cache.js +1 -1
  2. package/commands/diff.d.ts +3 -0
  3. package/commands/diff.js +359 -0
  4. package/commands/dump.js +3 -3
  5. package/commands/export.js +24 -5
  6. package/commands/import.d.ts +3 -0
  7. package/commands/import.js +235 -0
  8. package/commands/init/boilerplate.js +1 -1
  9. package/commands/init/index.js +3 -3
  10. package/commands/package.js +0 -1
  11. package/commands/slice.js +1 -1
  12. package/commands/test-packages.js +1 -1
  13. package/commands/transform.d.ts +13 -0
  14. package/commands/transform.js +317 -0
  15. package/commands/update.js +1 -1
  16. package/commands.js +7 -0
  17. package/esm/commands/cache.js +1 -1
  18. package/esm/commands/diff.js +324 -0
  19. package/esm/commands/dump.js +3 -3
  20. package/esm/commands/export.js +22 -3
  21. package/esm/commands/import.js +200 -0
  22. package/esm/commands/init/boilerplate.js +1 -1
  23. package/esm/commands/init/index.js +3 -3
  24. package/esm/commands/package.js +0 -1
  25. package/esm/commands/slice.js +1 -1
  26. package/esm/commands/test-packages.js +1 -1
  27. package/esm/commands/transform.js +278 -0
  28. package/esm/commands/update.js +1 -1
  29. package/esm/commands.js +7 -0
  30. package/esm/utils/display.js +3 -0
  31. package/esm/utils/driver.js +1 -1
  32. package/esm/utils/emit-package.js +1 -0
  33. package/esm/utils/engine.js +1 -1
  34. package/esm/utils/module-projections.js +82 -0
  35. package/esm/utils/scratch-db.js +61 -0
  36. package/package.json +14 -12
  37. package/utils/display.d.ts +1 -1
  38. package/utils/display.js +3 -0
  39. package/utils/driver.js +1 -1
  40. package/utils/emit-package.d.ts +9 -0
  41. package/utils/emit-package.js +7 -0
  42. package/utils/engine.js +1 -1
  43. package/utils/module-projections.d.ts +36 -0
  44. package/utils/module-projections.js +123 -0
  45. package/utils/scratch-db.d.ts +16 -0
  46. package/utils/scratch-db.js +66 -0
@@ -0,0 +1,235 @@
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 module_projections_1 = require("../utils/module-projections");
43
+ const log = new logger_1.Logger('import');
44
+ const importUsageText = `
45
+ Import Command:
46
+
47
+ pgpm import <dump.sql | dir-of-sql-files> --pkg <module-name> [OPTIONS]
48
+
49
+ pgpm-itize an arbitrary SQL dump (e.g. pg_dump --schema-only output): parse
50
+ it, classify every statement, and emit a complete deployable pgpm module —
51
+ module dir, pgpm.plan, and deploy/revert/verify trees with spec-derived
52
+ change paths, graph-derived requires, and generated revert/verify scripts.
53
+
54
+ Directory inputs concatenate their .sql files in sorted (lexicographic)
55
+ filename order.
56
+
57
+ Dump handling:
58
+ - pg_dump preamble noise (SET ..., SELECT set_config(...), psql backslash
59
+ commands) is skipped; COMMENT ON and GRANT statements are kept, attached
60
+ to their host object's change.
61
+ - CREATE EXTENSION statements become .control requires (pgpm owns
62
+ extension creation at deploy time).
63
+ - COPY ... FROM stdin blocks and INSERTs are skipped with a warning unless
64
+ --with-data is passed, which emits them as seed fixture changes (COPY
65
+ data is converted to INSERTs).
66
+ - Statements that classify to no object land in a misc/statements change
67
+ with a warning — never dropped silently.
68
+
69
+ Options:
70
+ --help, -h Show this help message
71
+ --pkg <name> Module name for the generated package (required)
72
+ --granularity <level> Granularity dial: atomic | object | consolidated
73
+ (default: object)
74
+ --change-granularity <level>
75
+ Change-level distribution: alteration | object | single
76
+ (default: object)
77
+ --naming <style> Change path naming style: directory | flat (default: directory)
78
+ --out <dir> Output base directory (default: current directory);
79
+ the module is written to <out>/<pkg>
80
+ --with-data Import COPY/INSERT data as seed fixture changes
81
+ --partition <file> Partition config (JSON: rules/defaultPackage/splitRiders)
82
+ splitting the import into multiple pgpm packages with
83
+ derived cross-package requires
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.
91
+ --cwd <directory> Working directory (default: current directory)
92
+ --dry-run Print the resulting plan/paths without writing
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
+
97
+ Examples:
98
+ pgpm import dump.sql --pkg my-app
99
+ pgpm import dump.sql --pkg my-app --granularity consolidated --naming flat
100
+ pgpm import ./sql-files --pkg my-app --out ./packages --with-data
101
+ pgpm import dump.sql --pkg my-app --partition partition.json
102
+ pgpm import dump.sql --pkg my-app --emit-sql my-app.sql
103
+ `;
104
+ const NAMING_STYLES = ['directory', 'flat'];
105
+ exports.default = async (argv, prompter, _options) => {
106
+ if (argv.help || argv.h) {
107
+ console.log(importUsageText);
108
+ process.exit(0);
109
+ }
110
+ const cwd = argv.cwd || process.cwd();
111
+ const input = argv._?.filter(Boolean)[0];
112
+ if (!input) {
113
+ await (0, inquirerer_1.cliExitWithError)('Missing input: pgpm import <dump.sql | dir-of-sql-files> --pkg <name>');
114
+ }
115
+ const pkgName = argv.pkg;
116
+ if (!pkgName || typeof pkgName !== 'string') {
117
+ await (0, inquirerer_1.cliExitWithError)('--pkg <module-name> is required.');
118
+ }
119
+ const granularityRaw = argv.granularity ?? 'object';
120
+ if (!(0, transform_1.isExportGranularity)(granularityRaw)) {
121
+ await (0, inquirerer_1.cliExitWithError)(`Invalid --granularity "${granularityRaw}". Expected one of: ${transform_1.EXPORT_GRANULARITIES.join(', ')}.`);
122
+ }
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;
129
+ const namingRaw = argv.naming ?? 'directory';
130
+ if (!NAMING_STYLES.includes(namingRaw)) {
131
+ await (0, inquirerer_1.cliExitWithError)(`Invalid --naming "${namingRaw}". Expected one of: ${NAMING_STYLES.join(', ')}.`);
132
+ }
133
+ const naming = namingRaw;
134
+ let partition;
135
+ if (typeof argv.partition === 'string' && argv.partition) {
136
+ try {
137
+ partition = (0, transform_1.parsePartitionConfig)(path.resolve(cwd, argv.partition));
138
+ }
139
+ catch (err) {
140
+ await (0, inquirerer_1.cliExitWithError)(err instanceof Error ? err.message : String(err));
141
+ }
142
+ }
143
+ const outBase = typeof argv.out === 'string' && argv.out ? path.resolve(cwd, argv.out) : path.resolve(cwd);
144
+ const withData = Boolean(argv['with-data'] ?? argv.withData);
145
+ const write = Boolean(argv.write);
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
+ }
153
+ let source;
154
+ try {
155
+ source = (0, import_1.loadDumpSource)(path.resolve(cwd, input));
156
+ }
157
+ catch (err) {
158
+ await (0, inquirerer_1.cliExitWithError)(err instanceof Error ? err.message : String(err));
159
+ return;
160
+ }
161
+ if (source.files.length > 1 && !sqlToStdout) {
162
+ log.info(`concatenated ${source.files.length} .sql files in sorted order`);
163
+ }
164
+ for (const warning of (0, import_1.dumpCompatibilityWarnings)(source)) {
165
+ console.warn(`\nWARNING: ${warning}\n`);
166
+ }
167
+ const result = await (0, import_1.importDumpRows)(source, { granularity, changeGranularity, naming, withData });
168
+ let packages;
169
+ if (partition) {
170
+ try {
171
+ const partitioned = await (0, transform_1.partitionExportRows)(result.rows, partition);
172
+ result.warnings.push(...partitioned.warnings.map(w => `partition: ${w}`));
173
+ packages = partitioned.packages.map(pkg => ({
174
+ name: pkg.name,
175
+ requires: [...result.controlRequires, ...pkg.requires],
176
+ rows: (0, import_1.linkTextualDeps)(pkg.rows)
177
+ }));
178
+ }
179
+ catch (err) {
180
+ if (err instanceof transform_1.PartitionCycleError) {
181
+ await (0, inquirerer_1.cliExitWithError)(`Partition failed: ${err.message}`);
182
+ return;
183
+ }
184
+ throw err;
185
+ }
186
+ }
187
+ else {
188
+ packages = [{ name: pkgName, requires: result.controlRequires, rows: result.rows }];
189
+ }
190
+ for (const warning of result.warnings) {
191
+ console.warn(`import: ${warning}`);
192
+ }
193
+ if (dryRun) {
194
+ for (const pkg of packages) {
195
+ console.log(`package ${pkg.name} -> ${path.join(outBase, pkg.name)}`);
196
+ if (pkg.requires.length) {
197
+ console.log(` requires: ${pkg.requires.join(', ')}`);
198
+ }
199
+ for (const row of pkg.rows) {
200
+ const deps = row.deps?.length ? ` [${row.deps.join(' ')}]` : '';
201
+ console.log(` ${row.deploy}${deps}`);
202
+ }
203
+ }
204
+ }
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
+ }
209
+ for (const pkg of packages) {
210
+ const guard = (0, emit_package_1.checkOverwrite)(path.join(outBase, pkg.name), source.files[0], write);
211
+ if (guard) {
212
+ await (0, inquirerer_1.cliExitWithError)(guard);
213
+ }
214
+ }
215
+ let firstDir;
216
+ for (const pkg of packages) {
217
+ const dir = (0, emit_package_1.writePackage)(outBase, pkg);
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));
225
+ }
226
+ }
227
+ const { summary } = result;
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
+ }
233
+ prompter.close();
234
+ return argv;
235
+ };
@@ -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
  *
@@ -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 = `
@@ -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,
package/commands/slice.js CHANGED
@@ -3,8 +3,8 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  const core_1 = require("@pgpmjs/core");
4
4
  const slice_1 = require("@pgpmjs/slice");
5
5
  const types_1 = require("@pgpmjs/types");
6
- const path_1 = require("path");
7
6
  const fs_1 = require("fs");
7
+ const path_1 = require("path");
8
8
  const sliceUsageText = `
9
9
  Slice Command:
10
10
 
@@ -7,8 +7,8 @@ const core_1 = require("@pgpmjs/core");
7
7
  const env_1 = require("@pgpmjs/env");
8
8
  const logger_1 = require("@pgpmjs/logger");
9
9
  const path_1 = __importDefault(require("path"));
10
- const pg_env_1 = require("pg-env");
11
10
  const pg_cache_1 = require("pg-cache");
11
+ const pg_env_1 = require("pg-env");
12
12
  const log = new logger_1.Logger('test-packages');
13
13
  // ANSI color codes
14
14
  const RED = '\x1b[0;31m';
@@ -0,0 +1,13 @@
1
+ import { PartitionedPackageRows } from '@pgpmjs/transform';
2
+ import { CLIOptions, Inquirerer, ParsedArgs } from 'inquirerer';
3
+ export { checkOverwrite } from '../utils/emit-package';
4
+ /**
5
+ * Resolve the base directory package dir(s) are created in. Without --out,
6
+ * packages are written as siblings of the source module — a plain transform
7
+ * of `my-mod` at granularity `object` lands in `../my-mod-object`.
8
+ */
9
+ export declare const resolveOutBase: (modulePath: string, out?: string) => string;
10
+ /** Order partition packages so prerequisites come before dependents. */
11
+ export declare const orderPackages: (packages: PartitionedPackageRows[]) => PartitionedPackageRows[];
12
+ declare const _default: (argv: Partial<ParsedArgs>, prompter: Inquirerer, _options: CLIOptions) => Promise<Partial<ParsedArgs>>;
13
+ export default _default;
@@ -0,0 +1,317 @@
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.orderPackages = exports.resolveOutBase = exports.checkOverwrite = void 0;
37
+ const core_1 = require("@pgpmjs/core");
38
+ const logger_1 = require("@pgpmjs/logger");
39
+ const transform_1 = require("@pgpmjs/transform");
40
+ const fs = __importStar(require("fs"));
41
+ const inquirerer_1 = require("inquirerer");
42
+ const path = __importStar(require("path"));
43
+ const pg_env_1 = require("pg-env");
44
+ const emit_package_1 = require("../utils/emit-package");
45
+ const module_projections_1 = require("../utils/module-projections");
46
+ const scratch_db_1 = require("../utils/scratch-db");
47
+ var emit_package_2 = require("../utils/emit-package");
48
+ Object.defineProperty(exports, "checkOverwrite", { enumerable: true, get: function () { return emit_package_2.checkOverwrite; } });
49
+ const log = new logger_1.Logger('transform');
50
+ const transformUsageText = `
51
+ Transform Command:
52
+
53
+ pgpm transform --granularity <atomic|object|consolidated> [OPTIONS]
54
+
55
+ Re-dial an existing pgpm module (or every module in a workspace) through the
56
+ dials pipeline: flatten the deploy scripts in plan order, re-project them at
57
+ the requested granularity with spec-derived change paths, graph-derived
58
+ requires, and generated revert/verify scripts.
59
+
60
+ Options:
61
+ --help, -h Show this help message
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.
69
+ --partition <file> Partition config (JSON: rules/defaultPackage/splitRiders)
70
+ splitting the module into multiple pgpm packages with
71
+ derived cross-package requires.
72
+ --naming <style> Change path naming style: directory | flat (default: directory)
73
+ --cwd <directory> Module or workspace directory (default: current directory)
74
+ --out <dir> Output directory (default: sibling <module>-<granularity>)
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.
82
+ --check Deploy original and transformed output into scratch
83
+ databases and assert the catalogs are equivalent
84
+ --dry-run Print the resulting plan/paths without writing
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
+
89
+ Examples:
90
+ pgpm transform --granularity object
91
+ pgpm transform --granularity atomic --naming flat --out ./out
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
95
+ `;
96
+ const NAMING_STYLES = ['directory', 'flat'];
97
+ /**
98
+ * Resolve the base directory package dir(s) are created in. Without --out,
99
+ * packages are written as siblings of the source module — a plain transform
100
+ * of `my-mod` at granularity `object` lands in `../my-mod-object`.
101
+ */
102
+ const resolveOutBase = (modulePath, out) => {
103
+ if (out)
104
+ return path.resolve(out);
105
+ return path.dirname(modulePath);
106
+ };
107
+ exports.resolveOutBase = resolveOutBase;
108
+ /** Order partition packages so prerequisites come before dependents. */
109
+ const orderPackages = (packages) => {
110
+ const byName = new Map(packages.map(pkg => [pkg.name, pkg]));
111
+ const ordered = [];
112
+ const seen = new Set();
113
+ const visit = (pkg) => {
114
+ if (seen.has(pkg.name))
115
+ return;
116
+ seen.add(pkg.name);
117
+ for (const req of pkg.requires) {
118
+ const dep = byName.get(req);
119
+ if (dep)
120
+ visit(dep);
121
+ }
122
+ ordered.push(pkg);
123
+ };
124
+ for (const pkg of packages)
125
+ visit(pkg);
126
+ return ordered;
127
+ };
128
+ exports.orderPackages = orderPackages;
129
+ const readControlRequires = (modulePath, name) => {
130
+ const controlPath = path.join(modulePath, `${name}.control`);
131
+ if (!fs.existsSync(controlPath))
132
+ return [];
133
+ const match = fs.readFileSync(controlPath, 'utf-8').match(/^requires\s*=\s*'([^']*)'\s*$/m);
134
+ if (!match)
135
+ return [];
136
+ return match[1].split(',').map(s => s.trim()).filter(Boolean);
137
+ };
138
+ const deployModules = async (config, modulePaths) => {
139
+ const client = new core_1.PgpmMigrate(config);
140
+ for (const modulePath of modulePaths) {
141
+ const result = await client.deploy({ modulePath });
142
+ if (result.failed) {
143
+ throw new Error(`deploy failed at change ${result.failed} (module ${modulePath})`);
144
+ }
145
+ }
146
+ };
147
+ /**
148
+ * Prove the transform is structurally lossless: deploy the original module and
149
+ * the transformed package(s) into two scratch databases and compare catalogs.
150
+ */
151
+ const runCheck = async (transformed) => {
152
+ const config = (0, pg_env_1.getPgEnvOptions)();
153
+ const stamp = Date.now();
154
+ const names = [`pgpm_transform_check_a_${stamp}`, `pgpm_transform_check_b_${stamp}`];
155
+ return (0, scratch_db_1.withScratchDatabases)(config, names, async ([cfgOriginal, cfgTransformed]) => {
156
+ await deployModules(cfgOriginal, [transformed.modulePath]);
157
+ const orderedDirs = (0, exports.orderPackages)(transformed.packages).map(pkg => path.join(transformed.outBase, pkg.name));
158
+ await deployModules(cfgTransformed, orderedDirs);
159
+ return (0, scratch_db_1.catalogDifferences)(cfgOriginal, cfgTransformed);
160
+ });
161
+ };
162
+ const transformModule = async (modulePath, granularity, changeGranularity, naming, partition, out) => {
163
+ const source = (0, transform_1.loadModuleSource)(modulePath);
164
+ const warnings = [...source.warnings];
165
+ const rows = source.changes.map(change => ({
166
+ name: change.name,
167
+ deploy: change.name,
168
+ deps: change.dependencies,
169
+ content: change.deploy
170
+ }));
171
+ const restructured = await (0, transform_1.restructureExportRows)(rows, granularity, { naming, changeGranularity });
172
+ warnings.push(...restructured.warnings.map(w => `restructure (${granularity}): ${w}`));
173
+ const outBase = (0, exports.resolveOutBase)(modulePath, out);
174
+ let packages;
175
+ if (partition) {
176
+ const result = await (0, transform_1.partitionExportRows)(restructured.rows, partition);
177
+ warnings.push(...result.warnings.map(w => `partition: ${w}`));
178
+ packages = result.packages;
179
+ }
180
+ else {
181
+ // Distinct package name so the output can live beside the source module
182
+ // in the same workspace without colliding.
183
+ packages = [{ name: `${source.name}-${granularity}`, requires: [], rows: restructured.rows }];
184
+ }
185
+ return { modulePath, name: source.name, outBase, packages, warnings };
186
+ };
187
+ const printDryRun = (transformed) => {
188
+ console.log(`module ${transformed.name} (${transformed.modulePath})`);
189
+ for (const pkg of transformed.packages) {
190
+ console.log(` package ${pkg.name} -> ${path.join(transformed.outBase, pkg.name)}`);
191
+ for (const row of pkg.rows) {
192
+ const deps = row.deps?.length ? ` [${row.deps.join(' ')}]` : '';
193
+ console.log(` ${row.deploy}${deps}`);
194
+ }
195
+ }
196
+ };
197
+ exports.default = async (argv, prompter, _options) => {
198
+ if (argv.help || argv.h) {
199
+ console.log(transformUsageText);
200
+ process.exit(0);
201
+ }
202
+ const granularityRaw = argv.granularity;
203
+ if (granularityRaw === undefined) {
204
+ await (0, inquirerer_1.cliExitWithError)(`--granularity is required. Expected one of: ${transform_1.EXPORT_GRANULARITIES.join(', ')}.`);
205
+ }
206
+ if (!(0, transform_1.isExportGranularity)(granularityRaw)) {
207
+ await (0, inquirerer_1.cliExitWithError)(`Invalid --granularity "${granularityRaw}". Expected one of: ${transform_1.EXPORT_GRANULARITIES.join(', ')}.`);
208
+ }
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;
215
+ const namingRaw = argv.naming ?? 'directory';
216
+ if (!NAMING_STYLES.includes(namingRaw)) {
217
+ await (0, inquirerer_1.cliExitWithError)(`Invalid --naming "${namingRaw}". Expected one of: ${NAMING_STYLES.join(', ')}.`);
218
+ }
219
+ const naming = namingRaw;
220
+ const cwd = argv.cwd || process.cwd();
221
+ let partition;
222
+ if (typeof argv.partition === 'string' && argv.partition) {
223
+ try {
224
+ partition = (0, transform_1.parsePartitionConfig)(path.resolve(cwd, argv.partition));
225
+ }
226
+ catch (err) {
227
+ await (0, inquirerer_1.cliExitWithError)(err instanceof Error ? err.message : String(err));
228
+ }
229
+ }
230
+ const out = typeof argv.out === 'string' && argv.out ? path.resolve(cwd, argv.out) : undefined;
231
+ const write = Boolean(argv.write);
232
+ const check = Boolean(argv.check);
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;
237
+ const pkg = new core_1.PgpmPackage(path.resolve(cwd));
238
+ let modulePaths;
239
+ if (pkg.isInModule()) {
240
+ modulePaths = [pkg.modulePath];
241
+ }
242
+ else if (pkg.workspacePath) {
243
+ const moduleMap = pkg.getModuleMap();
244
+ modulePaths = Object.values(moduleMap).map(mod => path.resolve(pkg.workspacePath, mod.path));
245
+ if (modulePaths.length === 0) {
246
+ await (0, inquirerer_1.cliExitWithError)('No modules found in workspace.');
247
+ }
248
+ if (out && modulePaths.length > 1) {
249
+ await (0, inquirerer_1.cliExitWithError)('--out is not supported when transforming a multi-module workspace.');
250
+ }
251
+ }
252
+ else {
253
+ await (0, inquirerer_1.cliExitWithError)('Not inside a pgpm module or workspace (pass --cwd <dir>).');
254
+ return;
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
+ }
262
+ for (const modulePath of modulePaths) {
263
+ let transformed;
264
+ try {
265
+ transformed = await transformModule(modulePath, granularity, changeGranularity, naming, partition, out);
266
+ }
267
+ catch (err) {
268
+ if (err instanceof transform_1.PartitionCycleError) {
269
+ await (0, inquirerer_1.cliExitWithError)(`Partition failed: ${err.message}`);
270
+ return;
271
+ }
272
+ throw err;
273
+ }
274
+ for (const warning of transformed.warnings) {
275
+ console.warn(`transform: ${warning}`);
276
+ }
277
+ if (dryRun) {
278
+ printDryRun(transformed);
279
+ continue;
280
+ }
281
+ for (const pkgRows of transformed.packages) {
282
+ const targetDir = path.join(transformed.outBase, pkgRows.name);
283
+ const guard = (0, emit_package_1.checkOverwrite)(targetDir, modulePath, write);
284
+ if (guard) {
285
+ await (0, inquirerer_1.cliExitWithError)(guard);
286
+ }
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
+ }
291
+ const sourceRequires = readControlRequires(modulePath, transformed.name);
292
+ let firstDir;
293
+ for (const pkgRows of transformed.packages) {
294
+ const dir = (0, emit_package_1.writePackage)(transformed.outBase, pkgRows, sourceRequires);
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));
302
+ }
303
+ if (check) {
304
+ log.info('running --check: deploying original and transformed output to scratch databases...');
305
+ const diffs = await runCheck(transformed);
306
+ if (diffs.length) {
307
+ console.error(`--check failed: catalogs differ (${diffs.length} differences):`);
308
+ for (const diff of diffs)
309
+ console.error(` ${diff}`);
310
+ await (0, inquirerer_1.cliExitWithError)('Transform is not structurally lossless.');
311
+ }
312
+ log.success('--check passed: catalogs are structurally equivalent.');
313
+ }
314
+ }
315
+ prompter.close();
316
+ return argv;
317
+ };
@@ -2,8 +2,8 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  const utils_1 = require("@inquirerer/utils");
4
4
  const logger_1 = require("@pgpmjs/logger");
5
- const inquirerer_1 = require("inquirerer");
6
5
  const child_process_1 = require("child_process");
6
+ const inquirerer_1 = require("inquirerer");
7
7
  const npm_version_1 = require("../utils/npm-version");
8
8
  const log = new logger_1.Logger('update');
9
9
  const updateUsageText = `