@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,1470 @@
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 __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ var __importDefault = (this && this.__importDefault) || function (mod) {
36
+ return (mod && mod.__esModule) ? mod : { "default": mod };
37
+ };
38
+ Object.defineProperty(exports, "__esModule", { value: true });
39
+ exports.PgpmPackage = exports.PackageContext = void 0;
40
+ const env_1 = require("@pgpmjs/env");
41
+ const logger_1 = require("@pgpmjs/logger");
42
+ const types_1 = require("@pgpmjs/types");
43
+ const yanse_1 = __importDefault(require("yanse"));
44
+ const child_process_1 = require("child_process");
45
+ const fs_1 = __importDefault(require("fs"));
46
+ const glob = __importStar(require("glob"));
47
+ const os_1 = __importDefault(require("os"));
48
+ const parse_package_name_1 = require("parse-package-name");
49
+ const path_1 = __importStar(require("path"));
50
+ const pg_cache_1 = require("pg-cache");
51
+ const template_scaffold_1 = require("../template-scaffold");
52
+ const extensions_1 = require("../../extensions/extensions");
53
+ const files_1 = require("../../files");
54
+ const parser_1 = require("../../files/plan/parser");
55
+ const validators_1 = require("../../files/plan/validators");
56
+ const generator_1 = require("../../files/plan/generator");
57
+ const files_2 = require("../../files");
58
+ const writer_1 = require("../../files/extension/writer");
59
+ const client_1 = require("../../migrate/client");
60
+ const modules_1 = require("../../modules/modules");
61
+ const package_1 = require("../../packaging/package");
62
+ const deps_1 = require("../../resolution/deps");
63
+ const target_utils_1 = require("../../utils/target-utils");
64
+ const logger = new logger_1.Logger('pgpm');
65
+ function getUTCTimestamp(d = new Date()) {
66
+ return (d.getUTCFullYear() +
67
+ '-' + String(d.getUTCMonth() + 1).padStart(2, '0') +
68
+ '-' + String(d.getUTCDate()).padStart(2, '0') +
69
+ 'T' + String(d.getUTCHours()).padStart(2, '0') +
70
+ ':' + String(d.getUTCMinutes()).padStart(2, '0') +
71
+ ':' + String(d.getUTCSeconds()).padStart(2, '0') +
72
+ 'Z');
73
+ }
74
+ function sortObjectByKey(obj) {
75
+ return Object.fromEntries(Object.entries(obj).sort(([a], [b]) => a.localeCompare(b)));
76
+ }
77
+ const getNow = () => process.env.NODE_ENV === 'test'
78
+ ? getUTCTimestamp(new Date('2017-08-11T08:11:51Z'))
79
+ : getUTCTimestamp(new Date());
80
+ /**
81
+ * Truncates workspace extensions to include only modules from the target onwards.
82
+ * This prevents processing unnecessary modules that come before the target in dependency order.
83
+ *
84
+ * @param workspaceExtensions - The full workspace extension dependencies
85
+ * @param targetName - The target module name to truncate from
86
+ * @returns Truncated extensions starting from the target module
87
+ */
88
+ const truncateExtensionsToTarget = (workspaceExtensions, targetName) => {
89
+ const targetIndex = workspaceExtensions.resolved.indexOf(targetName);
90
+ if (targetIndex === -1) {
91
+ return workspaceExtensions;
92
+ }
93
+ return {
94
+ resolved: workspaceExtensions.resolved.slice(targetIndex),
95
+ external: workspaceExtensions.external
96
+ };
97
+ };
98
+ var PackageContext;
99
+ (function (PackageContext) {
100
+ PackageContext["Outside"] = "outside";
101
+ PackageContext["Workspace"] = "workspace-root";
102
+ PackageContext["Module"] = "module";
103
+ PackageContext["ModuleInsideWorkspace"] = "module-in-workspace";
104
+ })(PackageContext || (exports.PackageContext = PackageContext = {}));
105
+ class PgpmPackage {
106
+ cwd;
107
+ workspacePath;
108
+ modulePath;
109
+ config;
110
+ allowedDirs = [];
111
+ allowedParentDirs = [];
112
+ _moduleMap;
113
+ _moduleInfo;
114
+ constructor(cwd = process.cwd()) {
115
+ this.resetCwd(cwd);
116
+ }
117
+ resetCwd(cwd) {
118
+ this.cwd = cwd;
119
+ this.workspacePath = (0, env_1.resolvePgpmPath)(this.cwd);
120
+ this.modulePath = this.resolveSqitchPath();
121
+ if (this.workspacePath) {
122
+ this.config = this.loadConfigSync();
123
+ this.allowedDirs = this.loadAllowedDirs();
124
+ this.allowedParentDirs = this.loadAllowedParentDirs();
125
+ }
126
+ }
127
+ resolveSqitchPath() {
128
+ try {
129
+ return (0, env_1.walkUp)(this.cwd, 'pgpm.plan');
130
+ }
131
+ catch {
132
+ return undefined;
133
+ }
134
+ }
135
+ loadConfigSync() {
136
+ return (0, env_1.loadConfigSyncFromDir)(this.workspacePath);
137
+ }
138
+ loadAllowedDirs() {
139
+ const globs = this.config?.packages ?? [];
140
+ const dirs = globs.flatMap(pattern => glob.sync(path_1.default.join(this.workspacePath, pattern)));
141
+ const resolvedDirs = dirs.map(dir => path_1.default.resolve(dir));
142
+ // Remove duplicates by converting to Set and back to array
143
+ return [...new Set(resolvedDirs)];
144
+ }
145
+ loadAllowedParentDirs() {
146
+ const globs = this.config?.packages ?? [];
147
+ const parentDirs = globs.map(pattern => {
148
+ // Remove glob characters (*, **, ?, etc.) to get the base path
149
+ const basePath = pattern.replace(/[*?[\]{}]/g, '').replace(/\/$/, '');
150
+ return path_1.default.resolve(this.workspacePath, basePath);
151
+ });
152
+ // Remove duplicates by converting to Set and back to array
153
+ return [...new Set(parentDirs)];
154
+ }
155
+ isInsideAllowedDirs(cwd) {
156
+ return this.allowedDirs.some(dir => cwd.startsWith(dir));
157
+ }
158
+ isParentOfAllowedDirs(cwd) {
159
+ const resolvedCwd = path_1.default.resolve(cwd);
160
+ return this.allowedDirs.some(dir => dir.startsWith(resolvedCwd + path_1.default.sep)) ||
161
+ this.allowedParentDirs.some(dir => path_1.default.resolve(dir) === resolvedCwd);
162
+ }
163
+ createModuleDirectory(modName) {
164
+ this.ensureWorkspace();
165
+ const isRoot = path_1.default.resolve(this.workspacePath) === path_1.default.resolve(this.cwd);
166
+ const isParentDir = this.isParentOfAllowedDirs(this.cwd);
167
+ const isInsideModule = this.isInsideAllowedDirs(this.cwd);
168
+ let targetPath;
169
+ if (isRoot) {
170
+ const packagesDir = path_1.default.join(this.cwd, 'packages');
171
+ fs_1.default.mkdirSync(packagesDir, { recursive: true });
172
+ targetPath = path_1.default.join(packagesDir, modName);
173
+ }
174
+ else if (isParentDir) {
175
+ targetPath = path_1.default.join(this.cwd, modName);
176
+ }
177
+ else if (isInsideModule) {
178
+ console.error(yanse_1.default.red(`Error: Cannot create a module inside an existing module. Please run 'lql init' from the workspace root or from a parent directory like 'packages/'.`));
179
+ process.exit(1);
180
+ }
181
+ else {
182
+ console.error(yanse_1.default.red(`Error: You must be inside the workspace root, a parent directory of modules (like 'packages/'), or inside one of the workspace packages: ${this.allowedDirs.join(', ')}`));
183
+ process.exit(1);
184
+ }
185
+ fs_1.default.mkdirSync(targetPath, { recursive: true });
186
+ return targetPath;
187
+ }
188
+ ensureModule() {
189
+ if (!this.modulePath)
190
+ throw new Error('Not inside a module');
191
+ }
192
+ ensureWorkspace() {
193
+ if (!this.workspacePath)
194
+ throw new Error('Not inside a workspace');
195
+ }
196
+ getContext() {
197
+ if (this.modulePath && this.workspacePath) {
198
+ const rel = path_1.default.relative(this.workspacePath, this.modulePath);
199
+ const nested = !rel.startsWith('..') && !path_1.default.isAbsolute(rel);
200
+ return nested ? PackageContext.ModuleInsideWorkspace : PackageContext.Module;
201
+ }
202
+ if (this.modulePath)
203
+ return PackageContext.Module;
204
+ if (this.workspacePath)
205
+ return PackageContext.Workspace;
206
+ return PackageContext.Outside;
207
+ }
208
+ isInWorkspace() {
209
+ return this.getContext() === PackageContext.Workspace;
210
+ }
211
+ isInModule() {
212
+ return (this.getContext() === PackageContext.Module ||
213
+ this.getContext() === PackageContext.ModuleInsideWorkspace);
214
+ }
215
+ getWorkspacePath() {
216
+ return this.workspacePath;
217
+ }
218
+ getModulePath() {
219
+ return this.modulePath;
220
+ }
221
+ clearCache() {
222
+ delete this._moduleInfo;
223
+ delete this._moduleMap;
224
+ }
225
+ // ──────────────── Workspace-wide ────────────────
226
+ async getModules() {
227
+ if (!this.workspacePath || !this.config)
228
+ return [];
229
+ const dirs = this.loadAllowedDirs();
230
+ const results = [];
231
+ for (const dir of dirs) {
232
+ const proj = new PgpmPackage(dir);
233
+ if (proj.isInModule()) {
234
+ results.push(proj);
235
+ }
236
+ }
237
+ return results;
238
+ }
239
+ /**
240
+ * List all modules by parsing .control files in the workspace directory.
241
+ * Handles naming collisions by preferring the shortest path.
242
+ */
243
+ listModules() {
244
+ if (!this.workspacePath)
245
+ return {};
246
+ const moduleFiles = glob.sync(`${this.workspacePath}/**/*.control`).filter((file) => !/node_modules/.test(file));
247
+ // Group files by module name to handle collisions
248
+ const filesByName = new Map();
249
+ moduleFiles.forEach((file) => {
250
+ const moduleName = path_1.default.basename(file).split('.control')[0];
251
+ if (!filesByName.has(moduleName)) {
252
+ filesByName.set(moduleName, []);
253
+ }
254
+ filesByName.get(moduleName).push(file);
255
+ });
256
+ // For each module name, pick the shortest path in case of collisions
257
+ const selectedFiles = new Map();
258
+ filesByName.forEach((files, moduleName) => {
259
+ if (files.length === 1) {
260
+ selectedFiles.set(moduleName, files[0]);
261
+ }
262
+ else {
263
+ // Multiple files with same name - pick shortest path
264
+ const shortestFile = files.reduce((shortest, current) => current.length < shortest.length ? current : shortest);
265
+ selectedFiles.set(moduleName, shortestFile);
266
+ }
267
+ });
268
+ // Parse the selected control files
269
+ return Array.from(selectedFiles.entries()).reduce((acc, [moduleName, file]) => {
270
+ const module = (0, files_2.parseControlFile)(file, this.workspacePath);
271
+ acc[moduleName] = module;
272
+ return acc;
273
+ }, {});
274
+ }
275
+ getModuleMap() {
276
+ if (!this.workspacePath)
277
+ return {};
278
+ if (this._moduleMap)
279
+ return this._moduleMap;
280
+ this._moduleMap = this.listModules();
281
+ return this._moduleMap;
282
+ }
283
+ getAvailableModules() {
284
+ const modules = this.getModuleMap();
285
+ return (0, extensions_1.getAvailableExtensions)(modules);
286
+ }
287
+ getModuleProject(name) {
288
+ this.ensureWorkspace();
289
+ if (this.isInModule() && name === this.getModuleName()) {
290
+ return this;
291
+ }
292
+ const modules = this.getModuleMap();
293
+ if (!modules[name]) {
294
+ throw types_1.errors.MODULE_NOT_FOUND({ name });
295
+ }
296
+ const modulePath = path_1.default.resolve(this.workspacePath, modules[name].path);
297
+ return new PgpmPackage(modulePath);
298
+ }
299
+ // ──────────────── Module-scoped ────────────────
300
+ getModuleInfo() {
301
+ this.ensureModule();
302
+ if (!this._moduleInfo) {
303
+ this._moduleInfo = (0, files_2.getExtensionInfo)(this.cwd);
304
+ }
305
+ return this._moduleInfo;
306
+ }
307
+ getModuleName() {
308
+ this.ensureModule();
309
+ return (0, files_2.getExtensionName)(this.cwd);
310
+ }
311
+ getRequiredModules() {
312
+ this.ensureModule();
313
+ const info = this.getModuleInfo();
314
+ return (0, files_2.getInstalledExtensions)(info.controlFile);
315
+ }
316
+ setModuleDependencies(modules) {
317
+ this.ensureModule();
318
+ // Validate for circular dependencies
319
+ this.validateModuleDependencies(modules);
320
+ (0, files_2.writeExtensions)(this.cwd, modules);
321
+ }
322
+ validateModuleDependencies(modules) {
323
+ const currentModuleName = this.getModuleName();
324
+ if (modules.includes(currentModuleName)) {
325
+ throw types_1.errors.CIRCULAR_DEPENDENCY({ module: currentModuleName, dependency: currentModuleName });
326
+ }
327
+ // Check for circular dependencies by examining each module's dependencies
328
+ const visited = new Set();
329
+ const visiting = new Set();
330
+ const checkCircular = (moduleName, path = []) => {
331
+ if (visiting.has(moduleName)) {
332
+ throw types_1.errors.CIRCULAR_DEPENDENCY({ module: path.join(' -> '), dependency: moduleName });
333
+ }
334
+ if (visited.has(moduleName)) {
335
+ return;
336
+ }
337
+ visiting.add(moduleName);
338
+ // More complex dependency resolution would require loading other modules' dependencies
339
+ visiting.delete(moduleName);
340
+ visited.add(moduleName);
341
+ };
342
+ modules.forEach(module => checkCircular(module, [currentModuleName]));
343
+ }
344
+ initModuleSqitch(modName, targetPath) {
345
+ const plan = (0, files_1.generatePlan)({
346
+ moduleName: modName,
347
+ uri: modName,
348
+ entries: []
349
+ });
350
+ (0, files_1.writePlan)(path_1.default.join(targetPath, 'pgpm.plan'), plan);
351
+ // Create deploy, revert, and verify directories
352
+ const dirs = ['deploy', 'revert', 'verify'];
353
+ dirs.forEach(dir => {
354
+ const dirPath = path_1.default.join(targetPath, dir);
355
+ if (!fs_1.default.existsSync(dirPath)) {
356
+ fs_1.default.mkdirSync(dirPath, { recursive: true });
357
+ }
358
+ });
359
+ }
360
+ async initModule(options) {
361
+ this.ensureWorkspace();
362
+ const targetPath = this.createModuleDirectory(options.name);
363
+ const answers = {
364
+ ...options.answers,
365
+ name: options.name,
366
+ moduleDesc: options.description,
367
+ description: options.description,
368
+ author: options.author,
369
+ extensions: options.extensions
370
+ };
371
+ await (0, template_scaffold_1.scaffoldTemplate)({
372
+ type: 'module',
373
+ outputDir: targetPath,
374
+ templateRepo: options.templateRepo ?? template_scaffold_1.DEFAULT_TEMPLATE_REPO,
375
+ branch: options.branch,
376
+ // Don't set default templatePath - let scaffoldTemplate use metadata-driven resolution
377
+ templatePath: options.templatePath,
378
+ answers,
379
+ noTty: options.noTty ?? false,
380
+ cacheTtlMs: options.cacheTtlMs ?? template_scaffold_1.DEFAULT_TEMPLATE_TTL_MS,
381
+ toolName: options.toolName ?? template_scaffold_1.DEFAULT_TEMPLATE_TOOL_NAME,
382
+ cwd: this.cwd
383
+ });
384
+ this.initModuleSqitch(options.name, targetPath);
385
+ (0, files_2.writeExtensions)(targetPath, options.extensions);
386
+ }
387
+ // ──────────────── Dependency Analysis ────────────────
388
+ getLatestChange(moduleName) {
389
+ const modules = this.getModuleMap();
390
+ return (0, modules_1.latestChange)(moduleName, modules, this.workspacePath);
391
+ }
392
+ getLatestChangeAndVersion(moduleName) {
393
+ const modules = this.getModuleMap();
394
+ return (0, modules_1.latestChangeAndVersion)(moduleName, modules, this.workspacePath);
395
+ }
396
+ getModuleExtensions() {
397
+ this.ensureModule();
398
+ const moduleName = this.getModuleName();
399
+ const moduleMap = this.getModuleMap();
400
+ return (0, deps_1.resolveExtensionDependencies)(moduleName, moduleMap);
401
+ }
402
+ getModuleDependencies(moduleName) {
403
+ const modules = this.getModuleMap();
404
+ const { native, sqitch } = (0, modules_1.getExtensionsAndModules)(moduleName, modules);
405
+ return { native, modules: sqitch };
406
+ }
407
+ getModuleDependencyChanges(moduleName) {
408
+ const modules = this.getModuleMap();
409
+ const { native, sqitch } = (0, modules_1.getExtensionsAndModulesChanges)(moduleName, modules, this.workspacePath);
410
+ return { native, modules: sqitch };
411
+ }
412
+ // ──────────────── Plans ────────────────
413
+ getModulePlan() {
414
+ this.ensureModule();
415
+ const planPath = path_1.default.join(this.getModulePath(), 'pgpm.plan');
416
+ return fs_1.default.readFileSync(planPath, 'utf8');
417
+ }
418
+ getModuleControlFile() {
419
+ this.ensureModule();
420
+ const info = this.getModuleInfo();
421
+ return fs_1.default.readFileSync(info.controlFile, 'utf8');
422
+ }
423
+ getModuleMakefile() {
424
+ this.ensureModule();
425
+ const info = this.getModuleInfo();
426
+ return fs_1.default.readFileSync(info.Makefile, 'utf8');
427
+ }
428
+ getModuleSQL() {
429
+ this.ensureModule();
430
+ const info = this.getModuleInfo();
431
+ return fs_1.default.readFileSync(info.sqlFile, 'utf8');
432
+ }
433
+ generateModulePlan(options) {
434
+ this.ensureModule();
435
+ const info = this.getModuleInfo();
436
+ const moduleName = info.extname;
437
+ // Get raw dependencies and resolved list
438
+ const tagResolution = options.includeTags === true ? 'preserve' : 'internal';
439
+ let { resolved, deps } = (0, deps_1.resolveDependencies)(this.cwd, moduleName, { tagResolution });
440
+ // Helper to extract module name from a change reference
441
+ const getModuleName = (change) => {
442
+ const colonIndex = change.indexOf(':');
443
+ return colonIndex > 0 ? change.substring(0, colonIndex) : null;
444
+ };
445
+ // Helper to determine if a change is truly from an external package
446
+ const isExternalChange = (change) => {
447
+ const changeModule = getModuleName(change);
448
+ return changeModule !== null && changeModule !== moduleName;
449
+ };
450
+ // Helper to normalize change name (remove package prefix)
451
+ const normalizeChangeName = (change) => {
452
+ return change.includes(':') ? change.split(':').pop() : change;
453
+ };
454
+ // Clean up the resolved list to handle both formats
455
+ const uniqueChangeNames = new Set();
456
+ const normalizedResolved = [];
457
+ // First, add local changes without prefixes
458
+ resolved.forEach(change => {
459
+ const normalized = normalizeChangeName(change);
460
+ // Skip if we've already added this change
461
+ if (uniqueChangeNames.has(normalized))
462
+ return;
463
+ // Skip truly external changes - they should only be in dependencies
464
+ if (isExternalChange(change))
465
+ return;
466
+ uniqueChangeNames.add(normalized);
467
+ normalizedResolved.push(normalized);
468
+ });
469
+ // Clean up the deps object
470
+ const normalizedDeps = {};
471
+ // Process each deps entry
472
+ Object.keys(deps).forEach(key => {
473
+ // Normalize the key - strip "/deploy/" and ".sql" if present
474
+ let normalizedKey = key;
475
+ if (normalizedKey.startsWith('/deploy/')) {
476
+ normalizedKey = normalizedKey.substring(8); // Remove "/deploy/"
477
+ }
478
+ if (normalizedKey.endsWith('.sql')) {
479
+ normalizedKey = normalizedKey.substring(0, normalizedKey.length - 4); // Remove ".sql"
480
+ }
481
+ // Skip keys for truly external changes - we only want local changes as keys
482
+ if (isExternalChange(normalizedKey))
483
+ return;
484
+ // Normalize the key for all changes, removing any same-package prefix
485
+ const cleanKey = normalizeChangeName(normalizedKey);
486
+ // Build the standard key format for our normalized deps
487
+ const standardKey = `/deploy/${cleanKey}.sql`;
488
+ // Initialize the dependencies array for this key if it doesn't exist
489
+ normalizedDeps[standardKey] = normalizedDeps[standardKey] || [];
490
+ // Add dependencies, handling both formats
491
+ const dependencies = deps[key] || [];
492
+ dependencies.forEach(dep => {
493
+ // For truly external dependencies, keep the full reference
494
+ if (isExternalChange(dep)) {
495
+ if (!normalizedDeps[standardKey].includes(dep)) {
496
+ normalizedDeps[standardKey].push(dep);
497
+ }
498
+ }
499
+ else {
500
+ // For same-package dependencies, normalize by removing prefix
501
+ const normalizedDep = normalizeChangeName(dep);
502
+ if (!normalizedDeps[standardKey].includes(normalizedDep)) {
503
+ normalizedDeps[standardKey].push(normalizedDep);
504
+ }
505
+ }
506
+ });
507
+ });
508
+ // Update with normalized versions
509
+ resolved = normalizedResolved;
510
+ deps = normalizedDeps;
511
+ // Process external dependencies if needed
512
+ const includePackages = options.includePackages === true;
513
+ const preferTags = options.includeTags === true;
514
+ if (includePackages && this.workspacePath) {
515
+ const depData = this.getModuleDependencyChanges(moduleName);
516
+ if (resolved.length > 0) {
517
+ const firstKey = `/deploy/${resolved[0]}.sql`;
518
+ deps[firstKey] = deps[firstKey] || [];
519
+ depData.modules.forEach(m => {
520
+ const extModuleName = m.name;
521
+ const hasTagDependency = deps[firstKey].some(dep => dep.startsWith(`${extModuleName}:@`));
522
+ let depToken = `${extModuleName}:${m.latest}`;
523
+ if (preferTags) {
524
+ try {
525
+ const moduleMap = this.getModuleMap();
526
+ const modInfo = moduleMap[extModuleName];
527
+ if (modInfo && this.workspacePath) {
528
+ const planPath = path_1.default.join(this.workspacePath, modInfo.path, 'pgpm.plan');
529
+ const parsed = (0, parser_1.parsePlanFile)(planPath);
530
+ const changes = parsed.data?.changes || [];
531
+ const tags = parsed.data?.tags || [];
532
+ if (changes.length > 0 && tags.length > 0) {
533
+ const lastChangeName = changes[changes.length - 1]?.name;
534
+ const lastTag = tags[tags.length - 1];
535
+ if (lastTag && lastTag.change === lastChangeName) {
536
+ depToken = `${extModuleName}:@${lastTag.name}`;
537
+ }
538
+ }
539
+ }
540
+ }
541
+ catch { }
542
+ }
543
+ if (!hasTagDependency && !deps[firstKey].includes(depToken)) {
544
+ deps[firstKey].push(depToken);
545
+ }
546
+ });
547
+ }
548
+ }
549
+ // For debugging - log the cleaned structures
550
+ // console.log("CLEAN DEPS GRAPH", JSON.stringify(deps, null, 2));
551
+ // console.log("CLEAN RES GRAPH", JSON.stringify(resolved, null, 2));
552
+ // Prepare entries for the plan file
553
+ const entries = resolved.map(res => {
554
+ const key = `/deploy/${res}.sql`;
555
+ const dependencies = deps[key] || [];
556
+ // Filter out dependencies that match the current change name
557
+ // This prevents listing a change as dependent on itself
558
+ const filteredDeps = dependencies.filter(dep => normalizeChangeName(dep) !== res);
559
+ return {
560
+ change: res,
561
+ dependencies: filteredDeps,
562
+ comment: `add ${res}`
563
+ };
564
+ });
565
+ // Use the package-files package to generate the plan
566
+ return (0, files_1.generatePlan)({
567
+ moduleName,
568
+ uri: options.uri,
569
+ entries
570
+ });
571
+ }
572
+ writeModulePlan(options) {
573
+ this.ensureModule();
574
+ const name = this.getModuleName();
575
+ const plan = this.generateModulePlan(options);
576
+ const moduleMap = this.getModuleMap();
577
+ const mod = moduleMap[name];
578
+ const planPath = path_1.default.join(this.workspacePath, mod.path, 'pgpm.plan');
579
+ // Use the package-files package to write the plan
580
+ (0, files_1.writePlan)(planPath, plan);
581
+ }
582
+ /**
583
+ * Add a tag to the current module's plan file
584
+ */
585
+ addTag(tagName, changeName, comment) {
586
+ this.ensureModule();
587
+ if (!this.modulePath) {
588
+ throw types_1.errors.PATH_NOT_FOUND({ path: 'module path', type: 'module' });
589
+ }
590
+ // Validate tag name
591
+ if (!(0, validators_1.isValidTagName)(tagName)) {
592
+ throw types_1.errors.INVALID_NAME({ name: tagName, type: 'tag', rules: "Tag names must follow Sqitch naming rules and cannot contain '/'" });
593
+ }
594
+ const planPath = path_1.default.join(this.modulePath, 'pgpm.plan');
595
+ // Parse existing plan file
596
+ const planResult = (0, parser_1.parsePlanFile)(planPath);
597
+ if (!planResult.data) {
598
+ throw types_1.errors.PLAN_PARSE_ERROR({ planPath, errors: planResult.errors.map(e => e.message).join(', ') });
599
+ }
600
+ const plan = planResult.data;
601
+ let targetChange = changeName;
602
+ if (!targetChange) {
603
+ if (plan.changes.length === 0) {
604
+ throw new Error('No changes found in plan file. Cannot add tag without a target change.');
605
+ }
606
+ targetChange = plan.changes[plan.changes.length - 1].name;
607
+ }
608
+ else {
609
+ // Validate that the specified change exists
610
+ const changeExists = plan.changes.some(c => c.name === targetChange);
611
+ if (!changeExists) {
612
+ throw types_1.errors.CHANGE_NOT_FOUND({ change: targetChange });
613
+ }
614
+ }
615
+ // Check if tag already exists
616
+ const existingTag = plan.tags.find(t => t.name === tagName);
617
+ if (existingTag) {
618
+ throw new Error(`Tag '${tagName}' already exists and points to change '${existingTag.change}'.`);
619
+ }
620
+ // Create new tag
621
+ const newTag = {
622
+ name: tagName,
623
+ change: targetChange,
624
+ timestamp: (0, generator_1.getNow)(),
625
+ planner: 'launchql',
626
+ email: 'launchql@5b0c196eeb62',
627
+ comment
628
+ };
629
+ plan.tags.push(newTag);
630
+ // Write updated plan file
631
+ (0, files_1.writePlanFile)(planPath, plan);
632
+ }
633
+ /**
634
+ * Add a change to the current module's plan file and create SQL files
635
+ */
636
+ addChange(changeName, dependencies, comment) {
637
+ // Validate change name first
638
+ if (!changeName || !changeName.trim()) {
639
+ throw new Error('Change name is required');
640
+ }
641
+ if (!(0, validators_1.isValidChangeName)(changeName)) {
642
+ throw types_1.errors.INVALID_NAME({ name: changeName, type: 'change', rules: "Change names must follow Sqitch naming rules" });
643
+ }
644
+ if (!this.isInWorkspace() && !this.isInModule()) {
645
+ throw new Error('This command must be run inside a PGPM workspace or module.');
646
+ }
647
+ if (this.isInModule()) {
648
+ this.ensureModule();
649
+ if (!this.modulePath) {
650
+ throw types_1.errors.PATH_NOT_FOUND({ path: 'module path', type: 'module' });
651
+ }
652
+ this.addChangeToModule(changeName, dependencies, comment);
653
+ return;
654
+ }
655
+ throw new Error('When running from workspace root, please specify --package or run from within a module directory.');
656
+ }
657
+ /**
658
+ * Add change to the current module (internal helper)
659
+ */
660
+ addChangeToModule(changeName, dependencies, comment) {
661
+ const planPath = path_1.default.join(this.modulePath, 'pgpm.plan');
662
+ // Parse existing plan file
663
+ const planResult = (0, parser_1.parsePlanFile)(planPath);
664
+ if (!planResult.data) {
665
+ throw types_1.errors.PLAN_PARSE_ERROR({ planPath, errors: planResult.errors.map(e => e.message).join(', ') });
666
+ }
667
+ const plan = planResult.data;
668
+ // Check if change already exists
669
+ const existingChange = plan.changes.find(c => c.name === changeName);
670
+ if (existingChange) {
671
+ throw new Error(`Change '${changeName}' already exists in plan.`);
672
+ }
673
+ // Validate dependencies exist if provided
674
+ if (dependencies && dependencies.length > 0) {
675
+ const currentPackage = plan.package;
676
+ for (const dep of dependencies) {
677
+ // Parse the dependency to check if it's a cross-module reference
678
+ const parsed = (0, validators_1.parseReference)(dep);
679
+ if (parsed && parsed.package && parsed.package !== currentPackage) {
680
+ continue;
681
+ }
682
+ const depExists = plan.changes.some(c => c.name === dep);
683
+ if (!depExists) {
684
+ throw new Error(`Dependency '${dep}' not found in plan. Add dependencies before referencing them.`);
685
+ }
686
+ }
687
+ }
688
+ // Create new change
689
+ const newChange = {
690
+ name: changeName,
691
+ dependencies: dependencies || [],
692
+ timestamp: (0, generator_1.getNow)(),
693
+ planner: 'launchql',
694
+ email: 'launchql@5b0c196eeb62',
695
+ comment: comment || `add ${changeName}`
696
+ };
697
+ plan.changes.push(newChange);
698
+ // Write updated plan file
699
+ (0, files_1.writePlanFile)(planPath, plan);
700
+ // Create SQL files
701
+ this.createSqlFiles(changeName, dependencies || [], comment || `add ${changeName}`);
702
+ }
703
+ /**
704
+ * Create deploy/revert/verify SQL files for a change
705
+ */
706
+ createSqlFiles(changeName, dependencies, comment) {
707
+ if (!this.modulePath) {
708
+ throw types_1.errors.PATH_NOT_FOUND({ path: 'module path', type: 'module' });
709
+ }
710
+ const createdFiles = [];
711
+ const createSqlFile = (type, content) => {
712
+ const dir = path_1.default.dirname(changeName);
713
+ const fileName = path_1.default.basename(changeName);
714
+ const typeDir = path_1.default.join(this.modulePath, type);
715
+ const targetDir = path_1.default.join(typeDir, dir);
716
+ const filePath = path_1.default.join(targetDir, `${fileName}.sql`);
717
+ fs_1.default.mkdirSync(targetDir, { recursive: true });
718
+ fs_1.default.writeFileSync(filePath, content);
719
+ // Track the relative path from module root
720
+ const relativePath = path_1.default.relative(this.modulePath, filePath);
721
+ createdFiles.push(relativePath);
722
+ };
723
+ // Create deploy file
724
+ const deployContent = `-- Deploy: ${changeName}
725
+ -- made with <3 @ constructive.io
726
+
727
+ ${dependencies.length > 0 ? dependencies.map(dep => `-- requires: ${dep}`).join('\n') + '\n' : ''}
728
+ -- Add your deployment SQL here
729
+ `;
730
+ // Create revert file
731
+ const revertContent = `-- Revert: ${changeName}
732
+
733
+ -- Add your revert SQL here
734
+ `;
735
+ // Create verify file
736
+ const verifyContent = `-- Verify: ${changeName}
737
+
738
+ -- Add your verification SQL here
739
+ `;
740
+ createSqlFile('deploy', deployContent);
741
+ createSqlFile('revert', revertContent);
742
+ createSqlFile('verify', verifyContent);
743
+ // Log created files to stdout
744
+ process.stdout.write('\n✔ Files created\n\n');
745
+ createdFiles.forEach(file => {
746
+ process.stdout.write(` create ${file}\n`);
747
+ });
748
+ process.stdout.write('\n✨ All set!\n\n');
749
+ }
750
+ // ──────────────── Packaging and npm ────────────────
751
+ publishToDist(distFolder = 'dist') {
752
+ this.ensureModule();
753
+ const modPath = this.modulePath; // use modulePath, not cwd
754
+ const name = this.getModuleName();
755
+ const controlFile = `${name}.control`;
756
+ const fullDist = path_1.default.join(modPath, distFolder);
757
+ if (fs_1.default.existsSync(fullDist)) {
758
+ fs_1.default.rmSync(fullDist, { recursive: true, force: true });
759
+ }
760
+ fs_1.default.mkdirSync(fullDist, { recursive: true });
761
+ const folders = ['deploy', 'revert', 'sql', 'verify'];
762
+ const files = ['Makefile', 'package.json', 'pgpm.plan', controlFile];
763
+ // Add README file regardless of casing
764
+ const readmeFile = fs_1.default.readdirSync(modPath).find(f => /^readme\.md$/i.test(f));
765
+ if (readmeFile) {
766
+ files.push(readmeFile); // Include it in the list of files to copy
767
+ }
768
+ for (const folder of folders) {
769
+ const src = path_1.default.join(modPath, folder);
770
+ if (fs_1.default.existsSync(src)) {
771
+ fs_1.default.cpSync(src, path_1.default.join(fullDist, folder), { recursive: true });
772
+ }
773
+ }
774
+ for (const file of files) {
775
+ const src = path_1.default.join(modPath, file);
776
+ if (!fs_1.default.existsSync(src)) {
777
+ throw new Error(`Missing required file: ${file}`);
778
+ }
779
+ fs_1.default.cpSync(src, path_1.default.join(fullDist, file));
780
+ }
781
+ }
782
+ /**
783
+ * Installs an extension npm package into the local skitch extensions directory,
784
+ * and automatically adds it to the current module’s package.json dependencies.
785
+ */
786
+ async installModules(...pkgstrs) {
787
+ this.ensureWorkspace();
788
+ this.ensureModule();
789
+ const originalDir = process.cwd();
790
+ const skitchExtDir = path_1.default.join(this.workspacePath, 'extensions');
791
+ const pkgJsonPath = path_1.default.join(this.modulePath, 'package.json');
792
+ if (!fs_1.default.existsSync(pkgJsonPath)) {
793
+ throw new Error(`No package.json found at module path: ${this.modulePath}`);
794
+ }
795
+ const pkgData = JSON.parse(fs_1.default.readFileSync(pkgJsonPath, 'utf-8'));
796
+ pkgData.dependencies = pkgData.dependencies || {};
797
+ const newlyAdded = [];
798
+ for (const pkgstr of pkgstrs) {
799
+ const { name } = (0, parse_package_name_1.parse)(pkgstr);
800
+ const tempDir = fs_1.default.mkdtempSync(path_1.default.join(os_1.default.tmpdir(), 'lql-install-'));
801
+ try {
802
+ process.chdir(tempDir);
803
+ (0, child_process_1.execSync)(`npm install ${pkgstr} --production --prefix ./extensions`, {
804
+ stdio: 'inherit'
805
+ });
806
+ const matches = glob.sync('./extensions/**/pgpm.plan');
807
+ const installs = matches.map((conf) => {
808
+ const fullConf = (0, path_1.resolve)(conf);
809
+ const extDir = (0, path_1.dirname)(fullConf);
810
+ const relativeDir = extDir.split('node_modules/')[1];
811
+ const dstDir = path_1.default.join(skitchExtDir, relativeDir);
812
+ return { src: extDir, dst: dstDir, pkg: relativeDir };
813
+ });
814
+ for (const { src, dst, pkg } of installs) {
815
+ if (fs_1.default.existsSync(dst)) {
816
+ fs_1.default.rmSync(dst, { recursive: true, force: true });
817
+ }
818
+ fs_1.default.mkdirSync(path_1.default.dirname(dst), { recursive: true });
819
+ (0, child_process_1.execSync)(`mv "${src}" "${dst}"`);
820
+ logger.success(`✔ installed ${pkg}`);
821
+ const pkgJsonFile = path_1.default.join(dst, 'package.json');
822
+ if (!fs_1.default.existsSync(pkgJsonFile)) {
823
+ throw new Error(`Missing package.json in installed extension: ${dst}`);
824
+ }
825
+ const { version } = JSON.parse(fs_1.default.readFileSync(pkgJsonFile, 'utf-8'));
826
+ pkgData.dependencies[name] = `${version}`;
827
+ const extensionName = (0, files_2.getExtensionName)(dst);
828
+ newlyAdded.push(extensionName);
829
+ }
830
+ }
831
+ finally {
832
+ fs_1.default.rmSync(tempDir, { recursive: true, force: true });
833
+ process.chdir(originalDir);
834
+ }
835
+ }
836
+ const { dependencies, devDependencies, ...rest } = pkgData;
837
+ const finalPkgData = { ...rest };
838
+ if (dependencies) {
839
+ finalPkgData.dependencies = sortObjectByKey(dependencies);
840
+ }
841
+ if (devDependencies) {
842
+ finalPkgData.devDependencies = sortObjectByKey(devDependencies);
843
+ }
844
+ fs_1.default.writeFileSync(pkgJsonPath, JSON.stringify(finalPkgData, null, 2));
845
+ logger.success(`📦 Updated package.json with: ${pkgstrs.join(', ')}`);
846
+ // ─── Update .control file with actual extension names ──────────────
847
+ const currentDeps = this.getRequiredModules();
848
+ const updatedDeps = Array.from(new Set([...currentDeps, ...newlyAdded])).sort();
849
+ (0, files_2.writeExtensions)(this.modulePath, updatedDeps);
850
+ }
851
+ // ──────────────── Package Operations ────────────────
852
+ /**
853
+ * Get the set of modules that have been deployed to the database
854
+ */
855
+ async getDeployedModules(pgConfig) {
856
+ try {
857
+ const client = new client_1.PgpmMigrate(pgConfig);
858
+ await client.initialize();
859
+ const status = await client.status();
860
+ return new Set(status.map(s => s.package));
861
+ }
862
+ catch (error) {
863
+ if (error.code === '42P01' || error.code === '3F000') {
864
+ return new Set();
865
+ }
866
+ throw error;
867
+ }
868
+ }
869
+ async resolveWorkspaceExtensionDependencies(opts) {
870
+ const modules = this.getModuleMap();
871
+ const allModuleNames = Object.keys(modules);
872
+ if (allModuleNames.length === 0) {
873
+ return { resolved: [], external: [] };
874
+ }
875
+ // Create a virtual module that depends on all workspace modules
876
+ const virtualModuleName = '_virtual/workspace';
877
+ const virtualModuleMap = {
878
+ ...modules,
879
+ [virtualModuleName]: {
880
+ requires: allModuleNames
881
+ }
882
+ };
883
+ const { resolved, external } = (0, deps_1.resolveExtensionDependencies)(virtualModuleName, virtualModuleMap);
884
+ let filteredResolved = resolved.filter((moduleName) => moduleName !== virtualModuleName);
885
+ // Filter by deployment status if requested
886
+ if (opts?.filterDeployed && opts?.pgConfig) {
887
+ const deployedModules = await this.getDeployedModules(opts.pgConfig);
888
+ filteredResolved = filteredResolved.filter(module => deployedModules.has(module));
889
+ }
890
+ return {
891
+ resolved: filteredResolved,
892
+ external: external
893
+ };
894
+ }
895
+ parsePackageTarget(target) {
896
+ let name;
897
+ let toChange;
898
+ if (!target) {
899
+ const context = this.getContext();
900
+ if (context === PackageContext.Module || context === PackageContext.ModuleInsideWorkspace) {
901
+ name = this.getModuleName();
902
+ }
903
+ else if (context === PackageContext.Workspace) {
904
+ const modules = this.getModuleMap();
905
+ const moduleNames = Object.keys(modules);
906
+ if (moduleNames.length === 0) {
907
+ throw new Error('No modules found in workspace');
908
+ }
909
+ name = null; // Indicates workspace-wide operation
910
+ }
911
+ else {
912
+ throw new Error('Not in a PGPM workspace or module');
913
+ }
914
+ }
915
+ else {
916
+ const parsed = (0, target_utils_1.parseTarget)(target);
917
+ name = parsed.packageName;
918
+ toChange = parsed.toChange;
919
+ }
920
+ return { name, toChange };
921
+ }
922
+ async deploy(opts, target, recursive = true) {
923
+ const log = new logger_1.Logger('deploy');
924
+ const { name, toChange } = this.parsePackageTarget(target);
925
+ if (recursive) {
926
+ // Cache for fast deployment
927
+ const deployFastCache = {};
928
+ const getCacheKey = (pg, name, database) => {
929
+ const { host, port, user } = pg ?? {};
930
+ return `${host}:${port}:${user}:${database}:${name}`;
931
+ };
932
+ const modules = this.getModuleMap();
933
+ let extensions;
934
+ if (name === null) {
935
+ // When name is null, deploy ALL modules in the workspace
936
+ extensions = await this.resolveWorkspaceExtensionDependencies();
937
+ }
938
+ else {
939
+ const moduleProject = this.getModuleProject(name);
940
+ extensions = moduleProject.getModuleExtensions();
941
+ }
942
+ const pgPool = (0, pg_cache_1.getPgPool)(opts.pg);
943
+ const targetDescription = name === null ? 'all modules' : name;
944
+ log.success(`🚀 Starting deployment to database ${opts.pg.database}...`);
945
+ for (const extension of extensions.resolved) {
946
+ try {
947
+ if (extensions.external.includes(extension)) {
948
+ const msg = `CREATE EXTENSION IF NOT EXISTS "${extension}" CASCADE;`;
949
+ log.info(`📥 Installing external extension: ${extension}`);
950
+ await pgPool.query(msg);
951
+ }
952
+ else {
953
+ const modulePath = (0, path_1.resolve)(this.workspacePath, modules[extension].path);
954
+ log.info(`📂 Deploying local module: ${extension}`);
955
+ if (opts.deployment.fast) {
956
+ const localProject = this.getModuleProject(extension);
957
+ const cacheKey = getCacheKey(opts.pg, extension, opts.pg.database);
958
+ if (opts.deployment.cache && deployFastCache[cacheKey]) {
959
+ log.warn(`⚡ Using cached pkg for ${extension}.`);
960
+ await pgPool.query(deployFastCache[cacheKey].sql);
961
+ continue;
962
+ }
963
+ let pkg;
964
+ try {
965
+ pkg = await (0, package_1.packageModule)(localProject.modulePath, {
966
+ usePlan: opts.deployment.usePlan,
967
+ extension: false
968
+ });
969
+ }
970
+ catch (err) {
971
+ const errorLines = [];
972
+ errorLines.push(`❌ Failed to package module "${extension}" at path: ${modulePath}`);
973
+ errorLines.push(` Module Path: ${modulePath}`);
974
+ errorLines.push(` Workspace Path: ${this.workspacePath}`);
975
+ errorLines.push(` Error Code: ${err.code || 'N/A'}`);
976
+ errorLines.push(` Error Message: ${err.message || 'Unknown error'}`);
977
+ if (err.code === 'ENOENT') {
978
+ errorLines.push('💡 Hint: File or directory not found. Check if the module path is correct.');
979
+ }
980
+ else if (err.code === 'EACCES') {
981
+ errorLines.push('💡 Hint: Permission denied. Check file permissions.');
982
+ }
983
+ else if (err.message && err.message.includes('pgpm.plan')) {
984
+ errorLines.push('💡 Hint: pgpm.plan file issue. Check if the plan file exists and is valid.');
985
+ }
986
+ log.error(errorLines.join('\n'));
987
+ console.error(err);
988
+ throw types_1.errors.DEPLOYMENT_FAILED({
989
+ type: 'Deployment',
990
+ module: extension
991
+ });
992
+ }
993
+ await pgPool.query(pkg.sql);
994
+ if (opts.deployment.cache) {
995
+ deployFastCache[cacheKey] = pkg;
996
+ }
997
+ }
998
+ else {
999
+ try {
1000
+ const client = new client_1.PgpmMigrate(opts.pg);
1001
+ // Only apply toChange to the target module, not its dependencies
1002
+ const moduleToChange = extension === name ? toChange : undefined;
1003
+ const result = await client.deploy({
1004
+ modulePath,
1005
+ toChange: moduleToChange,
1006
+ useTransaction: opts.deployment.useTx,
1007
+ logOnly: opts.deployment.logOnly,
1008
+ usePlan: opts.deployment.usePlan
1009
+ });
1010
+ if (result.failed) {
1011
+ throw types_1.errors.OPERATION_FAILED({ operation: 'Deployment', target: result.failed });
1012
+ }
1013
+ }
1014
+ catch (deployError) {
1015
+ log.error(`❌ Deployment failed for module ${extension}`);
1016
+ console.error(deployError);
1017
+ throw types_1.errors.DEPLOYMENT_FAILED({ type: 'Deployment', module: extension });
1018
+ }
1019
+ }
1020
+ }
1021
+ }
1022
+ catch (err) {
1023
+ log.error(`🛑 Error during deployment: ${err instanceof Error ? err.message : err}`);
1024
+ console.error(err);
1025
+ throw types_1.errors.DEPLOYMENT_FAILED({ type: 'Deployment', module: extension });
1026
+ }
1027
+ }
1028
+ log.success(`✅ Deployment complete for ${targetDescription}.`);
1029
+ }
1030
+ else {
1031
+ if (name === null) {
1032
+ throw types_1.errors.WORKSPACE_OPERATION_ERROR({ operation: 'deployment' });
1033
+ }
1034
+ const moduleProject = this.getModuleProject(name);
1035
+ const modulePath = moduleProject.getModulePath();
1036
+ if (!modulePath) {
1037
+ throw types_1.errors.PATH_NOT_FOUND({ path: name, type: 'module' });
1038
+ }
1039
+ const client = new client_1.PgpmMigrate(opts.pg);
1040
+ const result = await client.deploy({
1041
+ modulePath,
1042
+ toChange,
1043
+ useTransaction: opts.deployment?.useTx,
1044
+ logOnly: opts.deployment?.logOnly,
1045
+ usePlan: opts.deployment?.usePlan
1046
+ });
1047
+ if (result.failed) {
1048
+ throw types_1.errors.OPERATION_FAILED({ operation: 'Deployment', target: result.failed });
1049
+ }
1050
+ log.success(`✅ Single module deployment complete for ${name}.`);
1051
+ }
1052
+ }
1053
+ /**
1054
+ * Reverts database changes for modules. Unlike verify operations, revert operations
1055
+ * modify database state and must ensure dependent modules are reverted before their
1056
+ * dependencies to prevent database constraint violations.
1057
+ */
1058
+ async revert(opts, target, recursive = true) {
1059
+ const log = new logger_1.Logger('revert');
1060
+ const { name, toChange } = this.parsePackageTarget(target);
1061
+ if (recursive) {
1062
+ const modules = this.getModuleMap();
1063
+ // Mirror deploy logic: find all modules that depend on the target module
1064
+ let extensionsToRevert;
1065
+ if (name === null) {
1066
+ // When name is null, revert ALL deployed modules in the workspace
1067
+ extensionsToRevert = await this.resolveWorkspaceExtensionDependencies({
1068
+ filterDeployed: true,
1069
+ pgConfig: opts.pg
1070
+ });
1071
+ }
1072
+ else {
1073
+ // Always use workspace-wide resolution in recursive mode, but filter to deployed modules
1074
+ const workspaceExtensions = await this.resolveWorkspaceExtensionDependencies({
1075
+ filterDeployed: true,
1076
+ pgConfig: opts.pg
1077
+ });
1078
+ extensionsToRevert = truncateExtensionsToTarget(workspaceExtensions, name);
1079
+ }
1080
+ const pgPool = (0, pg_cache_1.getPgPool)(opts.pg);
1081
+ const targetDescription = name === null ? 'all modules' : name;
1082
+ log.success(`🧹 Starting revert process on database ${opts.pg.database}...`);
1083
+ const reversedExtensions = [...extensionsToRevert.resolved].reverse();
1084
+ for (const extension of reversedExtensions) {
1085
+ try {
1086
+ if (extensionsToRevert.external.includes(extension)) {
1087
+ const msg = `DROP EXTENSION IF EXISTS "${extension}" RESTRICT;`;
1088
+ log.warn(`⚠️ Dropping external extension: ${extension}`);
1089
+ try {
1090
+ await pgPool.query(msg);
1091
+ }
1092
+ catch (err) {
1093
+ if (err.code === '2BP01') {
1094
+ log.warn(`⚠️ Cannot drop extension ${extension} due to dependencies, skipping`);
1095
+ }
1096
+ else {
1097
+ throw err;
1098
+ }
1099
+ }
1100
+ }
1101
+ else {
1102
+ const modulePath = (0, path_1.resolve)(this.workspacePath, modules[extension].path);
1103
+ log.info(`📂 Reverting local module: ${extension}`);
1104
+ try {
1105
+ const client = new client_1.PgpmMigrate(opts.pg);
1106
+ // Only apply toChange to the target module, not its dependencies
1107
+ const moduleToChange = extension === name ? toChange : undefined;
1108
+ const result = await client.revert({
1109
+ modulePath,
1110
+ toChange: moduleToChange,
1111
+ useTransaction: opts.deployment.useTx
1112
+ });
1113
+ if (result.failed) {
1114
+ throw types_1.errors.OPERATION_FAILED({ operation: 'Revert', target: result.failed });
1115
+ }
1116
+ }
1117
+ catch (revertError) {
1118
+ log.error(`❌ Revert failed for module ${extension}`);
1119
+ throw types_1.errors.DEPLOYMENT_FAILED({ type: 'Revert', module: extension });
1120
+ }
1121
+ }
1122
+ }
1123
+ catch (e) {
1124
+ log.error(`🛑 Error during revert: ${e instanceof Error ? e.message : e}`);
1125
+ console.error(e);
1126
+ throw types_1.errors.DEPLOYMENT_FAILED({ type: 'Revert', module: extension });
1127
+ }
1128
+ }
1129
+ log.success(`✅ Revert complete for ${targetDescription}.`);
1130
+ }
1131
+ else {
1132
+ if (name === null) {
1133
+ throw types_1.errors.WORKSPACE_OPERATION_ERROR({ operation: 'revert' });
1134
+ }
1135
+ const moduleProject = this.getModuleProject(name);
1136
+ const modulePath = moduleProject.getModulePath();
1137
+ if (!modulePath) {
1138
+ throw types_1.errors.PATH_NOT_FOUND({ path: name, type: 'module' });
1139
+ }
1140
+ const client = new client_1.PgpmMigrate(opts.pg);
1141
+ const result = await client.revert({
1142
+ modulePath,
1143
+ toChange,
1144
+ useTransaction: opts.deployment?.useTx
1145
+ });
1146
+ if (result.failed) {
1147
+ throw types_1.errors.OPERATION_FAILED({ operation: 'Revert', target: result.failed });
1148
+ }
1149
+ log.success(`✅ Single module revert complete for ${name}.`);
1150
+ }
1151
+ }
1152
+ async verify(opts, target, recursive = true) {
1153
+ const log = new logger_1.Logger('verify');
1154
+ const { name, toChange } = this.parsePackageTarget(target);
1155
+ if (recursive) {
1156
+ const modules = this.getModuleMap();
1157
+ let extensions;
1158
+ if (name === null) {
1159
+ // When name is null, verify ALL modules in the workspace
1160
+ extensions = await this.resolveWorkspaceExtensionDependencies();
1161
+ }
1162
+ else {
1163
+ const moduleProject = this.getModuleProject(name);
1164
+ extensions = moduleProject.getModuleExtensions();
1165
+ }
1166
+ const pgPool = (0, pg_cache_1.getPgPool)(opts.pg);
1167
+ const targetDescription = name === null ? 'all modules' : name;
1168
+ log.success(`🔎 Verifying deployment of ${targetDescription} on database ${opts.pg.database}...`);
1169
+ for (const extension of extensions.resolved) {
1170
+ try {
1171
+ if (extensions.external.includes(extension)) {
1172
+ const query = `SELECT 1/count(*) FROM pg_available_extensions WHERE name = $1`;
1173
+ log.info(`🔍 Verifying external extension: ${extension}`);
1174
+ await pgPool.query(query, [extension]);
1175
+ }
1176
+ else {
1177
+ const modulePath = (0, path_1.resolve)(this.workspacePath, modules[extension].path);
1178
+ log.info(`📂 Verifying local module: ${extension}`);
1179
+ try {
1180
+ const client = new client_1.PgpmMigrate(opts.pg);
1181
+ // Only apply toChange to the target module, not its dependencies
1182
+ const moduleToChange = extension === name ? toChange : undefined;
1183
+ const result = await client.verify({
1184
+ modulePath,
1185
+ toChange: moduleToChange
1186
+ });
1187
+ if (result.failed.length > 0) {
1188
+ throw types_1.errors.OPERATION_FAILED({ operation: 'Verification', reason: `${result.failed.length} changes: ${result.failed.join(', ')}` });
1189
+ }
1190
+ }
1191
+ catch (verifyError) {
1192
+ log.error(`❌ Verification failed for module ${extension}`);
1193
+ throw types_1.errors.DEPLOYMENT_FAILED({ type: 'Verify', module: extension });
1194
+ }
1195
+ }
1196
+ }
1197
+ catch (e) {
1198
+ log.error(`🛑 Error during verification: ${e instanceof Error ? e.message : e}`);
1199
+ console.error(e);
1200
+ throw types_1.errors.DEPLOYMENT_FAILED({ type: 'Verify', module: extension });
1201
+ }
1202
+ }
1203
+ log.success(`✅ Verification complete for ${targetDescription}.`);
1204
+ }
1205
+ else {
1206
+ if (name === null) {
1207
+ throw types_1.errors.WORKSPACE_OPERATION_ERROR({ operation: 'verification' });
1208
+ }
1209
+ const moduleProject = this.getModuleProject(name);
1210
+ const modulePath = moduleProject.getModulePath();
1211
+ if (!modulePath) {
1212
+ throw types_1.errors.PATH_NOT_FOUND({ path: name, type: 'module' });
1213
+ }
1214
+ const client = new client_1.PgpmMigrate(opts.pg);
1215
+ const result = await client.verify({
1216
+ modulePath,
1217
+ toChange
1218
+ });
1219
+ if (result.failed.length > 0) {
1220
+ throw types_1.errors.OPERATION_FAILED({ operation: 'Verification', reason: `${result.failed.length} changes: ${result.failed.join(', ')}` });
1221
+ }
1222
+ log.success(`✅ Single module verification complete for ${name}.`);
1223
+ }
1224
+ }
1225
+ async removeFromPlan(toChange) {
1226
+ const log = new logger_1.Logger('remove');
1227
+ const modulePath = this.getModulePath();
1228
+ if (!modulePath) {
1229
+ throw types_1.errors.PATH_NOT_FOUND({ path: 'module path', type: 'module' });
1230
+ }
1231
+ const planPath = path_1.default.join(modulePath, 'pgpm.plan');
1232
+ const result = (0, parser_1.parsePlanFile)(planPath);
1233
+ if (result.errors.length > 0) {
1234
+ throw types_1.errors.PLAN_PARSE_ERROR({ planPath, errors: result.errors.map(e => e.message).join(', ') });
1235
+ }
1236
+ const plan = result.data;
1237
+ if (toChange.startsWith('@')) {
1238
+ const tagName = toChange.substring(1); // Remove the '@' prefix
1239
+ const tagToRemove = plan.tags.find(tag => tag.name === tagName);
1240
+ if (!tagToRemove) {
1241
+ throw types_1.errors.TAG_NOT_FOUND({ tag: toChange });
1242
+ }
1243
+ const tagChangeIndex = plan.changes.findIndex(c => c.name === tagToRemove.change);
1244
+ if (tagChangeIndex === -1) {
1245
+ throw types_1.errors.CHANGE_NOT_FOUND({ change: tagToRemove.change, plan: `for tag '${toChange}'` });
1246
+ }
1247
+ const changesToRemove = plan.changes.slice(tagChangeIndex);
1248
+ plan.changes = plan.changes.slice(0, tagChangeIndex);
1249
+ plan.tags = plan.tags.filter(tag => tag.name !== tagName && !changesToRemove.some(change => change.name === tag.change));
1250
+ for (const change of changesToRemove) {
1251
+ for (const scriptType of ['deploy', 'revert', 'verify']) {
1252
+ const scriptPath = path_1.default.join(modulePath, scriptType, `${change.name}.sql`);
1253
+ if (fs_1.default.existsSync(scriptPath)) {
1254
+ fs_1.default.unlinkSync(scriptPath);
1255
+ log.info(`Deleted ${scriptType}/${change.name}.sql`);
1256
+ }
1257
+ }
1258
+ }
1259
+ // Write updated plan file
1260
+ (0, files_1.writePlanFile)(planPath, plan);
1261
+ log.success(`Removed tag ${toChange} and ${changesToRemove.length} subsequent changes from plan`);
1262
+ return;
1263
+ }
1264
+ const targetIndex = plan.changes.findIndex(c => c.name === toChange);
1265
+ if (targetIndex === -1) {
1266
+ throw types_1.errors.CHANGE_NOT_FOUND({ change: toChange });
1267
+ }
1268
+ const changesToRemove = plan.changes.slice(targetIndex);
1269
+ plan.changes = plan.changes.slice(0, targetIndex);
1270
+ plan.tags = plan.tags.filter(tag => !changesToRemove.some(change => change.name === tag.change));
1271
+ for (const change of changesToRemove) {
1272
+ for (const scriptType of ['deploy', 'revert', 'verify']) {
1273
+ const scriptPath = path_1.default.join(modulePath, scriptType, `${change.name}.sql`);
1274
+ if (fs_1.default.existsSync(scriptPath)) {
1275
+ fs_1.default.unlinkSync(scriptPath);
1276
+ log.info(`Deleted ${scriptType}/${change.name}.sql`);
1277
+ }
1278
+ }
1279
+ }
1280
+ // Write updated plan file
1281
+ (0, files_1.writePlanFile)(planPath, plan);
1282
+ log.success(`Removed ${changesToRemove.length} changes from plan`);
1283
+ }
1284
+ analyzeModule() {
1285
+ this.ensureModule();
1286
+ const info = this.getModuleInfo();
1287
+ const modPath = this.getModulePath();
1288
+ const issues = [];
1289
+ const exists = (p) => fs_1.default.existsSync(p);
1290
+ const read = (p) => (exists(p) ? fs_1.default.readFileSync(p, 'utf8') : undefined);
1291
+ const planPath = path_1.default.join(modPath, 'pgpm.plan');
1292
+ if (!exists(planPath))
1293
+ issues.push({ code: 'missing_plan', message: 'Missing pgpm.plan', file: planPath });
1294
+ const pkgJsonPath = path_1.default.join(modPath, 'package.json');
1295
+ if (!exists(pkgJsonPath))
1296
+ issues.push({ code: 'missing_package_json', message: 'Missing package.json', file: pkgJsonPath });
1297
+ const makefilePath = info.Makefile;
1298
+ if (!exists(makefilePath))
1299
+ issues.push({ code: 'missing_makefile', message: 'Missing Makefile', file: makefilePath });
1300
+ const controlPath = info.controlFile;
1301
+ if (!exists(controlPath))
1302
+ issues.push({ code: 'missing_control', message: 'Missing control file', file: controlPath });
1303
+ const sqlCombined = info.sqlFile ? path_1.default.join(modPath, info.sqlFile) : path_1.default.join(modPath, 'sql', `${info.extname}--${info.version}.sql`);
1304
+ if (!exists(sqlCombined))
1305
+ issues.push({ code: 'missing_sql', message: 'Missing combined sql file', file: sqlCombined });
1306
+ const deployDir = path_1.default.join(modPath, 'deploy');
1307
+ if (!exists(deployDir))
1308
+ issues.push({ code: 'missing_deploy_dir', message: 'Missing deploy directory', file: deployDir });
1309
+ const revertDir = path_1.default.join(modPath, 'revert');
1310
+ if (!exists(revertDir))
1311
+ issues.push({ code: 'missing_revert_dir', message: 'Missing revert directory', file: revertDir });
1312
+ const verifyDir = path_1.default.join(modPath, 'verify');
1313
+ if (!exists(verifyDir))
1314
+ issues.push({ code: 'missing_verify_dir', message: 'Missing verify directory', file: verifyDir });
1315
+ if (exists(planPath)) {
1316
+ try {
1317
+ const parsed = (0, parser_1.parsePlanFile)(planPath);
1318
+ const pkgName = parsed.data?.package;
1319
+ if (!pkgName)
1320
+ issues.push({ code: 'plan_missing_project', message: '%project missing', file: planPath });
1321
+ if (pkgName && pkgName !== info.extname)
1322
+ issues.push({ code: 'plan_project_mismatch', message: `pgpm.plan %project ${pkgName} != ${info.extname}`, file: planPath });
1323
+ const uri = parsed.data?.uri;
1324
+ if (uri && uri !== info.extname)
1325
+ issues.push({ code: 'plan_uri_mismatch', message: `pgpm.plan %uri ${uri} != ${info.extname}`, file: planPath });
1326
+ }
1327
+ catch (e) {
1328
+ issues.push({ code: 'plan_parse_error', message: e?.message || 'Plan parse error', file: planPath });
1329
+ }
1330
+ }
1331
+ if (exists(makefilePath)) {
1332
+ const mf = read(makefilePath) || '';
1333
+ const extMatch = mf.match(/^EXTENSION\s*=\s*(.+)$/m);
1334
+ const dataMatch = mf.match(/^DATA\s*=\s*sql\/(.+)\.sql$/m);
1335
+ if (!extMatch)
1336
+ issues.push({ code: 'makefile_missing_extension', message: 'Makefile missing EXTENSION', file: makefilePath });
1337
+ if (!dataMatch)
1338
+ issues.push({ code: 'makefile_missing_data', message: 'Makefile missing DATA', file: makefilePath });
1339
+ if (extMatch && extMatch[1].trim() !== info.extname)
1340
+ issues.push({ code: 'makefile_extension_mismatch', message: `Makefile EXTENSION ${extMatch[1].trim()} != ${info.extname}`, file: makefilePath });
1341
+ const expectedData = `${info.extname}--${info.version}`;
1342
+ if (dataMatch && dataMatch[1].trim() !== expectedData)
1343
+ issues.push({ code: 'makefile_data_mismatch', message: `Makefile DATA sql/${dataMatch[1].trim()}.sql != sql/${expectedData}.sql`, file: makefilePath });
1344
+ }
1345
+ if (exists(controlPath)) {
1346
+ const base = path_1.default.basename(controlPath);
1347
+ const expected = `${info.extname}.control`;
1348
+ if (base !== expected)
1349
+ issues.push({ code: 'control_filename_mismatch', message: `Control filename ${base} != ${expected}`, file: controlPath });
1350
+ }
1351
+ return { ok: issues.length === 0, name: info.extname, path: modPath, issues };
1352
+ }
1353
+ renameModule(newName, opts) {
1354
+ this.ensureModule();
1355
+ const info = this.getModuleInfo();
1356
+ const modPath = this.getModulePath();
1357
+ const changed = [];
1358
+ const warnings = [];
1359
+ const dry = !!opts?.dryRun;
1360
+ const valid = /^[a-z][a-z0-9_]*$/;
1361
+ if (!valid.test(newName)) {
1362
+ throw types_1.errors.INVALID_NAME({ name: newName, type: 'module', rules: 'lowercase letters, digits, underscores; must start with letter' });
1363
+ }
1364
+ const planPath = path_1.default.join(modPath, 'pgpm.plan');
1365
+ if (fs_1.default.existsSync(planPath)) {
1366
+ try {
1367
+ const parsed = (0, parser_1.parsePlanFile)(planPath);
1368
+ if (parsed.data) {
1369
+ parsed.data.package = newName;
1370
+ parsed.data.uri = newName;
1371
+ if (!dry)
1372
+ (0, files_1.writePlanFile)(planPath, parsed.data);
1373
+ changed.push(planPath);
1374
+ }
1375
+ }
1376
+ catch (e) {
1377
+ warnings.push(`failed to update pgpm.plan`);
1378
+ }
1379
+ }
1380
+ else {
1381
+ warnings.push('missing pgpm.plan');
1382
+ }
1383
+ const pkgJsonPath = path_1.default.join(modPath, 'package.json');
1384
+ if (fs_1.default.existsSync(pkgJsonPath) && opts?.syncPackageJsonName) {
1385
+ try {
1386
+ const pkg = JSON.parse(fs_1.default.readFileSync(pkgJsonPath, 'utf8'));
1387
+ const oldName = pkg.name;
1388
+ if (oldName) {
1389
+ if (oldName.startsWith('@')) {
1390
+ const parts = oldName.split('/');
1391
+ if (parts.length === 2)
1392
+ pkg.name = `${parts[0]}/${newName}`;
1393
+ else
1394
+ pkg.name = newName;
1395
+ }
1396
+ else {
1397
+ pkg.name = newName;
1398
+ }
1399
+ }
1400
+ else {
1401
+ pkg.name = newName;
1402
+ }
1403
+ if (!dry)
1404
+ fs_1.default.writeFileSync(pkgJsonPath, JSON.stringify(pkg, null, 2));
1405
+ changed.push(pkgJsonPath);
1406
+ }
1407
+ catch {
1408
+ warnings.push('failed to update package.json name');
1409
+ }
1410
+ }
1411
+ const oldControl = info.controlFile;
1412
+ const newControl = path_1.default.join(modPath, `${newName}.control`);
1413
+ const version = info.version;
1414
+ const requires = (() => {
1415
+ try {
1416
+ const c = fs_1.default.readFileSync(oldControl, 'utf8');
1417
+ const line = c.split('\n').find(l => /^requires/.test(l));
1418
+ if (!line)
1419
+ return [];
1420
+ return line.split('=')[1].split("'")[1].split(',').map(s => s.trim()).filter(Boolean);
1421
+ }
1422
+ catch {
1423
+ return [];
1424
+ }
1425
+ })();
1426
+ if (fs_1.default.existsSync(oldControl)) {
1427
+ if (!dry) {
1428
+ const content = (0, writer_1.generateControlFileContent)({ name: newName, version, requires });
1429
+ fs_1.default.writeFileSync(newControl, content);
1430
+ if (oldControl !== newControl && fs_1.default.existsSync(oldControl))
1431
+ fs_1.default.rmSync(oldControl);
1432
+ }
1433
+ changed.push(newControl);
1434
+ }
1435
+ else {
1436
+ warnings.push('missing control file');
1437
+ }
1438
+ const makefilePath = info.Makefile;
1439
+ if (fs_1.default.existsSync(makefilePath)) {
1440
+ if (!dry)
1441
+ (0, writer_1.writeExtensionMakefile)(makefilePath, newName, version);
1442
+ changed.push(makefilePath);
1443
+ }
1444
+ else {
1445
+ warnings.push('missing Makefile');
1446
+ }
1447
+ const oldSql = path_1.default.join(modPath, 'sql', `${info.extname}--${version}.sql`);
1448
+ const newSql = path_1.default.join(modPath, 'sql', `${newName}--${version}.sql`);
1449
+ if (fs_1.default.existsSync(oldSql)) {
1450
+ if (!dry) {
1451
+ if (oldSql !== newSql) {
1452
+ fs_1.default.mkdirSync(path_1.default.dirname(newSql), { recursive: true });
1453
+ fs_1.default.renameSync(oldSql, newSql);
1454
+ }
1455
+ }
1456
+ changed.push(newSql);
1457
+ }
1458
+ else {
1459
+ if (fs_1.default.existsSync(newSql)) {
1460
+ changed.push(newSql);
1461
+ }
1462
+ else {
1463
+ warnings.push('missing combined sql file');
1464
+ }
1465
+ }
1466
+ this.clearCache();
1467
+ return { changed, warnings };
1468
+ }
1469
+ }
1470
+ exports.PgpmPackage = PgpmPackage;