@pgpmjs/export 1.5.2 → 1.8.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.
@@ -41,6 +41,14 @@ const exportMigrationsToDisk = async ({ project, options, database, databaseId,
41
41
  and (category is null or category != ALL($2::text[]))
42
42
  order by id`, [databaseId, excludeCategories])
43
43
  : await pgPool.query(`select * from db_migrate.sql_actions where database_id = $1 order by id`, [databaseId]);
44
+ // Registry fixtures — meta_registration actions that insert into
45
+ // metaschema_public.* (e.g. pg_partman partition registration) — FK-reference
46
+ // the database/schema/table/field identity rows exported into the
47
+ // meta/service package. Hoist them into that package, after its metaschema
48
+ // fixtures, so both packages deploy cleanly in app → service order.
49
+ const isRegistryFixture = (row) => row.category === 'meta_registration' && /\bmetaschema_public\./.test(row.content ?? '');
50
+ const registryRows = results.rows.filter(isRegistryFixture);
51
+ const appRows = results.rows.filter((row) => !isRegistryFixture(row));
44
52
  const opts = {
45
53
  name,
46
54
  replacer,
@@ -49,7 +57,7 @@ const exportMigrationsToDisk = async ({ project, options, database, databaseId,
49
57
  };
50
58
  // Build description for the database extension package
51
59
  const dbExtensionDesc = extensionDesc || `${name} database schema for ${databaseName}`;
52
- if (results?.rows?.length > 0) {
60
+ if (appRows.length > 0) {
53
61
  // Detect missing modules at workspace level and prompt user
54
62
  const dbMissingResult = await detectMissingModules(project, [...DB_REQUIRED_EXTENSIONS], prompter, argv);
55
63
  // Create/prepare the module directory
@@ -68,8 +76,8 @@ const exportMigrationsToDisk = async ({ project, options, database, databaseId,
68
76
  if (dbMissingResult.shouldInstall) {
69
77
  await installMissingModules(dbModuleDir, dbMissingResult.missingModules);
70
78
  }
71
- writePgpmPlan(results.rows, opts);
72
- writePgpmFiles(results.rows, opts);
79
+ writePgpmPlan(appRows, opts);
80
+ writePgpmFiles(appRows, opts);
73
81
  }
74
82
  else {
75
83
  console.log('No sql_actions found — skipping database module. Meta/service module will still be exported.');
@@ -86,6 +94,11 @@ const exportMigrationsToDisk = async ({ project, options, database, databaseId,
86
94
  const metaDesc = metaExtensionDesc || `${metaExtensionName} service utilities for managing domains, APIs, and services`;
87
95
  // Detect missing modules at workspace level and prompt user
88
96
  const svcMissingResult = await detectMissingModules(project, [...SERVICE_REQUIRED_EXTENSIONS], prompter, argv);
97
+ // Hoisted registry fixtures reference the application package's DDL, so the
98
+ // service package requires it as an extension when any are present.
99
+ const svcExtensions = registryRows.length > 0 && appRows.length > 0
100
+ ? [...SERVICE_REQUIRED_EXTENSIONS, name]
101
+ : [...SERVICE_REQUIRED_EXTENSIONS];
89
102
  // Create/prepare the module directory (use serviceOutdir if provided)
90
103
  const svcModuleDir = await preparePackage({
91
104
  project,
@@ -93,7 +106,7 @@ const exportMigrationsToDisk = async ({ project, options, database, databaseId,
93
106
  outdir: svcOutdir,
94
107
  name: metaExtensionName,
95
108
  description: metaDesc,
96
- extensions: [...SERVICE_REQUIRED_EXTENSIONS],
109
+ extensions: svcExtensions,
97
110
  prompter,
98
111
  repoName,
99
112
  username
@@ -143,6 +156,23 @@ ${META_COMMON_FOOTER}
143
156
  tablesWithContent.push(tableName);
144
157
  }
145
158
  }
159
+ // Append the hoisted registry fixtures after the metaschema identity
160
+ // fixtures they FK-reference. Their DDL prerequisites live in the
161
+ // application package, so those deps become cross-package requires.
162
+ const lastMetaChange = tablesWithContent.length > 0
163
+ ? `migrate/${tablesWithContent[tablesWithContent.length - 1]}`
164
+ : null;
165
+ for (const row of registryRows) {
166
+ const crossDeps = (row.deps ?? []).map((dep) => `${name}:${dep}`);
167
+ metaPackage.push({
168
+ name: row.name,
169
+ deploy: row.deploy,
170
+ content: row.content,
171
+ revert: row.revert,
172
+ verify: row.verify,
173
+ deps: lastMetaChange ? [...crossDeps, lastMetaChange] : crossDeps
174
+ });
175
+ }
146
176
  opts.replacer = metaReplacer.replacer;
147
177
  opts.name = metaExtensionName;
148
178
  opts.outdir = svcOutdir;
@@ -1,5 +1,5 @@
1
1
  import { getMissingInstallableModules, parseAuthor, PgpmPackage } from '@pgpmjs/core';
2
- import { mkdirSync, rmSync } from 'fs';
2
+ import { existsSync, mkdirSync, readFileSync, rmSync, writeFileSync } from 'fs';
3
3
  import { sync as glob } from 'glob';
4
4
  import { toSnakeCase } from 'inflekt';
5
5
  import path from 'path';
@@ -260,6 +260,26 @@ export const makeReplacer = ({ schemas, name, schemaPrefix }) => {
260
260
  };
261
261
  return { replacer, replace };
262
262
  };
263
+ /**
264
+ * Ensures the module's control file `requires` line contains every requested
265
+ * extension, preserving any extras already listed.
266
+ */
267
+ const syncControlRequires = (pgpmDir, name, extensions) => {
268
+ const controlPath = path.join(pgpmDir, `${name}.control`);
269
+ if (!existsSync(controlPath))
270
+ return;
271
+ const content = readFileSync(controlPath, 'utf-8');
272
+ const match = content.match(/^requires\s*=\s*'([^']*)'\s*$/m);
273
+ const existing = match ? match[1].split(',').map((s) => s.trim()).filter(Boolean) : [];
274
+ const merged = [...existing, ...extensions.filter((ext) => !existing.includes(ext))];
275
+ if (merged.length === existing.length)
276
+ return;
277
+ const requiresLine = `requires = '${merged.join(',')}'`;
278
+ const updated = match
279
+ ? content.replace(match[0], requiresLine)
280
+ : `${content.trimEnd()}\n${requiresLine}\n`;
281
+ writeFileSync(controlPath, updated);
282
+ };
263
283
  /**
264
284
  * Creates a PGPM package directory or resets the deploy/revert/verify directories if it exists.
265
285
  * If the module already exists and a prompter is provided, prompts the user for confirmation.
@@ -317,6 +337,9 @@ export const preparePackage = async ({ project, author, outdir, name, descriptio
317
337
  rmSync(path.resolve(pgpmDir, 'deploy'), { recursive: true, force: true });
318
338
  rmSync(path.resolve(pgpmDir, 'revert'), { recursive: true, force: true });
319
339
  rmSync(path.resolve(pgpmDir, 'verify'), { recursive: true, force: true });
340
+ // Re-exports over an existing package skip initModule, so sync the
341
+ // control file's requires with the requested extensions.
342
+ syncControlRequires(pgpmDir, name, extensions);
320
343
  }
321
344
  }
322
345
  finally {
@@ -44,6 +44,14 @@ const exportMigrationsToDisk = async ({ project, options, database, databaseId,
44
44
  and (category is null or category != ALL($2::text[]))
45
45
  order by id`, [databaseId, excludeCategories])
46
46
  : await pgPool.query(`select * from db_migrate.sql_actions where database_id = $1 order by id`, [databaseId]);
47
+ // Registry fixtures — meta_registration actions that insert into
48
+ // metaschema_public.* (e.g. pg_partman partition registration) — FK-reference
49
+ // the database/schema/table/field identity rows exported into the
50
+ // meta/service package. Hoist them into that package, after its metaschema
51
+ // fixtures, so both packages deploy cleanly in app → service order.
52
+ const isRegistryFixture = (row) => row.category === 'meta_registration' && /\bmetaschema_public\./.test(row.content ?? '');
53
+ const registryRows = results.rows.filter(isRegistryFixture);
54
+ const appRows = results.rows.filter((row) => !isRegistryFixture(row));
47
55
  const opts = {
48
56
  name,
49
57
  replacer,
@@ -52,7 +60,7 @@ const exportMigrationsToDisk = async ({ project, options, database, databaseId,
52
60
  };
53
61
  // Build description for the database extension package
54
62
  const dbExtensionDesc = extensionDesc || `${name} database schema for ${databaseName}`;
55
- if (results?.rows?.length > 0) {
63
+ if (appRows.length > 0) {
56
64
  // Detect missing modules at workspace level and prompt user
57
65
  const dbMissingResult = await (0, export_utils_1.detectMissingModules)(project, [...export_utils_1.DB_REQUIRED_EXTENSIONS], prompter, argv);
58
66
  // Create/prepare the module directory
@@ -71,8 +79,8 @@ const exportMigrationsToDisk = async ({ project, options, database, databaseId,
71
79
  if (dbMissingResult.shouldInstall) {
72
80
  await (0, export_utils_1.installMissingModules)(dbModuleDir, dbMissingResult.missingModules);
73
81
  }
74
- (0, core_1.writePgpmPlan)(results.rows, opts);
75
- (0, core_1.writePgpmFiles)(results.rows, opts);
82
+ (0, core_1.writePgpmPlan)(appRows, opts);
83
+ (0, core_1.writePgpmFiles)(appRows, opts);
76
84
  }
77
85
  else {
78
86
  console.log('No sql_actions found — skipping database module. Meta/service module will still be exported.');
@@ -89,6 +97,11 @@ const exportMigrationsToDisk = async ({ project, options, database, databaseId,
89
97
  const metaDesc = metaExtensionDesc || `${metaExtensionName} service utilities for managing domains, APIs, and services`;
90
98
  // Detect missing modules at workspace level and prompt user
91
99
  const svcMissingResult = await (0, export_utils_1.detectMissingModules)(project, [...export_utils_1.SERVICE_REQUIRED_EXTENSIONS], prompter, argv);
100
+ // Hoisted registry fixtures reference the application package's DDL, so the
101
+ // service package requires it as an extension when any are present.
102
+ const svcExtensions = registryRows.length > 0 && appRows.length > 0
103
+ ? [...export_utils_1.SERVICE_REQUIRED_EXTENSIONS, name]
104
+ : [...export_utils_1.SERVICE_REQUIRED_EXTENSIONS];
92
105
  // Create/prepare the module directory (use serviceOutdir if provided)
93
106
  const svcModuleDir = await (0, export_utils_1.preparePackage)({
94
107
  project,
@@ -96,7 +109,7 @@ const exportMigrationsToDisk = async ({ project, options, database, databaseId,
96
109
  outdir: svcOutdir,
97
110
  name: metaExtensionName,
98
111
  description: metaDesc,
99
- extensions: [...export_utils_1.SERVICE_REQUIRED_EXTENSIONS],
112
+ extensions: svcExtensions,
100
113
  prompter,
101
114
  repoName,
102
115
  username
@@ -146,6 +159,23 @@ ${export_utils_1.META_COMMON_FOOTER}
146
159
  tablesWithContent.push(tableName);
147
160
  }
148
161
  }
162
+ // Append the hoisted registry fixtures after the metaschema identity
163
+ // fixtures they FK-reference. Their DDL prerequisites live in the
164
+ // application package, so those deps become cross-package requires.
165
+ const lastMetaChange = tablesWithContent.length > 0
166
+ ? `migrate/${tablesWithContent[tablesWithContent.length - 1]}`
167
+ : null;
168
+ for (const row of registryRows) {
169
+ const crossDeps = (row.deps ?? []).map((dep) => `${name}:${dep}`);
170
+ metaPackage.push({
171
+ name: row.name,
172
+ deploy: row.deploy,
173
+ content: row.content,
174
+ revert: row.revert,
175
+ verify: row.verify,
176
+ deps: lastMetaChange ? [...crossDeps, lastMetaChange] : crossDeps
177
+ });
178
+ }
149
179
  opts.replacer = metaReplacer.replacer;
150
180
  opts.name = metaExtensionName;
151
181
  opts.outdir = svcOutdir;
package/export-utils.js CHANGED
@@ -273,6 +273,26 @@ const makeReplacer = ({ schemas, name, schemaPrefix }) => {
273
273
  return { replacer, replace };
274
274
  };
275
275
  exports.makeReplacer = makeReplacer;
276
+ /**
277
+ * Ensures the module's control file `requires` line contains every requested
278
+ * extension, preserving any extras already listed.
279
+ */
280
+ const syncControlRequires = (pgpmDir, name, extensions) => {
281
+ const controlPath = path_1.default.join(pgpmDir, `${name}.control`);
282
+ if (!(0, fs_1.existsSync)(controlPath))
283
+ return;
284
+ const content = (0, fs_1.readFileSync)(controlPath, 'utf-8');
285
+ const match = content.match(/^requires\s*=\s*'([^']*)'\s*$/m);
286
+ const existing = match ? match[1].split(',').map((s) => s.trim()).filter(Boolean) : [];
287
+ const merged = [...existing, ...extensions.filter((ext) => !existing.includes(ext))];
288
+ if (merged.length === existing.length)
289
+ return;
290
+ const requiresLine = `requires = '${merged.join(',')}'`;
291
+ const updated = match
292
+ ? content.replace(match[0], requiresLine)
293
+ : `${content.trimEnd()}\n${requiresLine}\n`;
294
+ (0, fs_1.writeFileSync)(controlPath, updated);
295
+ };
276
296
  /**
277
297
  * Creates a PGPM package directory or resets the deploy/revert/verify directories if it exists.
278
298
  * If the module already exists and a prompter is provided, prompts the user for confirmation.
@@ -330,6 +350,9 @@ const preparePackage = async ({ project, author, outdir, name, description, exte
330
350
  (0, fs_1.rmSync)(path_1.default.resolve(pgpmDir, 'deploy'), { recursive: true, force: true });
331
351
  (0, fs_1.rmSync)(path_1.default.resolve(pgpmDir, 'revert'), { recursive: true, force: true });
332
352
  (0, fs_1.rmSync)(path_1.default.resolve(pgpmDir, 'verify'), { recursive: true, force: true });
353
+ // Re-exports over an existing package skip initModule, so sync the
354
+ // control file's requires with the requested extensions.
355
+ syncControlRequires(pgpmDir, name, extensions);
333
356
  }
334
357
  }
335
358
  finally {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pgpmjs/export",
3
- "version": "1.5.2",
3
+ "version": "1.8.0",
4
4
  "author": "Constructive <developers@constructive.io>",
5
5
  "description": "PGPM export tools for SQL and GraphQL database migration extraction",
6
6
  "main": "index.js",
@@ -41,20 +41,20 @@
41
41
  "@pgsql/types": "^18.0.0",
42
42
  "@types/pg": "^8.20.0",
43
43
  "makage": "^0.3.0",
44
- "pgsql-test": "^5.1.4"
44
+ "pgsql-test": "^5.4.0"
45
45
  },
46
46
  "dependencies": {
47
- "@constructive-io/query-builder": "^3.1.2",
48
- "@pgpmjs/core": "^7.5.1",
49
- "@pgpmjs/migrate-client": "^1.2.2",
50
- "@pgpmjs/types": "^2.37.2",
51
- "csv-to-pg": "^4.1.2",
47
+ "@constructive-io/query-builder": "^3.4.0",
48
+ "@pgpmjs/core": "^7.8.0",
49
+ "@pgpmjs/migrate-client": "^1.5.0",
50
+ "@pgpmjs/types": "^2.40.0",
51
+ "csv-to-pg": "^4.4.0",
52
52
  "glob": "^13.0.6",
53
53
  "inflekt": "^0.7.1",
54
54
  "inquirerer": "^4.9.1",
55
55
  "pg": "^8.21.0",
56
- "pg-cache": "^3.16.2",
57
- "pg-env": "^1.19.2"
56
+ "pg-cache": "^3.19.0",
57
+ "pg-env": "^1.22.0"
58
58
  },
59
- "gitHead": "217ead37de876d05763874856347a48a6f356a69"
59
+ "gitHead": "73f55ad24d9a92e7ef1e268526b31bfb7bb12b28"
60
60
  }