pgpm 5.17.4 → 5.19.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/export.js +17 -2
- package/commands/package.js +48 -0
- package/commands/regen.d.ts +3 -0
- package/commands/regen.js +171 -0
- package/commands.js +3 -0
- package/esm/commands/export.js +18 -3
- package/esm/commands/package.js +49 -1
- package/esm/commands/regen.js +136 -0
- package/esm/commands.js +3 -0
- package/esm/utils/display.js +1 -0
- package/package.json +7 -6
- package/utils/display.d.ts +1 -1
- package/utils/display.js +1 -0
package/commands/export.js
CHANGED
|
@@ -25,11 +25,17 @@ Options:
|
|
|
25
25
|
--metaExtensionName <name> Meta extension name (default: svc)
|
|
26
26
|
--exclude-categories <list> Comma-separated sql_actions categories to omit
|
|
27
27
|
(e.g. security,permissions,auth,memberships). Works in SQL and GraphQL modes.
|
|
28
|
+
--granularity <level> Restructure the exported database module through the
|
|
29
|
+
dials pipeline: atomic | object | consolidated.
|
|
30
|
+
Change paths are derived from the naming spec and
|
|
31
|
+
requires from the statement graph. When omitted,
|
|
32
|
+
sql_actions rows are exported unchanged.
|
|
28
33
|
--cwd <directory> Working directory (default: current directory)
|
|
29
34
|
|
|
30
35
|
Examples:
|
|
31
36
|
pgpm export Export migrations from selected database (SQL mode)
|
|
32
37
|
pgpm export --exclude-categories security,permissions,auth,memberships
|
|
38
|
+
pgpm export --granularity consolidated
|
|
33
39
|
pgpm export --graphql-endpoint 'http://[::1]:3002/graphql' --migrate-endpoint 'http://[::1]:3000/graphql' --migrate-host db_migrate.localhost:3000
|
|
34
40
|
`;
|
|
35
41
|
exports.default = async (argv, prompter, _options) => {
|
|
@@ -54,6 +60,13 @@ exports.default = async (argv, prompter, _options) => {
|
|
|
54
60
|
: Array.isArray(excludeCategoriesRaw)
|
|
55
61
|
? excludeCategoriesRaw
|
|
56
62
|
: undefined;
|
|
63
|
+
const granularityRaw = argv.granularity;
|
|
64
|
+
if (granularityRaw !== undefined && !(0, export_1.isExportGranularity)(granularityRaw)) {
|
|
65
|
+
console.error(`Invalid --granularity "${granularityRaw}". Expected one of: ${export_1.EXPORT_GRANULARITIES.join(', ')}.`);
|
|
66
|
+
prompter.close();
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
const granularity = granularityRaw;
|
|
57
70
|
if (graphqlEndpoint) {
|
|
58
71
|
// =========================================================================
|
|
59
72
|
// GraphQL export mode
|
|
@@ -150,7 +163,8 @@ exports.default = async (argv, prompter, _options) => {
|
|
|
150
163
|
prompter,
|
|
151
164
|
argv,
|
|
152
165
|
username,
|
|
153
|
-
excludeCategories
|
|
166
|
+
excludeCategories,
|
|
167
|
+
granularity
|
|
154
168
|
});
|
|
155
169
|
}
|
|
156
170
|
else {
|
|
@@ -270,7 +284,8 @@ exports.default = async (argv, prompter, _options) => {
|
|
|
270
284
|
prompter,
|
|
271
285
|
argv,
|
|
272
286
|
username,
|
|
273
|
-
excludeCategories
|
|
287
|
+
excludeCategories,
|
|
288
|
+
granularity
|
|
274
289
|
});
|
|
275
290
|
}
|
|
276
291
|
prompter.close();
|
package/commands/package.js
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
const core_1 = require("@pgpmjs/core");
|
|
4
|
+
const logger_1 = require("@pgpmjs/logger");
|
|
5
|
+
const log = new logger_1.Logger('package');
|
|
4
6
|
const packageUsageText = `
|
|
5
7
|
Package Command:
|
|
6
8
|
|
|
@@ -17,11 +19,23 @@ Options:
|
|
|
17
19
|
--bundle Also emit sql/<name>--<version>.bundle.tar.gz (default: true)
|
|
18
20
|
--cwd <directory> Working directory (default: current directory)
|
|
19
21
|
|
|
22
|
+
Check mode (no writes — verify committed artifacts are in sync with deploy/):
|
|
23
|
+
--check Verify committed sql/<name>--<version>.bundle.tar.gz
|
|
24
|
+
matches deploy/ for changed modules; exit 1 on drift
|
|
25
|
+
--since <ref> Git branch/ref/tag to diff HEAD against for change
|
|
26
|
+
detection (default: PR base in CI, else working tree)
|
|
27
|
+
--all Check every workspace module (skip change detection)
|
|
28
|
+
--dependents Also re-check modules that require a changed module
|
|
29
|
+
--no-fail-fast Report all drifted modules instead of stopping at the first
|
|
30
|
+
|
|
20
31
|
Examples:
|
|
21
32
|
pgpm package Package with defaults
|
|
22
33
|
pgpm package --no-plan Package without plan
|
|
23
34
|
pgpm package --outputDiff Package and export AST diff files if mismatch detected
|
|
24
35
|
pgpm package --no-bundle Package without emitting the bundle artifact
|
|
36
|
+
pgpm package --check Verify changed modules' bundles are in sync
|
|
37
|
+
pgpm package --check --since origin/main Verify modules changed vs origin/main
|
|
38
|
+
pgpm package --check --all Verify every module in the workspace
|
|
25
39
|
`;
|
|
26
40
|
exports.default = async (argv, prompter, _options) => {
|
|
27
41
|
// Show usage if explicitly requested
|
|
@@ -29,6 +43,40 @@ exports.default = async (argv, prompter, _options) => {
|
|
|
29
43
|
console.log(packageUsageText);
|
|
30
44
|
process.exit(0);
|
|
31
45
|
}
|
|
46
|
+
// Check mode: verify committed artifacts, never write.
|
|
47
|
+
if (argv.check) {
|
|
48
|
+
const result = await (0, core_1.checkPackages)({
|
|
49
|
+
cwd: argv.cwd,
|
|
50
|
+
since: typeof argv.since === 'string' ? argv.since : undefined,
|
|
51
|
+
all: argv.all === true,
|
|
52
|
+
dependents: argv.dependents === true,
|
|
53
|
+
failFast: argv.failFast !== false,
|
|
54
|
+
});
|
|
55
|
+
if (argv.all) {
|
|
56
|
+
log.info(`Checking all ${result.targeted.length} workspace module(s)`);
|
|
57
|
+
}
|
|
58
|
+
else {
|
|
59
|
+
log.info(`Change detection${result.base ? ` vs ${result.base}` : ' (working tree)'}: ` +
|
|
60
|
+
`${result.changedModules.length} changed module(s)` +
|
|
61
|
+
(result.targeted.length !== result.changedModules.length
|
|
62
|
+
? ` (${result.targeted.length} incl. dependents)`
|
|
63
|
+
: ''));
|
|
64
|
+
}
|
|
65
|
+
if (result.drifted.length) {
|
|
66
|
+
for (const drift of result.drifted) {
|
|
67
|
+
log.error(`✖ ${drift.name}: ${drift.detail}`);
|
|
68
|
+
}
|
|
69
|
+
log.error(`${result.drifted.length} module(s) out of sync. Run \`pgpm package\` in each and commit the artifacts.`);
|
|
70
|
+
process.exit(1);
|
|
71
|
+
}
|
|
72
|
+
if (result.checked.length === 0) {
|
|
73
|
+
log.success('No changed modules to check.');
|
|
74
|
+
}
|
|
75
|
+
else {
|
|
76
|
+
log.success(`${result.checked.length} module(s) in sync.`);
|
|
77
|
+
}
|
|
78
|
+
return argv;
|
|
79
|
+
}
|
|
32
80
|
const questions = [
|
|
33
81
|
{
|
|
34
82
|
type: 'confirm',
|
|
@@ -0,0 +1,171 @@
|
|
|
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 logger_1 = require("@pgpmjs/logger");
|
|
38
|
+
const transform_1 = require("@pgpmjs/transform");
|
|
39
|
+
const fs = __importStar(require("fs"));
|
|
40
|
+
const inquirerer_1 = require("inquirerer");
|
|
41
|
+
const path = __importStar(require("path"));
|
|
42
|
+
const log = new logger_1.Logger('regen');
|
|
43
|
+
const regenUsageText = `
|
|
44
|
+
Regen Command:
|
|
45
|
+
|
|
46
|
+
pgpm regen [change...] [OPTIONS]
|
|
47
|
+
|
|
48
|
+
Generate revert/verify scripts from deploy scripts. By default sweeps every
|
|
49
|
+
change in pgpm.plan and fills in scripts that are missing or empty stubs
|
|
50
|
+
(only comments and a BEGIN/COMMIT wrapper). Reverts are mechanical inverses
|
|
51
|
+
in reverse dependency order; verifies are existence checks per created
|
|
52
|
+
object. Statements with no derivable inverse become
|
|
53
|
+
'-- revert not derivable: <reason>' comments.
|
|
54
|
+
|
|
55
|
+
Arguments:
|
|
56
|
+
change Regenerate only the named change(s) (targeted mode)
|
|
57
|
+
|
|
58
|
+
Options:
|
|
59
|
+
--help, -h Show this help message
|
|
60
|
+
--revert-only Only generate revert scripts
|
|
61
|
+
--verify-only Only generate verify scripts
|
|
62
|
+
--dry-run Report what would be written without writing
|
|
63
|
+
--force Overwrite non-empty existing scripts
|
|
64
|
+
--cwd <directory> Working directory (default: current directory)
|
|
65
|
+
|
|
66
|
+
Examples:
|
|
67
|
+
pgpm regen Fill in all missing/stub revert+verify scripts
|
|
68
|
+
pgpm regen schemas/app/tables/users/table Regenerate one change
|
|
69
|
+
pgpm regen --verify-only --dry-run Preview verify generation
|
|
70
|
+
pgpm regen foo --force Overwrite foo's scripts even if non-empty
|
|
71
|
+
`;
|
|
72
|
+
const wrapScript = (type, change, body) => {
|
|
73
|
+
const header = type === 'revert' ? `-- Revert ${change} from pg` : `-- Verify ${change} on pg`;
|
|
74
|
+
const close = type === 'revert' ? 'COMMIT;' : 'ROLLBACK;';
|
|
75
|
+
const sql = body.trim();
|
|
76
|
+
return `${header}\n\nBEGIN;\n\n${sql ? `${sql}\n\n` : ''}${close}\n`;
|
|
77
|
+
};
|
|
78
|
+
exports.default = async (argv, prompter, _options) => {
|
|
79
|
+
if (argv.help || argv.h) {
|
|
80
|
+
console.log(regenUsageText);
|
|
81
|
+
process.exit(0);
|
|
82
|
+
}
|
|
83
|
+
const cwd = argv.cwd || process.cwd();
|
|
84
|
+
const revertOnly = Boolean(argv['revert-only'] ?? argv.revertOnly);
|
|
85
|
+
const verifyOnly = Boolean(argv['verify-only'] ?? argv.verifyOnly);
|
|
86
|
+
const dryRun = Boolean(argv['dry-run'] ?? argv.dryRun);
|
|
87
|
+
const force = Boolean(argv.force);
|
|
88
|
+
if (revertOnly && verifyOnly) {
|
|
89
|
+
await (0, inquirerer_1.cliExitWithError)('--revert-only and --verify-only are mutually exclusive');
|
|
90
|
+
}
|
|
91
|
+
const types = revertOnly
|
|
92
|
+
? ['revert']
|
|
93
|
+
: verifyOnly
|
|
94
|
+
? ['verify']
|
|
95
|
+
: ['revert', 'verify'];
|
|
96
|
+
const pkg = new core_1.PgpmPackage(path.resolve(cwd));
|
|
97
|
+
if (!pkg.isInModule()) {
|
|
98
|
+
await (0, inquirerer_1.cliExitWithError)('This command must be run inside a pgpm module (or pass --cwd <module>).');
|
|
99
|
+
}
|
|
100
|
+
const modulePath = pkg.modulePath;
|
|
101
|
+
const planResult = (0, core_1.parsePlanFile)(path.join(modulePath, 'pgpm.plan'));
|
|
102
|
+
if (!planResult.data) {
|
|
103
|
+
await (0, inquirerer_1.cliExitWithError)(`Failed to parse pgpm.plan: ${planResult.errors.map(e => e.message).join(', ')}`);
|
|
104
|
+
}
|
|
105
|
+
const planChanges = planResult.data.changes.map(c => c.name);
|
|
106
|
+
const requested = argv._?.filter(Boolean) ?? [];
|
|
107
|
+
let targets;
|
|
108
|
+
if (requested.length > 0) {
|
|
109
|
+
const missing = requested.filter(name => !planChanges.includes(name));
|
|
110
|
+
if (missing.length > 0) {
|
|
111
|
+
await (0, inquirerer_1.cliExitWithError)(`Change(s) not found in pgpm.plan: ${missing.join(', ')}`);
|
|
112
|
+
}
|
|
113
|
+
targets = requested;
|
|
114
|
+
}
|
|
115
|
+
else {
|
|
116
|
+
targets = planChanges;
|
|
117
|
+
}
|
|
118
|
+
const result = { generated: [], skipped: [], warnings: [] };
|
|
119
|
+
await (0, transform_1.loadModule)();
|
|
120
|
+
for (const change of targets) {
|
|
121
|
+
const deploySql = (0, core_1.readScript)(modulePath, 'deploy', change);
|
|
122
|
+
if (!deploySql) {
|
|
123
|
+
result.warnings.push(`${change}: no deploy script found, skipping`);
|
|
124
|
+
continue;
|
|
125
|
+
}
|
|
126
|
+
const needed = types.filter(type => {
|
|
127
|
+
const existing = (0, core_1.readScript)(modulePath, type, change);
|
|
128
|
+
return force || (0, transform_1.isStubScript)(existing);
|
|
129
|
+
});
|
|
130
|
+
const untouched = types.filter(type => !needed.includes(type));
|
|
131
|
+
for (const type of untouched) {
|
|
132
|
+
result.skipped.push(`${type}/${change}`);
|
|
133
|
+
}
|
|
134
|
+
if (needed.length === 0)
|
|
135
|
+
continue;
|
|
136
|
+
let scripts;
|
|
137
|
+
try {
|
|
138
|
+
scripts = (0, transform_1.regenerateScripts)(deploySql);
|
|
139
|
+
}
|
|
140
|
+
catch (err) {
|
|
141
|
+
result.warnings.push(`${change}: failed to parse deploy script (${err?.message ?? err})`);
|
|
142
|
+
continue;
|
|
143
|
+
}
|
|
144
|
+
for (const warning of scripts.revert.warnings) {
|
|
145
|
+
result.warnings.push(`${change}: ${warning}`);
|
|
146
|
+
}
|
|
147
|
+
for (const warning of scripts.verify.warnings) {
|
|
148
|
+
result.warnings.push(`${change}: ${warning}`);
|
|
149
|
+
}
|
|
150
|
+
for (const type of needed) {
|
|
151
|
+
const body = type === 'revert' ? scripts.revert.sql : scripts.verify.sql;
|
|
152
|
+
const content = wrapScript(type, change, body);
|
|
153
|
+
const target = `${type}/${change}`;
|
|
154
|
+
if (dryRun) {
|
|
155
|
+
log.info(`would write ${target}.sql`);
|
|
156
|
+
}
|
|
157
|
+
else {
|
|
158
|
+
const filePath = path.join(modulePath, type, `${change}.sql`);
|
|
159
|
+
fs.mkdirSync(path.dirname(filePath), { recursive: true });
|
|
160
|
+
fs.writeFileSync(filePath, content);
|
|
161
|
+
}
|
|
162
|
+
result.generated.push(target);
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
for (const warning of result.warnings) {
|
|
166
|
+
log.warn(warning);
|
|
167
|
+
}
|
|
168
|
+
const verb = dryRun ? 'would generate' : 'generated';
|
|
169
|
+
log.success(`regen: ${verb} ${result.generated.length} script(s), skipped ${result.skipped.length} existing, ${result.warnings.length} warning(s)`);
|
|
170
|
+
return argv;
|
|
171
|
+
};
|
package/commands.js
CHANGED
|
@@ -26,6 +26,7 @@ const materialize_1 = __importDefault(require("./commands/materialize"));
|
|
|
26
26
|
const migrate_1 = __importDefault(require("./commands/migrate"));
|
|
27
27
|
const package_1 = __importDefault(require("./commands/package"));
|
|
28
28
|
const plan_1 = __importDefault(require("./commands/plan"));
|
|
29
|
+
const regen_1 = __importDefault(require("./commands/regen"));
|
|
29
30
|
const remove_1 = __importDefault(require("./commands/remove"));
|
|
30
31
|
const rename_1 = __importDefault(require("./commands/rename"));
|
|
31
32
|
const revert_1 = __importDefault(require("./commands/revert"));
|
|
@@ -57,6 +58,7 @@ const ENGINE_EXEMPT_COMMANDS = new Set([
|
|
|
57
58
|
'package',
|
|
58
59
|
'materialize',
|
|
59
60
|
'plan',
|
|
61
|
+
'regen',
|
|
60
62
|
'remove',
|
|
61
63
|
'rename',
|
|
62
64
|
'slice',
|
|
@@ -93,6 +95,7 @@ const createPgpmCommandMap = (skipPgTeardown = false) => {
|
|
|
93
95
|
init: pgt(init_1.default),
|
|
94
96
|
extension: pgt(extension_1.default),
|
|
95
97
|
plan: pgt(plan_1.default),
|
|
98
|
+
regen: regen_1.default,
|
|
96
99
|
export: pgt(export_1.default),
|
|
97
100
|
package: pgt(package_1.default),
|
|
98
101
|
tag: pgt(tag_1.default),
|
package/esm/commands/export.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { PgpmPackage } from '@pgpmjs/core';
|
|
2
|
-
import { exportMigrations, exportGraphQL, GraphQLClient, graphqlRowToPostgresRow } from '@pgpmjs/export';
|
|
2
|
+
import { exportMigrations, exportGraphQL, GraphQLClient, graphqlRowToPostgresRow, isExportGranularity, EXPORT_GRANULARITIES } from '@pgpmjs/export';
|
|
3
3
|
import { getEnvOptions } from '@pgpmjs/env';
|
|
4
4
|
import { getGitConfigInfo } from '@pgpmjs/types';
|
|
5
5
|
import { resolve } from 'path';
|
|
@@ -23,11 +23,17 @@ Options:
|
|
|
23
23
|
--metaExtensionName <name> Meta extension name (default: svc)
|
|
24
24
|
--exclude-categories <list> Comma-separated sql_actions categories to omit
|
|
25
25
|
(e.g. security,permissions,auth,memberships). Works in SQL and GraphQL modes.
|
|
26
|
+
--granularity <level> Restructure the exported database module through the
|
|
27
|
+
dials pipeline: atomic | object | consolidated.
|
|
28
|
+
Change paths are derived from the naming spec and
|
|
29
|
+
requires from the statement graph. When omitted,
|
|
30
|
+
sql_actions rows are exported unchanged.
|
|
26
31
|
--cwd <directory> Working directory (default: current directory)
|
|
27
32
|
|
|
28
33
|
Examples:
|
|
29
34
|
pgpm export Export migrations from selected database (SQL mode)
|
|
30
35
|
pgpm export --exclude-categories security,permissions,auth,memberships
|
|
36
|
+
pgpm export --granularity consolidated
|
|
31
37
|
pgpm export --graphql-endpoint 'http://[::1]:3002/graphql' --migrate-endpoint 'http://[::1]:3000/graphql' --migrate-host db_migrate.localhost:3000
|
|
32
38
|
`;
|
|
33
39
|
export default async (argv, prompter, _options) => {
|
|
@@ -52,6 +58,13 @@ export default async (argv, prompter, _options) => {
|
|
|
52
58
|
: Array.isArray(excludeCategoriesRaw)
|
|
53
59
|
? excludeCategoriesRaw
|
|
54
60
|
: undefined;
|
|
61
|
+
const granularityRaw = argv.granularity;
|
|
62
|
+
if (granularityRaw !== undefined && !isExportGranularity(granularityRaw)) {
|
|
63
|
+
console.error(`Invalid --granularity "${granularityRaw}". Expected one of: ${EXPORT_GRANULARITIES.join(', ')}.`);
|
|
64
|
+
prompter.close();
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
67
|
+
const granularity = granularityRaw;
|
|
55
68
|
if (graphqlEndpoint) {
|
|
56
69
|
// =========================================================================
|
|
57
70
|
// GraphQL export mode
|
|
@@ -148,7 +161,8 @@ export default async (argv, prompter, _options) => {
|
|
|
148
161
|
prompter,
|
|
149
162
|
argv,
|
|
150
163
|
username,
|
|
151
|
-
excludeCategories
|
|
164
|
+
excludeCategories,
|
|
165
|
+
granularity
|
|
152
166
|
});
|
|
153
167
|
}
|
|
154
168
|
else {
|
|
@@ -268,7 +282,8 @@ export default async (argv, prompter, _options) => {
|
|
|
268
282
|
prompter,
|
|
269
283
|
argv,
|
|
270
284
|
username,
|
|
271
|
-
excludeCategories
|
|
285
|
+
excludeCategories,
|
|
286
|
+
granularity
|
|
272
287
|
});
|
|
273
288
|
}
|
|
274
289
|
prompter.close();
|
package/esm/commands/package.js
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
|
-
import { PgpmPackage, writePackage } from '@pgpmjs/core';
|
|
1
|
+
import { checkPackages, PgpmPackage, writePackage } from '@pgpmjs/core';
|
|
2
|
+
import { Logger } from '@pgpmjs/logger';
|
|
3
|
+
const log = new Logger('package');
|
|
2
4
|
const packageUsageText = `
|
|
3
5
|
Package Command:
|
|
4
6
|
|
|
@@ -15,11 +17,23 @@ Options:
|
|
|
15
17
|
--bundle Also emit sql/<name>--<version>.bundle.tar.gz (default: true)
|
|
16
18
|
--cwd <directory> Working directory (default: current directory)
|
|
17
19
|
|
|
20
|
+
Check mode (no writes — verify committed artifacts are in sync with deploy/):
|
|
21
|
+
--check Verify committed sql/<name>--<version>.bundle.tar.gz
|
|
22
|
+
matches deploy/ for changed modules; exit 1 on drift
|
|
23
|
+
--since <ref> Git branch/ref/tag to diff HEAD against for change
|
|
24
|
+
detection (default: PR base in CI, else working tree)
|
|
25
|
+
--all Check every workspace module (skip change detection)
|
|
26
|
+
--dependents Also re-check modules that require a changed module
|
|
27
|
+
--no-fail-fast Report all drifted modules instead of stopping at the first
|
|
28
|
+
|
|
18
29
|
Examples:
|
|
19
30
|
pgpm package Package with defaults
|
|
20
31
|
pgpm package --no-plan Package without plan
|
|
21
32
|
pgpm package --outputDiff Package and export AST diff files if mismatch detected
|
|
22
33
|
pgpm package --no-bundle Package without emitting the bundle artifact
|
|
34
|
+
pgpm package --check Verify changed modules' bundles are in sync
|
|
35
|
+
pgpm package --check --since origin/main Verify modules changed vs origin/main
|
|
36
|
+
pgpm package --check --all Verify every module in the workspace
|
|
23
37
|
`;
|
|
24
38
|
export default async (argv, prompter, _options) => {
|
|
25
39
|
// Show usage if explicitly requested
|
|
@@ -27,6 +41,40 @@ export default async (argv, prompter, _options) => {
|
|
|
27
41
|
console.log(packageUsageText);
|
|
28
42
|
process.exit(0);
|
|
29
43
|
}
|
|
44
|
+
// Check mode: verify committed artifacts, never write.
|
|
45
|
+
if (argv.check) {
|
|
46
|
+
const result = await checkPackages({
|
|
47
|
+
cwd: argv.cwd,
|
|
48
|
+
since: typeof argv.since === 'string' ? argv.since : undefined,
|
|
49
|
+
all: argv.all === true,
|
|
50
|
+
dependents: argv.dependents === true,
|
|
51
|
+
failFast: argv.failFast !== false,
|
|
52
|
+
});
|
|
53
|
+
if (argv.all) {
|
|
54
|
+
log.info(`Checking all ${result.targeted.length} workspace module(s)`);
|
|
55
|
+
}
|
|
56
|
+
else {
|
|
57
|
+
log.info(`Change detection${result.base ? ` vs ${result.base}` : ' (working tree)'}: ` +
|
|
58
|
+
`${result.changedModules.length} changed module(s)` +
|
|
59
|
+
(result.targeted.length !== result.changedModules.length
|
|
60
|
+
? ` (${result.targeted.length} incl. dependents)`
|
|
61
|
+
: ''));
|
|
62
|
+
}
|
|
63
|
+
if (result.drifted.length) {
|
|
64
|
+
for (const drift of result.drifted) {
|
|
65
|
+
log.error(`✖ ${drift.name}: ${drift.detail}`);
|
|
66
|
+
}
|
|
67
|
+
log.error(`${result.drifted.length} module(s) out of sync. Run \`pgpm package\` in each and commit the artifacts.`);
|
|
68
|
+
process.exit(1);
|
|
69
|
+
}
|
|
70
|
+
if (result.checked.length === 0) {
|
|
71
|
+
log.success('No changed modules to check.');
|
|
72
|
+
}
|
|
73
|
+
else {
|
|
74
|
+
log.success(`${result.checked.length} module(s) in sync.`);
|
|
75
|
+
}
|
|
76
|
+
return argv;
|
|
77
|
+
}
|
|
30
78
|
const questions = [
|
|
31
79
|
{
|
|
32
80
|
type: 'confirm',
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
import { parsePlanFile, PgpmPackage, readScript } from '@pgpmjs/core';
|
|
2
|
+
import { Logger } from '@pgpmjs/logger';
|
|
3
|
+
import { isStubScript, loadModule, regenerateScripts } from '@pgpmjs/transform';
|
|
4
|
+
import * as fs from 'fs';
|
|
5
|
+
import { cliExitWithError } from 'inquirerer';
|
|
6
|
+
import * as path from 'path';
|
|
7
|
+
const log = new Logger('regen');
|
|
8
|
+
const regenUsageText = `
|
|
9
|
+
Regen Command:
|
|
10
|
+
|
|
11
|
+
pgpm regen [change...] [OPTIONS]
|
|
12
|
+
|
|
13
|
+
Generate revert/verify scripts from deploy scripts. By default sweeps every
|
|
14
|
+
change in pgpm.plan and fills in scripts that are missing or empty stubs
|
|
15
|
+
(only comments and a BEGIN/COMMIT wrapper). Reverts are mechanical inverses
|
|
16
|
+
in reverse dependency order; verifies are existence checks per created
|
|
17
|
+
object. Statements with no derivable inverse become
|
|
18
|
+
'-- revert not derivable: <reason>' comments.
|
|
19
|
+
|
|
20
|
+
Arguments:
|
|
21
|
+
change Regenerate only the named change(s) (targeted mode)
|
|
22
|
+
|
|
23
|
+
Options:
|
|
24
|
+
--help, -h Show this help message
|
|
25
|
+
--revert-only Only generate revert scripts
|
|
26
|
+
--verify-only Only generate verify scripts
|
|
27
|
+
--dry-run Report what would be written without writing
|
|
28
|
+
--force Overwrite non-empty existing scripts
|
|
29
|
+
--cwd <directory> Working directory (default: current directory)
|
|
30
|
+
|
|
31
|
+
Examples:
|
|
32
|
+
pgpm regen Fill in all missing/stub revert+verify scripts
|
|
33
|
+
pgpm regen schemas/app/tables/users/table Regenerate one change
|
|
34
|
+
pgpm regen --verify-only --dry-run Preview verify generation
|
|
35
|
+
pgpm regen foo --force Overwrite foo's scripts even if non-empty
|
|
36
|
+
`;
|
|
37
|
+
const wrapScript = (type, change, body) => {
|
|
38
|
+
const header = type === 'revert' ? `-- Revert ${change} from pg` : `-- Verify ${change} on pg`;
|
|
39
|
+
const close = type === 'revert' ? 'COMMIT;' : 'ROLLBACK;';
|
|
40
|
+
const sql = body.trim();
|
|
41
|
+
return `${header}\n\nBEGIN;\n\n${sql ? `${sql}\n\n` : ''}${close}\n`;
|
|
42
|
+
};
|
|
43
|
+
export default async (argv, prompter, _options) => {
|
|
44
|
+
if (argv.help || argv.h) {
|
|
45
|
+
console.log(regenUsageText);
|
|
46
|
+
process.exit(0);
|
|
47
|
+
}
|
|
48
|
+
const cwd = argv.cwd || process.cwd();
|
|
49
|
+
const revertOnly = Boolean(argv['revert-only'] ?? argv.revertOnly);
|
|
50
|
+
const verifyOnly = Boolean(argv['verify-only'] ?? argv.verifyOnly);
|
|
51
|
+
const dryRun = Boolean(argv['dry-run'] ?? argv.dryRun);
|
|
52
|
+
const force = Boolean(argv.force);
|
|
53
|
+
if (revertOnly && verifyOnly) {
|
|
54
|
+
await cliExitWithError('--revert-only and --verify-only are mutually exclusive');
|
|
55
|
+
}
|
|
56
|
+
const types = revertOnly
|
|
57
|
+
? ['revert']
|
|
58
|
+
: verifyOnly
|
|
59
|
+
? ['verify']
|
|
60
|
+
: ['revert', 'verify'];
|
|
61
|
+
const pkg = new PgpmPackage(path.resolve(cwd));
|
|
62
|
+
if (!pkg.isInModule()) {
|
|
63
|
+
await cliExitWithError('This command must be run inside a pgpm module (or pass --cwd <module>).');
|
|
64
|
+
}
|
|
65
|
+
const modulePath = pkg.modulePath;
|
|
66
|
+
const planResult = parsePlanFile(path.join(modulePath, 'pgpm.plan'));
|
|
67
|
+
if (!planResult.data) {
|
|
68
|
+
await cliExitWithError(`Failed to parse pgpm.plan: ${planResult.errors.map(e => e.message).join(', ')}`);
|
|
69
|
+
}
|
|
70
|
+
const planChanges = planResult.data.changes.map(c => c.name);
|
|
71
|
+
const requested = argv._?.filter(Boolean) ?? [];
|
|
72
|
+
let targets;
|
|
73
|
+
if (requested.length > 0) {
|
|
74
|
+
const missing = requested.filter(name => !planChanges.includes(name));
|
|
75
|
+
if (missing.length > 0) {
|
|
76
|
+
await cliExitWithError(`Change(s) not found in pgpm.plan: ${missing.join(', ')}`);
|
|
77
|
+
}
|
|
78
|
+
targets = requested;
|
|
79
|
+
}
|
|
80
|
+
else {
|
|
81
|
+
targets = planChanges;
|
|
82
|
+
}
|
|
83
|
+
const result = { generated: [], skipped: [], warnings: [] };
|
|
84
|
+
await loadModule();
|
|
85
|
+
for (const change of targets) {
|
|
86
|
+
const deploySql = readScript(modulePath, 'deploy', change);
|
|
87
|
+
if (!deploySql) {
|
|
88
|
+
result.warnings.push(`${change}: no deploy script found, skipping`);
|
|
89
|
+
continue;
|
|
90
|
+
}
|
|
91
|
+
const needed = types.filter(type => {
|
|
92
|
+
const existing = readScript(modulePath, type, change);
|
|
93
|
+
return force || isStubScript(existing);
|
|
94
|
+
});
|
|
95
|
+
const untouched = types.filter(type => !needed.includes(type));
|
|
96
|
+
for (const type of untouched) {
|
|
97
|
+
result.skipped.push(`${type}/${change}`);
|
|
98
|
+
}
|
|
99
|
+
if (needed.length === 0)
|
|
100
|
+
continue;
|
|
101
|
+
let scripts;
|
|
102
|
+
try {
|
|
103
|
+
scripts = regenerateScripts(deploySql);
|
|
104
|
+
}
|
|
105
|
+
catch (err) {
|
|
106
|
+
result.warnings.push(`${change}: failed to parse deploy script (${err?.message ?? err})`);
|
|
107
|
+
continue;
|
|
108
|
+
}
|
|
109
|
+
for (const warning of scripts.revert.warnings) {
|
|
110
|
+
result.warnings.push(`${change}: ${warning}`);
|
|
111
|
+
}
|
|
112
|
+
for (const warning of scripts.verify.warnings) {
|
|
113
|
+
result.warnings.push(`${change}: ${warning}`);
|
|
114
|
+
}
|
|
115
|
+
for (const type of needed) {
|
|
116
|
+
const body = type === 'revert' ? scripts.revert.sql : scripts.verify.sql;
|
|
117
|
+
const content = wrapScript(type, change, body);
|
|
118
|
+
const target = `${type}/${change}`;
|
|
119
|
+
if (dryRun) {
|
|
120
|
+
log.info(`would write ${target}.sql`);
|
|
121
|
+
}
|
|
122
|
+
else {
|
|
123
|
+
const filePath = path.join(modulePath, type, `${change}.sql`);
|
|
124
|
+
fs.mkdirSync(path.dirname(filePath), { recursive: true });
|
|
125
|
+
fs.writeFileSync(filePath, content);
|
|
126
|
+
}
|
|
127
|
+
result.generated.push(target);
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
for (const warning of result.warnings) {
|
|
131
|
+
log.warn(warning);
|
|
132
|
+
}
|
|
133
|
+
const verb = dryRun ? 'would generate' : 'generated';
|
|
134
|
+
log.success(`regen: ${verb} ${result.generated.length} script(s), skipped ${result.skipped.length} existing, ${result.warnings.length} warning(s)`);
|
|
135
|
+
return argv;
|
|
136
|
+
};
|
package/esm/commands.js
CHANGED
|
@@ -20,6 +20,7 @@ import materialize from './commands/materialize';
|
|
|
20
20
|
import migrate from './commands/migrate';
|
|
21
21
|
import _package from './commands/package';
|
|
22
22
|
import plan from './commands/plan';
|
|
23
|
+
import regen from './commands/regen';
|
|
23
24
|
import remove from './commands/remove';
|
|
24
25
|
import renameCmd from './commands/rename';
|
|
25
26
|
import revert from './commands/revert';
|
|
@@ -51,6 +52,7 @@ const ENGINE_EXEMPT_COMMANDS = new Set([
|
|
|
51
52
|
'package',
|
|
52
53
|
'materialize',
|
|
53
54
|
'plan',
|
|
55
|
+
'regen',
|
|
54
56
|
'remove',
|
|
55
57
|
'rename',
|
|
56
58
|
'slice',
|
|
@@ -87,6 +89,7 @@ export const createPgpmCommandMap = (skipPgTeardown = false) => {
|
|
|
87
89
|
init: pgt(init),
|
|
88
90
|
extension: pgt(extension),
|
|
89
91
|
plan: pgt(plan),
|
|
92
|
+
regen,
|
|
90
93
|
export: pgt(_export),
|
|
91
94
|
package: pgt(_package),
|
|
92
95
|
tag: pgt(tag),
|
package/esm/utils/display.js
CHANGED
|
@@ -11,6 +11,7 @@ export const usageText = `
|
|
|
11
11
|
init Initialize workspace or module
|
|
12
12
|
extension Manage module dependencies
|
|
13
13
|
plan Generate module deployment plans
|
|
14
|
+
regen Generate revert/verify scripts from deploy scripts
|
|
14
15
|
package Package module for distribution
|
|
15
16
|
materialize Transpile an apply proxy into a plain, committed module
|
|
16
17
|
sync-versions Sync .control/Makefile/sql metadata to package.json versions
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pgpm",
|
|
3
|
-
"version": "5.
|
|
3
|
+
"version": "5.19.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,11 +46,12 @@
|
|
|
46
46
|
},
|
|
47
47
|
"dependencies": {
|
|
48
48
|
"@inquirerer/utils": "^3.3.9",
|
|
49
|
-
"@pgpmjs/core": "^7.
|
|
49
|
+
"@pgpmjs/core": "^7.21.1",
|
|
50
50
|
"@pgpmjs/env": "^2.40.2",
|
|
51
|
-
"@pgpmjs/export": "^1.
|
|
51
|
+
"@pgpmjs/export": "^1.14.1",
|
|
52
52
|
"@pgpmjs/logger": "^2.23.2",
|
|
53
|
-
"@pgpmjs/slice": "^0.13.
|
|
53
|
+
"@pgpmjs/slice": "^0.13.6",
|
|
54
|
+
"@pgpmjs/transform": "^0.17.0",
|
|
54
55
|
"@pgpmjs/types": "^2.49.2",
|
|
55
56
|
"@pgsql/quotes": "^18.1.0",
|
|
56
57
|
"appstash": "^0.7.0",
|
|
@@ -60,7 +61,7 @@
|
|
|
60
61
|
"js-yaml": "^4.1.0",
|
|
61
62
|
"pg-cache": "^3.24.2",
|
|
62
63
|
"pg-env": "^1.27.2",
|
|
63
|
-
"pgsql-deparser": "^18.
|
|
64
|
+
"pgsql-deparser": "^18.2.0",
|
|
64
65
|
"semver": "^7.8.1",
|
|
65
66
|
"shelljs": "^0.10.0",
|
|
66
67
|
"yanse": "^0.2.1"
|
|
@@ -77,5 +78,5 @@
|
|
|
77
78
|
"pg",
|
|
78
79
|
"pgsql"
|
|
79
80
|
],
|
|
80
|
-
"gitHead": "
|
|
81
|
+
"gitHead": "45cd961cf956d2d28e4d09b20db4204982c33593"
|
|
81
82
|
}
|
package/utils/display.d.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export declare const usageText = "\n Usage: pgpm <command> [options]\n\n Core Database Operations:\n add Add database changes to plans and create SQL files\n deploy Deploy database changes and migrations\n verify Verify database state and migrations\n revert Revert database changes and migrations\n\n Project Management:\n init Initialize workspace or module\n extension Manage module dependencies\n plan Generate module deployment plans\n package Package module for distribution\n materialize Transpile an apply proxy into a plain, committed module\n sync-versions Sync .control/Makefile/sql metadata to package.json versions\n export Export database migrations from existing databases\n update Update pgpm to the latest version\n cache Manage cached templates (clean)\n upgrade Upgrade installed pgpm modules to latest versions (alias: up)\n\n Database Administration:\n dump Dump a database to a sql file\n kill Terminate database connections and optionally drop databases\n install Install database modules\n tag Add tags to changes for versioning\n clear Clear database state\n remove Remove database changes\n analyze Analyze database structure\n rename Rename database changes\n admin-users Manage admin users\n tune Tune PostgreSQL for throwaway environments (CI/test)\n\n Testing:\n test-packages Run integration tests on all workspace packages\n\n Migration Tools:\n migrate Migration management subcommands\n init Initialize migration tracking\n status Show migration status\n list List all changes\n deps Show change dependencies\n \n Development Tools:\n docker Manage Docker containers (start/stop/ls, --minio)\n doctor Check local dependencies (node, docker, psql) with install guidance\n env Manage environment variables (--supabase, --minio)\n test-packages Run integration tests on workspace packages\n \n Global Options:\n -h, --help Display this help information\n -v, --version Display version information\n --cwd <directory> Working directory (default: current directory)\n --engine <name> Migration backend: pg (default, Postgres server) or\n pglite (in-process WASM Postgres, no server). Also set by\n the \"engine\" key of pgpm.json or PGPM_ENGINE.\n --driver <pkg> Driver plugin package backing the engine (escape hatch)\n --pglite[=dataDir] Sugar for --engine pglite, persisted to <dataDir> if given\n\n Individual Command Help:\n pgpm <command> --help Display detailed help for specific command\n pgpm <command> -h Display detailed help for specific command\n\n Examples:\n pgpm deploy --help Show deploy command options\n pgpm init workspace Initialize new workspace\n pgpm install @pgpm/base32 Install a database module\n ";
|
|
1
|
+
export declare const usageText = "\n Usage: pgpm <command> [options]\n\n Core Database Operations:\n add Add database changes to plans and create SQL files\n deploy Deploy database changes and migrations\n verify Verify database state and migrations\n revert Revert database changes and migrations\n\n Project Management:\n init Initialize workspace or module\n extension Manage module dependencies\n plan Generate module deployment plans\n regen Generate revert/verify scripts from deploy scripts\n package Package module for distribution\n materialize Transpile an apply proxy into a plain, committed module\n sync-versions Sync .control/Makefile/sql metadata to package.json versions\n export Export database migrations from existing databases\n update Update pgpm to the latest version\n cache Manage cached templates (clean)\n upgrade Upgrade installed pgpm modules to latest versions (alias: up)\n\n Database Administration:\n dump Dump a database to a sql file\n kill Terminate database connections and optionally drop databases\n install Install database modules\n tag Add tags to changes for versioning\n clear Clear database state\n remove Remove database changes\n analyze Analyze database structure\n rename Rename database changes\n admin-users Manage admin users\n tune Tune PostgreSQL for throwaway environments (CI/test)\n\n Testing:\n test-packages Run integration tests on all workspace packages\n\n Migration Tools:\n migrate Migration management subcommands\n init Initialize migration tracking\n status Show migration status\n list List all changes\n deps Show change dependencies\n \n Development Tools:\n docker Manage Docker containers (start/stop/ls, --minio)\n doctor Check local dependencies (node, docker, psql) with install guidance\n env Manage environment variables (--supabase, --minio)\n test-packages Run integration tests on workspace packages\n \n Global Options:\n -h, --help Display this help information\n -v, --version Display version information\n --cwd <directory> Working directory (default: current directory)\n --engine <name> Migration backend: pg (default, Postgres server) or\n pglite (in-process WASM Postgres, no server). Also set by\n the \"engine\" key of pgpm.json or PGPM_ENGINE.\n --driver <pkg> Driver plugin package backing the engine (escape hatch)\n --pglite[=dataDir] Sugar for --engine pglite, persisted to <dataDir> if given\n\n Individual Command Help:\n pgpm <command> --help Display detailed help for specific command\n pgpm <command> -h Display detailed help for specific command\n\n Examples:\n pgpm deploy --help Show deploy command options\n pgpm init workspace Initialize new workspace\n pgpm install @pgpm/base32 Install a database module\n ";
|
package/utils/display.js
CHANGED
|
@@ -14,6 +14,7 @@ exports.usageText = `
|
|
|
14
14
|
init Initialize workspace or module
|
|
15
15
|
extension Manage module dependencies
|
|
16
16
|
plan Generate module deployment plans
|
|
17
|
+
regen Generate revert/verify scripts from deploy scripts
|
|
17
18
|
package Package module for distribution
|
|
18
19
|
materialize Transpile an apply proxy into a plain, committed module
|
|
19
20
|
sync-versions Sync .control/Makefile/sql metadata to package.json versions
|