@pgpmjs/core 7.0.0 → 7.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (102) hide show
  1. package/bundle/apply.d.ts +93 -0
  2. package/bundle/apply.js +137 -0
  3. package/bundle/index.d.ts +12 -0
  4. package/bundle/index.js +31 -0
  5. package/core/class/pgpm.d.ts +2 -2
  6. package/core/class/pgpm.js +6 -6
  7. package/esm/bundle/apply.js +131 -0
  8. package/esm/bundle/index.js +15 -0
  9. package/esm/core/class/pgpm.js +6 -6
  10. package/esm/index.js +7 -3
  11. package/esm/migrate/client.js +1 -1
  12. package/esm/migrate/utils/hash.js +3 -6
  13. package/esm/modules/modules.js +1 -1
  14. package/esm/packaging/package.js +40 -24
  15. package/esm/rebundle/index.js +4 -0
  16. package/esm/rebundle/module.js +111 -0
  17. package/esm/rebundle/rebundle.js +170 -0
  18. package/esm/rebundle/workspace.js +170 -0
  19. package/esm/refactor/rename.js +5 -27
  20. package/esm/resolution/deps.js +2 -2
  21. package/esm/resolution/resolve.js +2 -2
  22. package/esm/slice/slice.js +1 -1
  23. package/esm/workspace/minimal.js +73 -0
  24. package/index.d.ts +6 -2
  25. package/index.js +8 -4
  26. package/migrate/client.js +1 -1
  27. package/migrate/utils/hash.d.ts +2 -4
  28. package/migrate/utils/hash.js +4 -8
  29. package/modules/modules.d.ts +1 -1
  30. package/modules/modules.js +1 -1
  31. package/package.json +4 -2
  32. package/packaging/package.d.ts +23 -1
  33. package/packaging/package.js +42 -25
  34. package/rebundle/index.d.ts +4 -0
  35. package/{files/sql-scripts → rebundle}/index.js +4 -2
  36. package/rebundle/module.d.ts +23 -0
  37. package/rebundle/module.js +116 -0
  38. package/rebundle/rebundle.d.ts +26 -0
  39. package/rebundle/rebundle.js +175 -0
  40. package/rebundle/types.d.ts +185 -0
  41. package/rebundle/workspace.d.ts +15 -0
  42. package/rebundle/workspace.js +173 -0
  43. package/refactor/rename.d.ts +2 -8
  44. package/refactor/rename.js +6 -29
  45. package/resolution/deps.d.ts +1 -1
  46. package/resolution/deps.js +2 -2
  47. package/resolution/resolve.js +2 -2
  48. package/slice/slice.d.ts +1 -1
  49. package/slice/slice.js +1 -1
  50. package/slice/types.d.ts +1 -1
  51. package/workspace/minimal.d.ts +64 -0
  52. package/workspace/minimal.js +82 -0
  53. package/esm/files/extension/index.js +0 -3
  54. package/esm/files/extension/reader.js +0 -79
  55. package/esm/files/extension/writer.js +0 -99
  56. package/esm/files/index.js +0 -6
  57. package/esm/files/plan/generator.js +0 -82
  58. package/esm/files/plan/index.js +0 -5
  59. package/esm/files/plan/parser.js +0 -297
  60. package/esm/files/plan/validators.js +0 -181
  61. package/esm/files/plan/writer.js +0 -142
  62. package/esm/files/sql/header.js +0 -259
  63. package/esm/files/sql/index.js +0 -2
  64. package/esm/files/sql/writer.js +0 -107
  65. package/esm/files/sql-scripts/index.js +0 -2
  66. package/esm/files/sql-scripts/reader.js +0 -19
  67. package/esm/files/types/index.js +0 -1
  68. package/esm/utils/author.js +0 -11
  69. package/files/extension/index.d.ts +0 -2
  70. package/files/extension/index.js +0 -19
  71. package/files/extension/reader.d.ts +0 -24
  72. package/files/extension/reader.js +0 -86
  73. package/files/extension/writer.d.ts +0 -48
  74. package/files/extension/writer.js +0 -107
  75. package/files/index.d.ts +0 -5
  76. package/files/index.js +0 -22
  77. package/files/plan/generator.d.ts +0 -23
  78. package/files/plan/generator.js +0 -90
  79. package/files/plan/index.d.ts +0 -4
  80. package/files/plan/index.js +0 -21
  81. package/files/plan/parser.d.ts +0 -28
  82. package/files/plan/parser.js +0 -304
  83. package/files/plan/validators.d.ts +0 -52
  84. package/files/plan/validators.js +0 -187
  85. package/files/plan/writer.d.ts +0 -27
  86. package/files/plan/writer.js +0 -152
  87. package/files/sql/header.d.ts +0 -114
  88. package/files/sql/header.js +0 -266
  89. package/files/sql/index.d.ts +0 -2
  90. package/files/sql/index.js +0 -18
  91. package/files/sql/writer.d.ts +0 -12
  92. package/files/sql/writer.js +0 -114
  93. package/files/sql-scripts/index.d.ts +0 -1
  94. package/files/sql-scripts/reader.d.ts +0 -8
  95. package/files/sql-scripts/reader.js +0 -23
  96. package/files/types/index.d.ts +0 -46
  97. package/files/types/index.js +0 -17
  98. package/files/types/package.d.ts +0 -20
  99. package/utils/author.d.ts +0 -5
  100. package/utils/author.js +0 -14
  101. /package/esm/{files/types/package.js → rebundle/types.js} +0 -0
  102. /package/{files/types/package.js → rebundle/types.js} +0 -0
