pgpm 5.13.0 → 5.15.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.
@@ -17,11 +17,18 @@ Deploy Command:
17
17
  Options:
18
18
  --help, -h Show this help message
19
19
  --createdb Create database if it doesn't exist
20
+ --engine <name> Migration backend to deploy to (default: pg).
21
+ Built-in engines: pg (Postgres server), pglite (in-process WASM).
22
+ --driver <pkg> Driver plugin package to deploy through (escape hatch for --engine)
23
+ --pglite[=dataDir] Sugar for --engine pglite; persists to <dataDir> when given
20
24
  --recursive Deploy recursively through dependencies
21
25
  --package <name> Target specific package
22
26
  --to <target> Deploy to specific change or tag
23
27
  --tx Use transactions (default: true)
24
- --fast Use fast deployment strategy
28
+ --fast Fast strategy: one-shot SQL + bulk migration ledger,
29
+ reading each module's verified sql/*.bundle.tar.gz artifact
30
+ when present and building it from deploy/ when not
31
+ --bundled Alias for --fast
25
32
  --logOnly Log-only mode, skip script execution
26
33
  --usePlan Use deployment plan
27
34
  --cache Enable caching
@@ -32,6 +39,9 @@ Examples:
32
39
  pgpm deploy --createdb Deploy with database creation
33
40
  pgpm deploy --package mypackage --to @v1.0.0 Deploy specific package to tag
34
41
  pgpm deploy --fast --no-tx Fast deployment without transactions
42
+ pgpm deploy --bundled Same as --fast
43
+ pgpm deploy --engine pglite Deploy into in-process PGlite (no server)
44
+ pgpm deploy --pglite=./.pglite Deploy into a persisted PGlite data directory
35
45
  `;
36
46
  exports.default = async (argv, prompter, _options) => {
37
47
  // Show usage if explicitly requested
@@ -90,6 +100,14 @@ exports.default = async (argv, prompter, _options) => {
90
100
  default: false,
91
101
  required: false
92
102
  },
103
+ {
104
+ name: 'bundled',
105
+ type: 'confirm',
106
+ message: 'Prefer prebuilt bundle artifacts (alias of fast)?',
107
+ useDefault: true,
108
+ default: false,
109
+ required: false
110
+ },
93
111
  {
94
112
  name: 'logOnly',
95
113
  type: 'confirm',
@@ -99,17 +117,23 @@ exports.default = async (argv, prompter, _options) => {
99
117
  required: false
100
118
  }
101
119
  ];
102
- let { yes, recursive, createdb, cwd, tx, fast, logOnly } = await prompter.prompt(argv, questions);
120
+ let { yes, recursive, createdb, cwd, tx, fast, bundled, logOnly } = await prompter.prompt(argv, questions);
103
121
  if (!yes) {
104
122
  log.info('Operation cancelled.');
105
123
  return;
106
124
  }
107
125
  log.debug(`Using current directory: ${cwd}`);
126
+ const { engine, capabilities } = (0, utils_1.getActiveEngine)();
108
127
  if (createdb) {
109
- log.info(`Creating database ${database}...`);
110
- (0, child_process_1.execSync)(`createdb ${database}`, {
111
- env: (0, pg_env_1.getSpawnEnvWithPg)(pgEnv)
112
- });
128
+ if (capabilities.createdb) {
129
+ log.info(`Creating database ${database}...`);
130
+ (0, child_process_1.execSync)(`createdb ${database}`, {
131
+ env: (0, pg_env_1.getSpawnEnvWithPg)(pgEnv)
132
+ });
133
+ }
134
+ else {
135
+ log.info(`Skipping createdb: the "${engine.name}" engine's instance is the database.`);
136
+ }
113
137
  }
114
138
  let packageName;
