@pgpmjs/core 3.0.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.
Files changed (140) hide show
  1. package/LICENSE +23 -0
  2. package/README.md +99 -0
  3. package/core/boilerplate-scanner.d.ts +41 -0
  4. package/core/boilerplate-scanner.js +106 -0
  5. package/core/boilerplate-types.d.ts +52 -0
  6. package/core/boilerplate-types.js +6 -0
  7. package/core/class/pgpm.d.ts +150 -0
  8. package/core/class/pgpm.js +1470 -0
  9. package/core/template-scaffold.d.ts +29 -0
  10. package/core/template-scaffold.js +168 -0
  11. package/esm/core/boilerplate-scanner.js +96 -0
  12. package/esm/core/boilerplate-types.js +5 -0
  13. package/esm/core/class/pgpm.js +1430 -0
  14. package/esm/core/template-scaffold.js +161 -0
  15. package/esm/export/export-meta.js +240 -0
  16. package/esm/export/export-migrations.js +180 -0
  17. package/esm/extensions/extensions.js +31 -0
  18. package/esm/files/extension/index.js +3 -0
  19. package/esm/files/extension/reader.js +79 -0
  20. package/esm/files/extension/writer.js +63 -0
  21. package/esm/files/index.js +6 -0
  22. package/esm/files/plan/generator.js +49 -0
  23. package/esm/files/plan/index.js +5 -0
  24. package/esm/files/plan/parser.js +296 -0
  25. package/esm/files/plan/validators.js +181 -0
  26. package/esm/files/plan/writer.js +114 -0
  27. package/esm/files/sql/index.js +1 -0
  28. package/esm/files/sql/writer.js +107 -0
  29. package/esm/files/sql-scripts/index.js +2 -0
  30. package/esm/files/sql-scripts/reader.js +19 -0
  31. package/esm/files/types/index.js +1 -0
  32. package/esm/files/types/package.js +1 -0
  33. package/esm/index.js +21 -0
  34. package/esm/init/client.js +144 -0
  35. package/esm/init/sql/bootstrap-roles.sql +55 -0
  36. package/esm/init/sql/bootstrap-test-roles.sql +72 -0
  37. package/esm/migrate/clean.js +23 -0
  38. package/esm/migrate/client.js +551 -0
  39. package/esm/migrate/index.js +5 -0
  40. package/esm/migrate/sql/procedures.sql +258 -0
  41. package/esm/migrate/sql/schema.sql +37 -0
  42. package/esm/migrate/types.js +1 -0
  43. package/esm/migrate/utils/event-logger.js +28 -0
  44. package/esm/migrate/utils/hash.js +27 -0
  45. package/esm/migrate/utils/transaction.js +125 -0
  46. package/esm/modules/modules.js +49 -0
  47. package/esm/packaging/package.js +96 -0
  48. package/esm/packaging/transform.js +70 -0
  49. package/esm/projects/deploy.js +123 -0
  50. package/esm/projects/revert.js +75 -0
  51. package/esm/projects/verify.js +61 -0
  52. package/esm/resolution/deps.js +526 -0
  53. package/esm/resolution/resolve.js +101 -0
  54. package/esm/utils/debug.js +147 -0
  55. package/esm/utils/target-utils.js +37 -0
  56. package/esm/workspace/paths.js +43 -0
  57. package/esm/workspace/utils.js +31 -0
  58. package/export/export-meta.d.ts +8 -0
  59. package/export/export-meta.js +244 -0
  60. package/export/export-migrations.d.ts +17 -0
  61. package/export/export-migrations.js +187 -0
  62. package/extensions/extensions.d.ts +5 -0
  63. package/extensions/extensions.js +35 -0
  64. package/files/extension/index.d.ts +2 -0
  65. package/files/extension/index.js +19 -0
  66. package/files/extension/reader.d.ts +24 -0
  67. package/files/extension/reader.js +86 -0
  68. package/files/extension/writer.d.ts +39 -0
  69. package/files/extension/writer.js +70 -0
  70. package/files/index.d.ts +5 -0
  71. package/files/index.js +22 -0
  72. package/files/plan/generator.d.ts +22 -0
  73. package/files/plan/generator.js +57 -0
  74. package/files/plan/index.d.ts +4 -0
  75. package/files/plan/index.js +21 -0
  76. package/files/plan/parser.d.ts +27 -0
  77. package/files/plan/parser.js +303 -0
  78. package/files/plan/validators.d.ts +52 -0
  79. package/files/plan/validators.js +187 -0
  80. package/files/plan/writer.d.ts +27 -0
  81. package/files/plan/writer.js +124 -0
  82. package/files/sql/index.d.ts +1 -0
  83. package/files/sql/index.js +17 -0
  84. package/files/sql/writer.d.ts +12 -0
  85. package/files/sql/writer.js +114 -0
  86. package/files/sql-scripts/index.d.ts +1 -0
  87. package/files/sql-scripts/index.js +18 -0
  88. package/files/sql-scripts/reader.d.ts +8 -0
  89. package/files/sql-scripts/reader.js +23 -0
  90. package/files/types/index.d.ts +46 -0
  91. package/files/types/index.js +17 -0
  92. package/files/types/package.d.ts +20 -0
  93. package/files/types/package.js +2 -0
  94. package/index.d.ts +21 -0
  95. package/index.js +45 -0
  96. package/init/client.d.ts +26 -0
  97. package/init/client.js +148 -0
  98. package/init/sql/bootstrap-roles.sql +55 -0
  99. package/init/sql/bootstrap-test-roles.sql +72 -0
  100. package/migrate/clean.d.ts +1 -0
  101. package/migrate/clean.js +27 -0
  102. package/migrate/client.d.ts +80 -0
  103. package/migrate/client.js +555 -0
  104. package/migrate/index.d.ts +5 -0
  105. package/migrate/index.js +21 -0
  106. package/migrate/sql/procedures.sql +258 -0
  107. package/migrate/sql/schema.sql +37 -0
  108. package/migrate/types.d.ts +67 -0
  109. package/migrate/types.js +2 -0
  110. package/migrate/utils/event-logger.d.ts +13 -0
  111. package/migrate/utils/event-logger.js +32 -0
  112. package/migrate/utils/hash.d.ts +12 -0
  113. package/migrate/utils/hash.js +32 -0
  114. package/migrate/utils/transaction.d.ts +27 -0
  115. package/migrate/utils/transaction.js +129 -0
  116. package/modules/modules.d.ts +31 -0
  117. package/modules/modules.js +56 -0
  118. package/package.json +70 -0
  119. package/packaging/package.d.ts +19 -0
  120. package/packaging/package.js +102 -0
  121. package/packaging/transform.d.ts +22 -0
  122. package/packaging/transform.js +75 -0
  123. package/projects/deploy.d.ts +8 -0
  124. package/projects/deploy.js +160 -0
  125. package/projects/revert.d.ts +15 -0
  126. package/projects/revert.js +112 -0
  127. package/projects/verify.d.ts +8 -0
  128. package/projects/verify.js +98 -0
  129. package/resolution/deps.d.ts +57 -0
  130. package/resolution/deps.js +531 -0
  131. package/resolution/resolve.d.ts +37 -0
  132. package/resolution/resolve.js +107 -0
  133. package/utils/debug.d.ts +21 -0
  134. package/utils/debug.js +153 -0
  135. package/utils/target-utils.d.ts +5 -0
  136. package/utils/target-utils.js +40 -0
  137. package/workspace/paths.d.ts +14 -0
  138. package/workspace/paths.js +50 -0
  139. package/workspace/utils.d.ts +8 -0
  140. package/workspace/utils.js +36 -0