@@ -1,181 +0,0 @@
1
- /**
2
- * Validators for Sqitch change and tag names according to the spec:
3
- * https://sqitch.org/docs/manual/sqitchchanges/
4
- */
5
- /**
6
- * Check if a character is punctuation (excluding underscore)
7
- */
8
- function isPunctuation(char) {
9
- // Common punctuation marks, excluding underscore
10
- const punctuation = /[!"#$%&'()*+,\-./:;<=>?@[\\\]^`{|}~]/;
11
- return punctuation.test(char);
12
- }
13
- /**
14
- * Validate a change name according to Sqitch rules:
15
- * - ≥1 character
16
- * - Cannot contain whitespace
17
- * - First and last characters must not be punctuation (except _)
18
- * - Must not end in ~, ^, /, = or % followed by digits
19
- * - Disallowed characters anywhere: :, @, #, \
20
- */
21
- export function isValidChangeName(name) {
22
- if (!name || name.length === 0) {
23
- return false;
24
- }
25
- // Check for whitespace
26
- if (/\s/.test(name)) {
27
- return false;
28
- }
29
- // Check for disallowed characters anywhere
30
- if (/[:@#\\]/.test(name)) {
31
- return false;
32
- }
33
- // Check first character - must not be punctuation except underscore
34
- const firstChar = name[0];
35
- if (firstChar !== '_' && isPunctuation(firstChar)) {
36
- return false;
37
- }
38
- // Check last character - must not be punctuation except underscore
39
- const lastChar = name[name.length - 1];
40
- if (lastChar !== '_' && isPunctuation(lastChar)) {
41
- return false;
42
- }
43
- // Check for endings like ~N, ^N, /N, =N, %N where N is digits
44
- if (/[~^/=%]\d+$/.test(name)) {
45
- return false;
46
- }
47
- return true;
48
- }
49
- /**
50
- * Validate a tag name according to Sqitch rules:
51
- * - Same as change name rules
52
- * - Additionally, must not contain /
53
- */
54
- export function isValidTagName(name) {
55
- if (!isValidChangeName(name)) {
56
- return false;
57
- }
58
- // Tags must not contain /
59
- if (name.includes('/')) {
60
- return false;
61
- }
62
- return true;
63
- }
64
- /**
65
- * Check if a string is a valid SHA1 hash (40 hex characters)
66
- */
67
- function isSHA1(str) {
68
- return /^[0-9a-f]{40}$/i.test(str);
69
- }
70
- /**
71
- * Parse a reference string into its components
72
- */
73
- export function parseReference(ref) {
74
- if (!ref) {
75
- return null;
76
- }
77
- const result = {};
78
- // Check for package qualifier
79
- let workingRef = ref;
80
- const colonIndex = ref.indexOf(':');
81
- if (colonIndex > 0) {
82
- const projectPart = ref.substring(0, colonIndex);
83
- const remainingPart = ref.substring(colonIndex + 1);
84
- // Check if this is actually a package qualifier or just a colon in the name
85
- // Package names should be valid identifiers (alphanumeric, dash, underscore)
86
- // Also check that the remaining part doesn't have more colons
87
- if (/^[a-zA-Z0-9_-]+$/.test(projectPart) &&
88
- remainingPart.length > 0 &&
89
- remainingPart.indexOf(':') === -1) {
90
- result.package = projectPart;
91
- workingRef = remainingPart;
92
- }
93
- else {
94
- // Not a valid package qualifier, treat the whole thing as a reference
95
- workingRef = ref;
96
- }
97
- }
98
- // Check for symbolic references
99
- if (workingRef === 'HEAD' || workingRef === '@HEAD') {
100
- result.symbolic = 'HEAD';
101
- return result;
102
- }
103
- if (workingRef === 'ROOT' || workingRef === '@ROOT') {
104
- result.symbolic = 'ROOT';
105
- return result;
106
- }
107
- // Check for relative references (e.g., HEAD^, @beta~2, HEAD^^)
108
- // Handle multiple ^ characters
109
- const multiCaretMatch = workingRef.match(/^(.+?)(\^+)$/);
110
- if (multiCaretMatch) {
111
- const [, base, carets] = multiCaretMatch;
112
- result.relative = {
113
- base: base, // Keep the original base including @ prefix
114
- direction: '^',
115
- count: carets.length
116
- };
117
- return result;
118
- }
119
- // Handle ^N or ~N format
120
- const relativeMatch = workingRef.match(/^(.+?)([~^])(\d+)$/);
121
- if (relativeMatch) {
122
- const [, base, direction, countStr] = relativeMatch;
123
- const count = parseInt(countStr, 10);
124
- result.relative = {
125
- base: base, // Keep the original base including @ prefix
126
- direction: direction,
127
- count
128
- };
129
- return result;
130
- }
131
- // Handle single ^ or ~ without number
132
- const singleRelativeMatch = workingRef.match(/^(.+?)([~^])$/);
133
- if (singleRelativeMatch) {
134
- const [, base, direction] = singleRelativeMatch;
135
- result.relative = {
136
- base: base, // Keep the original base including @ prefix
137
- direction: direction,
138
- count: 1
139
- };
140
- return result;
141
- }
142
- // Check if it's a SHA1
143
- if (isSHA1(workingRef)) {
144
- result.sha1 = workingRef;
145
- return result;
146
- }
147
- // Check for tag reference (starts with @)
148
- if (workingRef.startsWith('@')) {
149
- const tagName = workingRef.substring(1);
150
- if (isValidTagName(tagName)) {
151
- result.tag = tagName;
152
- return result;
153
- }
154
- return null; // Invalid tag name
155
- }
156
- // Check for change@tag format
157
- const atIndex = workingRef.indexOf('@');
158
- if (atIndex > 0) {
159
- const changeName = workingRef.substring(0, atIndex);
160
- const tagName = workingRef.substring(atIndex + 1);
161
- if (isValidChangeName(changeName) && isValidTagName(tagName)) {
162
- result.change = changeName;
163
- result.tag = tagName;
164
- return result;
165
- }
166
- return null; // Invalid change or tag name
167
- }
168
- // Must be a plain change name
169
- if (isValidChangeName(workingRef)) {
170
- result.change = workingRef;
171
- return result;
172
- }
173
- return null; // Invalid reference
174
- }
175
- /**
176
- * Validate a dependency reference
177
- * Dependencies can be any valid reference format
178
- */
179
- export function isValidDependency(dep) {
180
- return parseReference(dep) !== null;
181
- }
@@ -1,142 +0,0 @@
1
- import fs from 'fs';
2
- import path from 'path';
3
- import { parseAuthor } from '../../utils/author';
4
- /**
5
- * Sort dependencies by their order in the supplied plan, so prerequisites
6
- * (parent migrations) appear before their dependents in the bracket.
7
- * Internal dependencies are sorted by their plan index, and external
8
- * dependencies (not in the plan) preserve their original order.
9
- */
10
- const sortDependencies = (deps, order) => {
11
- const originalOrder = new Map(deps.map((dep, index) => [dep, index]));
12
- return [...deps].sort((a, b) => {
13
- const aIndex = order.get(a);
14
- const bIndex = order.get(b);
15
- const aOriginal = originalOrder.get(a);
16
- const bOriginal = originalOrder.get(b);
17
- if (aIndex !== undefined && bIndex !== undefined)
18
- return aIndex - bIndex || aOriginal - bOriginal;
19
- if (aIndex !== undefined)
20
- return -1;
21
- if (bIndex !== undefined)
22
- return 1;
23
- return aOriginal - bOriginal;
24
- });
25
- };
26
- /**
27
- * Write a PGPM plan file based on the provided rows
28
- */
29
- export function writePgpmPlan(rows, opts) {
30
- const dir = path.resolve(path.join(opts.outdir, opts.name));
31
- fs.mkdirSync(dir, { recursive: true });
32
- const date = () => '2017-08-11T08:11:51Z'; // stubbed timestamp
33
- const { fullName, email } = parseAuthor(opts.author || 'constructive');
34
- const authorName = fullName;
35
- const authorEmail = email || `${fullName}@5b0c196eeb62`;
36
- const duplicates = {};
37
- // Build an order map from the row list so brackets can be sorted by plan order
38
- const rowOrder = new Map(rows.map((row, index) => [row.deploy, index]));
39
- const plan = opts.replacer(`%syntax-version=1.0.0
40
- %project=constructive-extension-name
41
- %uri=constructive-extension-name
42
-
43
- ${rows
44
- .map((row) => {
45
- if (duplicates[row.deploy]) {
46
- console.log('DUPLICATE ' + row.deploy);
47
- return '';
48
- }
49
- duplicates[row.deploy] = true;
50
- if (row.deps?.length) {
51
- return `${row.deploy} [${sortDependencies(row.deps, rowOrder).join(' ')}] ${date()} ${authorName} <${authorEmail}> # add ${row.name}`;
52
- }
53
- return `${row.deploy} ${date()} ${authorName} <${authorEmail}> # add ${row.name}`;
54
- })
55
- .join('\n')}
56
- `);
57
- fs.writeFileSync(path.join(dir, 'pgpm.plan'), plan);
58
- }
59
- /**
60
- * Write a plan file with the provided content
61
- */
62
- export function writePlanFile(planPath, plan) {
63
- const content = generatePlanFileContent(plan);
64
- fs.writeFileSync(planPath, content);
65
- }
66
- /**
67
- * Generate content for a plan file
68
- */
69
- export function generatePlanFileContent(plan) {
70
- const { package: packageName, uri, changes, tags } = plan;
71
- let content = `%syntax-version=1.0.0\n`;
72
- content += `%project=${packageName}\n`;
73
- if (uri) {
74
- content += `%uri=${uri}\n`;
75
- }
76
- content += `\n`;
77
- // Build an order map from the change list so brackets can be sorted by plan order
78
- const changeOrder = new Map(changes.map((change, index) => [change.name, index]));
79
- // Add changes and their associated tags
80
- for (const change of changes) {
81
- content += generateChangeLineContent(change, changeOrder);
82
- content += `\n`;
83
- const associatedTags = tags.filter(tag => tag.change === change.name);
84
- for (const tag of associatedTags) {
85
- content += generateTagLineContent(tag);
86
- content += `\n`;
87
- }
88
- }
89
- return content;
90
- }
91
- /**
92
- * Generate a line for a change in a plan file
93
- */
94
- export function generateChangeLineContent(change, changeOrder) {
95
- const { name, dependencies, timestamp, planner, email, comment } = change;
96
- let line = name;
97
- // Add dependencies if present, sorted by plan order
98
- if (dependencies && dependencies.length > 0) {
99
- line += ` [${sortDependencies(dependencies, changeOrder || new Map()).join(' ')}]`;
100
- }
101
- // Add timestamp if present
102
- if (timestamp) {
103
- line += ` ${timestamp}`;
104
- // Add planner if present
105
- if (planner) {
106
- line += ` ${planner}`;
107
- // Add email if present
108
- if (email) {
109
- line += ` <${email}>`;
110
- }
111
- }
112
- }
113
- // Add comment if present
114
- if (comment) {
115
- line += ` # ${comment}`;
116
- }
117
- return line;
118
- }
119
- /**
120
- * Generate a line for a tag in a plan file
121
- */
122
- export function generateTagLineContent(tag) {
123
- const { name, timestamp, planner, email, comment } = tag;
124
- let line = `@${name}`;
125
- // Add timestamp if present
126
- if (timestamp) {
127
- line += ` ${timestamp}`;
128
- // Add planner if present
129
- if (planner) {
130
- line += ` ${planner}`;
131
- // Add email if present
132
- if (email) {
133
- line += ` <${email}>`;
134
- }
135
- }
136
- }
137
- // Add comment if present
138
- if (comment) {
139
- line += ` # ${comment}`;
140
- }
141
- return line;
142
- }
@@ -1,259 +0,0 @@
1
- import { readFileSync } from 'fs';
2
- import { join } from 'path';
3
- import { parsePlanFile } from '../plan/parser';
4
- import { parseReference } from '../plan/validators';
5
- const HEADER_LINE = /^--\s*(Deploy|Revert|Verify)\b(:)?\s*(.*?)\s*$/;
6
- const REQUIRES_LINE = /^--\s*requires:\s*(.*?)\s*$/;
7
- const COMMENT_LINE = /^\s*--/;
8
- const TO_PG_SUFFIX = /\s+to\s+pg\s*$/;
9
- /**
10
- * Parse the header block of a pgpm SQL script into a structured model.
11
- *
12
- * Recognizes both header formats:
13
- * `-- Deploy <project>:<change> to pg` (legacy sqitch-compatible)
14
- * `-- Deploy <change>` / `-- Deploy: <change>` (new)
15
- * along with `-- requires: <ref>` lines where `<ref>` may be a plain change
16
- * name, `{package}:{change}`, `@tag`, or `{package}:@tag`.
17
- *
18
- * The parse is lossless: `header.lines` plus `body` reassemble the original
19
- * content byte-exactly (see {@link writePgpmScript}).
20
- */
21
- export function parsePgpmHeader(content) {
22
- const header = {
23
- verb: null,
24
- project: null,
25
- change: null,
26
- toPg: false,
27
- requires: [],
28
- lines: []
29
- };
30
- const lines = content.split('\n');
31
- let lastHeaderIdx = -1;
32
- for (let i = 0; i < lines.length; i++) {
33
- const line = lines[i];
34
- if (line.trim() === '') {
35
- continue;
36
- }
37
- if (!COMMENT_LINE.test(line)) {
38
- break;
39
- }
40
- const headerMatch = line.match(HEADER_LINE);
41
- if (headerMatch && header.verb === null) {
42
- header.verb = headerMatch[1];
43
- let target = headerMatch[3];
44
- if (TO_PG_SUFFIX.test(target)) {
45
- header.toPg = true;
46
- target = target.replace(TO_PG_SUFFIX, '');
47
- }
48
- const colonIndex = target.indexOf(':');
49
- if (colonIndex > 0 && /^[a-zA-Z0-9_-]+$/.test(target.substring(0, colonIndex))) {
50
- header.project = target.substring(0, colonIndex);
51
- header.change = target.substring(colonIndex + 1).trim();
52
- }
53
- else {
54
- header.change = target.trim() || null;
55
- }
56
- lastHeaderIdx = i;
57
- continue;
58
- }
59
- const requiresMatch = line.match(REQUIRES_LINE);
60
- if (requiresMatch) {
61
- const raw = requiresMatch[1];
62
- header.requires.push({ raw, ref: parseReference(raw) });
63
- lastHeaderIdx = i;
64
- continue;
65
- }
66
- // Other comment lines (e.g. attribution) before/between header lines are
67
- // tolerated but only absorbed into the header when a later header line
68
- // follows; trailing comments belong to the body.
69
- }
70
- header.lines = lines.slice(0, lastHeaderIdx + 1);
71
- return {
72
- header,
73
- body: lines.slice(lastHeaderIdx + 1).join('\n')
74
- };
75
- }
76
- /**
77
- * Serialize a header back to its raw lines. When the structured fields have
78
- * been modified via {@link renameInHeader}, the raw lines already reflect the
79
- * change; this simply joins header and body losslessly.
80
- */
81
- export function writePgpmScript(script) {
82
- if (script.header.lines.length === 0)
83
- return script.body;
84
- return script.header.lines.join('\n') + '\n' + script.body;
85
- }
86
- /**
87
- * Rewrite change names in a script header according to a rename map
88
- * (old change name -> new change name). Rewrites both the `-- Deploy` line's
89
- * change identity and any `-- requires:` lines referencing renamed changes,
90
- * preserving format (project qualifier, ` to pg` suffix, tag refs untouched).
91
- *
92
- * Returns the number of substitutions applied; mutates `script` in place.
93
- */
94
- export function renameInHeader(script, renames) {
95
- const { header } = script;
96
- let count = 0;
97
- const renameRef = (raw) => {
98
- const ref = parseReference(raw);
99
- if (!ref) {
100
- const to = renames.get(raw);
101
- return to ?? null;
102
- }
103
- if (ref.tag || ref.symbolic || ref.relative)
104
- return null;
105
- const change = ref.change;
106
- if (!change)
107
- return null;
108
- const to = renames.get(change);
109
- if (!to)
110
- return null;
111
- return ref.package ? `${ref.package}:${to}` : to;
112
- };
113
- header.lines = header.lines.map(line => {
114
- const headerMatch = line.match(HEADER_LINE);
115
- if (headerMatch && header.change && renames.has(header.change)) {
116
- const to = renames.get(header.change);
117
- const oldTarget = header.project ? `${header.project}:${header.change}` : header.change;
118
- const newTarget = header.project ? `${header.project}:${to}` : to;
119
- const rewritten = line.replace(oldTarget, newTarget);
120
- if (rewritten !== line) {
121
- header.change = to;
122
- count++;
123
- return rewritten;
124
- }
125
- return line;
126
- }
127
- const requiresMatch = line.match(REQUIRES_LINE);
128
- if (requiresMatch) {
129
- const raw = requiresMatch[1];
130
- const renamed = renameRef(raw);
131
- if (renamed !== null) {
132
- count++;
133
- return line.replace(raw, renamed);
134
- }
135
- }
136
- return line;
137
- });
138
- header.requires = header.requires.map(req => {
139
- const renamed = renameRef(req.raw);
140
- if (renamed === null)
141
- return req;
142
- return { raw: renamed, ref: parseReference(renamed) };
143
- });
144
- return count;
145
- }
146
- /**
147
- * Scan a deploy script the way dependency resolution historically has:
148
- * line-by-line over the whole file, collecting `-- requires: <ref>` lines
149
- * verbatim and validating any `-- Deploy <project>:<change>[ to pg]` line
150
- * against the script's path identity and project name.
151
- *
152
- * Kept strictly line-regex-compatible with the original resolver behavior
153
- * (requires lines are honored anywhere in the file, and the legacy
154
- * `-- Deploy: <change>` colon form is not identity-validated).
155
- *
156
- * @throws when the Deploy line's project or change identity mismatches.
157
- */
158
- export function scanDeployScript(content, options) {
159
- const { key, extname, makeKey } = options;
160
- const requires = [];
161
- for (const line of content.split('\n')) {
162
- const requiresMatch = line.match(/^-- requires: (.*)/);
163
- if (requiresMatch) {
164
- requires.push(requiresMatch[1].trim());
165
- continue;
166
- }
167
- if (/:/.test(line)) {
168
- const m2 = line.match(/^-- Deploy ([^:]*):([\w\/]+)(?:\s+to\s+pg)?/);
169
- if (m2) {
170
- const actualProject = m2[1];
171
- const keyToTest = m2[2];
172
- if (extname !== actualProject) {
173
- throw new Error(`Mismatched project name in deploy file:
174
- Expected project: ${extname}
175
- Found in line : ${actualProject}
176
- Line : ${line}`);
177
- }
178
- const expectedKey = makeKey(keyToTest);
179
- if (key !== expectedKey) {
180
- throw new Error(`Deployment script path or internal name mismatch:
181
- Expected key : ${key}
182
- Found in line : ${expectedKey}
183
- Line : ${line}`);
184
- }
185
- }
186
- }
187
- else {
188
- const m2 = line.match(/^-- Deploy (.*?)(?:\s+to\s+pg)?\s*$/);
189
- if (m2) {
190
- const keyToTest = m2[1].trim();
191
- if (key !== makeKey(keyToTest)) {
192
- throw new Error('deployment script in wrong place or is named wrong internally\n' + line);
193
- }
194
- }
195
- }
196
- }
197
- return { requires };
198
- }
199
- /**
200
- * Verify that every change in a module's pgpm.plan has a deploy script whose
201
- * header identity matches its path, and report dependency drift between the
202
- * plan's dep lists and the scripts' `-- requires:` lines.
203
- *
204
- * Read-only diagnostic; returns issues rather than throwing.
205
- */
206
- export function verifyPlanMatchesHeaders(moduleDir) {
207
- const issues = [];
208
- const planPath = join(moduleDir, 'pgpm.plan');
209
- const parsed = parsePlanFile(planPath);
210
- if (!parsed.data) {
211
- throw new Error(`Failed to parse plan file: ${planPath}`);
212
- }
213
- for (const planChange of parsed.data.changes) {
214
- const change = planChange.name;
215
- const file = join(moduleDir, 'deploy', `${change}.sql`);
216
- let content;
217
- try {
218
- content = readFileSync(file, 'utf-8');
219
- }
220
- catch {
221
- issues.push({
222
- change,
223
- file,
224
- kind: 'missing-file',
225
- message: `deploy script not found for plan change "${change}"`
226
- });
227
- continue;
228
- }
229
- const { header } = parsePgpmHeader(content);
230
- if (header.change && header.change !== change) {
231
- issues.push({
232
- change,
233
- file,
234
- kind: 'identity-mismatch',
235
- message: `header declares "${header.change}" but path identity is "${change}"`
236
- });
237
- }
238
- const planDeps = new Set(planChange.dependencies || []);
239
- const headerDeps = new Set(header.requires.map(r => r.raw));
240
- const missingInHeader = [...planDeps].filter(d => !headerDeps.has(d));
241
- const missingInPlan = [...headerDeps].filter(d => !planDeps.has(d));
242
- if (missingInHeader.length > 0 || missingInPlan.length > 0) {
243
- const parts = [];
244
- if (missingInHeader.length > 0) {
245
- parts.push(`in plan but not in header requires: ${missingInHeader.join(', ')}`);
246
- }
247
- if (missingInPlan.length > 0) {
248
- parts.push(`in header requires but not in plan: ${missingInPlan.join(', ')}`);
249
- }
250
- issues.push({
251
- change,
252
- file,
253
- kind: 'requires-drift',
254
- message: parts.join('; ')
255
- });
256
- }
257
- }
258
- return issues;
259
- }
@@ -1,2 +0,0 @@
1
- export * from './header';
2
- export * from './writer';
@@ -1,107 +0,0 @@
1
- import { getEnvOptions } from '@pgpmjs/env';
2
- import fs from 'fs';
3
- import path from 'path';
4
- /**
5
- * Write SQL files for PGPM migrations (deploy, revert, verify)
6
- */
7
- export const writePgpmFiles = (rows, opts) => {
8
- rows.forEach((row) => writeVerify(row, opts));
9
- rows.forEach((row) => writeRevert(row, opts));
10
- rows.forEach((row) => writeDeploy(row, opts));
11
- };
12
- /**
13
- * Sort dependencies in a consistent order
14
- */
15
- const ordered = (arr) => {
16
- if (!arr)
17
- return [];
18
- return arr.sort((a, b) => a.length - b.length || a.localeCompare(b));
19
- };
20
- /**
21
- * Write a deploy SQL file for a PGPM change
22
- */
23
- const writeDeploy = (row, opts) => {
24
- const globalOpts = getEnvOptions({
25
- migrations: {
26
- codegen: {
27
- useTx: opts.useTx
28
- }
29
- }
30
- });
31
- const useTx = globalOpts.migrations.codegen.useTx;
32
- const deploy = opts.replacer(row.deploy);
33
- const dir = path.dirname(deploy);
34
- const prefix = path.join(opts.outdir, opts.name, 'deploy');
35
- const actualDir = path.resolve(prefix, dir);
36
- const actualFile = path.resolve(prefix, `${deploy}.sql`);
37
- fs.mkdirSync(actualDir, { recursive: true });
38
- const sqlContent = opts.replacer(row.content);
39
- const content = `-- Deploy: ${deploy}
40
- -- made with <3 @ constructive.io
41
-
42
- ${opts.replacer(ordered(row?.deps)
43
- .map((dep) => `-- requires: ${dep}`)
44
- .join('\n') || '')}
45
-
46
- ${useTx ? 'BEGIN;' : ''}
47
- ${sqlContent}
48
- ${useTx ? 'COMMIT;' : ''}
49
- `;
50
- fs.writeFileSync(actualFile, content);
51
- };
52
- /**
53
- * Write a verify SQL file for a PGPM change
54
- */
55
- const writeVerify = (row, opts) => {
56
- const globalOpts = getEnvOptions({
57
- migrations: {
58
- codegen: {
59
- useTx: opts.useTx
60
- }
61
- }
62
- });
63
- const useTx = globalOpts.migrations.codegen.useTx;
64
- const deploy = opts.replacer(row.deploy);
65
- const dir = path.dirname(deploy);
66
- const prefix = path.join(opts.outdir, opts.name, 'verify');
67
- const actualDir = path.resolve(prefix, dir);
68
- const actualFile = path.resolve(prefix, `${deploy}.sql`);
69
- fs.mkdirSync(actualDir, { recursive: true });
70
- const sqlContent = opts.replacer(row.verify);
71
- const content = opts.replacer(`-- Verify: ${deploy}
72
-
73
- ${useTx ? 'BEGIN;' : ''}
74
- ${sqlContent}
75
- ${useTx ? 'COMMIT;' : ''}
76
-
77
- `);
78
- fs.writeFileSync(actualFile, content);
79
- };
80
- /**
81
- * Write a revert SQL file for a PGPM change
82
- */
83
- const writeRevert = (row, opts) => {
84
- const globalOpts = getEnvOptions({
85
- migrations: {
86
- codegen: {
87
- useTx: opts.useTx
88
- }
89
- }
90
- });
91
- const useTx = globalOpts.migrations.codegen.useTx;
92
- const deploy = opts.replacer(row.deploy);
93
- const dir = path.dirname(deploy);
94
- const prefix = path.join(opts.outdir, opts.name, 'revert');
95
- const actualDir = path.resolve(prefix, dir);
96
- const actualFile = path.resolve(prefix, `${deploy}.sql`);
97
- fs.mkdirSync(actualDir, { recursive: true });
98
- const sqlContent = opts.replacer(row.revert);
99
- const content = `-- Revert: ${deploy}
100
-
101
- ${useTx ? 'BEGIN;' : ''}
102
- ${sqlContent}
103
- ${useTx ? 'COMMIT;' : ''}
104
-
105
- `;
106
- fs.writeFileSync(actualFile, content);
107
- };
@@ -1,2 +0,0 @@
1
- // Re-export all sql-scripts functionality
2
- export * from './reader';