pgpm 5.24.5 → 5.25.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/README.md +15 -2
- package/commands/test-packages.d.ts +20 -0
- package/commands/test-packages.js +78 -6
- package/esm/commands/test-packages.js +78 -7
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -376,12 +376,24 @@ pgpm kill --no-drop
|
|
|
376
376
|
|
|
377
377
|
#### `pgpm test-packages`
|
|
378
378
|
|
|
379
|
-
Run integration tests on
|
|
379
|
+
Run integration tests on the modules in a workspace. Creates a temporary database per tested module, deploys, and optionally runs verify/revert/deploy cycles.
|
|
380
|
+
|
|
381
|
+
By default only the **minimal covering set** is tested: the modules nothing else in the
|
|
382
|
+
workspace requires. Testing one of those deploys its whole dependency closure in
|
|
383
|
+
dependency order, so every module is still exercised — once per covering module instead
|
|
384
|
+
of once per module. Excluding a covering module promotes whatever it alone covered.
|
|
385
|
+
|
|
386
|
+
The property the default trades away is standalone deployability — that a module's own
|
|
387
|
+
`requires` is complete rather than satisfied by a sibling in the same database. Use
|
|
388
|
+
`--force-all` to get it back.
|
|
380
389
|
|
|
381
390
|
```bash
|
|
382
|
-
# Test
|
|
391
|
+
# Test the minimal covering set (deploy only)
|
|
383
392
|
pgpm test-packages
|
|
384
393
|
|
|
394
|
+
# Test every module in its own database
|
|
395
|
+
pgpm test-packages --force-all
|
|
396
|
+
|
|
385
397
|
# Run full deploy/verify/revert/deploy cycle
|
|
386
398
|
pgpm test-packages --full-cycle
|
|
387
399
|
|
|
@@ -397,6 +409,7 @@ pgpm test-packages --full-cycle --continue-on-fail --exclude legacy-module
|
|
|
397
409
|
|
|
398
410
|
**Options:**
|
|
399
411
|
|
|
412
|
+
- `--force-all` - Test every module in its own database instead of the minimal covering set
|
|
400
413
|
- `--full-cycle` - Run full deploy/verify/revert/deploy cycle (default: deploy only)
|
|
401
414
|
- `--continue-on-fail` - Continue testing all packages even after failures (default: stop on first failure)
|
|
402
415
|
- `--exclude <modules>` - Comma-separated module names to exclude
|
|
@@ -1,3 +1,23 @@
|
|
|
1
1
|
import { CLIOptions, Inquirerer, ParsedArgs } from 'inquirerer';
|
|
2
|
+
/**
|
|
3
|
+
* Reduce the module list to a minimal covering set: the modules that no other
|
|
4
|
+
* candidate requires, transitively.
|
|
5
|
+
*
|
|
6
|
+
* Testing a module deploys its whole dependency closure in dependency order, so
|
|
7
|
+
* a module that some other candidate requires is already deployed, verified,
|
|
8
|
+
* reverted and re-deployed inside that candidate's database. Selection runs over
|
|
9
|
+
* the post-exclude candidates, so excluding a covering module promotes whatever
|
|
10
|
+
* it alone covered rather than dropping it.
|
|
11
|
+
*
|
|
12
|
+
* The property this trades away is standalone deployability — that a module's own
|
|
13
|
+
* `requires` is complete rather than satisfied by a sibling in the same database.
|
|
14
|
+
* Use --force-all to get it back.
|
|
15
|
+
*/
|
|
16
|
+
export declare function minimalCoveringSet(moduleMap: Record<string, {
|
|
17
|
+
requires: string[];
|
|
18
|
+
}>, candidateNames: string[]): {
|
|
19
|
+
selected: string[];
|
|
20
|
+
coveredBy: Map<string, string>;
|
|
21
|
+
};
|
|
2
22
|
declare const _default: (argv: Partial<ParsedArgs>, _prompter: Inquirerer, _options: CLIOptions) => Promise<never>;
|
|
3
23
|
export default _default;
|
|
@@ -3,6 +3,7 @@ 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.minimalCoveringSet = minimalCoveringSet;
|
|
6
7
|
const core_1 = require("@pgpmjs/core");
|
|
7
8
|
const env_1 = require("@pgpmjs/env");
|
|
8
9
|
const logger_1 = require("@pgpmjs/logger");
|
|
@@ -20,22 +21,85 @@ Test Packages Command:
|
|
|
20
21
|
|
|
21
22
|
pgpm test-packages [OPTIONS]
|
|
22
23
|
|
|
23
|
-
Run integration tests on
|
|
24
|
+
Run integration tests on the PGPM packages in a workspace.
|
|
24
25
|
Tests each package with a deploy/verify/revert/deploy cycle.
|
|
25
26
|
|
|
27
|
+
By default only the minimal covering set is tested: the modules nothing else
|
|
28
|
+
in the workspace requires. Deploying one of those deploys everything it
|
|
29
|
+
requires first, so every module in the workspace is still exercised — just
|
|
30
|
+
once per covering module instead of once per module.
|
|
31
|
+
|
|
26
32
|
Options:
|
|
27
33
|
--help, -h Show this help message
|
|
28
34
|
--exclude <pkgs> Comma-separated module names to exclude
|
|
29
35
|
--continue-on-fail Continue testing all packages even after failures
|
|
30
36
|
--full-cycle Run full deploy/verify/revert/deploy cycle (default: deploy only)
|
|
37
|
+
--force-all Test every module in its own database (pre-minimal-set behavior)
|
|
31
38
|
--cwd <directory> Working directory (default: current directory)
|
|
32
39
|
|
|
33
40
|
Examples:
|
|
34
|
-
pgpm test-packages Test
|
|
41
|
+
pgpm test-packages Test the minimal covering set
|
|
42
|
+
pgpm test-packages --force-all Test every module in its own database
|
|
35
43
|
pgpm test-packages --full-cycle Run full test cycle with verify/revert
|
|
36
44
|
pgpm test-packages --continue-on-fail Test all packages, collect all failures
|
|
37
45
|
pgpm test-packages --exclude my-module Exclude specific modules
|
|
38
46
|
`;
|
|
47
|
+
/**
|
|
48
|
+
* Reduce the module list to a minimal covering set: the modules that no other
|
|
49
|
+
* candidate requires, transitively.
|
|
50
|
+
*
|
|
51
|
+
* Testing a module deploys its whole dependency closure in dependency order, so
|
|
52
|
+
* a module that some other candidate requires is already deployed, verified,
|
|
53
|
+
* reverted and re-deployed inside that candidate's database. Selection runs over
|
|
54
|
+
* the post-exclude candidates, so excluding a covering module promotes whatever
|
|
55
|
+
* it alone covered rather than dropping it.
|
|
56
|
+
*
|
|
57
|
+
* The property this trades away is standalone deployability — that a module's own
|
|
58
|
+
* `requires` is complete rather than satisfied by a sibling in the same database.
|
|
59
|
+
* Use --force-all to get it back.
|
|
60
|
+
*/
|
|
61
|
+
function minimalCoveringSet(moduleMap, candidateNames) {
|
|
62
|
+
const candidates = new Set(candidateNames);
|
|
63
|
+
// name -> its transitive requires, restricted to the candidates
|
|
64
|
+
const closures = new Map();
|
|
65
|
+
for (const name of candidates) {
|
|
66
|
+
const { resolved } = (0, core_1.resolveExtensionDependencies)(name, moduleMap);
|
|
67
|
+
closures.set(name, resolved.filter((dep) => dep !== name && candidates.has(dep)));
|
|
68
|
+
}
|
|
69
|
+
const coveredBy = new Map();
|
|
70
|
+
for (const [name, deps] of closures) {
|
|
71
|
+
for (const dep of deps) {
|
|
72
|
+
if (!coveredBy.has(dep)) {
|
|
73
|
+
coveredBy.set(dep, name);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
const selected = candidateNames.filter((name) => !coveredBy.has(name));
|
|
78
|
+
// Nothing may fall out of the selection. Covering-set membership is derived
|
|
79
|
+
// from pairwise closures, so assert the union actually reaches every candidate
|
|
80
|
+
// rather than trusting that argument.
|
|
81
|
+
const covered = new Set();
|
|
82
|
+
for (const name of selected) {
|
|
83
|
+
covered.add(name);
|
|
84
|
+
for (const dep of closures.get(name) ?? []) {
|
|
85
|
+
covered.add(dep);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
const uncovered = candidateNames.filter((name) => !covered.has(name));
|
|
89
|
+
if (uncovered.length > 0) {
|
|
90
|
+
throw new Error(`Minimal module selection left ${uncovered.length} module(s) untested: ${uncovered.join(', ')}. ` +
|
|
91
|
+
'Re-run with --force-all and report this as a pgpm bug.');
|
|
92
|
+
}
|
|
93
|
+
return { selected, coveredBy };
|
|
94
|
+
}
|
|
95
|
+
function selectMinimalModules(workspacePkg, candidates) {
|
|
96
|
+
const { selected, coveredBy } = minimalCoveringSet(workspacePkg.getModuleMap(), candidates.map((mod) => mod.getModuleName()));
|
|
97
|
+
const selectedNames = new Set(selected);
|
|
98
|
+
return {
|
|
99
|
+
selected: candidates.filter((mod) => selectedNames.has(mod.getModuleName())),
|
|
100
|
+
coveredBy
|
|
101
|
+
};
|
|
102
|
+
}
|
|
39
103
|
function dbSafeName(moduleName) {
|
|
40
104
|
return `test_${moduleName}`.replace(/[^a-zA-Z0-9]/g, '_').toLowerCase();
|
|
41
105
|
}
|
|
@@ -209,6 +273,7 @@ exports.default = async (argv, _prompter, _options) => {
|
|
|
209
273
|
const continueOnFail = argv['continue-on-fail'] === true || argv.continueOnFail === true;
|
|
210
274
|
const stopOnFail = !continueOnFail;
|
|
211
275
|
const fullCycle = argv['full-cycle'] === true || argv.fullCycle === true;
|
|
276
|
+
const forceAll = argv['force-all'] === true || argv.forceAll === true;
|
|
212
277
|
const cwd = argv.cwd || process.cwd();
|
|
213
278
|
// Parse excludes
|
|
214
279
|
let excludes = [];
|
|
@@ -216,7 +281,7 @@ exports.default = async (argv, _prompter, _options) => {
|
|
|
216
281
|
excludes = argv.exclude.split(',').map(e => e.trim());
|
|
217
282
|
}
|
|
218
283
|
console.log('=== PGPM Package Integration Test ===');
|
|
219
|
-
console.log(`Testing all packages with ${fullCycle ? 'deploy/verify/revert/deploy cycle' : 'deploy only'}`);
|
|
284
|
+
console.log(`Testing ${forceAll ? 'all packages' : 'the minimal covering set'} with ${fullCycle ? 'deploy/verify/revert/deploy cycle' : 'deploy only'}`);
|
|
220
285
|
if (!stopOnFail) {
|
|
221
286
|
console.log('Mode: Test all packages (collect all failures)');
|
|
222
287
|
}
|
|
@@ -255,14 +320,21 @@ exports.default = async (argv, _prompter, _options) => {
|
|
|
255
320
|
});
|
|
256
321
|
console.log(`Excluding: ${excludes.join(', ')}`);
|
|
257
322
|
}
|
|
258
|
-
|
|
259
|
-
|
|
323
|
+
let selectedModules = filteredModules;
|
|
324
|
+
if (!forceAll) {
|
|
325
|
+
const { selected, coveredBy } = selectMinimalModules(workspacePkg, filteredModules);
|
|
326
|
+
selectedModules = selected;
|
|
327
|
+
console.log(`Minimal covering set: ${selected.length} of ${filteredModules.length} modules ` +
|
|
328
|
+
`(${coveredBy.size} covered transitively; --force-all to test each on its own)`);
|
|
329
|
+
}
|
|
330
|
+
console.log(`Found ${selectedModules.length} modules to test:`);
|
|
331
|
+
for (const mod of selectedModules) {
|
|
260
332
|
console.log(` - ${mod.getModuleName()}`);
|
|
261
333
|
}
|
|
262
334
|
console.log('');
|
|
263
335
|
const failedPackages = [];
|
|
264
336
|
const successfulPackages = [];
|
|
265
|
-
for (const modulePkg of
|
|
337
|
+
for (const modulePkg of selectedModules) {
|
|
266
338
|
const result = await testModule(workspacePkg, modulePkg, fullCycle);
|
|
267
339
|
if (result.success) {
|
|
268
340
|
successfulPackages.push(result);
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { PgpmPackage } from '@pgpmjs/core';
|
|
1
|
+
import { PgpmPackage, resolveExtensionDependencies } from '@pgpmjs/core';
|
|
2
2
|
import { getEnvOptions } from '@pgpmjs/env';
|
|
3
3
|
import { Logger } from '@pgpmjs/logger';
|
|
4
4
|
import path from 'path';
|
|
@@ -15,22 +15,85 @@ Test Packages Command:
|
|
|
15
15
|
|
|
16
16
|
pgpm test-packages [OPTIONS]
|
|
17
17
|
|
|
18
|
-
Run integration tests on
|
|
18
|
+
Run integration tests on the PGPM packages in a workspace.
|
|
19
19
|
Tests each package with a deploy/verify/revert/deploy cycle.
|
|
20
20
|
|
|
21
|
+
By default only the minimal covering set is tested: the modules nothing else
|
|
22
|
+
in the workspace requires. Deploying one of those deploys everything it
|
|
23
|
+
requires first, so every module in the workspace is still exercised — just
|
|
24
|
+
once per covering module instead of once per module.
|
|
25
|
+
|
|
21
26
|
Options:
|
|
22
27
|
--help, -h Show this help message
|
|
23
28
|
--exclude <pkgs> Comma-separated module names to exclude
|
|
24
29
|
--continue-on-fail Continue testing all packages even after failures
|
|
25
30
|
--full-cycle Run full deploy/verify/revert/deploy cycle (default: deploy only)
|
|
31
|
+
--force-all Test every module in its own database (pre-minimal-set behavior)
|
|
26
32
|
--cwd <directory> Working directory (default: current directory)
|
|
27
33
|
|
|
28
34
|
Examples:
|
|
29
|
-
pgpm test-packages Test
|
|
35
|
+
pgpm test-packages Test the minimal covering set
|
|
36
|
+
pgpm test-packages --force-all Test every module in its own database
|
|
30
37
|
pgpm test-packages --full-cycle Run full test cycle with verify/revert
|
|
31
38
|
pgpm test-packages --continue-on-fail Test all packages, collect all failures
|
|
32
39
|
pgpm test-packages --exclude my-module Exclude specific modules
|
|
33
40
|
`;
|
|
41
|
+
/**
|
|
42
|
+
* Reduce the module list to a minimal covering set: the modules that no other
|
|
43
|
+
* candidate requires, transitively.
|
|
44
|
+
*
|
|
45
|
+
* Testing a module deploys its whole dependency closure in dependency order, so
|
|
46
|
+
* a module that some other candidate requires is already deployed, verified,
|
|
47
|
+
* reverted and re-deployed inside that candidate's database. Selection runs over
|
|
48
|
+
* the post-exclude candidates, so excluding a covering module promotes whatever
|
|
49
|
+
* it alone covered rather than dropping it.
|
|
50
|
+
*
|
|
51
|
+
* The property this trades away is standalone deployability — that a module's own
|
|
52
|
+
* `requires` is complete rather than satisfied by a sibling in the same database.
|
|
53
|
+
* Use --force-all to get it back.
|
|
54
|
+
*/
|
|
55
|
+
export function minimalCoveringSet(moduleMap, candidateNames) {
|
|
56
|
+
const candidates = new Set(candidateNames);
|
|
57
|
+
// name -> its transitive requires, restricted to the candidates
|
|
58
|
+
const closures = new Map();
|
|
59
|
+
for (const name of candidates) {
|
|
60
|
+
const { resolved } = resolveExtensionDependencies(name, moduleMap);
|
|
61
|
+
closures.set(name, resolved.filter((dep) => dep !== name && candidates.has(dep)));
|
|
62
|
+
}
|
|
63
|
+
const coveredBy = new Map();
|
|
64
|
+
for (const [name, deps] of closures) {
|
|
65
|
+
for (const dep of deps) {
|
|
66
|
+
if (!coveredBy.has(dep)) {
|
|
67
|
+
coveredBy.set(dep, name);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
const selected = candidateNames.filter((name) => !coveredBy.has(name));
|
|
72
|
+
// Nothing may fall out of the selection. Covering-set membership is derived
|
|
73
|
+
// from pairwise closures, so assert the union actually reaches every candidate
|
|
74
|
+
// rather than trusting that argument.
|
|
75
|
+
const covered = new Set();
|
|
76
|
+
for (const name of selected) {
|
|
77
|
+
covered.add(name);
|
|
78
|
+
for (const dep of closures.get(name) ?? []) {
|
|
79
|
+
covered.add(dep);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
const uncovered = candidateNames.filter((name) => !covered.has(name));
|
|
83
|
+
if (uncovered.length > 0) {
|
|
84
|
+
throw new Error(`Minimal module selection left ${uncovered.length} module(s) untested: ${uncovered.join(', ')}. ` +
|
|
85
|
+
'Re-run with --force-all and report this as a pgpm bug.');
|
|
86
|
+
}
|
|
87
|
+
return { selected, coveredBy };
|
|
88
|
+
}
|
|
89
|
+
function selectMinimalModules(workspacePkg, candidates) {
|
|
90
|
+
const { selected, coveredBy } = minimalCoveringSet(workspacePkg.getModuleMap(), candidates.map((mod) => mod.getModuleName()));
|
|
91
|
+
const selectedNames = new Set(selected);
|
|
92
|
+
return {
|
|
93
|
+
selected: candidates.filter((mod) => selectedNames.has(mod.getModuleName())),
|
|
94
|
+
coveredBy
|
|
95
|
+
};
|
|
96
|
+
}
|
|
34
97
|
function dbSafeName(moduleName) {
|
|
35
98
|
return `test_${moduleName}`.replace(/[^a-zA-Z0-9]/g, '_').toLowerCase();
|
|
36
99
|
}
|
|
@@ -204,6 +267,7 @@ export default async (argv, _prompter, _options) => {
|
|
|
204
267
|
const continueOnFail = argv['continue-on-fail'] === true || argv.continueOnFail === true;
|
|
205
268
|
const stopOnFail = !continueOnFail;
|
|
206
269
|
const fullCycle = argv['full-cycle'] === true || argv.fullCycle === true;
|
|
270
|
+
const forceAll = argv['force-all'] === true || argv.forceAll === true;
|
|
207
271
|
const cwd = argv.cwd || process.cwd();
|
|
208
272
|
// Parse excludes
|
|
209
273
|
let excludes = [];
|
|
@@ -211,7 +275,7 @@ export default async (argv, _prompter, _options) => {
|
|
|
211
275
|
excludes = argv.exclude.split(',').map(e => e.trim());
|
|
212
276
|
}
|
|
213
277
|
console.log('=== PGPM Package Integration Test ===');
|
|
214
|
-
console.log(`Testing all packages with ${fullCycle ? 'deploy/verify/revert/deploy cycle' : 'deploy only'}`);
|
|
278
|
+
console.log(`Testing ${forceAll ? 'all packages' : 'the minimal covering set'} with ${fullCycle ? 'deploy/verify/revert/deploy cycle' : 'deploy only'}`);
|
|
215
279
|
if (!stopOnFail) {
|
|
216
280
|
console.log('Mode: Test all packages (collect all failures)');
|
|
217
281
|
}
|
|
@@ -250,14 +314,21 @@ export default async (argv, _prompter, _options) => {
|
|
|
250
314
|
});
|
|
251
315
|
console.log(`Excluding: ${excludes.join(', ')}`);
|
|
252
316
|
}
|
|
253
|
-
|
|
254
|
-
|
|
317
|
+
let selectedModules = filteredModules;
|
|
318
|
+
if (!forceAll) {
|
|
319
|
+
const { selected, coveredBy } = selectMinimalModules(workspacePkg, filteredModules);
|
|
320
|
+
selectedModules = selected;
|
|
321
|
+
console.log(`Minimal covering set: ${selected.length} of ${filteredModules.length} modules ` +
|
|
322
|
+
`(${coveredBy.size} covered transitively; --force-all to test each on its own)`);
|
|
323
|
+
}
|
|
324
|
+
console.log(`Found ${selectedModules.length} modules to test:`);
|
|
325
|
+
for (const mod of selectedModules) {
|
|
255
326
|
console.log(` - ${mod.getModuleName()}`);
|
|
256
327
|
}
|
|
257
328
|
console.log('');
|
|
258
329
|
const failedPackages = [];
|
|
259
330
|
const successfulPackages = [];
|
|
260
|
-
for (const modulePkg of
|
|
331
|
+
for (const modulePkg of selectedModules) {
|
|
261
332
|
const result = await testModule(workspacePkg, modulePkg, fullCycle);
|
|
262
333
|
if (result.success) {
|
|
263
334
|
successfulPackages.push(result);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pgpm",
|
|
3
|
-
"version": "5.
|
|
3
|
+
"version": "5.25.0",
|
|
4
4
|
"author": "Constructive <developers@constructive.io>",
|
|
5
5
|
"description": "PostgreSQL Package Manager - Database migration and package management CLI",
|
|
6
6
|
"main": "index.js",
|
|
@@ -49,7 +49,7 @@
|
|
|
49
49
|
"@pgpmjs/core": "^7.25.4",
|
|
50
50
|
"@pgpmjs/diff": "^0.3.5",
|
|
51
51
|
"@pgpmjs/env": "^2.41.3",
|
|
52
|
-
"@pgpmjs/export": "^1.16.
|
|
52
|
+
"@pgpmjs/export": "^1.16.9",
|
|
53
53
|
"@pgpmjs/import": "^0.3.6",
|
|
54
54
|
"@pgpmjs/logger": "^2.24.1",
|
|
55
55
|
"@pgpmjs/slice": "^0.14.6",
|
|
@@ -80,5 +80,5 @@
|
|
|
80
80
|
"pg",
|
|
81
81
|
"pgsql"
|
|
82
82
|
],
|
|
83
|
-
"gitHead": "
|
|
83
|
+
"gitHead": "44f56ee80a348f649a22e5bd32b35f2822726509"
|
|
84
84
|
}
|