115
139
  if (recursive) {
@@ -120,6 +144,7 @@ exports.default = async (argv, prompter, _options) => {
120
144
  deployment: {
121
145
  useTx: tx !== false,
122
146
  fast: fast !== false,
147
+ bundled: bundled === true,
123
148
  usePlan: argv.usePlan !== false,
124
149
  cache: argv.cache !== false,
125
150
  logOnly: argv.logOnly !== false,
@@ -14,12 +14,14 @@ Options:
14
14
  --pretty Pretty-print output (default: true)
15
15
  --functionDelimiter <delimiter> Function delimiter (default: $EOFCODE$)
16
16
  --outputDiff Export AST diff files when round-trip mismatch detected (default: false)
17
+ --bundle Also emit sql/<name>--<version>.bundle.tar.gz (default: true)
17
18
  --cwd <directory> Working directory (default: current directory)
18
19
 
19
20
  Examples:
20
21
  pgpm package Package with defaults
21
22
  pgpm package --no-plan Package without plan
22
23
  pgpm package --outputDiff Package and export AST diff files if mismatch detected
24
+ pgpm package --no-bundle Package without emitting the bundle artifact
23
25
  `;
24
26
  exports.default = async (argv, prompter, _options) => {
25
27
  // Show usage if explicitly requested
@@ -55,9 +57,16 @@ exports.default = async (argv, prompter, _options) => {
55
57
  default: false,
56
58
  useDefault: true,
57
59
  required: false
60
+ },
61
+ {
62
+ type: 'confirm',
63
+ name: 'bundle',
64
+ default: true,
65
+ useDefault: true,
66
+ required: false
58
67
  }
59
68
  ];
60
- let { cwd, plan, pretty, functionDelimiter, outputDiff } = await prompter.prompt(argv, questions);
69
+ let { cwd, plan, pretty, functionDelimiter, outputDiff, bundle } = await prompter.prompt(argv, questions);
61
70
  const project = new core_1.PgpmPackage(cwd);
62
71
  project.ensureModule();
63
72
  const info = project.getModuleInfo();
@@ -69,7 +78,8 @@ exports.default = async (argv, prompter, _options) => {
69
78
  packageDir: project.modulePath,
70
79
  pretty,
71
80
  functionDelimiter,
72
- outputDiff
81
+ outputDiff,
82
+ bundle: bundle !== false
73
83
  });
74
84
  return argv;
75
85
  };
@@ -22,6 +22,7 @@ Options:
22
22
  --to <target> Revert to specific change or tag
23
23
  --to Interactive selection of deployed changes
24
24
  --tx Use transactions (default: true)
25
+ --engine <name> Migration backend to revert in (default: pg; also pglite)
25
26
  --cwd <directory> Working directory (default: current directory)
26
27
 
27
28
  Examples:
@@ -21,6 +21,7 @@ Options:
21
21
  --package <name> Verify specific package
22
22
  --to <target> Verify up to specific change or tag
23
23
  --to Interactive selection of deployed changes
24
+ --engine <name> Migration backend to verify against (default: pg; also pglite)
24
25
  --cwd <directory> Working directory (default: current directory)
25
26
 
26
27
  Examples:
package/commands.js CHANGED
@@ -37,6 +37,12 @@ const update_1 = __importDefault(require("./commands/update"));
37
37
  const upgrade_1 = __importDefault(require("./commands/upgrade"));
38
38
  const verify_1 = __importDefault(require("./commands/verify"));
39
39
  const utils_2 = require("./utils");
40
+ /**
41
+ * Commands that never talk to a database and must not activate a driver.
42
+ * `init` owns its own `--pglite` meaning (scaffold from the PGlite boilerplates),
43
+ * and it runs before the plugin it would scaffold is even installed.
44
+ */
45
+ const ENGINE_EXEMPT_COMMANDS = new Set(['init']);
40
46
  const withPgTeardown = (fn, skipTeardown = false) => async (...args) => {
41
47
  try {
42
48
  await fn(...args);
@@ -144,7 +150,27 @@ const commands = async (argv, prompter, options) => {
144
150
  console.log(utils_2.usageText);
145
151
  await (0, inquirerer_1.cliExitWithError)(`Unknown command: ${command}`, { beforeExit: pg_cache_1.teardownPgPools });
146
152
  }
147
- await commandFn(newArgv, prompter, options);
153
+ // Activate the selected migration backend (`--engine`/`--driver`/`--pglite` or
154
+ // pgpm.json) before the command runs: the driver plugin registers its
155
+ // pool/client factories, so the unmodified engine targets it. The built-in
156
+ // `pg` engine activates nothing and behaves exactly as before.
157
+ const engineArgv = newArgv;
158
+ const { engine, capabilities } = ENGINE_EXEMPT_COMMANDS.has(command)
159
+ ? (0, utils_2.getActiveEngine)()
160
+ : await (0, utils_2.activateEngine)(engineArgv, engineArgv.cwd).catch(async (error) => {
161
+ await (0, inquirerer_1.cliExitWithError)(error.message, { beforeExit: pg_cache_1.teardownPgPools });
162
+ throw error;
163
+ });
164
+ try {
165
+ const blocked = (0, utils_2.engineCommandBlocker)(command, engine, capabilities);
166
+ if (blocked) {
167
+ await (0, inquirerer_1.cliExitWithError)(blocked, { beforeExit: pg_cache_1.teardownPgPools });
168
+ }
169
+ await commandFn(newArgv, prompter, options);
170
+ }
171
+ finally {
172
+ await (0, utils_2.deactivateEngine)();
173
+ }
148
174
  prompter.close();
149
175
  return argv;
150
176
  };
@@ -3,7 +3,7 @@ import { getEnvOptions } from '@pgpmjs/env';
3
3
  import { Logger } from '@pgpmjs/logger';
4
4
  import { execSync } from 'child_process';
5
5
  import { getPgEnvOptions, getSpawnEnvWithPg, } from 'pg-env';
6
- import { getTargetDatabase, resolvePackageAlias } from '../utils';
6
+ import { getActiveEngine, getTargetDatabase, resolvePackageAlias } from '../utils';
7
7
  import { selectPackage } from '../utils/module-utils';
8
8
  const deployUsageText = `
9
9
  Deploy Command:
@@ -15,11 +15,18 @@ Deploy Command:
15
15
  Options:
16
16
  --help, -h Show this help message
17
17
  --createdb Create database if it doesn't exist
18
+ --engine <name> Migration backend to deploy to (default: pg).
19
+ Built-in engines: pg (Postgres server), pglite (in-process WASM).
20
+ --driver <pkg> Driver plugin package to deploy through (escape hatch for --engine)
21
+ --pglite[=dataDir] Sugar for --engine pglite; persists to <dataDir> when given
18
22
  --recursive Deploy recursively through dependencies
19
23
  --package <name> Target specific package
20
24
  --to <target> Deploy to specific change or tag
21
25
  --tx Use transactions (default: true)
22
- --fast Use fast deployment strategy
26
+ --fast Fast strategy: one-shot SQL + bulk migration ledger,
27
+ reading each module's verified sql/*.bundle.tar.gz artifact
28
+ when present and building it from deploy/ when not
29
+ --bundled Alias for --fast
23
30
  --logOnly Log-only mode, skip script execution
24
31
  --usePlan Use deployment plan
25
32
  --cache Enable caching
@@ -30,6 +37,9 @@ Examples:
30
37
  pgpm deploy --createdb Deploy with database creation
31
38
  pgpm deploy --package mypackage --to @v1.0.0 Deploy specific package to tag
32
39
  pgpm deploy --fast --no-tx Fast deployment without transactions
40
+ pgpm deploy --bundled Same as --fast
41
+ pgpm deploy --engine pglite Deploy into in-process PGlite (no server)
42
+ pgpm deploy --pglite=./.pglite Deploy into a persisted PGlite data directory
33
43
  `;
34
44
  export default async (argv, prompter, _options) => {
35
45
  // Show usage if explicitly requested
@@ -88,6 +98,14 @@ export default async (argv, prompter, _options) => {
88
98
  default: false,
89
99
  required: false
90
100
  },
101
+ {
102
+ name: 'bundled',
103
+ type: 'confirm',
104
+ message: 'Prefer prebuilt bundle artifacts (alias of fast)?',
105
+ useDefault: true,
106
+ default: false,
107
+ required: false
108
+ },
91
109
  {
92
110
  name: 'logOnly',
93
111
  type: 'confirm',
@@ -97,17 +115,23 @@ export default async (argv, prompter, _options) => {
97
115
  required: false
98
116
  }
99
117
  ];
100
- let { yes, recursive, createdb, cwd, tx, fast, logOnly } = await prompter.prompt(argv, questions);
118
+ let { yes, recursive, createdb, cwd, tx, fast, bundled, logOnly } = await prompter.prompt(argv, questions);
101
119
  if (!yes) {
102
120
  log.info('Operation cancelled.');
103
121
  return;
104
122
  }
105
123
  log.debug(`Using current directory: ${cwd}`);
124
+ const { engine, capabilities } = getActiveEngine();
106
125
  if (createdb) {
107
- log.info(`Creating database ${database}...`);
108
- execSync(`createdb ${database}`, {
109
- env: getSpawnEnvWithPg(pgEnv)
110
- });
126
+ if (capabilities.createdb) {
127
+ log.info(`Creating database ${database}...`);
128
+ execSync(`createdb ${database}`, {
129
+ env: getSpawnEnvWithPg(pgEnv)
130
+ });
131
+ }
132
+ else {
133
+ log.info(`Skipping createdb: the "${engine.name}" engine's instance is the database.`);
134
+ }
111
135
  }
112
136
  let packageName;
113
137
  if (recursive) {
@@ -118,6 +142,7 @@ export default async (argv, prompter, _options) => {
118
142
  deployment: {
119
143
  useTx: tx !== false,
120
144
  fast: fast !== false,
145
+ bundled: bundled === true,
121
146
  usePlan: argv.usePlan !== false,
122
147
  cache: argv.cache !== false,
123
148
  logOnly: argv.logOnly !== false,
@@ -12,12 +12,14 @@ Options:
12
12
  --pretty Pretty-print output (default: true)
13
13
  --functionDelimiter <delimiter> Function delimiter (default: $EOFCODE$)
14
14
  --outputDiff Export AST diff files when round-trip mismatch detected (default: false)
15
+ --bundle Also emit sql/<name>--<version>.bundle.tar.gz (default: true)
15
16
  --cwd <directory> Working directory (default: current directory)
16
17
 
17
18
  Examples:
18
19
  pgpm package Package with defaults
19
20
  pgpm package --no-plan Package without plan
20
21
  pgpm package --outputDiff Package and export AST diff files if mismatch detected
22
+ pgpm package --no-bundle Package without emitting the bundle artifact
21
23
  `;
22
24
  export default async (argv, prompter, _options) => {
23
25
  // Show usage if explicitly requested
@@ -53,9 +55,16 @@ export default async (argv, prompter, _options) => {
53
55
  default: false,
54
56
  useDefault: true,
55
57
  required: false
58
+ },
59
+ {
60
+ type: 'confirm',
61
+ name: 'bundle',
62
+ default: true,
63
+ useDefault: true,
64
+ required: false
56
65
  }
57
66
  ];
58
- let { cwd, plan, pretty, functionDelimiter, outputDiff } = await prompter.prompt(argv, questions);
67
+ let { cwd, plan, pretty, functionDelimiter, outputDiff, bundle } = await prompter.prompt(argv, questions);
59
68
  const project = new PgpmPackage(cwd);
60
69
  project.ensureModule();
61
70
  const info = project.getModuleInfo();
@@ -67,7 +76,8 @@ export default async (argv, prompter, _options) => {
67
76
  packageDir: project.modulePath,
68
77
  pretty,
69
78
  functionDelimiter,
70
- outputDiff
79
+ outputDiff,
80
+ bundle: bundle !== false
71
81
  });
72
82
  return argv;
73
83
  };
@@ -20,6 +20,7 @@ Options:
20
20
  --to <target> Revert to specific change or tag
21
21
  --to Interactive selection of deployed changes
22
22
  --tx Use transactions (default: true)
23
+ --engine <name> Migration backend to revert in (default: pg; also pglite)
23
24
  --cwd <directory> Working directory (default: current directory)
24
25
 
25
26
  Examples:
@@ -19,6 +19,7 @@ Options:
19
19
  --package <name> Verify specific package
20
20
  --to <target> Verify up to specific change or tag
21
21
  --to Interactive selection of deployed changes
22
+ --engine <name> Migration backend to verify against (default: pg; also pglite)
22
23
  --cwd <directory> Working directory (default: current directory)
23
24
 
24
25
  Examples:
package/esm/commands.js CHANGED
@@ -30,7 +30,13 @@ import tune from './commands/tune';
30
30
  import updateCmd from './commands/update';
31
31
  import upgrade from './commands/upgrade';
32
32
  import verify from './commands/verify';
33
- import { usageText } from './utils';
33
+ import { activateEngine, deactivateEngine, engineCommandBlocker, getActiveEngine, usageText } from './utils';
34
+ /**
35
+ * Commands that never talk to a database and must not activate a driver.
36
+ * `init` owns its own `--pglite` meaning (scaffold from the PGlite boilerplates),
37
+ * and it runs before the plugin it would scaffold is even installed.
38
+ */
39
+ const ENGINE_EXEMPT_COMMANDS = new Set(['init']);
34
40
  const withPgTeardown = (fn, skipTeardown = false) => async (...args) => {
35
41
  try {
36
42
  await fn(...args);
@@ -137,7 +143,27 @@ export const commands = async (argv, prompter, options) => {
137
143
  console.log(usageText);
138
144
  await cliExitWithError(`Unknown command: ${command}`, { beforeExit: teardownPgPools });
139
145
  }
140
- await commandFn(newArgv, prompter, options);
146
+ // Activate the selected migration backend (`--engine`/`--driver`/`--pglite` or
147
+ // pgpm.json) before the command runs: the driver plugin registers its
148
+ // pool/client factories, so the unmodified engine targets it. The built-in
149
+ // `pg` engine activates nothing and behaves exactly as before.
150
+ const engineArgv = newArgv;
151
+ const { engine, capabilities } = ENGINE_EXEMPT_COMMANDS.has(command)
152
+ ? getActiveEngine()
153
+ : await activateEngine(engineArgv, engineArgv.cwd).catch(async (error) => {
154
+ await cliExitWithError(error.message, { beforeExit: teardownPgPools });
155
+ throw error;
156
+ });
157
+ try {
158
+ const blocked = engineCommandBlocker(command, engine, capabilities);
159
+ if (blocked) {
160
+ await cliExitWithError(blocked, { beforeExit: teardownPgPools });
161
+ }
162
+ await commandFn(newArgv, prompter, options);
163
+ }
164
+ finally {
165
+ await deactivateEngine();
166
+ }
141
167
  prompter.close();
142
168
  return argv;
143
169
  };
@@ -50,6 +50,11 @@ export const usageText = `
50
50
  -h, --help Display this help information
51
51
  -v, --version Display version information
52
52
  --cwd <directory> Working directory (default: current directory)
53
+ --engine <name> Migration backend: pg (default, Postgres server) or
54
+ pglite (in-process WASM Postgres, no server). Also set by
55
+ the "engine" key of pgpm.json or PGPM_ENGINE.
56
+ --driver <pkg> Driver plugin package backing the engine (escape hatch)
57
+ --pglite[=dataDir] Sugar for --engine pglite, persisted to <dataDir> if given
53
58
 
54
59
  Individual Command Help:
55
60
  pgpm <command> --help Display detailed help for specific command
@@ -0,0 +1,28 @@
1
+ /**
2
+ * Commands that need a capability the backend may not have. Gating is declared
3
+ * here rather than branching per backend inside each command, so a new driver
4
+ * plugin only has to describe itself through {@link PgpmDriverCapabilities}.
5
+ */
6
+ const COMMAND_REQUIREMENTS = {
7
+ docker: 'serverLifecycle',
8
+ kill: 'serverLifecycle',
9
+ tune: 'serverLifecycle',
10
+ 'admin-users': 'multiConnection',
11
+ dump: 'dump',
12
+ };
13
+ const REQUIREMENT_REASONS = {
14
+ createdb: 'it cannot create databases (the instance is the database)',
15
+ dump: 'it has no pg_dump',
16
+ serverLifecycle: 'it has no server to manage',
17
+ multiConnection: 'it is a single-session backend',
18
+ };
19
+ /**
20
+ * The reason a command cannot run on the active engine, or undefined when it can.
21
+ */
22
+ export const engineCommandBlocker = (command, engine, capabilities) => {
23
+ const requirement = COMMAND_REQUIREMENTS[command];
24
+ if (!requirement || capabilities[requirement])
25
+ return undefined;
26
+ return (`pgpm ${command} is not supported by the "${engine.name}" engine — ` +
27
+ `${REQUIREMENT_REASONS[requirement]}.`);
28
+ };
@@ -0,0 +1,89 @@
1
+ import { getEnvOptions } from '@pgpmjs/env';
2
+ import { BUILTIN_ENGINES, DEFAULT_ENGINE, } from '@pgpmjs/types';
3
+ import { activateDriver, driverOverrideFromArgv, PGLITE_DRIVER_PLUGIN } from './driver';
4
+ /** Everything a real Postgres server can do — the built-in `pg` engine. */
5
+ export const SERVER_CAPABILITIES = {
6
+ createdb: true,
7
+ dump: true,
8
+ serverLifecycle: true,
9
+ multiConnection: true,
10
+ };
11
+ /**
12
+ * The engines available by name: the built-ins, overridden/extended by the
13
+ * `engines` block of `pgpm.json` (sqitch's `[engine "name"]` sections).
14
+ *
15
+ * A configured entry is merged over its built-in, so declaring only options
16
+ * (e.g. `engines.pglite.options.dataDir`) keeps the built-in plugin.
17
+ */
18
+ export const engineDefinitions = (config) => {
19
+ const definitions = { ...BUILTIN_ENGINES };
20
+ for (const [name, configured] of Object.entries(config.engines ?? {})) {
21
+ definitions[name] = { ...definitions[name], ...configured };
22
+ }
23
+ return definitions;
24
+ };
25
+ const definitionToDriver = (definition, options) => definition.plugin
26
+ ? { plugin: definition.plugin, options: { ...definition.options, ...options } }
27
+ : undefined;
28
+ const lookupEngine = (name, config, options) => {
29
+ const definition = engineDefinitions(config)[name];
30
+ if (!definition) {
31
+ const known = Object.keys(engineDefinitions(config)).sort().join(', ');
32
+ throw new Error(`Unknown pgpm engine "${name}". Known engines: ${known}.\n` +
33
+ 'Declare it in the "engines" block of pgpm.json, or name its plugin with --driver <package>.');
34
+ }
35
+ return { name, driver: definitionToDriver(definition, options) };
36
+ };
37
+ /**
38
+ * Resolve which engine a command targets, mirroring sqitch's `core.engine` model:
39
+ * a short engine name selects the backend, and the built-in default is `pg`.
40
+ *
41
+ * Precedence (first match wins):
42
+ * 1. `--driver <package>` — name a driver plugin directly (escape hatch)
43
+ * 2. `--pglite[=dataDir]` — sugar for `--engine pglite`
44
+ * 3. `--engine <name>`
45
+ * 4. the `driver` block of `pgpm.json` — plugin-level configuration
46
+ * 5. the `engine` key of `pgpm.json` / `PGPM_ENGINE`
47
+ * 6. `pg`, the built-in server path
48
+ */
49
+ export const resolveEngine = (argv, config) => {
50
+ const override = driverOverrideFromArgv(argv);
51
+ if (override) {
52
+ if (override.plugin === PGLITE_DRIVER_PLUGIN && !argv.driver) {
53
+ // `--pglite` is the pglite engine, so it inherits that engine's configured
54
+ // options (e.g. a dataDir declared in pgpm.json) instead of bypassing them.
55
+ return lookupEngine('pglite', config, override.options);
56
+ }
57
+ return { name: override.plugin, driver: override };
58
+ }
59
+ if (argv.engine)
60
+ return lookupEngine(argv.engine, config);
61
+ if (config.driver)
62
+ return { name: config.driver.plugin, driver: config.driver };
63
+ return lookupEngine(config.engine ?? DEFAULT_ENGINE, config);
64
+ };
65
+ let active;
66
+ /**
67
+ * The engine activated for the running command. Commands read this to skip
68
+ * server-only steps (e.g. `deploy --createdb`) instead of hard-coding backends.
69
+ * Defaults to the built-in server path when nothing has been activated.
70
+ */
71
+ export const getActiveEngine = () => active ?? { engine: { name: DEFAULT_ENGINE }, capabilities: SERVER_CAPABILITIES };
72
+ /**
73
+ * Resolve the engine from argv + `pgpm.json` and activate its driver plugin.
74
+ * The driver has registered its pool/client factories by the time this resolves,
75
+ * so the unmodified pgpm engine runs against the selected backend.
76
+ */
77
+ export const activateEngine = async (argv, cwd = process.cwd()) => {
78
+ const config = getEnvOptions({}, cwd);
79
+ const engine = resolveEngine(argv, config);
80
+ const session = await activateDriver(engine.driver, undefined, cwd);
81
+ active = { engine, session, capabilities: session?.capabilities ?? SERVER_CAPABILITIES };
82
+ return active;
83
+ };
84
+ /** Tear down the active driver session and restore the built-in server path. */
85
+ export const deactivateEngine = async () => {
86
+ const session = active?.session;
87
+ active = undefined;
88
+ await session?.teardown();
89
+ };
@@ -3,6 +3,8 @@ export * from './deployed-changes';
3
3
  export * from './display';
4
4
  export * from './doctor';
5
5
  export * from './driver';
6
+ export * from './engine';
7
+ export * from './engine-gating';
6
8
  export * from './module-utils';
7
9
  export * from './npm-version';
8
10
  export * from './package-alias';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pgpm",
3
- "version": "5.13.0",
3
+ "version": "5.15.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,19 +46,19 @@
46
46
  },
47
47
  "dependencies": {
48
48
  "@inquirerer/utils": "^3.3.9",
49
- "@pgpmjs/core": "^7.14.0",
50
- "@pgpmjs/env": "^2.37.0",
51
- "@pgpmjs/export": "^1.12.1",
49
+ "@pgpmjs/core": "^7.16.0",
50
+ "@pgpmjs/env": "^2.39.0",
51
+ "@pgpmjs/export": "^1.12.3",
52
52
  "@pgpmjs/logger": "^2.22.0",
53
- "@pgpmjs/slice": "^0.10.0",
54
- "@pgpmjs/types": "^2.45.0",
53
+ "@pgpmjs/slice": "^0.10.2",
54
+ "@pgpmjs/types": "^2.47.0",
55
55
  "@pgsql/quotes": "^18.1.0",
56
56
  "appstash": "^0.7.0",
57
57
  "find-and-require-package-json": "^0.9.1",
58
58
  "genomic": "^5.6.2",
59
59
  "inquirerer": "^4.9.1",
60
60
  "js-yaml": "^4.1.0",
61
- "pg-cache": "^3.23.0",
61
+ "pg-cache": "^3.23.2",
62
62
  "pg-env": "^1.26.0",
63
63
  "pgsql-deparser": "^18.1.1",
64
64
  "semver": "^7.8.1",
@@ -77,5 +77,5 @@
77
77
  "pg",
78
78
  "pgsql"
79
79
  ],
80
- "gitHead": "d85911699e8a50a9fdfeb7506195442525c1f238"
80
+ "gitHead": "0286d994d98717cb9b28842233d2db6d1e37c48e"
81
81
  }
@@ -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 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\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 package Package module for distribution\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
@@ -53,6 +53,11 @@ exports.usageText = `
53
53
  -h, --help Display this help information
54
54
  -v, --version Display version information
55
55
  --cwd <directory> Working directory (default: current directory)
56
+ --engine <name> Migration backend: pg (default, Postgres server) or
57
+ pglite (in-process WASM Postgres, no server). Also set by
58
+ the "engine" key of pgpm.json or PGPM_ENGINE.
59
+ --driver <pkg> Driver plugin package backing the engine (escape hatch)
60
+ --pglite[=dataDir] Sugar for --engine pglite, persisted to <dataDir> if given
56
61
 
57
62
  Individual Command Help:
58
63
  pgpm <command> --help Display detailed help for specific command
@@ -0,0 +1,6 @@
1
+ import { PgpmDriverCapabilities } from '@pgpmjs/types';
2
+ import { ResolvedEngine } from './engine';
3
+ /**
4
+ * The reason a command cannot run on the active engine, or undefined when it can.
5
+ */
6
+ export declare const engineCommandBlocker: (command: string, engine: ResolvedEngine, capabilities: PgpmDriverCapabilities) => string | undefined;
@@ -0,0 +1,32 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.engineCommandBlocker = void 0;
4
+ /**
5
+ * Commands that need a capability the backend may not have. Gating is declared
6
+ * here rather than branching per backend inside each command, so a new driver
7
+ * plugin only has to describe itself through {@link PgpmDriverCapabilities}.
8
+ */
9
+ const COMMAND_REQUIREMENTS = {
10
+ docker: 'serverLifecycle',
11
+ kill: 'serverLifecycle',
12
+ tune: 'serverLifecycle',
13
+ 'admin-users': 'multiConnection',
14
+ dump: 'dump',
15
+ };
16
+ const REQUIREMENT_REASONS = {
17
+ createdb: 'it cannot create databases (the instance is the database)',
18
+ dump: 'it has no pg_dump',
19
+ serverLifecycle: 'it has no server to manage',
20
+ multiConnection: 'it is a single-session backend',
21
+ };
22
+ /**
23
+ * The reason a command cannot run on the active engine, or undefined when it can.
24
+ */
25
+ const engineCommandBlocker = (command, engine, capabilities) => {
26
+ const requirement = COMMAND_REQUIREMENTS[command];
27
+ if (!requirement || capabilities[requirement])
28
+ return undefined;
29
+ return (`pgpm ${command} is not supported by the "${engine.name}" engine — ` +
30
+ `${REQUIREMENT_REASONS[requirement]}.`);
31
+ };
32
+ exports.engineCommandBlocker = engineCommandBlocker;
@@ -0,0 +1,57 @@
1
+ import { PgpmDriverCapabilities, PgpmDriverConfig, PgpmDriverSession, PgpmEngineConfig, PgpmOptions } from '@pgpmjs/types';
2
+ /** Everything a real Postgres server can do — the built-in `pg` engine. */
3
+ export declare const SERVER_CAPABILITIES: PgpmDriverCapabilities;
4
+ /** The engine/driver selectors every command accepts. */
5
+ export interface EngineArgv {
6
+ engine?: string;
7
+ driver?: string;
8
+ pglite?: boolean | string;
9
+ }
10
+ /** The engine a command runs against, plus the driver plugin backing it. */
11
+ export interface ResolvedEngine {
12
+ /** Engine name, for messages: a registered name or a plugin package. */
13
+ name: string;
14
+ /** Driver plugin to activate; undefined = built-in `pg` (server) path. */
15
+ driver?: PgpmDriverConfig;
16
+ }
17
+ /**
18
+ * The engines available by name: the built-ins, overridden/extended by the
19
+ * `engines` block of `pgpm.json` (sqitch's `[engine "name"]` sections).
20
+ *
21
+ * A configured entry is merged over its built-in, so declaring only options
22
+ * (e.g. `engines.pglite.options.dataDir`) keeps the built-in plugin.
23
+ */
24
+ export declare const engineDefinitions: (config: PgpmOptions) => Record<string, PgpmEngineConfig>;
25
+ /**
26
+ * Resolve which engine a command targets, mirroring sqitch's `core.engine` model:
27
+ * a short engine name selects the backend, and the built-in default is `pg`.
28
+ *
29
+ * Precedence (first match wins):
30
+ * 1. `--driver <package>` — name a driver plugin directly (escape hatch)
31
+ * 2. `--pglite[=dataDir]` — sugar for `--engine pglite`
32
+ * 3. `--engine <name>`
33
+ * 4. the `driver` block of `pgpm.json` — plugin-level configuration
34
+ * 5. the `engine` key of `pgpm.json` / `PGPM_ENGINE`
35
+ * 6. `pg`, the built-in server path
36
+ */
37
+ export declare const resolveEngine: (argv: EngineArgv, config: PgpmOptions) => ResolvedEngine;
38
+ /** The engine activated for the running command. */
39
+ export interface ActiveEngine {
40
+ engine: ResolvedEngine;
41
+ session?: PgpmDriverSession;
42
+ capabilities: PgpmDriverCapabilities;
43
+ }
44
+ /**
45
+ * The engine activated for the running command. Commands read this to skip
46
+ * server-only steps (e.g. `deploy --createdb`) instead of hard-coding backends.
47
+ * Defaults to the built-in server path when nothing has been activated.
48
+ */
49
+ export declare const getActiveEngine: () => ActiveEngine;
50
+ /**
51
+ * Resolve the engine from argv + `pgpm.json` and activate its driver plugin.
52
+ * The driver has registered its pool/client factories by the time this resolves,
53
+ * so the unmodified pgpm engine runs against the selected backend.
54
+ */
55
+ export declare const activateEngine: (argv: EngineArgv, cwd?: string) => Promise<ActiveEngine>;
56
+ /** Tear down the active driver session and restore the built-in server path. */
57
+ export declare const deactivateEngine: () => Promise<void>;
@@ -0,0 +1,97 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.deactivateEngine = exports.activateEngine = exports.getActiveEngine = exports.resolveEngine = exports.engineDefinitions = exports.SERVER_CAPABILITIES = void 0;
4
+ const env_1 = require("@pgpmjs/env");
5
+ const types_1 = require("@pgpmjs/types");
6
+ const driver_1 = require("./driver");
7
+ /** Everything a real Postgres server can do — the built-in `pg` engine. */
8
+ exports.SERVER_CAPABILITIES = {
9
+ createdb: true,
10
+ dump: true,
11
+ serverLifecycle: true,
12
+ multiConnection: true,
13
+ };
14
+ /**
15
+ * The engines available by name: the built-ins, overridden/extended by the
16
+ * `engines` block of `pgpm.json` (sqitch's `[engine "name"]` sections).
17
+ *
18
+ * A configured entry is merged over its built-in, so declaring only options
19
+ * (e.g. `engines.pglite.options.dataDir`) keeps the built-in plugin.
20
+ */
21
+ const engineDefinitions = (config) => {
22
+ const definitions = { ...types_1.BUILTIN_ENGINES };
23
+ for (const [name, configured] of Object.entries(config.engines ?? {})) {
24
+ definitions[name] = { ...definitions[name], ...configured };
25
+ }
26
+ return definitions;
27
+ };
28
+ exports.engineDefinitions = engineDefinitions;
29
+ const definitionToDriver = (definition, options) => definition.plugin
30
+ ? { plugin: definition.plugin, options: { ...definition.options, ...options } }
31
+ : undefined;
32
+ const lookupEngine = (name, config, options) => {
33
+ const definition = (0, exports.engineDefinitions)(config)[name];
34
+ if (!definition) {
35
+ const known = Object.keys((0, exports.engineDefinitions)(config)).sort().join(', ');
36
+ throw new Error(`Unknown pgpm engine "${name}". Known engines: ${known}.\n` +
37
+ 'Declare it in the "engines" block of pgpm.json, or name its plugin with --driver <package>.');
38
+ }
39
+ return { name, driver: definitionToDriver(definition, options) };
40
+ };
41
+ /**
42
+ * Resolve which engine a command targets, mirroring sqitch's `core.engine` model:
43
+ * a short engine name selects the backend, and the built-in default is `pg`.
44
+ *
45
+ * Precedence (first match wins):
46
+ * 1. `--driver <package>` — name a driver plugin directly (escape hatch)
47
+ * 2. `--pglite[=dataDir]` — sugar for `--engine pglite`
48
+ * 3. `--engine <name>`
49
+ * 4. the `driver` block of `pgpm.json` — plugin-level configuration
50
+ * 5. the `engine` key of `pgpm.json` / `PGPM_ENGINE`
51
+ * 6. `pg`, the built-in server path
52
+ */
53
+ const resolveEngine = (argv, config) => {
54
+ const override = (0, driver_1.driverOverrideFromArgv)(argv);
55
+ if (override) {
56
+ if (override.plugin === driver_1.PGLITE_DRIVER_PLUGIN && !argv.driver) {
57
+ // `--pglite` is the pglite engine, so it inherits that engine's configured
58
+ // options (e.g. a dataDir declared in pgpm.json) instead of bypassing them.
59
+ return lookupEngine('pglite', config, override.options);
60
+ }
61
+ return { name: override.plugin, driver: override };
62
+ }
63
+ if (argv.engine)
64
+ return lookupEngine(argv.engine, config);
65
+ if (config.driver)
66
+ return { name: config.driver.plugin, driver: config.driver };
67
+ return lookupEngine(config.engine ?? types_1.DEFAULT_ENGINE, config);
68
+ };
69
+ exports.resolveEngine = resolveEngine;
70
+ let active;
71
+ /**
72
+ * The engine activated for the running command. Commands read this to skip
73
+ * server-only steps (e.g. `deploy --createdb`) instead of hard-coding backends.
74
+ * Defaults to the built-in server path when nothing has been activated.
75
+ */
76
+ const getActiveEngine = () => active ?? { engine: { name: types_1.DEFAULT_ENGINE }, capabilities: exports.SERVER_CAPABILITIES };
77
+ exports.getActiveEngine = getActiveEngine;
78
+ /**
79
+ * Resolve the engine from argv + `pgpm.json` and activate its driver plugin.
80
+ * The driver has registered its pool/client factories by the time this resolves,
81
+ * so the unmodified pgpm engine runs against the selected backend.
82
+ */
83
+ const activateEngine = async (argv, cwd = process.cwd()) => {
84
+ const config = (0, env_1.getEnvOptions)({}, cwd);
85
+ const engine = (0, exports.resolveEngine)(argv, config);
86
+ const session = await (0, driver_1.activateDriver)(engine.driver, undefined, cwd);
87
+ active = { engine, session, capabilities: session?.capabilities ?? exports.SERVER_CAPABILITIES };
88
+ return active;
89
+ };
90
+ exports.activateEngine = activateEngine;
91
+ /** Tear down the active driver session and restore the built-in server path. */
92
+ const deactivateEngine = async () => {
93
+ const session = active?.session;
94
+ active = undefined;
95
+ await session?.teardown();
96
+ };
97
+ exports.deactivateEngine = deactivateEngine;
package/utils/index.d.ts CHANGED
@@ -3,6 +3,8 @@ export * from './deployed-changes';
3
3
  export * from './display';
4
4
  export * from './doctor';
5
5
  export * from './driver';
6
+ export * from './engine';
7
+ export * from './engine-gating';
6
8
  export * from './module-utils';
7
9
  export * from './npm-version';
8
10
  export * from './package-alias';
package/utils/index.js CHANGED
@@ -19,6 +19,8 @@ __exportStar(require("./deployed-changes"), exports);
19
19
  __exportStar(require("./display"), exports);
20
20
  __exportStar(require("./doctor"), exports);
21
21
  __exportStar(require("./driver"), exports);
22
+ __exportStar(require("./engine"), exports);
23
+ __exportStar(require("./engine-gating"), exports);
22
24
  __exportStar(require("./module-utils"), exports);
23
25
  __exportStar(require("./npm-version"), exports);
24
26
  __exportStar(require("./package-alias"), exports);