pgpm 1.1.4 → 1.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/README.md CHANGED
@@ -90,6 +90,10 @@ Here are some useful commands for reference:
90
90
  - `pgpm plan` - Generate deployment plans for your modules
91
91
  - `pgpm package` - Package your module for distribution
92
92
 
93
+ ### Testing
94
+
95
+ - `pgpm test-packages` - Run integration tests on all modules in a workspace
96
+
93
97
  ### Utilities
94
98
 
95
99
  - `pgpm add` - Add a new database change
@@ -302,6 +306,43 @@ pgpm kill
302
306
  pgpm kill --no-drop
303
307
  ```
304
308
 
309
+ ### Testing
310
+
311
+ #### `pgpm test-packages`
312
+
313
+ Run integration tests on all modules in a workspace. Creates a temporary database for each module, deploys, and optionally runs verify/revert/deploy cycles.
314
+
315
+ ```bash
316
+ # Test all modules in workspace (deploy only)
317
+ pgpm test-packages
318
+
319
+ # Run full deploy/verify/revert/deploy cycle
320
+ pgpm test-packages --full-cycle
321
+
322
+ # Stop on first failure
323
+ pgpm test-packages --stop-on-fail
324
+
325
+ # Exclude specific modules
326
+ pgpm test-packages --exclude my-module,another-module
327
+
328
+ # Combine options
329
+ pgpm test-packages --full-cycle --stop-on-fail --exclude legacy-module
330
+ ```
331
+
332
+ **Options:**
333
+
334
+ - `--full-cycle` - Run full deploy/verify/revert/deploy cycle (default: deploy only)
335
+ - `--stop-on-fail` - Stop testing immediately when a module fails
336
+ - `--exclude <modules>` - Comma-separated module names to exclude
337
+ - `--cwd <directory>` - Working directory (default: current directory)
338
+
339
+ **Notes:**
340
+
341
+ - Discovers modules from workspace `pgpm.json` configuration
342
+ - Creates isolated test databases (`test_<module_name>`) for each module
343
+ - Automatically cleans up test databases after each test
344
+ - Uses internal APIs for deploy/verify/revert operations
345
+
305
346
  ## ⚙️ Configuration
306
347
 
307
348
  ### Environment Variables
@@ -0,0 +1,4 @@
1
+ import { CLIOptions, Inquirerer } from 'inquirerer';
2
+ import { ParsedArgs } from 'minimist';
3
+ declare const _default: (argv: Partial<ParsedArgs>, _prompter: Inquirerer, _options: CLIOptions) => Promise<never>;
4
+ export default _default;
@@ -0,0 +1,320 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ const core_1 = require("@pgpmjs/core");
7
+ const env_1 = require("@pgpmjs/env");
8
+ const logger_1 = require("@pgpmjs/logger");
9
+ const path_1 = __importDefault(require("path"));
10
+ const pg_env_1 = require("pg-env");
11
+ const pg_cache_1 = require("pg-cache");
12
+ const log = new logger_1.Logger('test-packages');
13
+ // ANSI color codes
14
+ const RED = '\x1b[0;31m';
15
+ const GREEN = '\x1b[0;32m';
16
+ const YELLOW = '\x1b[1;33m';
17
+ const NC = '\x1b[0m'; // No Color
18
+ const testPackagesUsageText = `
19
+ Test Packages Command:
20
+
21
+ pgpm test-packages [OPTIONS]
22
+
23
+ Run integration tests on all PGPM packages in a workspace.
24
+ Tests each package with a deploy/verify/revert/deploy cycle.
25
+
26
+ Options:
27
+ --help, -h Show this help message
28
+ --exclude <pkgs> Comma-separated module names to exclude
29
+ --stop-on-fail Stop testing immediately when a package fails
30
+ --full-cycle Run full deploy/verify/revert/deploy cycle (default: deploy only)
31
+ --cwd <directory> Working directory (default: current directory)
32
+
33
+ Examples:
34
+ pgpm test-packages Test all packages in workspace
35
+ pgpm test-packages --full-cycle Run full test cycle with verify/revert
36
+ pgpm test-packages --stop-on-fail Stop on first failure
37
+ pgpm test-packages --exclude my-module Exclude specific modules
38
+ `;
39
+ function dbSafeName(moduleName) {
40
+ return `test_${moduleName}`.replace(/[^a-zA-Z0-9]/g, '_').toLowerCase();
41
+ }
42
+ async function createDatabase(dbname, adminDb = 'postgres') {
43
+ try {
44
+ const pgEnv = (0, pg_env_1.getPgEnvOptions)({ database: adminDb });
45
+ const pool = (0, pg_cache_1.getPgPool)(pgEnv);
46
+ // Sanitize database name (only allow alphanumeric and underscore)
47
+ const safeName = dbname.replace(/[^a-zA-Z0-9_]/g, '_');
48
+ await pool.query(`CREATE DATABASE "${safeName}"`);
49
+ log.debug(`Created database: ${safeName}`);
50
+ return true;
51
+ }
52
+ catch (error) {
53
+ if (error.code === '42P04') {
54
+ // Database already exists, that's fine
55
+ log.debug(`Database ${dbname} already exists`);
56
+ return true;
57
+ }
58
+ log.error(`Failed to create database ${dbname}: ${error.message}`);
59
+ return false;
60
+ }
61
+ }
62
+ async function dropDatabase(dbname, adminDb = 'postgres') {
63
+ try {
64
+ const pgEnv = (0, pg_env_1.getPgEnvOptions)({ database: adminDb });
65
+ const pool = (0, pg_cache_1.getPgPool)(pgEnv);
66
+ // Sanitize database name
67
+ const safeName = dbname.replace(/[^a-zA-Z0-9_]/g, '_');
68
+ // Terminate all connections to the database first
69
+ await pool.query(`
70
+ SELECT pg_terminate_backend(pid)
71
+ FROM pg_stat_activity
72
+ WHERE datname = $1 AND pid <> pg_backend_pid()
73
+ `, [safeName]);
74
+ await pool.query(`DROP DATABASE IF EXISTS "${safeName}"`);
75
+ log.debug(`Dropped database: ${safeName}`);
76
+ }
77
+ catch (error) {
78
+ // Ignore errors when dropping (database might not exist)
79
+ log.debug(`Could not drop database ${dbname}: ${error.message}`);
80
+ }
81
+ }
82
+ async function checkPostgresConnection() {
83
+ try {
84
+ const pgEnv = (0, pg_env_1.getPgEnvOptions)({ database: 'postgres' });
85
+ const pool = (0, pg_cache_1.getPgPool)(pgEnv);
86
+ await pool.query('SELECT 1');
87
+ return true;
88
+ }
89
+ catch {
90
+ return false;
91
+ }
92
+ }
93
+ async function testModule(workspacePkg, modulePkg, fullCycle) {
94
+ const moduleName = modulePkg.getModuleName();
95
+ const modulePath = modulePkg.getModulePath() || '';
96
+ const dbname = dbSafeName(moduleName);
97
+ console.log(`${YELLOW}Testing module: ${moduleName}${NC}`);
98
+ console.log(` Module path: ${modulePath}`);
99
+ console.log(` Database name: ${dbname}`);
100
+ // Clean up any existing test database
101
+ await dropDatabase(dbname);
102
+ // Create fresh test database
103
+ console.log(` Creating database: ${dbname}`);
104
+ if (!await createDatabase(dbname)) {
105
+ return {
106
+ moduleName,
107
+ modulePath,
108
+ success: false,
109
+ error: `Could not create database ${dbname}`
110
+ };
111
+ }
112
+ try {
113
+ // Create options for this test database
114
+ const opts = (0, env_1.getEnvOptions)({
115
+ pg: (0, pg_env_1.getPgEnvOptions)({ database: dbname }),
116
+ deployment: {
117
+ useTx: true,
118
+ fast: false,
119
+ usePlan: true,
120
+ cache: false,
121
+ logOnly: false
122
+ }
123
+ });
124
+ // Deploy
125
+ console.log(' Running deploy...');
126
+ try {
127
+ await workspacePkg.deploy(opts, moduleName, true);
128
+ }
129
+ catch (error) {
130
+ await dropDatabase(dbname);
131
+ return {
132
+ moduleName,
133
+ modulePath,
134
+ success: false,
135
+ error: `Deploy failed: ${error.message}`
136
+ };
137
+ }
138
+ if (fullCycle) {
139
+ // Verify
140
+ console.log(' Running verify...');
141
+ try {
142
+ await workspacePkg.verify(opts, moduleName, true);
143
+ }
144
+ catch (error) {
145
+ await dropDatabase(dbname);
146
+ return {
147
+ moduleName,
148
+ modulePath,
149
+ success: false,
150
+ error: `Verify failed: ${error.message}`
151
+ };
152
+ }
153
+ // Revert
154
+ console.log(' Running revert...');
155
+ try {
156
+ await workspacePkg.revert(opts, moduleName, true);
157
+ }
158
+ catch (error) {
159
+ await dropDatabase(dbname);
160
+ return {
161
+ moduleName,
162
+ modulePath,
163
+ success: false,
164
+ error: `Revert failed: ${error.message}`
165
+ };
166
+ }
167
+ // Deploy again
168
+ console.log(' Running deploy (second time)...');
169
+ try {
170
+ await workspacePkg.deploy(opts, moduleName, true);
171
+ }
172
+ catch (error) {
173
+ await dropDatabase(dbname);
174
+ return {
175
+ moduleName,
176
+ modulePath,
177
+ success: false,
178
+ error: `Deploy (second time) failed: ${error.message}`
179
+ };
180
+ }
181
+ }
182
+ // Clean up test database
183
+ await dropDatabase(dbname);
184
+ console.log(`${GREEN}SUCCESS: Module ${moduleName} passed all tests${NC}`);
185
+ return {
186
+ moduleName,
187
+ modulePath,
188
+ success: true
189
+ };
190
+ }
191
+ catch (error) {
192
+ // Ensure cleanup on any unexpected error
193
+ await dropDatabase(dbname);
194
+ return {
195
+ moduleName,
196
+ modulePath,
197
+ success: false,
198
+ error: `Unexpected error: ${error.message}`
199
+ };
200
+ }
201
+ }
202
+ exports.default = async (argv, _prompter, _options) => {
203
+ // Show usage if explicitly requested
204
+ if (argv.help || argv.h) {
205
+ console.log(testPackagesUsageText);
206
+ process.exit(0);
207
+ }
208
+ // Parse options
209
+ const stopOnFail = argv['stop-on-fail'] === true || argv.stopOnFail === true;
210
+ const fullCycle = argv['full-cycle'] === true || argv.fullCycle === true;
211
+ const cwd = argv.cwd || process.cwd();
212
+ // Parse excludes
213
+ let excludes = [];
214
+ if (argv.exclude) {
215
+ excludes = argv.exclude.split(',').map(e => e.trim());
216
+ }
217
+ console.log('=== PGPM Package Integration Test ===');
218
+ console.log(`Testing all packages with ${fullCycle ? 'deploy/verify/revert/deploy cycle' : 'deploy only'}`);
219
+ if (stopOnFail) {
220
+ console.log('Mode: Stop on first failure');
221
+ }
222
+ else {
223
+ console.log('Mode: Test all packages (collect all failures)');
224
+ }
225
+ console.log('');
226
+ // Check PostgreSQL connection
227
+ console.log('Checking PostgreSQL connection...');
228
+ if (!await checkPostgresConnection()) {
229
+ log.error('PostgreSQL not accessible.');
230
+ console.log('Ensure PostgreSQL is running and connection settings are correct.');
231
+ console.log('For local development: docker-compose up -d');
232
+ console.log('For CI: ensure PostgreSQL service is running');
233
+ process.exit(1);
234
+ }
235
+ console.log('PostgreSQL connection successful');
236
+ console.log('');
237
+ // Initialize workspace package
238
+ const projectRoot = path_1.default.resolve(cwd);
239
+ const workspacePkg = new core_1.PgpmPackage(projectRoot);
240
+ if (!workspacePkg.getWorkspacePath()) {
241
+ log.error('Not in a PGPM workspace. Run this command from a workspace root.');
242
+ process.exit(1);
243
+ }
244
+ // Get all modules from workspace using internal API
245
+ console.log('Finding all PGPM modules in workspace...');
246
+ const modules = await workspacePkg.getModules();
247
+ if (modules.length === 0) {
248
+ log.warn('No modules found in workspace.');
249
+ process.exit(0);
250
+ }
251
+ // Filter out excluded modules
252
+ let filteredModules = modules;
253
+ if (excludes.length > 0) {
254
+ filteredModules = modules.filter(mod => {
255
+ const moduleName = mod.getModuleName();
256
+ return !excludes.includes(moduleName);
257
+ });
258
+ console.log(`Excluding: ${excludes.join(', ')}`);
259
+ }
260
+ console.log(`Found ${filteredModules.length} modules to test:`);
261
+ for (const mod of filteredModules) {
262
+ console.log(` - ${mod.getModuleName()}`);
263
+ }
264
+ console.log('');
265
+ const failedPackages = [];
266
+ const successfulPackages = [];
267
+ for (const modulePkg of filteredModules) {
268
+ const result = await testModule(workspacePkg, modulePkg, fullCycle);
269
+ if (result.success) {
270
+ successfulPackages.push(result);
271
+ }
272
+ else {
273
+ failedPackages.push(result);
274
+ if (stopOnFail) {
275
+ console.log('');
276
+ console.error(`${RED}STOPPING: Test failed for module ${result.moduleName} and --stop-on-fail was specified${NC}`);
277
+ console.log('');
278
+ console.log('=== TEST SUMMARY (PARTIAL) ===');
279
+ if (successfulPackages.length > 0) {
280
+ console.log(`${GREEN}Successful modules before failure (${successfulPackages.length}):${NC}`);
281
+ for (const pkg of successfulPackages) {
282
+ console.log(` ${GREEN}✓${NC} ${pkg.moduleName}`);
283
+ }
284
+ console.log('');
285
+ }
286
+ console.error(`${RED}Failed module: ${result.moduleName}${NC}`);
287
+ if (result.error) {
288
+ console.error(` Error: ${result.error}`);
289
+ }
290
+ console.log('');
291
+ console.error(`${RED}OVERALL RESULT: FAILED (stopped on first failure)${NC}`);
292
+ process.exit(1);
293
+ }
294
+ }
295
+ console.log('');
296
+ }
297
+ console.log('=== TEST SUMMARY ===');
298
+ console.log(`${GREEN}Successful modules (${successfulPackages.length}):${NC}`);
299
+ for (const pkg of successfulPackages) {
300
+ console.log(` ${GREEN}✓${NC} ${pkg.moduleName}`);
301
+ }
302
+ if (failedPackages.length > 0) {
303
+ console.log('');
304
+ console.error(`${RED}Failed modules (${failedPackages.length}):${NC}`);
305
+ for (const pkg of failedPackages) {
306
+ console.error(` ${RED}✗${NC} ${pkg.moduleName}`);
307
+ if (pkg.error) {
308
+ console.error(` Error: ${pkg.error}`);
309
+ }
310
+ }
311
+ console.log('');
312
+ console.error(`${RED}OVERALL RESULT: FAILED${NC}`);
313
+ process.exit(1);
314
+ }
315
+ else {
316
+ console.log('');
317
+ console.log(`${GREEN}OVERALL RESULT: ALL MODULES PASSED${NC}`);
318
+ process.exit(0);
319
+ }
320
+ };
package/commands.js CHANGED
@@ -27,6 +27,7 @@ const remove_1 = __importDefault(require("./commands/remove"));
27
27
  const rename_1 = __importDefault(require("./commands/rename"));
28
28
  const revert_1 = __importDefault(require("./commands/revert"));
29
29
  const tag_1 = __importDefault(require("./commands/tag"));
30
+ const test_packages_1 = __importDefault(require("./commands/test-packages"));
30
31
  const verify_1 = __importDefault(require("./commands/verify"));
31
32
  const utils_1 = require("./utils");
32
33
  const cli_error_1 = require("./utils/cli-error");
@@ -64,6 +65,7 @@ const createPgpmCommandMap = (skipPgTeardown = false) => {
64
65
  migrate: pgt(migrate_1.default),
65
66
  analyze: pgt(analyze_1.default),
66
67
  rename: pgt(rename_1.default),
68
+ 'test-packages': pgt(test_packages_1.default),
67
69
  cache: cache_1.default,
68
70
  update: update_1.default
69
71
  };
@@ -0,0 +1,315 @@
1
+ import { PgpmPackage } from '@pgpmjs/core';
2
+ import { getEnvOptions } from '@pgpmjs/env';
3
+ import { Logger } from '@pgpmjs/logger';
4
+ import path from 'path';
5
+ import { getPgEnvOptions } from 'pg-env';
6
+ import { getPgPool } from 'pg-cache';
7
+ const log = new Logger('test-packages');
8
+ // ANSI color codes
9
+ const RED = '\x1b[0;31m';
10
+ const GREEN = '\x1b[0;32m';
11
+ const YELLOW = '\x1b[1;33m';
12
+ const NC = '\x1b[0m'; // No Color
13
+ const testPackagesUsageText = `
14
+ Test Packages Command:
15
+
16
+ pgpm test-packages [OPTIONS]
17
+
18
+ Run integration tests on all PGPM packages in a workspace.
19
+ Tests each package with a deploy/verify/revert/deploy cycle.
20
+
21
+ Options:
22
+ --help, -h Show this help message
23
+ --exclude <pkgs> Comma-separated module names to exclude
24
+ --stop-on-fail Stop testing immediately when a package fails
25
+ --full-cycle Run full deploy/verify/revert/deploy cycle (default: deploy only)
26
+ --cwd <directory> Working directory (default: current directory)
27
+
28
+ Examples:
29
+ pgpm test-packages Test all packages in workspace
30
+ pgpm test-packages --full-cycle Run full test cycle with verify/revert
31
+ pgpm test-packages --stop-on-fail Stop on first failure
32
+ pgpm test-packages --exclude my-module Exclude specific modules
33
+ `;
34
+ function dbSafeName(moduleName) {
35
+ return `test_${moduleName}`.replace(/[^a-zA-Z0-9]/g, '_').toLowerCase();
36
+ }
37
+ async function createDatabase(dbname, adminDb = 'postgres') {
38
+ try {
39
+ const pgEnv = getPgEnvOptions({ database: adminDb });
40
+ const pool = getPgPool(pgEnv);
41
+ // Sanitize database name (only allow alphanumeric and underscore)
42
+ const safeName = dbname.replace(/[^a-zA-Z0-9_]/g, '_');
43
+ await pool.query(`CREATE DATABASE "${safeName}"`);
44
+ log.debug(`Created database: ${safeName}`);
45
+ return true;
46
+ }
47
+ catch (error) {
48
+ if (error.code === '42P04') {
49
+ // Database already exists, that's fine
50
+ log.debug(`Database ${dbname} already exists`);
51
+ return true;
52
+ }
53
+ log.error(`Failed to create database ${dbname}: ${error.message}`);
54
+ return false;
55
+ }
56
+ }
57
+ async function dropDatabase(dbname, adminDb = 'postgres') {
58
+ try {
59
+ const pgEnv = getPgEnvOptions({ database: adminDb });
60
+ const pool = getPgPool(pgEnv);
61
+ // Sanitize database name
62
+ const safeName = dbname.replace(/[^a-zA-Z0-9_]/g, '_');
63
+ // Terminate all connections to the database first
64
+ await pool.query(`
65
+ SELECT pg_terminate_backend(pid)
66
+ FROM pg_stat_activity
67
+ WHERE datname = $1 AND pid <> pg_backend_pid()
68
+ `, [safeName]);
69
+ await pool.query(`DROP DATABASE IF EXISTS "${safeName}"`);
70
+ log.debug(`Dropped database: ${safeName}`);
71
+ }
72
+ catch (error) {
73
+ // Ignore errors when dropping (database might not exist)
74
+ log.debug(`Could not drop database ${dbname}: ${error.message}`);
75
+ }
76
+ }
77
+ async function checkPostgresConnection() {
78
+ try {
79
+ const pgEnv = getPgEnvOptions({ database: 'postgres' });
80
+ const pool = getPgPool(pgEnv);
81
+ await pool.query('SELECT 1');
82
+ return true;
83
+ }
84
+ catch {
85
+ return false;
86
+ }
87
+ }
88
+ async function testModule(workspacePkg, modulePkg, fullCycle) {
89
+ const moduleName = modulePkg.getModuleName();
90
+ const modulePath = modulePkg.getModulePath() || '';
91
+ const dbname = dbSafeName(moduleName);
92
+ console.log(`${YELLOW}Testing module: ${moduleName}${NC}`);
93
+ console.log(` Module path: ${modulePath}`);
94
+ console.log(` Database name: ${dbname}`);
95
+ // Clean up any existing test database
96
+ await dropDatabase(dbname);
97
+ // Create fresh test database
98
+ console.log(` Creating database: ${dbname}`);
99
+ if (!await createDatabase(dbname)) {
100
+ return {
101
+ moduleName,
102
+ modulePath,
103
+ success: false,
104
+ error: `Could not create database ${dbname}`
105
+ };
106
+ }
107
+ try {
108
+ // Create options for this test database
109
+ const opts = getEnvOptions({
110
+ pg: getPgEnvOptions({ database: dbname }),
111
+ deployment: {
112
+ useTx: true,
113
+ fast: false,
114
+ usePlan: true,
115
+ cache: false,
116
+ logOnly: false
117
+ }
118
+ });
119
+ // Deploy
120
+ console.log(' Running deploy...');
121
+ try {
122
+ await workspacePkg.deploy(opts, moduleName, true);
123
+ }
124
+ catch (error) {
125
+ await dropDatabase(dbname);
126
+ return {
127
+ moduleName,
128
+ modulePath,
129
+ success: false,
130
+ error: `Deploy failed: ${error.message}`
131
+ };
132
+ }
133
+ if (fullCycle) {
134
+ // Verify
135
+ console.log(' Running verify...');
136
+ try {
137
+ await workspacePkg.verify(opts, moduleName, true);
138
+ }
139
+ catch (error) {
140
+ await dropDatabase(dbname);
141
+ return {
142
+ moduleName,
143
+ modulePath,
144
+ success: false,
145
+ error: `Verify failed: ${error.message}`
146
+ };
147
+ }
148
+ // Revert
149
+ console.log(' Running revert...');
150
+ try {
151
+ await workspacePkg.revert(opts, moduleName, true);
152
+ }
153
+ catch (error) {
154
+ await dropDatabase(dbname);
155
+ return {
156
+ moduleName,
157
+ modulePath,
158
+ success: false,
159
+ error: `Revert failed: ${error.message}`
160
+ };
161
+ }
162
+ // Deploy again
163
+ console.log(' Running deploy (second time)...');
164
+ try {
165
+ await workspacePkg.deploy(opts, moduleName, true);
166
+ }
167
+ catch (error) {
168
+ await dropDatabase(dbname);
169
+ return {
170
+ moduleName,
171
+ modulePath,
172
+ success: false,
173
+ error: `Deploy (second time) failed: ${error.message}`
174
+ };
175
+ }
176
+ }
177
+ // Clean up test database
178
+ await dropDatabase(dbname);
179
+ console.log(`${GREEN}SUCCESS: Module ${moduleName} passed all tests${NC}`);
180
+ return {
181
+ moduleName,
182
+ modulePath,
183
+ success: true
184
+ };
185
+ }
186
+ catch (error) {
187
+ // Ensure cleanup on any unexpected error
188
+ await dropDatabase(dbname);
189
+ return {
190
+ moduleName,
191
+ modulePath,
192
+ success: false,
193
+ error: `Unexpected error: ${error.message}`
194
+ };
195
+ }
196
+ }
197
+ export default async (argv, _prompter, _options) => {
198
+ // Show usage if explicitly requested
199
+ if (argv.help || argv.h) {
200
+ console.log(testPackagesUsageText);
201
+ process.exit(0);
202
+ }
203
+ // Parse options
204
+ const stopOnFail = argv['stop-on-fail'] === true || argv.stopOnFail === true;
205
+ const fullCycle = argv['full-cycle'] === true || argv.fullCycle === true;
206
+ const cwd = argv.cwd || process.cwd();
207
+ // Parse excludes
208
+ let excludes = [];
209
+ if (argv.exclude) {
210
+ excludes = argv.exclude.split(',').map(e => e.trim());
211
+ }
212
+ console.log('=== PGPM Package Integration Test ===');
213
+ console.log(`Testing all packages with ${fullCycle ? 'deploy/verify/revert/deploy cycle' : 'deploy only'}`);
214
+ if (stopOnFail) {
215
+ console.log('Mode: Stop on first failure');
216
+ }
217
+ else {
218
+ console.log('Mode: Test all packages (collect all failures)');
219
+ }
220
+ console.log('');
221
+ // Check PostgreSQL connection
222
+ console.log('Checking PostgreSQL connection...');
223
+ if (!await checkPostgresConnection()) {
224
+ log.error('PostgreSQL not accessible.');
225
+ console.log('Ensure PostgreSQL is running and connection settings are correct.');
226
+ console.log('For local development: docker-compose up -d');
227
+ console.log('For CI: ensure PostgreSQL service is running');
228
+ process.exit(1);
229
+ }
230
+ console.log('PostgreSQL connection successful');
231
+ console.log('');
232
+ // Initialize workspace package
233
+ const projectRoot = path.resolve(cwd);
234
+ const workspacePkg = new PgpmPackage(projectRoot);
235
+ if (!workspacePkg.getWorkspacePath()) {
236
+ log.error('Not in a PGPM workspace. Run this command from a workspace root.');
237
+ process.exit(1);
238
+ }
239
+ // Get all modules from workspace using internal API
240
+ console.log('Finding all PGPM modules in workspace...');
241
+ const modules = await workspacePkg.getModules();
242
+ if (modules.length === 0) {
243
+ log.warn('No modules found in workspace.');
244
+ process.exit(0);
245
+ }
246
+ // Filter out excluded modules
247
+ let filteredModules = modules;
248
+ if (excludes.length > 0) {
249
+ filteredModules = modules.filter(mod => {
250
+ const moduleName = mod.getModuleName();
251
+ return !excludes.includes(moduleName);
252
+ });
253
+ console.log(`Excluding: ${excludes.join(', ')}`);
254
+ }
255
+ console.log(`Found ${filteredModules.length} modules to test:`);
256
+ for (const mod of filteredModules) {
257
+ console.log(` - ${mod.getModuleName()}`);
258
+ }
259
+ console.log('');
260
+ const failedPackages = [];
261
+ const successfulPackages = [];
262
+ for (const modulePkg of filteredModules) {
263
+ const result = await testModule(workspacePkg, modulePkg, fullCycle);
264
+ if (result.success) {
265
+ successfulPackages.push(result);
266
+ }
267
+ else {
268
+ failedPackages.push(result);
269
+ if (stopOnFail) {
270
+ console.log('');
271
+ console.error(`${RED}STOPPING: Test failed for module ${result.moduleName} and --stop-on-fail was specified${NC}`);
272
+ console.log('');
273
+ console.log('=== TEST SUMMARY (PARTIAL) ===');
274
+ if (successfulPackages.length > 0) {
275
+ console.log(`${GREEN}Successful modules before failure (${successfulPackages.length}):${NC}`);
276
+ for (const pkg of successfulPackages) {
277
+ console.log(` ${GREEN}✓${NC} ${pkg.moduleName}`);
278
+ }
279
+ console.log('');
280
+ }
281
+ console.error(`${RED}Failed module: ${result.moduleName}${NC}`);
282
+ if (result.error) {
283
+ console.error(` Error: ${result.error}`);
284
+ }
285
+ console.log('');
286
+ console.error(`${RED}OVERALL RESULT: FAILED (stopped on first failure)${NC}`);
287
+ process.exit(1);
288
+ }
289
+ }
290
+ console.log('');
291
+ }
292
+ console.log('=== TEST SUMMARY ===');
293
+ console.log(`${GREEN}Successful modules (${successfulPackages.length}):${NC}`);
294
+ for (const pkg of successfulPackages) {
295
+ console.log(` ${GREEN}✓${NC} ${pkg.moduleName}`);
296
+ }
297
+ if (failedPackages.length > 0) {
298
+ console.log('');
299
+ console.error(`${RED}Failed modules (${failedPackages.length}):${NC}`);
300
+ for (const pkg of failedPackages) {
301
+ console.error(` ${RED}✗${NC} ${pkg.moduleName}`);
302
+ if (pkg.error) {
303
+ console.error(` Error: ${pkg.error}`);
304
+ }
305
+ }
306
+ console.log('');
307
+ console.error(`${RED}OVERALL RESULT: FAILED${NC}`);
308
+ process.exit(1);
309
+ }
310
+ else {
311
+ console.log('');
312
+ console.log(`${GREEN}OVERALL RESULT: ALL MODULES PASSED${NC}`);
313
+ process.exit(0);
314
+ }
315
+ };
package/esm/commands.js CHANGED
@@ -21,6 +21,7 @@ import remove from './commands/remove';
21
21
  import renameCmd from './commands/rename';
22
22
  import revert from './commands/revert';
23
23
  import tag from './commands/tag';
24
+ import testPackages from './commands/test-packages';
24
25
  import verify from './commands/verify';
25
26
  import { extractFirst, usageText } from './utils';
26
27
  import { cliExitWithError } from './utils/cli-error';
@@ -58,6 +59,7 @@ export const createPgpmCommandMap = (skipPgTeardown = false) => {
58
59
  migrate: pgt(migrate),
59
60
  analyze: pgt(analyze),
60
61
  rename: pgt(renameCmd),
62
+ 'test-packages': pgt(testPackages),
61
63
  cache,
62
64
  update: updateCmd
63
65
  };
package/esm/index.js CHANGED
@@ -23,6 +23,7 @@ export { default as remove } from './commands/remove';
23
23
  export { default as renameCmd } from './commands/rename';
24
24
  export { default as revert } from './commands/revert';
25
25
  export { default as tag } from './commands/tag';
26
+ export { default as testPackages } from './commands/test-packages';
26
27
  export { default as verify } from './commands/verify';
27
28
  export * from './utils';
28
29
  export const options = {
package/index.d.ts CHANGED
@@ -21,6 +21,7 @@ export { default as remove } from './commands/remove';
21
21
  export { default as renameCmd } from './commands/rename';
22
22
  export { default as revert } from './commands/revert';
23
23
  export { default as tag } from './commands/tag';
24
+ export { default as testPackages } from './commands/test-packages';
24
25
  export { default as verify } from './commands/verify';
25
26
  export * from './utils';
26
27
  export declare const options: Partial<CLIOptions>;
package/index.js CHANGED
@@ -18,7 +18,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
18
18
  return (mod && mod.__esModule) ? mod : { "default": mod };
19
19
  };
20
20
  Object.defineProperty(exports, "__esModule", { value: true });
21
- exports.options = exports.verify = exports.tag = exports.revert = exports.renameCmd = exports.remove = exports.plan = exports._package = exports.migrate = exports.kill = exports.install = exports.extension = exports._export = exports.env = exports.docker = exports.deploy = exports.clear = exports.analyze = exports.adminUsers = exports.add = exports.createPgpmCommandMap = exports.createInitUsageText = void 0;
21
+ exports.options = exports.verify = exports.testPackages = exports.tag = exports.revert = exports.renameCmd = exports.remove = exports.plan = exports._package = exports.migrate = exports.kill = exports.install = exports.extension = exports._export = exports.env = exports.docker = exports.deploy = exports.clear = exports.analyze = exports.adminUsers = exports.add = exports.createPgpmCommandMap = exports.createInitUsageText = void 0;
22
22
  const fs_1 = require("fs");
23
23
  const inquirerer_1 = require("inquirerer");
24
24
  const path_1 = require("path");
@@ -62,6 +62,8 @@ var revert_1 = require("./commands/revert");
62
62
  Object.defineProperty(exports, "revert", { enumerable: true, get: function () { return __importDefault(revert_1).default; } });
63
63
  var tag_1 = require("./commands/tag");
64
64
  Object.defineProperty(exports, "tag", { enumerable: true, get: function () { return __importDefault(tag_1).default; } });
65
+ var test_packages_1 = require("./commands/test-packages");
66
+ Object.defineProperty(exports, "testPackages", { enumerable: true, get: function () { return __importDefault(test_packages_1).default; } });
65
67
  var verify_1 = require("./commands/verify");
66
68
  Object.defineProperty(exports, "verify", { enumerable: true, get: function () { return __importDefault(verify_1).default; } });
67
69
  __exportStar(require("./utils"), exports);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pgpm",
3
- "version": "1.1.4",
3
+ "version": "1.2.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",
@@ -45,7 +45,7 @@
45
45
  "ts-node": "^10.9.2"
46
46
  },
47
47
  "dependencies": {
48
- "@pgpmjs/core": "^3.1.2",
48
+ "@pgpmjs/core": "^3.1.3",
49
49
  "@pgpmjs/env": "^2.8.8",
50
50
  "@pgpmjs/logger": "^1.3.5",
51
51
  "@pgpmjs/types": "^2.12.6",
@@ -73,5 +73,5 @@
73
73
  "pg",
74
74
  "pgsql"
75
75
  ],
76
- "gitHead": "deb816f0fb9aad6cef1accf0dc603e016e48d335"
76
+ "gitHead": "baf8d2ffc48a6792573cd613bba0b108574b9477"
77
77
  }