@pgpmjs/core 4.1.2 → 4.2.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.
@@ -3,9 +3,77 @@ import { sync as glob } from 'glob';
3
3
  import { toSnakeCase } from 'komoji';
4
4
  import path from 'path';
5
5
  import { getPgPool } from 'pg-cache';
6
- import { writeSqitchFiles, writeSqitchPlan } from '../files';
6
+ import { writePgpmFiles, writePgpmPlan } from '../files';
7
+ import { getMissingInstallableModules } from '../modules/modules';
7
8
  import { exportMeta } from './export-meta';
8
- const exportMigrationsToDisk = async ({ project, options, database, databaseId, author, outdir, schema_names, extensionName, metaExtensionName }) => {
9
+ /**
10
+ * Required extensions for database schema exports.
11
+ * Includes native PostgreSQL extensions and pgpm modules.
12
+ */
13
+ const DB_REQUIRED_EXTENSIONS = [
14
+ 'plpgsql',
15
+ 'uuid-ossp',
16
+ 'citext',
17
+ 'pgcrypto',
18
+ 'btree_gist',
19
+ 'postgis',
20
+ 'hstore',
21
+ 'db-meta-schema',
22
+ 'pgpm-inflection',
23
+ 'pgpm-uuid',
24
+ 'pgpm-utils',
25
+ 'pgpm-database-jobs',
26
+ 'pgpm-jwt-claims',
27
+ 'pgpm-stamps',
28
+ 'pgpm-base32',
29
+ 'pgpm-totp',
30
+ 'pgpm-types'
31
+ ];
32
+ /**
33
+ * Required extensions for service/meta exports.
34
+ * Includes native PostgreSQL extensions and pgpm modules for metadata management.
35
+ */
36
+ const SERVICE_REQUIRED_EXTENSIONS = [
37
+ 'plpgsql',
38
+ 'db-meta-schema',
39
+ 'db-meta-modules'
40
+ ];
41
+ /**
42
+ * Checks which pgpm modules from the extensions list are missing from the workspace
43
+ * and prompts the user to install them if a prompter is provided.
44
+ *
45
+ * @param project - The PgpmPackage instance
46
+ * @param extensions - List of extension names (control file names)
47
+ * @param prompter - Optional prompter for interactive confirmation
48
+ * @returns List of extensions that were successfully installed
49
+ */
50
+ const promptAndInstallMissingModules = async (project, extensions, prompter) => {
51
+ const { installed } = project.getInstalledModules();
52
+ const missingModules = getMissingInstallableModules(extensions, installed);
53
+ if (missingModules.length === 0) {
54
+ return [];
55
+ }
56
+ const missingNames = missingModules.map(m => m.npmName);
57
+ console.log(`\nMissing pgpm modules detected: ${missingNames.join(', ')}`);
58
+ if (prompter) {
59
+ const { install } = await prompter.prompt({}, [
60
+ {
61
+ type: 'confirm',
62
+ name: 'install',
63
+ message: `Install missing modules (${missingNames.join(', ')})?`,
64
+ default: true
65
+ }
66
+ ]);
67
+ if (install) {
68
+ console.log('Installing missing modules...');
69
+ await project.installModules(...missingNames);
70
+ console.log('Modules installed successfully.');
71
+ return missingModules.map(m => m.controlName);
72
+ }
73
+ }
74
+ return [];
75
+ };
76
+ const exportMigrationsToDisk = async ({ project, options, database, databaseId, databaseName, author, outdir, schema_names, extensionName, extensionDesc, metaExtensionName, metaExtensionDesc, prompter }) => {
9
77
  outdir = outdir + '/';
10
78
  const pgPool = getPgPool({
11
79
  ...options.pg,
@@ -33,46 +101,38 @@ const exportMigrationsToDisk = async ({ project, options, database, databaseId,
33
101
  outdir,
34
102
  author
35
103
  };
104
+ // Build description for the database extension package
105
+ const dbExtensionDesc = extensionDesc || `${name} database schema for ${databaseName}`;
36
106
  if (results?.rows?.length > 0) {
107
+ await promptAndInstallMissingModules(project, [...DB_REQUIRED_EXTENSIONS], prompter);
37
108
  await preparePackage({
38
109
  project,
39
110
  author,
40
111
  outdir,
41
112
  name,
42
- extensions: [
43
- 'plpgsql',
44
- 'uuid-ossp',
45
- 'citext',
46
- 'pgcrypto',
47
- 'btree_gist',
48
- 'postgis',
49
- 'hstore',
50
- 'db-meta-schema',
51
- 'pgpm-inflection',
52
- 'pgpm-uuid',
53
- 'pgpm-utils',
54
- 'pgpm-database-jobs',
55
- 'pgpm-jwt-claims',
56
- 'pgpm-stamps',
57
- 'pgpm-base32',
58
- 'pgpm-totp',
59
- 'pgpm-types'
60
- ]
113
+ description: dbExtensionDesc,
114
+ extensions: [...DB_REQUIRED_EXTENSIONS],
115
+ prompter
61
116
  });
62
- writeSqitchPlan(results.rows, opts);
63
- writeSqitchFiles(results.rows, opts);
117
+ writePgpmPlan(results.rows, opts);
118
+ writePgpmFiles(results.rows, opts);
64
119
  let meta = await exportMeta({
65
120
  opts: options,
66
121
  dbname: database,
67
122
  database_id: databaseId
68
123
  });
69
124
  meta = replacer(meta);
125
+ // Build description for the meta/service extension package
126
+ const metaDesc = metaExtensionDesc || `${metaExtensionName} service utilities for managing domains, APIs, and services`;
127
+ await promptAndInstallMissingModules(project, [...SERVICE_REQUIRED_EXTENSIONS], prompter);
70
128
  await preparePackage({
71
129
  project,
72
130
  author,
73
131
  outdir,
74
- extensions: ['plpgsql', 'db-meta-schema', 'db-meta-modules'],
75
- name: metaExtensionName
132
+ name: metaExtensionName,
133
+ description: metaDesc,
134
+ extensions: [...SERVICE_REQUIRED_EXTENSIONS],
135
+ prompter
76
136
  });
77
137
  const metaReplacer = makeReplacer({
78
138
  schemas: schemas.rows.filter((schema) => schema_names.includes(schema.schema_name)),
@@ -100,11 +160,15 @@ $LQLMIGRATION$;
100
160
 
101
161
  ${meta}
102
162
 
103
- UPDATE meta_public.apis
104
- SET dbname = current_database() WHERE TRUE;
163
+ -- TODO: Research needed - These UPDATE statements may be a security leak.
164
+ -- They appear to rebind exported metadata to the target database after import,
165
+ -- but exposing dbname in meta_public tables could leak internal database names.
166
+ -- Consider removing entirely or gating behind an explicit flag.
167
+ -- UPDATE meta_public.apis
168
+ -- SET dbname = current_database() WHERE TRUE;
105
169
 
106
- UPDATE meta_public.sites
107
- SET dbname = current_database() WHERE TRUE;
170
+ -- UPDATE meta_public.sites
171
+ -- SET dbname = current_database() WHERE TRUE;
108
172
 
109
173
  SET session_replication_role TO DEFAULT;
110
174
  `
@@ -112,48 +176,73 @@ SET session_replication_role TO DEFAULT;
112
176
  ];
113
177
  opts.replacer = metaReplacer.replacer;
114
178
  opts.name = metaExtensionName;
115
- writeSqitchPlan(metaPackage, opts);
116
- writeSqitchFiles(metaPackage, opts);
179
+ writePgpmPlan(metaPackage, opts);
180
+ writePgpmFiles(metaPackage, opts);
117
181
  }
118
182
  pgPool.end();
119
183
  };
120
- export const exportMigrations = async ({ project, options, dbInfo, author, outdir, schema_names, extensionName, metaExtensionName }) => {
184
+ export const exportMigrations = async ({ project, options, dbInfo, author, outdir, schema_names, extensionName, extensionDesc, metaExtensionName, metaExtensionDesc, prompter }) => {
121
185
  for (let v = 0; v < dbInfo.database_ids.length; v++) {
122
186
  const databaseId = dbInfo.database_ids[v];
123
187
  await exportMigrationsToDisk({
124
188
  project,
125
189
  options,
126
190
  extensionName,
191
+ extensionDesc,
127
192
  metaExtensionName,
193
+ metaExtensionDesc,
128
194
  database: dbInfo.dbname,
195
+ databaseName: dbInfo.databaseName,
129
196
  databaseId,
130
197
  schema_names,
131
198
  author,
132
- outdir
199
+ outdir,
200
+ prompter
133
201
  });
134
202
  }
135
203
  };
136
204
  /**
137
- * Creates a Sqitch package directory or resets the deploy/revert/verify directories if it exists.
205
+ * Creates a PGPM package directory or resets the deploy/revert/verify directories if it exists.
206
+ * If the module already exists and a prompter is provided, prompts the user for confirmation.
138
207
  */
139
- const preparePackage = async ({ project, author, outdir, name, extensions }) => {
208
+ const preparePackage = async ({ project, author, outdir, name, description, extensions, prompter }) => {
140
209
  const curDir = process.cwd();
141
- const sqitchDir = path.resolve(path.join(outdir, name));
142
- mkdirSync(sqitchDir, { recursive: true });
143
- process.chdir(sqitchDir);
144
- const plan = glob(path.join(sqitchDir, 'pgpm.plan'));
210
+ const pgpmDir = path.resolve(path.join(outdir, name));
211
+ mkdirSync(pgpmDir, { recursive: true });
212
+ process.chdir(pgpmDir);
213
+ const plan = glob(path.join(pgpmDir, 'pgpm.plan'));
145
214
  if (!plan.length) {
146
215
  await project.initModule({
147
216
  name,
148
- description: name,
217
+ description,
149
218
  author,
150
219
  extensions,
220
+ answers: {
221
+ moduleName: name,
222
+ moduleDesc: description,
223
+ access: 'restricted',
224
+ license: 'CLOSED'
225
+ }
151
226
  });
152
227
  }
153
228
  else {
154
- rmSync(path.resolve(sqitchDir, 'deploy'), { recursive: true, force: true });
155
- rmSync(path.resolve(sqitchDir, 'revert'), { recursive: true, force: true });
156
- rmSync(path.resolve(sqitchDir, 'verify'), { recursive: true, force: true });
229
+ if (prompter) {
230
+ const { overwrite } = await prompter.prompt({}, [
231
+ {
232
+ type: 'confirm',
233
+ name: 'overwrite',
234
+ message: `Module "${name}" already exists at ${pgpmDir}. Overwrite deploy/revert/verify directories?`,
235
+ default: false
236
+ }
237
+ ]);
238
+ if (!overwrite) {
239
+ process.chdir(curDir);
240
+ throw new Error(`Export cancelled: Module "${name}" already exists.`);
241
+ }
242
+ }
243
+ rmSync(path.resolve(pgpmDir, 'deploy'), { recursive: true, force: true });
244
+ rmSync(path.resolve(pgpmDir, 'revert'), { recursive: true, force: true });
245
+ rmSync(path.resolve(pgpmDir, 'verify'), { recursive: true, force: true });
157
246
  }
158
247
  process.chdir(curDir);
159
248
  };
@@ -1,9 +1,9 @@
1
1
  import fs from 'fs';
2
2
  import path from 'path';
3
3
  /**
4
- * Write a Sqitch plan file based on the provided rows
4
+ * Write a PGPM plan file based on the provided rows
5
5
  */
6
- export function writeSqitchPlan(rows, opts) {
6
+ export function writePgpmPlan(rows, opts) {
7
7
  const dir = path.resolve(path.join(opts.outdir, opts.name));
8
8
  fs.mkdirSync(dir, { recursive: true });
9
9
  const date = () => '2017-08-11T08:11:51Z'; // stubbed timestamp
@@ -126,3 +126,7 @@ export function generateTagLineContent(tag) {
126
126
  }
127
127
  return line;
128
128
  }
129
+ /**
130
+ * @deprecated Use writePgpmPlan instead. This alias is kept for backwards compatibility.
131
+ */
132
+ export const writeSqitchPlan = writePgpmPlan;
@@ -2,9 +2,9 @@ import { getEnvOptions } from '@pgpmjs/env';
2
2
  import fs from 'fs';
3
3
  import path from 'path';
4
4
  /**
5
- * Write SQL files for Sqitch migrations (deploy, revert, verify)
5
+ * Write SQL files for PGPM migrations (deploy, revert, verify)
6
6
  */
7
- export const writeSqitchFiles = (rows, opts) => {
7
+ export const writePgpmFiles = (rows, opts) => {
8
8
  rows.forEach((row) => writeVerify(row, opts));
9
9
  rows.forEach((row) => writeRevert(row, opts));
10
10
  rows.forEach((row) => writeDeploy(row, opts));
@@ -18,7 +18,7 @@ const ordered = (arr) => {
18
18
  return arr.sort((a, b) => a.length - b.length || a.localeCompare(b));
19
19
  };
20
20
  /**
21
- * Write a deploy SQL file for a Sqitch change
21
+ * Write a deploy SQL file for a PGPM change
22
22
  */
23
23
  const writeDeploy = (row, opts) => {
24
24
  const globalOpts = getEnvOptions({
@@ -50,7 +50,7 @@ ${useTx ? 'COMMIT;' : ''}
50
50
  fs.writeFileSync(actualFile, content);
51
51
  };
52
52
  /**
53
- * Write a verify SQL file for a Sqitch change
53
+ * Write a verify SQL file for a PGPM change
54
54
  */
55
55
  const writeVerify = (row, opts) => {
56
56
  const globalOpts = getEnvOptions({
@@ -78,7 +78,7 @@ ${useTx ? 'COMMIT;' : ''}
78
78
  fs.writeFileSync(actualFile, content);
79
79
  };
80
80
  /**
81
- * Write a revert SQL file for a Sqitch change
81
+ * Write a revert SQL file for a PGPM change
82
82
  */
83
83
  const writeRevert = (row, opts) => {
84
84
  const globalOpts = getEnvOptions({
@@ -105,3 +105,7 @@ ${useTx ? 'COMMIT;' : ''}
105
105
  `;
106
106
  fs.writeFileSync(actualFile, content);
107
107
  };
108
+ /**
109
+ * @deprecated Use writePgpmFiles instead. This alias is kept for backwards compatibility.
110
+ */
111
+ export const writeSqitchFiles = writePgpmFiles;
@@ -1,5 +1,44 @@
1
1
  import { getLatestChange } from '../files';
2
2
  import { errors } from '@pgpmjs/types';
3
+ /**
4
+ * Mapping from control file names (used in extensions list) to npm package names.
5
+ * Only includes modules that can be installed via pgpm install from @pgpm/* packages.
6
+ * Native PostgreSQL extensions (plpgsql, uuid-ossp, etc.) are not included.
7
+ */
8
+ export const PGPM_MODULE_MAP = {
9
+ 'pgpm-base32': '@pgpm/base32',
10
+ 'pgpm-database-jobs': '@pgpm/database-jobs',
11
+ 'db-meta-modules': '@pgpm/db-meta-modules',
12
+ 'db-meta-schema': '@pgpm/db-meta-schema',
13
+ 'pgpm-inflection': '@pgpm/inflection',
14
+ 'pgpm-jwt-claims': '@pgpm/jwt-claims',
15
+ 'pgpm-stamps': '@pgpm/stamps',
16
+ 'pgpm-totp': '@pgpm/totp',
17
+ 'pgpm-types': '@pgpm/types',
18
+ 'pgpm-utils': '@pgpm/utils',
19
+ 'pgpm-uuid': '@pgpm/uuid'
20
+ };
21
+ /**
22
+ * Determines which pgpm modules from an extensions list are missing from the installed modules.
23
+ * Only checks modules that are in PGPM_MODULE_MAP (installable via pgpm install).
24
+ *
25
+ * @param extensions - List of extension/control file names to check
26
+ * @param installedModules - List of installed npm package names (e.g., '@pgpm/base32')
27
+ * @returns Array of missing modules with their control names and npm package names
28
+ */
29
+ export const getMissingInstallableModules = (extensions, installedModules) => {
30
+ const missingModules = [];
31
+ for (const ext of extensions) {
32
+ const npmName = PGPM_MODULE_MAP[ext];
33
+ if (npmName && !installedModules.includes(npmName)) {
34
+ missingModules.push({
35
+ controlName: ext,
36
+ npmName
37
+ });
38
+ }
39
+ }
40
+ return missingModules;
41
+ };
3
42
  /**
4
43
  * Get the latest change from the pgpm.plan file for a specific module.
5
44
  */
@@ -1,17 +1,28 @@
1
1
  import { PgpmOptions } from '@pgpmjs/types';
2
2
  import { PgpmPackage } from '../core/class/pgpm';
3
+ /**
4
+ * Prompter interface for interactive prompts.
5
+ * Compatible with Inquirerer from the CLI.
6
+ */
7
+ interface Prompter {
8
+ prompt: (argv: any, questions: any[]) => Promise<Record<string, any>>;
9
+ }
3
10
  interface ExportOptions {
4
11
  project: PgpmPackage;
5
12
  options: PgpmOptions;
6
13
  dbInfo: {
7
14
  dbname: string;
15
+ databaseName: string;
8
16
  database_ids: string[];
9
17
  };
10
18
  author: string;
11
19
  outdir: string;
12
20
  schema_names: string[];
13
21
  extensionName?: string;
22
+ extensionDesc?: string;
14
23
  metaExtensionName: string;
24
+ metaExtensionDesc?: string;
25
+ prompter?: Prompter;
15
26
  }
16
- export declare const exportMigrations: ({ project, options, dbInfo, author, outdir, schema_names, extensionName, metaExtensionName }: ExportOptions) => Promise<void>;
27
+ export declare const exportMigrations: ({ project, options, dbInfo, author, outdir, schema_names, extensionName, extensionDesc, metaExtensionName, metaExtensionDesc, prompter }: ExportOptions) => Promise<void>;
17
28
  export {};
@@ -10,8 +10,76 @@ const komoji_1 = require("komoji");
10
10
  const path_1 = __importDefault(require("path"));
11
11
  const pg_cache_1 = require("pg-cache");
12
12
  const files_1 = require("../files");
13
+ const modules_1 = require("../modules/modules");
13
14
  const export_meta_1 = require("./export-meta");
14
- const exportMigrationsToDisk = async ({ project, options, database, databaseId, author, outdir, schema_names, extensionName, metaExtensionName }) => {
15
+ /**
16
+ * Required extensions for database schema exports.
17
+ * Includes native PostgreSQL extensions and pgpm modules.
18
+ */
19
+ const DB_REQUIRED_EXTENSIONS = [
20
+ 'plpgsql',
21
+ 'uuid-ossp',
22
+ 'citext',
23
+ 'pgcrypto',
24
+ 'btree_gist',
25
+ 'postgis',
26
+ 'hstore',
27
+ 'db-meta-schema',
28
+ 'pgpm-inflection',
29
+ 'pgpm-uuid',
30
+ 'pgpm-utils',
31
+ 'pgpm-database-jobs',
32
+ 'pgpm-jwt-claims',
33
+ 'pgpm-stamps',
34
+ 'pgpm-base32',
35
+ 'pgpm-totp',
36
+ 'pgpm-types'
37
+ ];
38
+ /**
39
+ * Required extensions for service/meta exports.
40
+ * Includes native PostgreSQL extensions and pgpm modules for metadata management.
41
+ */
42
+ const SERVICE_REQUIRED_EXTENSIONS = [
43
+ 'plpgsql',
44
+ 'db-meta-schema',
45
+ 'db-meta-modules'
46
+ ];
47
+ /**
48
+ * Checks which pgpm modules from the extensions list are missing from the workspace
49
+ * and prompts the user to install them if a prompter is provided.
50
+ *
51
+ * @param project - The PgpmPackage instance
52
+ * @param extensions - List of extension names (control file names)
53
+ * @param prompter - Optional prompter for interactive confirmation
54
+ * @returns List of extensions that were successfully installed
55
+ */
56
+ const promptAndInstallMissingModules = async (project, extensions, prompter) => {
57
+ const { installed } = project.getInstalledModules();
58
+ const missingModules = (0, modules_1.getMissingInstallableModules)(extensions, installed);
59
+ if (missingModules.length === 0) {
60
+ return [];
61
+ }
62
+ const missingNames = missingModules.map(m => m.npmName);
63
+ console.log(`\nMissing pgpm modules detected: ${missingNames.join(', ')}`);
64
+ if (prompter) {
65
+ const { install } = await prompter.prompt({}, [
66
+ {
67
+ type: 'confirm',
68
+ name: 'install',
69
+ message: `Install missing modules (${missingNames.join(', ')})?`,
70
+ default: true
71
+ }
72
+ ]);
73
+ if (install) {
74
+ console.log('Installing missing modules...');
75
+ await project.installModules(...missingNames);
76
+ console.log('Modules installed successfully.');
77
+ return missingModules.map(m => m.controlName);
78
+ }
79
+ }
80
+ return [];
81
+ };
82
+ const exportMigrationsToDisk = async ({ project, options, database, databaseId, databaseName, author, outdir, schema_names, extensionName, extensionDesc, metaExtensionName, metaExtensionDesc, prompter }) => {
15
83
  outdir = outdir + '/';
16
84
  const pgPool = (0, pg_cache_1.getPgPool)({
17
85
  ...options.pg,
@@ -39,46 +107,38 @@ const exportMigrationsToDisk = async ({ project, options, database, databaseId,
39
107
  outdir,
40
108
  author
41
109
  };
110
+ // Build description for the database extension package
111
+ const dbExtensionDesc = extensionDesc || `${name} database schema for ${databaseName}`;
42
112
  if (results?.rows?.length > 0) {
113
+ await promptAndInstallMissingModules(project, [...DB_REQUIRED_EXTENSIONS], prompter);
43
114
  await preparePackage({
44
115
  project,
45
116
  author,
46
117
  outdir,
47
118
  name,
48
- extensions: [
49
- 'plpgsql',
50
- 'uuid-ossp',
51
- 'citext',
52
- 'pgcrypto',
53
- 'btree_gist',
54
- 'postgis',
55
- 'hstore',
56
- 'db-meta-schema',
57
- 'pgpm-inflection',
58
- 'pgpm-uuid',
59
- 'pgpm-utils',
60
- 'pgpm-database-jobs',
61
- 'pgpm-jwt-claims',
62
- 'pgpm-stamps',
63
- 'pgpm-base32',
64
- 'pgpm-totp',
65
- 'pgpm-types'
66
- ]
119
+ description: dbExtensionDesc,
120
+ extensions: [...DB_REQUIRED_EXTENSIONS],
121
+ prompter
67
122
  });
68
- (0, files_1.writeSqitchPlan)(results.rows, opts);
69
- (0, files_1.writeSqitchFiles)(results.rows, opts);
123
+ (0, files_1.writePgpmPlan)(results.rows, opts);
124
+ (0, files_1.writePgpmFiles)(results.rows, opts);
70
125
  let meta = await (0, export_meta_1.exportMeta)({
71
126
  opts: options,
72
127
  dbname: database,
73
128
  database_id: databaseId
74
129
  });
75
130
  meta = replacer(meta);
131
+ // Build description for the meta/service extension package
132
+ const metaDesc = metaExtensionDesc || `${metaExtensionName} service utilities for managing domains, APIs, and services`;
133
+ await promptAndInstallMissingModules(project, [...SERVICE_REQUIRED_EXTENSIONS], prompter);
76
134
  await preparePackage({
77
135
  project,
78
136
  author,
79
137
  outdir,
80
- extensions: ['plpgsql', 'db-meta-schema', 'db-meta-modules'],
81
- name: metaExtensionName
138
+ name: metaExtensionName,
139
+ description: metaDesc,
140
+ extensions: [...SERVICE_REQUIRED_EXTENSIONS],
141
+ prompter
82
142
  });
83
143
  const metaReplacer = makeReplacer({
84
144
  schemas: schemas.rows.filter((schema) => schema_names.includes(schema.schema_name)),
@@ -106,11 +166,15 @@ $LQLMIGRATION$;
106
166
 
107
167
  ${meta}
108
168
 
109
- UPDATE meta_public.apis
110
- SET dbname = current_database() WHERE TRUE;
169
+ -- TODO: Research needed - These UPDATE statements may be a security leak.
170
+ -- They appear to rebind exported metadata to the target database after import,
171
+ -- but exposing dbname in meta_public tables could leak internal database names.
172
+ -- Consider removing entirely or gating behind an explicit flag.
173
+ -- UPDATE meta_public.apis
174
+ -- SET dbname = current_database() WHERE TRUE;
111
175
 
112
- UPDATE meta_public.sites
113
- SET dbname = current_database() WHERE TRUE;
176
+ -- UPDATE meta_public.sites
177
+ -- SET dbname = current_database() WHERE TRUE;
114
178
 
115
179
  SET session_replication_role TO DEFAULT;
116
180
  `
@@ -118,49 +182,74 @@ SET session_replication_role TO DEFAULT;
118
182
  ];
119
183
  opts.replacer = metaReplacer.replacer;
120
184
  opts.name = metaExtensionName;
121
- (0, files_1.writeSqitchPlan)(metaPackage, opts);
122
- (0, files_1.writeSqitchFiles)(metaPackage, opts);
185
+ (0, files_1.writePgpmPlan)(metaPackage, opts);
186
+ (0, files_1.writePgpmFiles)(metaPackage, opts);
123
187
  }
124
188
  pgPool.end();
125
189
  };
126
- const exportMigrations = async ({ project, options, dbInfo, author, outdir, schema_names, extensionName, metaExtensionName }) => {
190
+ const exportMigrations = async ({ project, options, dbInfo, author, outdir, schema_names, extensionName, extensionDesc, metaExtensionName, metaExtensionDesc, prompter }) => {
127
191
  for (let v = 0; v < dbInfo.database_ids.length; v++) {
128
192
  const databaseId = dbInfo.database_ids[v];
129
193
  await exportMigrationsToDisk({
130
194
  project,
131
195
  options,
132
196
  extensionName,
197
+ extensionDesc,
133
198
  metaExtensionName,
199
+ metaExtensionDesc,
134
200
  database: dbInfo.dbname,
201
+ databaseName: dbInfo.databaseName,
135
202
  databaseId,
136
203
  schema_names,
137
204
  author,
138
- outdir
205
+ outdir,
206
+ prompter
139
207
  });
140
208
  }
141
209
  };
142
210
  exports.exportMigrations = exportMigrations;
143
211
  /**
144
- * Creates a Sqitch package directory or resets the deploy/revert/verify directories if it exists.
212
+ * Creates a PGPM package directory or resets the deploy/revert/verify directories if it exists.
213
+ * If the module already exists and a prompter is provided, prompts the user for confirmation.
145
214
  */
146
- const preparePackage = async ({ project, author, outdir, name, extensions }) => {
215
+ const preparePackage = async ({ project, author, outdir, name, description, extensions, prompter }) => {
147
216
  const curDir = process.cwd();
148
- const sqitchDir = path_1.default.resolve(path_1.default.join(outdir, name));
149
- (0, fs_1.mkdirSync)(sqitchDir, { recursive: true });
150
- process.chdir(sqitchDir);
151
- const plan = (0, glob_1.sync)(path_1.default.join(sqitchDir, 'pgpm.plan'));
217
+ const pgpmDir = path_1.default.resolve(path_1.default.join(outdir, name));
218
+ (0, fs_1.mkdirSync)(pgpmDir, { recursive: true });
219
+ process.chdir(pgpmDir);
220
+ const plan = (0, glob_1.sync)(path_1.default.join(pgpmDir, 'pgpm.plan'));
152
221
  if (!plan.length) {
153
222
  await project.initModule({
154
223
  name,
155
- description: name,
224
+ description,
156
225
  author,
157
226
  extensions,
227
+ answers: {
228
+ moduleName: name,
229
+ moduleDesc: description,
230
+ access: 'restricted',
231
+ license: 'CLOSED'
232
+ }
158
233
  });
159
234
  }
160
235
  else {
161
- (0, fs_1.rmSync)(path_1.default.resolve(sqitchDir, 'deploy'), { recursive: true, force: true });
162
- (0, fs_1.rmSync)(path_1.default.resolve(sqitchDir, 'revert'), { recursive: true, force: true });
163
- (0, fs_1.rmSync)(path_1.default.resolve(sqitchDir, 'verify'), { recursive: true, force: true });
236
+ if (prompter) {
237
+ const { overwrite } = await prompter.prompt({}, [
238
+ {
239
+ type: 'confirm',
240
+ name: 'overwrite',
241
+ message: `Module "${name}" already exists at ${pgpmDir}. Overwrite deploy/revert/verify directories?`,
242
+ default: false
243
+ }
244
+ ]);
245
+ if (!overwrite) {
246
+ process.chdir(curDir);
247
+ throw new Error(`Export cancelled: Module "${name}" already exists.`);
248
+ }
249
+ }
250
+ (0, fs_1.rmSync)(path_1.default.resolve(pgpmDir, 'deploy'), { recursive: true, force: true });
251
+ (0, fs_1.rmSync)(path_1.default.resolve(pgpmDir, 'revert'), { recursive: true, force: true });
252
+ (0, fs_1.rmSync)(path_1.default.resolve(pgpmDir, 'verify'), { recursive: true, force: true });
164
253
  }
165
254
  process.chdir(curDir);
166
255
  };
@@ -1,4 +1,4 @@
1
- import { Change, SqitchRow, Tag, ExtendedPlanFile } from '../types';
1
+ import { Change, PgpmRow, Tag, ExtendedPlanFile } from '../types';
2
2
  export interface PlanWriteOptions {
3
3
  outdir: string;
4
4
  name: string;
@@ -6,9 +6,9 @@ export interface PlanWriteOptions {
6
6
  author?: string;
7
7
  }
8
8
  /**
9
- * Write a Sqitch plan file based on the provided rows
9
+ * Write a PGPM plan file based on the provided rows
10
10
  */
11
- export declare function writeSqitchPlan(rows: SqitchRow[], opts: PlanWriteOptions): void;
11
+ export declare function writePgpmPlan(rows: PgpmRow[], opts: PlanWriteOptions): void;
12
12
  /**
13
13
  * Write a plan file with the provided content
14
14
  */
@@ -25,3 +25,7 @@ export declare function generateChangeLineContent(change: Change): string;
25
25
  * Generate a line for a tag in a plan file
26
26
  */
27
27
  export declare function generateTagLineContent(tag: Tag): string;
28
+ /**
29
+ * @deprecated Use writePgpmPlan instead. This alias is kept for backwards compatibility.
30
+ */
31
+ export declare const writeSqitchPlan: typeof writePgpmPlan;
@@ -3,7 +3,8 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
3
3
  return (mod && mod.__esModule) ? mod : { "default": mod };
4
4
  };
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
- exports.writeSqitchPlan = writeSqitchPlan;
6
+ exports.writeSqitchPlan = void 0;
7
+ exports.writePgpmPlan = writePgpmPlan;
7
8
  exports.writePlanFile = writePlanFile;
8
9
  exports.generatePlanFileContent = generatePlanFileContent;
9
10
  exports.generateChangeLineContent = generateChangeLineContent;
@@ -11,9 +12,9 @@ exports.generateTagLineContent = generateTagLineContent;
11
12
  const fs_1 = __importDefault(require("fs"));
12
13
  const path_1 = __importDefault(require("path"));
13
14
  /**
14
- * Write a Sqitch plan file based on the provided rows
15
+ * Write a PGPM plan file based on the provided rows
15
16
  */
16
- function writeSqitchPlan(rows, opts) {
17
+ function writePgpmPlan(rows, opts) {
17
18
  const dir = path_1.default.resolve(path_1.default.join(opts.outdir, opts.name));
18
19
  fs_1.default.mkdirSync(dir, { recursive: true });
19
20
  const date = () => '2017-08-11T08:11:51Z'; // stubbed timestamp
@@ -136,3 +137,7 @@ function generateTagLineContent(tag) {
136
137
  }
137
138
  return line;
138
139
  }
140
+ /**
141
+ * @deprecated Use writePgpmPlan instead. This alias is kept for backwards compatibility.
142
+ */
143
+ exports.writeSqitchPlan = writePgpmPlan;
@@ -1,4 +1,4 @@
1
- import { SqitchRow } from '../types';
1
+ import { PgpmRow } from '../types';
2
2
  export interface SqlWriteOptions {
3
3
  outdir: string;
4
4
  name: string;
@@ -7,6 +7,10 @@ export interface SqlWriteOptions {
7
7
  useTx?: boolean;
8
8
  }
9
9
  /**
10
- * Write SQL files for Sqitch migrations (deploy, revert, verify)
10
+ * Write SQL files for PGPM migrations (deploy, revert, verify)
11
11
  */
12
- export declare const writeSqitchFiles: (rows: SqitchRow[], opts: SqlWriteOptions) => void;
12
+ export declare const writePgpmFiles: (rows: PgpmRow[], opts: SqlWriteOptions) => void;
13
+ /**
14
+ * @deprecated Use writePgpmFiles instead. This alias is kept for backwards compatibility.
15
+ */
16
+ export declare const writeSqitchFiles: (rows: PgpmRow[], opts: SqlWriteOptions) => void;
@@ -3,19 +3,19 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
3
3
  return (mod && mod.__esModule) ? mod : { "default": mod };
4
4
  };
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
- exports.writeSqitchFiles = void 0;
6
+ exports.writeSqitchFiles = exports.writePgpmFiles = void 0;
7
7
  const env_1 = require("@pgpmjs/env");
8
8
  const fs_1 = __importDefault(require("fs"));
9
9
  const path_1 = __importDefault(require("path"));
10
10
  /**
11
- * Write SQL files for Sqitch migrations (deploy, revert, verify)
11
+ * Write SQL files for PGPM migrations (deploy, revert, verify)
12
12
  */
13
- const writeSqitchFiles = (rows, opts) => {
13
+ const writePgpmFiles = (rows, opts) => {
14
14
  rows.forEach((row) => writeVerify(row, opts));
15
15
  rows.forEach((row) => writeRevert(row, opts));
16
16
  rows.forEach((row) => writeDeploy(row, opts));
17
17
  };
18
- exports.writeSqitchFiles = writeSqitchFiles;
18
+ exports.writePgpmFiles = writePgpmFiles;
19
19
  /**
20
20
  * Sort dependencies in a consistent order
21
21
  */
@@ -25,7 +25,7 @@ const ordered = (arr) => {
25
25
  return arr.sort((a, b) => a.length - b.length || a.localeCompare(b));
26
26
  };
27
27
  /**
28
- * Write a deploy SQL file for a Sqitch change
28
+ * Write a deploy SQL file for a PGPM change
29
29
  */
30
30
  const writeDeploy = (row, opts) => {
31
31
  const globalOpts = (0, env_1.getEnvOptions)({
@@ -57,7 +57,7 @@ ${useTx ? 'COMMIT;' : ''}
57
57
  fs_1.default.writeFileSync(actualFile, content);
58
58
  };
59
59
  /**
60
- * Write a verify SQL file for a Sqitch change
60
+ * Write a verify SQL file for a PGPM change
61
61
  */
62
62
  const writeVerify = (row, opts) => {
63
63
  const globalOpts = (0, env_1.getEnvOptions)({
@@ -85,7 +85,7 @@ ${useTx ? 'COMMIT;' : ''}
85
85
  fs_1.default.writeFileSync(actualFile, content);
86
86
  };
87
87
  /**
88
- * Write a revert SQL file for a Sqitch change
88
+ * Write a revert SQL file for a PGPM change
89
89
  */
90
90
  const writeRevert = (row, opts) => {
91
91
  const globalOpts = (0, env_1.getEnvOptions)({
@@ -112,3 +112,7 @@ ${useTx ? 'COMMIT;' : ''}
112
112
  `;
113
113
  fs_1.default.writeFileSync(actualFile, content);
114
114
  };
115
+ /**
116
+ * @deprecated Use writePgpmFiles instead. This alias is kept for backwards compatibility.
117
+ */
118
+ exports.writeSqitchFiles = exports.writePgpmFiles;
@@ -36,7 +36,7 @@ export interface ResolvedReference {
36
36
  target: string;
37
37
  resolved?: string;
38
38
  }
39
- export interface SqitchRow {
39
+ export interface PgpmRow {
40
40
  deploy: string;
41
41
  revert?: string;
42
42
  verify?: string;
@@ -44,3 +44,7 @@ export interface SqitchRow {
44
44
  deps?: string[];
45
45
  name?: string;
46
46
  }
47
+ /**
48
+ * @deprecated Use PgpmRow instead. This alias is kept for backwards compatibility.
49
+ */
50
+ export type SqitchRow = PgpmRow;
@@ -1,5 +1,27 @@
1
1
  import { Module } from '../files';
2
2
  export type ModuleMap = Record<string, Module>;
3
+ /**
4
+ * Mapping from control file names (used in extensions list) to npm package names.
5
+ * Only includes modules that can be installed via pgpm install from @pgpm/* packages.
6
+ * Native PostgreSQL extensions (plpgsql, uuid-ossp, etc.) are not included.
7
+ */
8
+ export declare const PGPM_MODULE_MAP: Record<string, string>;
9
+ /**
10
+ * Result of checking for missing installable modules.
11
+ */
12
+ export interface MissingModule {
13
+ controlName: string;
14
+ npmName: string;
15
+ }
16
+ /**
17
+ * Determines which pgpm modules from an extensions list are missing from the installed modules.
18
+ * Only checks modules that are in PGPM_MODULE_MAP (installable via pgpm install).
19
+ *
20
+ * @param extensions - List of extension/control file names to check
21
+ * @param installedModules - List of installed npm package names (e.g., '@pgpm/base32')
22
+ * @returns Array of missing modules with their control names and npm package names
23
+ */
24
+ export declare const getMissingInstallableModules: (extensions: string[], installedModules: string[]) => MissingModule[];
3
25
  /**
4
26
  * Get the latest change from the pgpm.plan file for a specific module.
5
27
  */
@@ -1,8 +1,48 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.getExtensionsAndModulesChanges = exports.getExtensionsAndModules = exports.latestChangeAndVersion = exports.latestChange = void 0;
3
+ exports.getExtensionsAndModulesChanges = exports.getExtensionsAndModules = exports.latestChangeAndVersion = exports.latestChange = exports.getMissingInstallableModules = exports.PGPM_MODULE_MAP = void 0;
4
4
  const files_1 = require("../files");
5
5
  const types_1 = require("@pgpmjs/types");
6
+ /**
7
+ * Mapping from control file names (used in extensions list) to npm package names.
8
+ * Only includes modules that can be installed via pgpm install from @pgpm/* packages.
9
+ * Native PostgreSQL extensions (plpgsql, uuid-ossp, etc.) are not included.
10
+ */
11
+ exports.PGPM_MODULE_MAP = {
12
+ 'pgpm-base32': '@pgpm/base32',
13
+ 'pgpm-database-jobs': '@pgpm/database-jobs',
14
+ 'db-meta-modules': '@pgpm/db-meta-modules',
15
+ 'db-meta-schema': '@pgpm/db-meta-schema',
16
+ 'pgpm-inflection': '@pgpm/inflection',
17
+ 'pgpm-jwt-claims': '@pgpm/jwt-claims',
18
+ 'pgpm-stamps': '@pgpm/stamps',
19
+ 'pgpm-totp': '@pgpm/totp',
20
+ 'pgpm-types': '@pgpm/types',
21
+ 'pgpm-utils': '@pgpm/utils',
22
+ 'pgpm-uuid': '@pgpm/uuid'
23
+ };
24
+ /**
25
+ * Determines which pgpm modules from an extensions list are missing from the installed modules.
26
+ * Only checks modules that are in PGPM_MODULE_MAP (installable via pgpm install).
27
+ *
28
+ * @param extensions - List of extension/control file names to check
29
+ * @param installedModules - List of installed npm package names (e.g., '@pgpm/base32')
30
+ * @returns Array of missing modules with their control names and npm package names
31
+ */
32
+ const getMissingInstallableModules = (extensions, installedModules) => {
33
+ const missingModules = [];
34
+ for (const ext of extensions) {
35
+ const npmName = exports.PGPM_MODULE_MAP[ext];
36
+ if (npmName && !installedModules.includes(npmName)) {
37
+ missingModules.push({
38
+ controlName: ext,
39
+ npmName
40
+ });
41
+ }
42
+ }
43
+ return missingModules;
44
+ };
45
+ exports.getMissingInstallableModules = getMissingInstallableModules;
6
46
  /**
7
47
  * Get the latest change from the pgpm.plan file for a specific module.
8
48
  */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pgpmjs/core",
3
- "version": "4.1.2",
3
+ "version": "4.2.0",
4
4
  "author": "Constructive <developers@constructive.io>",
5
5
  "description": "PGPM Package and Migration Tools",
6
6
  "main": "index.js",
@@ -64,5 +64,5 @@
64
64
  "pgsql-parser": "^17.9.5",
65
65
  "yanse": "^0.1.8"
66
66
  },
67
- "gitHead": "c2e68f9808a87e3231d9b9af5e706bef5dbd2af8"
67
+ "gitHead": "9b68e2d19937ad4e2a81a5de110a197a8572e3d9"
68
68
  }