@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.
- package/esm/export/export-migrations.js +132 -43
- package/esm/files/plan/writer.js +6 -2
- package/esm/files/sql/writer.js +9 -5
- package/esm/modules/modules.js +39 -0
- package/export/export-migrations.d.ts +12 -1
- package/export/export-migrations.js +131 -42
- package/files/plan/writer.d.ts +7 -3
- package/files/plan/writer.js +8 -3
- package/files/sql/writer.d.ts +7 -3
- package/files/sql/writer.js +11 -7
- package/files/types/index.d.ts +5 -1
- package/modules/modules.d.ts +22 -0
- package/modules/modules.js +41 -1
- package/package.json +2 -2
|
@@ -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 {
|
|
6
|
+
import { writePgpmFiles, writePgpmPlan } from '../files';
|
|
7
|
+
import { getMissingInstallableModules } from '../modules/modules';
|
|
7
8
|
import { exportMeta } from './export-meta';
|
|
8
|
-
|
|
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
|
-
|
|
43
|
-
|
|
44
|
-
|
|
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
|
-
|
|
63
|
-
|
|
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
|
-
|
|
75
|
-
|
|
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
|
|
104
|
-
|
|
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
|
-
|
|
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
|
-
|
|
116
|
-
|
|
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
|
|
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
|
|
142
|
-
mkdirSync(
|
|
143
|
-
process.chdir(
|
|
144
|
-
const plan = glob(path.join(
|
|
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
|
|
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
|
-
|
|
155
|
-
|
|
156
|
-
|
|
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
|
};
|
package/esm/files/plan/writer.js
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import fs from 'fs';
|
|
2
2
|
import path from 'path';
|
|
3
3
|
/**
|
|
4
|
-
* Write a
|
|
4
|
+
* Write a PGPM plan file based on the provided rows
|
|
5
5
|
*/
|
|
6
|
-
export function
|
|
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;
|
package/esm/files/sql/writer.js
CHANGED
|
@@ -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
|
|
5
|
+
* Write SQL files for PGPM migrations (deploy, revert, verify)
|
|
6
6
|
*/
|
|
7
|
-
export const
|
|
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
|
|
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
|
|
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
|
|
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;
|
package/esm/modules/modules.js
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
49
|
-
|
|
50
|
-
|
|
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.
|
|
69
|
-
(0, files_1.
|
|
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
|
-
|
|
81
|
-
|
|
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
|
|
110
|
-
|
|
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
|
-
|
|
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.
|
|
122
|
-
(0, files_1.
|
|
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
|
|
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
|
|
149
|
-
(0, fs_1.mkdirSync)(
|
|
150
|
-
process.chdir(
|
|
151
|
-
const plan = (0, glob_1.sync)(path_1.default.join(
|
|
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
|
|
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
|
-
|
|
162
|
-
|
|
163
|
-
|
|
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
|
};
|
package/files/plan/writer.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Change,
|
|
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
|
|
9
|
+
* Write a PGPM plan file based on the provided rows
|
|
10
10
|
*/
|
|
11
|
-
export declare function
|
|
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;
|
package/files/plan/writer.js
CHANGED
|
@@ -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 =
|
|
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
|
|
15
|
+
* Write a PGPM plan file based on the provided rows
|
|
15
16
|
*/
|
|
16
|
-
function
|
|
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;
|
package/files/sql/writer.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
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
|
|
10
|
+
* Write SQL files for PGPM migrations (deploy, revert, verify)
|
|
11
11
|
*/
|
|
12
|
-
export declare const
|
|
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;
|
package/files/sql/writer.js
CHANGED
|
@@ -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
|
|
11
|
+
* Write SQL files for PGPM migrations (deploy, revert, verify)
|
|
12
12
|
*/
|
|
13
|
-
const
|
|
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.
|
|
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
|
|
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
|
|
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
|
|
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;
|
package/files/types/index.d.ts
CHANGED
|
@@ -36,7 +36,7 @@ export interface ResolvedReference {
|
|
|
36
36
|
target: string;
|
|
37
37
|
resolved?: string;
|
|
38
38
|
}
|
|
39
|
-
export interface
|
|
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;
|
package/modules/modules.d.ts
CHANGED
|
@@ -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
|
*/
|
package/modules/modules.js
CHANGED
|
@@ -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.
|
|
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": "
|
|
67
|
+
"gitHead": "9b68e2d19937ad4e2a81a5de110a197a8572e3d9"
|
|
68
68
|
}
|