@@ -0,0 +1,27 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.cleanSql = void 0;
4
+ const pgsql_parser_1 = require("pgsql-parser");
5
+ const filterStatements = (stmts) => {
6
+ const filteredStmts = stmts.filter(node => {
7
+ const stmt = node.stmt;
8
+ return stmt && !stmt.hasOwnProperty('TransactionStmt') &&
9
+ !stmt.hasOwnProperty('CreateExtensionStmt');
10
+ });
11
+ const hasFiltered = filteredStmts.length !== stmts.length;
12
+ return { filteredStmts, hasFiltered };
13
+ };
14
+ const cleanSql = async (sql, pretty, functionDelimiter) => {
15
+ const parsed = await (0, pgsql_parser_1.parse)(sql);
16
+ const { filteredStmts, hasFiltered } = filterStatements(parsed.stmts);
17
+ if (!hasFiltered) {
18
+ return sql;
19
+ }
20
+ parsed.stmts = filteredStmts;
21
+ const finalSql = await (0, pgsql_parser_1.deparse)(parsed, {
22
+ pretty,
23
+ functionDelimiter
24
+ });
25
+ return finalSql;
26
+ };
27
+ exports.cleanSql = cleanSql;
@@ -0,0 +1,80 @@
1
+ import { PgConfig } from 'pg-env';
2
+ import { DeployOptions, DeployResult, RevertOptions, RevertResult, StatusResult, VerifyOptions, VerifyResult } from './types';
3
+ export type HashMethod = 'content' | 'ast';
4
+ export interface PgpmMigrateOptions {
5
+ /**
6
+ * Hash method for SQL files:
7
+ * - 'content': Hash the raw file content (fast, but sensitive to formatting changes)
8
+ * - 'ast': Hash the parsed AST structure (robust, ignores formatting/comments but slower)
9
+ */
10
+ hashMethod?: HashMethod;
11
+ }
12
+ export declare class PgpmMigrate {
13
+ private pool;
14
+ private pgConfig;
15
+ private hashMethod;
16
+ private eventLogger;
17
+ private initialized;
18
+ private toUnqualifiedLocal;
19
+ constructor(config: PgConfig, options?: PgpmMigrateOptions);
20
+ /**
21
+ * Calculate script hash using the configured method
22
+ */
23
+ private calculateScriptHash;
24
+ /**
25
+ * Initialize the migration schema
26
+ */
27
+ initialize(): Promise<void>;
28
+ /**
29
+ * Resolve toChange parameter, handling tag resolution if needed
30
+ */
31
+ private resolveToChange;
32
+ /**
33
+ * Deploy changes according to plan file
34
+ */
35
+ deploy(options: DeployOptions): Promise<DeployResult>;
36
+ /**
37
+ * Revert changes according to plan file
38
+ */
39
+ revert(options: RevertOptions): Promise<RevertResult>;
40
+ /**
41
+ * Verify deployed changes
42
+ */
43
+ verify(options: VerifyOptions): Promise<VerifyResult>;
44
+ /**
45
+ * Get deployment status
46
+ */
47
+ status(packageName?: string): Promise<StatusResult[]>;
48
+ /**
49
+ * Check if a change is deployed
50
+ */
51
+ isDeployed(packageName: string, changeName: string): Promise<boolean>;
52
+ /**
53
+ * Check if Sqitch tables exist in the database
54
+ */
55
+ hasSqitchTables(): Promise<boolean>;
56
+ /**
57
+ * Import from existing Sqitch deployment
58
+ */
59
+ importFromSqitch(): Promise<void>;
60
+ /**
61
+ * Get recent changes
62
+ */
63
+ getRecentChanges(targetDatabase: string, limit?: number): Promise<any[]>;
64
+ /**
65
+ * Get pending changes (in plan but not deployed)
66
+ */
67
+ getPendingChanges(planPath: string, targetDatabase: string): Promise<string[]>;
68
+ /**
69
+ * Get all deployed changes for a project
70
+ */
71
+ getDeployedChanges(targetDatabase: string, packageName: string): Promise<any[]>;
72
+ /**
73
+ * Get dependencies for a change
74
+ */
75
+ getDependencies(packageName: string, changeName: string): Promise<string[]>;
76
+ /**
77
+ * Close the database connection pool
78
+ */
79
+ close(): Promise<void>;
80
+ }
@@ -0,0 +1,555 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.PgpmMigrate = void 0;
4
+ const logger_1 = require("@pgpmjs/logger");
5
+ const types_1 = require("@pgpmjs/types");
6
+ const fs_1 = require("fs");
7
+ const path_1 = require("path");
8
+ const pg_cache_1 = require("pg-cache");
9
+ const files_1 = require("../files");
10
+ const deps_1 = require("../resolution/deps");
11
+ const resolve_1 = require("../resolution/resolve");
12
+ const clean_1 = require("./clean");
13
+ const event_logger_1 = require("./utils/event-logger");
14
+ const hash_1 = require("./utils/hash");
15
+ const transaction_1 = require("./utils/transaction");
16
+ // Helper function to get changes in order
17
+ function getChangesInOrder(planPath, reverse = false) {
18
+ const plan = (0, files_1.parsePlanFileSimple)(planPath);
19
+ return reverse ? [...plan.changes].reverse() : plan.changes;
20
+ }
21
+ const log = new logger_1.Logger('migrate');
22
+ class PgpmMigrate {
23
+ pool;
24
+ pgConfig;
25
+ hashMethod;
26
+ eventLogger;
27
+ initialized = false;
28
+ toUnqualifiedLocal(pkg, nm) {
29
+ if (!nm.includes(':'))
30
+ return nm;
31
+ const [p, local] = nm.split(':', 2);
32
+ if (p === pkg)
33
+ return local;
34
+ throw new Error(`Cross-package change encountered in local tracking: ${nm} (current package: ${pkg})`);
35
+ }
36
+ constructor(config, options = {}) {
37
+ this.pgConfig = config;
38
+ // Use environment variable DEPLOYMENT_HASH_METHOD if available, otherwise use options or default to 'content'
39
+ const envHashMethod = process.env.DEPLOYMENT_HASH_METHOD;
40
+ this.hashMethod = options.hashMethod || envHashMethod || 'content';
41
+ this.pool = (0, pg_cache_1.getPgPool)(this.pgConfig);
42
+ this.eventLogger = new event_logger_1.EventLogger(this.pgConfig);
43
+ }
44
+ /**
45
+ * Calculate script hash using the configured method
46
+ */
47
+ async calculateScriptHash(filePath) {
48
+ if (this.hashMethod === 'ast') {
49
+ return await (0, hash_1.hashSqlFile)(filePath);
50
+ }
51
+ else {
52
+ return await (0, hash_1.hashFile)(filePath);
53
+ }
54
+ }
55
+ /**
56
+ * Initialize the migration schema
57
+ */
58
+ async initialize() {
59
+ if (this.initialized)
60
+ return;
61
+ try {
62
+ log.info('Checking LaunchQL migration schema...');
63
+ // Check if pgpm_migrate schema exists
64
+ const result = await this.pool.query(`
65
+ SELECT schema_name
66
+ FROM information_schema.schemata
67
+ WHERE schema_name = 'pgpm_migrate'
68
+ `);
69
+ if (result.rows.length === 0) {
70
+ log.info('Schema not found, creating migration schema...');
71
+ // Read and execute schema SQL to create schema and tables
72
+ const schemaPath = (0, path_1.join)(__dirname, 'sql', 'schema.sql');
73
+ const proceduresPath = (0, path_1.join)(__dirname, 'sql', 'procedures.sql');
74
+ const schemaSql = (0, fs_1.readFileSync)(schemaPath, 'utf-8');
75
+ const proceduresSql = (0, fs_1.readFileSync)(proceduresPath, 'utf-8');
76
+ await this.pool.query(schemaSql);
77
+ await this.pool.query(proceduresSql);
78
+ log.success('Migration schema created successfully');
79
+ }
80
+ else {
81
+ log.success('Migration schema found and ready');
82
+ }
83
+ this.initialized = true;
84
+ }
85
+ catch (error) {
86
+ log.error('Failed to initialize migration schema:', error);
87
+ throw error;
88
+ }
89
+ }
90
+ /**
91
+ * Resolve toChange parameter, handling tag resolution if needed
92
+ */
93
+ resolveToChange(toChange, planPath, packageName) {
94
+ return toChange && toChange.includes('@') ? (0, resolve_1.resolveTagToChangeName)(planPath, toChange, packageName) : toChange;
95
+ }
96
+ /**
97
+ * Deploy changes according to plan file
98
+ */
99
+ async deploy(options) {
100
+ await this.initialize();
101
+ const { modulePath, toChange, useTransaction = true, debug = false, logOnly = false } = options;
102
+ const planPath = (0, path_1.join)(modulePath, 'pgpm.plan');
103
+ const plan = (0, files_1.parsePlanFileSimple)(planPath);
104
+ const resolvedToChange = this.resolveToChange(toChange, planPath, plan.package);
105
+ const changes = getChangesInOrder(planPath);
106
+ const fullPlanResult = (0, files_1.parsePlanFile)(planPath);
107
+ const packageDir = (0, path_1.dirname)(planPath);
108
+ const resolvedDeps = (0, deps_1.resolveDependencies)(packageDir, fullPlanResult.data?.package || plan.package, {
109
+ tagResolution: 'resolve',
110
+ loadPlanFiles: true,
111
+ source: options.usePlan === false ? 'sql' : 'plan'
112
+ });
113
+ const deployed = [];
114
+ const skipped = [];
115
+ let failed;
116
+ // Use a separate pool for the target database
117
+ const targetPool = (0, pg_cache_1.getPgPool)(this.pgConfig);
118
+ // Execute deployment with or without transaction
119
+ await (0, transaction_1.withTransaction)(targetPool, { useTransaction }, async (context) => {
120
+ for (const change of changes) {
121
+ // Stop if we've reached the target change
122
+ if (resolvedToChange && deployed.includes(resolvedToChange)) {
123
+ break;
124
+ }
125
+ const isDeployed = await this.isDeployed(plan.package, change.name);
126
+ if (isDeployed) {
127
+ log.info(`Skipping already deployed change: ${change.name}`);
128
+ const unqualified = this.toUnqualifiedLocal(plan.package, change.name);
129
+ skipped.push(unqualified);
130
+ continue;
131
+ }
132
+ // Read deploy script
133
+ const deployScript = (0, files_1.readScript)((0, path_1.dirname)(planPath), 'deploy', change.name);
134
+ if (!deployScript) {
135
+ log.error(`Deploy script not found for change: ${change.name}`);
136
+ failed = change.name;
137
+ break;
138
+ }
139
+ const cleanDeploySql = await (0, clean_1.cleanSql)(deployScript, false, '$EOFCODE$');
140
+ // Calculate script hash
141
+ const scriptHash = await this.calculateScriptHash((0, path_1.join)((0, path_1.dirname)(planPath), 'deploy', `${change.name}.sql`));
142
+ const changeKey = `/deploy/${change.name}.sql`;
143
+ const resolvedFromDeps = resolvedDeps?.deps[changeKey];
144
+ const resolvedChangeDeps = (resolvedFromDeps !== undefined) ? resolvedFromDeps : change.dependencies;
145
+ const qualifiedDeps = (resolvedChangeDeps && resolvedChangeDeps.length > 0)
146
+ ? Array.from(new Set(resolvedChangeDeps.map((dep) => (dep.includes(':') ? dep : `${plan.package}:${dep}`))))
147
+ : resolvedChangeDeps;
148
+ try {
149
+ // Call the deploy stored procedure
150
+ await (0, transaction_1.executeQuery)(context, 'CALL pgpm_migrate.deploy($1::TEXT, $2::TEXT, $3::TEXT, $4::TEXT[], $5::TEXT, $6::BOOLEAN)', [
151
+ plan.package,
152
+ change.name,
153
+ scriptHash,
154
+ qualifiedDeps && qualifiedDeps.length > 0 ? qualifiedDeps : null,
155
+ cleanDeploySql,
156
+ logOnly
157
+ ]);
158
+ const unqualified = this.toUnqualifiedLocal(plan.package, change.name);
159
+ deployed.push(unqualified);
160
+ log.success(`Successfully ${logOnly ? 'logged' : 'deployed'}: ${change.name}`);
161
+ }
162
+ catch (error) {
163
+ // Log failure event outside of transaction
164
+ await this.eventLogger.logEvent({
165
+ eventType: 'deploy',
166
+ changeName: change.name,
167
+ package: plan.package,
168
+ errorMessage: error.message || 'Unknown error',
169
+ errorCode: error.code || null
170
+ });
171
+ // Build comprehensive error message
172
+ const errorLines = [];
173
+ errorLines.push(`Failed to deploy ${change.name}:`);
174
+ errorLines.push(` Change: ${change.name}`);
175
+ errorLines.push(` Package: ${plan.package}`);
176
+ errorLines.push(` Script Hash: ${scriptHash}`);
177
+ errorLines.push(` Dependencies: ${qualifiedDeps && qualifiedDeps.length > 0 ? qualifiedDeps.join(', ') : 'none'}`);
178
+ errorLines.push(` Error Code: ${error.code || 'N/A'}`);
179
+ errorLines.push(` Error Message: ${error.message || 'N/A'}`);
180
+ // Show SQL script preview for debugging
181
+ if (cleanDeploySql) {
182
+ const sqlLines = cleanDeploySql.split('\n');
183
+ const previewLines = debug ? sqlLines : sqlLines.slice(0, 10);
184
+ if (debug) {
185
+ errorLines.push(` Full SQL Script (${sqlLines.length} lines):`);
186
+ previewLines.forEach((line, index) => {
187
+ errorLines.push(` ${index + 1}: ${line}`);
188
+ });
189
+ }
190
+ else {
191
+ errorLines.push(` SQL Preview (first 10 lines):`);
192
+ previewLines.forEach((line, index) => {
193
+ errorLines.push(` ${index + 1}: ${line}`);
194
+ });
195
+ if (sqlLines.length > 10) {
196
+ errorLines.push(` ... and ${sqlLines.length - 10} more lines`);
197
+ errorLines.push(` 💡 Use debug mode to see full SQL script`);
198
+ }
199
+ }
200
+ }
201
+ // Provide debugging hints based on error code
202
+ if (error.code === '25P02') {
203
+ errorLines.push(`🔍 Debug Info: This error means a previous command in the transaction failed.`);
204
+ errorLines.push(` The SQL script above may contain the failing command.`);
205
+ errorLines.push(` Check the transaction query history for more details.`);
206
+ }
207
+ else if (error.code === '42P01') {
208
+ errorLines.push(`💡 Hint: A table or view referenced in the SQL script does not exist.`);
209
+ errorLines.push(` Check if dependencies are applied in the correct order.`);
210
+ }
211
+ else if (error.code === '42883') {
212
+ errorLines.push(`💡 Hint: A function referenced in the SQL script does not exist.`);
213
+ errorLines.push(` Check if required extensions or previous migrations are applied.`);
214
+ }
215
+ // Log the consolidated error message
216
+ log.error(errorLines.join('\n'));
217
+ failed = change.name;
218
+ throw error; // Re-throw to trigger rollback if in transaction
219
+ }
220
+ // Stop if this was the target change
221
+ if (toChange && change.name === toChange) {
222
+ break;
223
+ }
224
+ }
225
+ });
226
+ return { deployed, skipped, failed };
227
+ }
228
+ /**
229
+ * Revert changes according to plan file
230
+ */
231
+ async revert(options) {
232
+ await this.initialize();
233
+ const { modulePath, toChange, useTransaction = true } = options;
234
+ const planPath = (0, path_1.join)(modulePath, 'pgpm.plan');
235
+ const plan = (0, files_1.parsePlanFileSimple)(planPath);
236
+ const resolvedToChange = this.resolveToChange(toChange, planPath, plan.package);
237
+ const changes = getChangesInOrder(planPath, true); // Reverse order for revert
238
+ const reverted = [];
239
+ const skipped = [];
240
+ let failed;
241
+ // Use a separate pool for the target database
242
+ const targetPool = (0, pg_cache_1.getPgPool)(this.pgConfig);
243
+ // Execute revert with or without transaction
244
+ await (0, transaction_1.withTransaction)(targetPool, { useTransaction }, async (context) => {
245
+ for (const change of changes) {
246
+ // Stop if we've reached the target change
247
+ if (resolvedToChange && change.name === resolvedToChange) {
248
+ break;
249
+ }
250
+ // Check if deployed
251
+ const isDeployed = await this.isDeployed(plan.package, change.name);
252
+ if (!isDeployed) {
253
+ log.info(`Skipping not deployed change: ${change.name}`);
254
+ const unqualified = this.toUnqualifiedLocal(plan.package, change.name);
255
+ skipped.push(unqualified);
256
+ continue;
257
+ }
258
+ // Read revert script
259
+ const revertScript = (0, files_1.readScript)((0, path_1.dirname)(planPath), 'revert', change.name);
260
+ if (!revertScript) {
261
+ log.error(`Revert script not found for change: ${change.name}`);
262
+ failed = change.name;
263
+ break;
264
+ }
265
+ const cleanRevertSql = await (0, clean_1.cleanSql)(revertScript, false, '$EOFCODE$');
266
+ try {
267
+ // Call the revert stored procedure
268
+ await (0, transaction_1.executeQuery)(context, 'CALL pgpm_migrate.revert($1, $2, $3)', [plan.package, change.name, cleanRevertSql]);
269
+ reverted.push(change.name);
270
+ log.success(`Successfully reverted: ${change.name}`);
271
+ }
272
+ catch (error) {
273
+ // Log failure event outside of transaction
274
+ await this.eventLogger.logEvent({
275
+ eventType: 'revert',
276
+ changeName: change.name,
277
+ package: plan.package,
278
+ errorMessage: error.message || 'Unknown error',
279
+ errorCode: error.code || null
280
+ });
281
+ log.error(`Failed to revert ${change.name}:`, error);
282
+ failed = change.name;
283
+ throw error; // Re-throw to trigger rollback if in transaction
284
+ }
285
+ }
286
+ });
287
+ return { reverted, skipped, failed };
288
+ }
289
+ /**
290
+ * Verify deployed changes
291
+ */
292
+ async verify(options) {
293
+ await this.initialize();
294
+ const { modulePath, toChange } = options;
295
+ const planPath = (0, path_1.join)(modulePath, 'pgpm.plan');
296
+ const plan = (0, files_1.parsePlanFileSimple)(planPath);
297
+ const resolvedToChange = this.resolveToChange(toChange, planPath, plan.package);
298
+ const changes = getChangesInOrder(planPath);
299
+ const verified = [];
300
+ const failed = [];
301
+ // Use a separate pool for the target database
302
+ const targetPool = (0, pg_cache_1.getPgPool)(this.pgConfig);
303
+ try {
304
+ for (const change of changes) {
305
+ // Stop if we've reached the target change
306
+ if (resolvedToChange && change.name === resolvedToChange) {
307
+ break;
308
+ }
309
+ // Check if deployed
310
+ const isDeployed = await this.isDeployed(plan.package, change.name);
311
+ if (!isDeployed) {
312
+ continue;
313
+ }
314
+ // Read verify script
315
+ const verifyScript = (0, files_1.readScript)((0, path_1.dirname)(planPath), 'verify', change.name);
316
+ if (!verifyScript) {
317
+ log.warn(`Verify script not found for change: ${change.name}`);
318
+ continue;
319
+ }
320
+ const cleanVerifySql = await (0, clean_1.cleanSql)(verifyScript, false, '$EOFCODE$');
321
+ try {
322
+ // Call the verify function
323
+ const result = await targetPool.query('SELECT pgpm_migrate.verify($1, $2, $3) as verified', [plan.package, change.name, cleanVerifySql]);
324
+ if (result.rows[0].verified) {
325
+ verified.push(change.name);
326
+ log.success(`Successfully verified: ${change.name}`);
327
+ }
328
+ else {
329
+ const verificationError = new Error(`Verification failed for ${change.name}`);
330
+ verificationError.code = 'VERIFICATION_FAILED';
331
+ throw verificationError;
332
+ }
333
+ }
334
+ catch (error) {
335
+ // Log failure event with rich error information
336
+ await this.eventLogger.logEvent({
337
+ eventType: 'verify',
338
+ changeName: change.name,
339
+ package: plan.package,
340
+ errorMessage: error.message || 'Unknown error',
341
+ errorCode: error.code || null
342
+ });
343
+ log.error(`Failed to verify ${change.name}:`, error);
344
+ failed.push(change.name);
345
+ }
346
+ }
347
+ }
348
+ finally {
349
+ }
350
+ if (failed.length > 0) {
351
+ throw types_1.errors.OPERATION_FAILED({ operation: 'Verification', reason: `${failed.length} change(s): ${failed.join(', ')}` });
352
+ }
353
+ return { verified, failed };
354
+ }
355
+ /**
356
+ * Get deployment status
357
+ */
358
+ async status(packageName) {
359
+ await this.initialize();
360
+ const result = await this.pool.query('SELECT * FROM pgpm_migrate.status($1)', [packageName]);
361
+ return result.rows.map(row => ({
362
+ package: row.package,
363
+ totalDeployed: row.total_deployed,
364
+ lastChange: row.last_change,
365
+ lastDeployed: new Date(row.last_deployed)
366
+ }));
367
+ }
368
+ /**
369
+ * Check if a change is deployed
370
+ */
371
+ async isDeployed(packageName, changeName) {
372
+ const result = await this.pool.query('SELECT pgpm_migrate.is_deployed($1::TEXT, $2::TEXT) as is_deployed', [packageName, changeName]);
373
+ return result.rows[0].is_deployed;
374
+ }
375
+ /**
376
+ * Check if Sqitch tables exist in the database
377
+ */
378
+ async hasSqitchTables() {
379
+ const result = await this.pool.query(`
380
+ SELECT EXISTS (
381
+ SELECT 1 FROM information_schema.tables
382
+ WHERE table_schema = 'sqitch'
383
+ AND table_name IN ('projects', 'changes', 'tags', 'events')
384
+ )
385
+ `);
386
+ return result.rows[0].exists;
387
+ }
388
+ /**
389
+ * Import from existing Sqitch deployment
390
+ */
391
+ async importFromSqitch() {
392
+ await this.initialize();
393
+ try {
394
+ log.info('Checking for existing Sqitch tables...');
395
+ // Check if sqitch schema exists
396
+ const schemaResult = await this.pool.query("SELECT EXISTS (SELECT 1 FROM information_schema.schemata WHERE schema_name = 'sqitch')");
397
+ if (!schemaResult.rows[0].exists) {
398
+ log.info('No Sqitch schema found, nothing to import');
399
+ return;
400
+ }
401
+ // Import packages
402
+ log.info('Importing Sqitch packages...');
403
+ await this.pool.query(`
404
+ INSERT INTO pgpm_migrate.packages (package, created_at)
405
+ SELECT DISTINCT project, now()
406
+ FROM sqitch.projects
407
+ ON CONFLICT (package) DO NOTHING
408
+ `);
409
+ // Import changes with dependencies
410
+ log.info('Importing Sqitch changes...');
411
+ await this.pool.query(`
412
+ WITH change_data AS (
413
+ SELECT
414
+ c.project,
415
+ c.change,
416
+ c.change_id,
417
+ c.committed_at
418
+ FROM sqitch.changes c
419
+ WHERE c.change_id IN (
420
+ SELECT change_id FROM sqitch.tags
421
+ )
422
+ )
423
+ INSERT INTO pgpm_migrate.changes (change_id, change_name, package, script_hash, deployed_at)
424
+ SELECT
425
+ encode(sha256((cd.project || cd.change || cd.change_id)::bytea), 'hex'),
426
+ cd.change,
427
+ cd.project,
428
+ cd.change_id,
429
+ cd.committed_at
430
+ FROM change_data cd
431
+ ON CONFLICT (package, change_name) DO NOTHING
432
+ `);
433
+ // Import dependencies
434
+ log.info('Importing Sqitch dependencies...');
435
+ await this.pool.query(`
436
+ INSERT INTO pgpm_migrate.dependencies (change_id, requires)
437
+ SELECT
438
+ c.change_id,
439
+ d.dependency
440
+ FROM pgpm_migrate.changes c
441
+ JOIN sqitch.dependencies d ON d.change_id = c.script_hash
442
+ ON CONFLICT DO NOTHING
443
+ `);
444
+ log.success('Successfully imported Sqitch deployment history');
445
+ }
446
+ catch (error) {
447
+ log.error('Failed to import from Sqitch:', error);
448
+ throw error;
449
+ }
450
+ }
451
+ /**
452
+ * Get recent changes
453
+ */
454
+ async getRecentChanges(targetDatabase, limit = 10) {
455
+ const targetPool = (0, pg_cache_1.getPgPool)({
456
+ ...this.pgConfig,
457
+ database: targetDatabase
458
+ });
459
+ try {
460
+ const result = await targetPool.query(`
461
+ SELECT
462
+ c.change_name,
463
+ c.deployed_at,
464
+ c.package
465
+ FROM pgpm_migrate.changes c
466
+ ORDER BY c.deployed_at DESC NULLS LAST
467
+ LIMIT $1
468
+ `, [limit]);
469
+ return result.rows;
470
+ }
471
+ catch (error) {
472
+ log.error('Failed to get recent changes:', error);
473
+ throw error;
474
+ }
475
+ }
476
+ /**
477
+ * Get pending changes (in plan but not deployed)
478
+ */
479
+ async getPendingChanges(planPath, targetDatabase) {
480
+ const plan = (0, files_1.parsePlanFileSimple)(planPath);
481
+ const allChanges = getChangesInOrder(planPath);
482
+ const targetPool = (0, pg_cache_1.getPgPool)({
483
+ ...this.pgConfig,
484
+ database: targetDatabase
485
+ });
486
+ try {
487
+ const deployedResult = await targetPool.query(`
488
+ SELECT c.change_name
489
+ FROM pgpm_migrate.changes c
490
+ WHERE c.package = $1 AND c.deployed_at IS NOT NULL
491
+ `, [plan.package]);
492
+ const deployedSet = new Set(deployedResult.rows.map((r) => r.change_name));
493
+ return allChanges.filter(c => !deployedSet.has(c.name)).map(c => c.name);
494
+ }
495
+ catch (error) {
496
+ // If schema doesn't exist, all changes are pending
497
+ if (error.code === '42P01') { // undefined_table
498
+ return allChanges.map(c => c.name);
499
+ }
500
+ throw error;
501
+ }
502
+ }
503
+ /**
504
+ * Get all deployed changes for a project
505
+ */
506
+ async getDeployedChanges(targetDatabase, packageName) {
507
+ const targetPool = (0, pg_cache_1.getPgPool)({
508
+ ...this.pgConfig,
509
+ database: targetDatabase
510
+ });
511
+ try {
512
+ const result = await targetPool.query(`
513
+ SELECT
514
+ c.change_name,
515
+ c.deployed_at,
516
+ c.script_hash
517
+ FROM pgpm_migrate.changes c
518
+ WHERE c.package = $1 AND c.deployed_at IS NOT NULL
519
+ ORDER BY c.deployed_at ASC
520
+ `, [packageName]);
521
+ return result.rows;
522
+ }
523
+ catch (error) {
524
+ // If schema doesn't exist, no changes are deployed
525
+ if (error.code === '42P01') { // undefined_table
526
+ return [];
527
+ }
528
+ throw error;
529
+ }
530
+ }
531
+ /**
532
+ * Get dependencies for a change
533
+ */
534
+ async getDependencies(packageName, changeName) {
535
+ await this.initialize();
536
+ try {
537
+ const result = await this.pool.query(`SELECT d.requires
538
+ FROM pgpm_migrate.dependencies d
539
+ JOIN pgpm_migrate.changes c ON c.change_id = d.change_id
540
+ WHERE c.package = $1 AND c.change_name = $2`, [packageName, changeName]);
541
+ return result.rows.map(row => row.requires);
542
+ }
543
+ catch (error) {
544
+ log.error(`Failed to get dependencies for ${packageName}:${changeName}:`, error);
545
+ return [];
546
+ }
547
+ }
548
+ /**
549
+ * Close the database connection pool
550
+ */
551
+ async close() {
552
+ // Pool is managed by PgPoolCacheManager, no need to close
553
+ }
554
+ }
555
+ exports.PgpmMigrate = PgpmMigrate;
@@ -0,0 +1,5 @@
1
+ export * from './clean';
2
+ export * from './client';
3
+ export * from './types';
4
+ export * from './utils/hash';
5
+ export * from './utils/transaction';
@@ -0,0 +1,21 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __exportStar = (this && this.__exportStar) || function(m, exports) {
14
+ for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
15
+ };
16
+ Object.defineProperty(exports, "__esModule", { value: true });
17
+ __exportStar(require("./clean"), exports);
18
+ __exportStar(require("./client"), exports);
19
+ __exportStar(require("./types"), exports);
20
+ __exportStar(require("./utils/hash"), exports);
21
+ __exportStar(require("./utils/transaction"), exports);