@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.
- package/esm/export-migrations.js +34 -4
- package/esm/export-utils.js +24 -1
- package/export-migrations.js +34 -4
- package/export-utils.js +23 -0
- package/package.json +10 -10
package/esm/export-migrations.js
CHANGED
|
@@ -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 (
|
|
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(
|
|
72
|
-
writePgpmFiles(
|
|
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:
|
|
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;
|
package/esm/export-utils.js
CHANGED
|
@@ -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 {
|
package/export-migrations.js
CHANGED
|
@@ -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 (
|
|
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)(
|
|
75
|
-
(0, core_1.writePgpmFiles)(
|
|
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:
|
|
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.
|
|
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.
|
|
44
|
+
"pgsql-test": "^5.4.0"
|
|
45
45
|
},
|
|
46
46
|
"dependencies": {
|
|
47
|
-
"@constructive-io/query-builder": "^3.
|
|
48
|
-
"@pgpmjs/core": "^7.
|
|
49
|
-
"@pgpmjs/migrate-client": "^1.
|
|
50
|
-
"@pgpmjs/types": "^2.
|
|
51
|
-
"csv-to-pg": "^4.
|
|
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.
|
|
57
|
-
"pg-env": "^1.
|
|
56
|
+
"pg-cache": "^3.19.0",
|
|
57
|
+
"pg-env": "^1.22.0"
|
|
58
58
|
},
|
|
59
|
-
"gitHead": "
|
|
59
|
+
"gitHead": "73f55ad24d9a92e7ef1e268526b31bfb7bb12b28"
|
|
60
60
|
}
|