@pgpmjs/core 7.1.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 (94) 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 +5 -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 +1 -1
  15. package/esm/rebundle/module.js +1 -1
  16. package/esm/rebundle/rebundle.js +2 -2
  17. package/esm/rebundle/workspace.js +1 -1
  18. package/esm/refactor/rename.js +5 -27
  19. package/esm/resolution/deps.js +2 -2
  20. package/esm/resolution/resolve.js +2 -2
  21. package/esm/slice/slice.js +1 -1
  22. package/index.d.ts +4 -2
  23. package/index.js +6 -4
  24. package/migrate/client.js +1 -1
  25. package/migrate/utils/hash.d.ts +2 -4
  26. package/migrate/utils/hash.js +4 -8
  27. package/modules/modules.d.ts +1 -1
  28. package/modules/modules.js +1 -1
  29. package/package.json +4 -2
  30. package/packaging/package.js +1 -1
  31. package/rebundle/module.js +1 -1
  32. package/rebundle/rebundle.js +2 -2
  33. package/rebundle/types.d.ts +1 -1
  34. package/rebundle/workspace.js +1 -1
  35. package/refactor/rename.d.ts +2 -8
  36. package/refactor/rename.js +6 -29
  37. package/resolution/deps.d.ts +1 -1
  38. package/resolution/deps.js +2 -2
  39. package/resolution/resolve.js +2 -2
  40. package/slice/slice.d.ts +1 -1
  41. package/slice/slice.js +1 -1
  42. package/slice/types.d.ts +1 -1
  43. package/workspace/minimal.d.ts +1 -1
  44. package/esm/files/extension/index.js +0 -3
  45. package/esm/files/extension/reader.js +0 -79
  46. package/esm/files/extension/writer.js +0 -99
  47. package/esm/files/index.js +0 -6
  48. package/esm/files/plan/generator.js +0 -82
  49. package/esm/files/plan/index.js +0 -5
  50. package/esm/files/plan/parser.js +0 -297
  51. package/esm/files/plan/validators.js +0 -181
  52. package/esm/files/plan/writer.js +0 -142
  53. package/esm/files/sql/header.js +0 -259
  54. package/esm/files/sql/index.js +0 -2
  55. package/esm/files/sql/writer.js +0 -107
  56. package/esm/files/sql-scripts/index.js +0 -2
  57. package/esm/files/sql-scripts/reader.js +0 -19
  58. package/esm/files/types/index.js +0 -1
  59. package/esm/files/types/package.js +0 -1
  60. package/esm/utils/author.js +0 -11
  61. package/files/extension/index.d.ts +0 -2
  62. package/files/extension/index.js +0 -19
  63. package/files/extension/reader.d.ts +0 -24
  64. package/files/extension/reader.js +0 -86
  65. package/files/extension/writer.d.ts +0 -48
  66. package/files/extension/writer.js +0 -107
  67. package/files/index.d.ts +0 -5
  68. package/files/index.js +0 -22
  69. package/files/plan/generator.d.ts +0 -23
  70. package/files/plan/generator.js +0 -90
  71. package/files/plan/index.d.ts +0 -4
  72. package/files/plan/index.js +0 -21
  73. package/files/plan/parser.d.ts +0 -28
  74. package/files/plan/parser.js +0 -304
  75. package/files/plan/validators.d.ts +0 -52
  76. package/files/plan/validators.js +0 -187
  77. package/files/plan/writer.d.ts +0 -27
  78. package/files/plan/writer.js +0 -152
  79. package/files/sql/header.d.ts +0 -114
  80. package/files/sql/header.js +0 -266
  81. package/files/sql/index.d.ts +0 -2
  82. package/files/sql/index.js +0 -18
  83. package/files/sql/writer.d.ts +0 -12
  84. package/files/sql/writer.js +0 -114
  85. package/files/sql-scripts/index.d.ts +0 -1
  86. package/files/sql-scripts/index.js +0 -18
  87. package/files/sql-scripts/reader.d.ts +0 -8
  88. package/files/sql-scripts/reader.js +0 -23
  89. package/files/types/index.d.ts +0 -46
  90. package/files/types/index.js +0 -17
  91. package/files/types/package.d.ts +0 -20
  92. package/files/types/package.js +0 -2
  93. package/utils/author.d.ts +0 -5
  94. package/utils/author.js +0 -14
@@ -1,107 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.writeExtensions = exports.writeExtensionControlFile = exports.writeExtensionMakefile = void 0;
4
- exports.generateControlFileContent = generateControlFileContent;
5
- exports.upsertRequiresLine = upsertRequiresLine;
6
- const fs_1 = require("fs");
7
- const reader_1 = require("./reader");
8
- /**
9
- * Write the Makefile for the extension.
10
- */
11
- const writeExtensionMakefile = (outputPath, extname, version) => {
12
- const content = `EXTENSION = ${extname}
13
- DATA = sql/${extname}--${version}.sql
14
-
15
- PG_CONFIG = pg_config
16
- PGXS := $(shell $(PG_CONFIG) --pgxs)
17
- include $(PGXS)
18
- `;
19
- (0, fs_1.writeFileSync)(outputPath, content);
20
- };
21
- exports.writeExtensionMakefile = writeExtensionMakefile;
22
- /**
23
- * Generate content for a .control file
24
- * https://www.postgresql.org/docs/current/extend-extensions.html
25
- */
26
- function generateControlFileContent(options) {
27
- const { name, version, comment = `${name} extension`, requires = [], default_version = version, relocatable = false, superuser = false, schema, module_pathname } = options;
28
- let content = `# ${name} extension
29
- comment = '${comment}'
30
- default_version = '${default_version}'
31
- `;
32
- if (module_pathname) {
33
- content += `module_pathname = '${module_pathname}'\n`;
34
- }
35
- else {
36
- content += `module_pathname = '$libdir/${name}'\n`;
37
- }
38
- if (requires.length > 0) {
39
- content += `requires = '${requires.join(',')}'\n`;
40
- }
41
- content += `relocatable = ${relocatable}
42
- superuser = ${superuser}\n`;
43
- if (schema) {
44
- content += `schema = ${schema}\n`;
45
- }
46
- return content;
47
- }
48
- /**
49
- * Update (or insert/remove) the `requires =` line of an existing .control file
50
- * in place, preserving every other field and the file's formatting. Used when
51
- * changing a module's dependencies after init (e.g. via `pgpm extension`) so
52
- * hand-tuned fields (comment, schema, relocatable, module_pathname) survive.
53
- */
54
- function upsertRequiresLine(content, requires) {
55
- const lines = content.split('\n');
56
- const idx = lines.findIndex((line) => /^\s*requires\s*=/.test(line));
57
- if (requires.length === 0) {
58
- if (idx !== -1)
59
- lines.splice(idx, 1);
60
- return lines.join('\n');
61
- }
62
- const requiresLine = `requires = '${requires.join(',')}'`;
63
- if (idx !== -1) {
64
- lines[idx] = requiresLine;
65
- return lines.join('\n');
66
- }
67
- // Insert to match generated ordering (before relocatable/superuser/schema).
68
- const insertAt = lines.findIndex((line) => /^\s*(relocatable|superuser|schema)\s*=/.test(line));
69
- if (insertAt !== -1) {
70
- lines.splice(insertAt, 0, requiresLine);
71
- }
72
- else {
73
- lines.push(requiresLine);
74
- }
75
- return lines.join('\n');
76
- }
77
- /**
78
- * Write the control file for the extension.
79
- * If the file already exists, only its `requires` line is updated so that any
80
- * custom fields are preserved; otherwise a fresh file is generated.
81
- */
82
- const writeExtensionControlFile = (outputPath, extname, extensions, version) => {
83
- if ((0, fs_1.existsSync)(outputPath)) {
84
- const existing = (0, fs_1.readFileSync)(outputPath, 'utf-8');
85
- (0, fs_1.writeFileSync)(outputPath, upsertRequiresLine(existing, extensions));
86
- return;
87
- }
88
- const content = generateControlFileContent({
89
- name: extname,
90
- version,
91
- requires: extensions
92
- });
93
- (0, fs_1.writeFileSync)(outputPath, content);
94
- };
95
- exports.writeExtensionControlFile = writeExtensionControlFile;
96
- /**
97
- * Write control and Makefile for the extension with given data.
98
- * If extInfo is not provided, it will be generated from packageDir.
99
- */
100
- const writeExtensions = (packageDir, extensions, extInfo) => {
101
- // If extInfo is not provided, get it from packageDir
102
- const info = extInfo || (0, reader_1.getExtensionInfo)(packageDir);
103
- const { controlFile, Makefile, extname, version } = info;
104
- (0, exports.writeExtensionControlFile)(controlFile, extname, extensions, version);
105
- (0, exports.writeExtensionMakefile)(Makefile, extname, version);
106
- };
107
- exports.writeExtensions = writeExtensions;
package/files/index.d.ts DELETED
@@ -1,5 +0,0 @@
1
- export * from './extension';
2
- export * from './plan';
3
- export * from './sql';
4
- export * from './sql-scripts';
5
- export * from './types';
package/files/index.js DELETED
@@ -1,22 +0,0 @@
1
- "use strict";
2
- var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
- if (k2 === undefined) k2 = k;
4
- var desc = Object.getOwnPropertyDescriptor(m, k);
5
- if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
- desc = { enumerable: true, get: function() { return m[k]; } };
7
- }
8
- Object.defineProperty(o, k2, desc);
9
- }) : (function(o, m, k, k2) {
10
- if (k2 === undefined) k2 = k;
11
- o[k2] = m[k];
12
- }));
13
- var __exportStar = (this && this.__exportStar) || function(m, exports) {
14
- for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
15
- };
16
- Object.defineProperty(exports, "__esModule", { value: true });
17
- // Re-export all functionality from package-files
18
- __exportStar(require("./extension"), exports);
19
- __exportStar(require("./plan"), exports);
20
- __exportStar(require("./sql"), exports);
21
- __exportStar(require("./sql-scripts"), exports);
22
- __exportStar(require("./types"), exports);
@@ -1,23 +0,0 @@
1
- /**
2
- * Get a timestamp for the plan file.
3
- * Uses the PGPM_FROZEN_TIME env var if present, otherwise the fixed epoch.
4
- */
5
- export declare function getNow(): string;
6
- export interface PlanEntry {
7
- change: string;
8
- dependencies: string[];
9
- comment?: string;
10
- }
11
- export interface GeneratePlanOptions {
12
- moduleName: string;
13
- uri?: string;
14
- entries: PlanEntry[];
15
- }
16
- /**
17
- * Generate a plan file content from structured data
18
- */
19
- export declare function generatePlan(options: GeneratePlanOptions): string;
20
- /**
21
- * Write a generated plan file to disk
22
- */
23
- export declare function writePlan(planPath: string, plan: string): void;
@@ -1,90 +0,0 @@
1
- "use strict";
2
- var __importDefault = (this && this.__importDefault) || function (mod) {
3
- return (mod && mod.__esModule) ? mod : { "default": mod };
4
- };
5
- Object.defineProperty(exports, "__esModule", { value: true });
6
- exports.getNow = getNow;
7
- exports.generatePlan = generatePlan;
8
- exports.writePlan = writePlan;
9
- const fs_1 = __importDefault(require("fs"));
10
- /**
11
- * Fixed epoch timestamp used for deterministic plan generation.
12
- * Can be overridden with the PGPM_FROZEN_TIME environment variable.
13
- */
14
- const DEFAULT_FROZEN_TIME = '2017-08-11T08:11:51Z';
15
- /**
16
- * Get a UTC timestamp string from a Date object.
17
- */
18
- function getUTCTimestamp(d) {
19
- return (d.getUTCFullYear() +
20
- '-' + String(d.getUTCMonth() + 1).padStart(2, '0') +
21
- '-' + String(d.getUTCDate()).padStart(2, '0') +
22
- 'T' + String(d.getUTCHours()).padStart(2, '0') +
23
- ':' + String(d.getUTCMinutes()).padStart(2, '0') +
24
- ':' + String(d.getUTCSeconds()).padStart(2, '0') +
25
- 'Z');
26
- }
27
- /**
28
- * Get a timestamp for the plan file.
29
- * Uses the PGPM_FROZEN_TIME env var if present, otherwise the fixed epoch.
30
- */
31
- function getNow() {
32
- const frozen = process.env.PGPM_FROZEN_TIME;
33
- if (frozen) {
34
- return getUTCTimestamp(new Date(frozen));
35
- }
36
- return DEFAULT_FROZEN_TIME;
37
- }
38
- /**
39
- * Sort dependencies by their order in the supplied plan, so prerequisites
40
- * (parent migrations) appear before their dependents in the bracket.
41
- * Internal dependencies are sorted by their plan index, and external
42
- * dependencies (not in the plan) preserve their original order.
43
- */
44
- const sortDependencies = (deps, order) => {
45
- const originalOrder = new Map(deps.map((dep, index) => [dep, index]));
46
- return [...deps].sort((a, b) => {
47
- const aIndex = order.get(a);
48
- const bIndex = order.get(b);
49
- const aOriginal = originalOrder.get(a);
50
- const bOriginal = originalOrder.get(b);
51
- if (aIndex !== undefined && bIndex !== undefined)
52
- return aIndex - bIndex || aOriginal - bOriginal;
53
- if (aIndex !== undefined)
54
- return -1;
55
- if (bIndex !== undefined)
56
- return 1;
57
- return aOriginal - bOriginal;
58
- });
59
- };
60
- /**
61
- * Generate a plan file content from structured data
62
- */
63
- function generatePlan(options) {
64
- const { moduleName, uri, entries } = options;
65
- const now = getNow();
66
- const planfile = [
67
- `%syntax-version=1.0.0`,
68
- `%project=${moduleName}`,
69
- `%uri=${uri || moduleName}`
70
- ];
71
- // Build an order map from the entry list so brackets can be sorted by plan order
72
- const entryOrder = new Map(entries.map((entry, index) => [entry.change, index]));
73
- // Generate the plan entries
74
- entries.forEach(entry => {
75
- const deps = sortDependencies(entry.dependencies || [], entryOrder);
76
- if (deps.length > 0) {
77
- planfile.push(`${entry.change} [${deps.join(' ')}] ${now} constructive <constructive@5b0c196eeb62>${entry.comment ? ` # ${entry.comment}` : ''}`);
78
- }
79
- else {
80
- planfile.push(`${entry.change} ${now} constructive <constructive@5b0c196eeb62>${entry.comment ? ` # ${entry.comment}` : ''}`);
81
- }
82
- });
83
- return planfile.join('\n');
84
- }
85
- /**
86
- * Write a generated plan file to disk
87
- */
88
- function writePlan(planPath, plan) {
89
- fs_1.default.writeFileSync(planPath, plan);
90
- }
@@ -1,4 +0,0 @@
1
- export * from './generator';
2
- export * from './parser';
3
- export * from './validators';
4
- export * from './writer';
@@ -1,21 +0,0 @@
1
- "use strict";
2
- var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
- if (k2 === undefined) k2 = k;
4
- var desc = Object.getOwnPropertyDescriptor(m, k);
5
- if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
- desc = { enumerable: true, get: function() { return m[k]; } };
7
- }
8
- Object.defineProperty(o, k2, desc);
9
- }) : (function(o, m, k, k2) {
10
- if (k2 === undefined) k2 = k;
11
- o[k2] = m[k];
12
- }));
13
- var __exportStar = (this && this.__exportStar) || function(m, exports) {
14
- for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
15
- };
16
- Object.defineProperty(exports, "__esModule", { value: true });
17
- // Re-export all plan functionality
18
- __exportStar(require("./generator"), exports);
19
- __exportStar(require("./parser"), exports);
20
- __exportStar(require("./validators"), exports);
21
- __exportStar(require("./writer"), exports);
@@ -1,28 +0,0 @@
1
- import { ExtendedPlanFile, ParseResult, PlanFile } from '../types';
2
- /**
3
- * Parse a Sqitch plan file with full validation
4
- * Supports both changes and tags
5
- */
6
- export declare function parsePlanFile(planPath: string): ParseResult<ExtendedPlanFile>;
7
- /**
8
- * Resolve a reference within a plan file context
9
- * Handles symbolic references (HEAD, ROOT) and relative references
10
- */
11
- export declare function resolveReference(ref: string, plan: ExtendedPlanFile, currentPackage?: string): {
12
- change?: string;
13
- tag?: string;
14
- error?: string;
15
- };
16
- /**
17
- * Simple plan file parser without validation.
18
- * Returns a PlanFile (without tags) for callers that don't need tag data.
19
- */
20
- export declare function parsePlanFileSimple(planPath: string): PlanFile;
21
- /**
22
- * Get all change names from a plan file
23
- */
24
- export declare function getChanges(planPath: string): string[];
25
- /**
26
- * Get the latest (last) change from a plan file
27
- */
28
- export declare function getLatestChange(planPath: string): string;
@@ -1,304 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.parsePlanFile = parsePlanFile;
4
- exports.resolveReference = resolveReference;
5
- exports.parsePlanFileSimple = parsePlanFileSimple;
6
- exports.getChanges = getChanges;
7
- exports.getLatestChange = getLatestChange;
8
- const fs_1 = require("fs");
9
- const validators_1 = require("./validators");
10
- const types_1 = require("@pgpmjs/types");
11
- /**
12
- * Parse a Sqitch plan file with full validation
13
- * Supports both changes and tags
14
- */
15
- function parsePlanFile(planPath) {
16
- const content = (0, fs_1.readFileSync)(planPath, 'utf-8');
17
- const lines = content.split('\n');
18
- let project = '';
19
- let uri = '';
20
- const changes = [];
21
- const tags = [];
22
- const errors = [];
23
- for (let i = 0; i < lines.length; i++) {
24
- const line = lines[i];
25
- const lineNum = i + 1;
26
- const trimmed = line.trim();
27
- // Skip empty lines
28
- if (!trimmed)
29
- continue;
30
- // Skip comments
31
- if (trimmed.startsWith('#'))
32
- continue;
33
- // Parse package metadata
34
- if (trimmed.startsWith('%project=')) {
35
- project = trimmed.substring('%project='.length);
36
- continue;
37
- }
38
- if (trimmed.startsWith('%uri=')) {
39
- uri = trimmed.substring('%uri='.length);
40
- continue;
41
- }
42
- // Skip other metadata lines
43
- if (trimmed.startsWith('%')) {
44
- continue;
45
- }
46
- // Parse tag lines
47
- if (trimmed.startsWith('@')) {
48
- const lastChangeName = changes.length > 0 ? changes[changes.length - 1].name : null;
49
- const tag = parseTagLine(trimmed, lastChangeName);
50
- if (tag) {
51
- if ((0, validators_1.isValidTagName)(tag.name)) {
52
- tags.push(tag);
53
- }
54
- else {
55
- errors.push({
56
- line: lineNum,
57
- message: `Invalid tag name: ${tag.name}`
58
- });
59
- }
60
- }
61
- else {
62
- errors.push({
63
- line: lineNum,
64
- message: 'Invalid tag line format'
65
- });
66
- }
67
- continue;
68
- }
69
- // Parse change lines
70
- const change = parseChangeLine(trimmed);
71
- if (change) {
72
- // Validate change name
73
- if (!(0, validators_1.isValidChangeName)(change.name)) {
74
- errors.push({
75
- line: lineNum,
76
- message: `Invalid change name: ${change.name}`
77
- });
78
- continue;
79
- }
80
- // Validate dependencies
81
- for (const dep of change.dependencies) {
82
- if (!(0, validators_1.isValidDependency)(dep)) {
83
- errors.push({
84
- line: lineNum,
85
- message: `Invalid dependency reference: ${dep}`
86
- });
87
- }
88
- }
89
- changes.push(change);
90
- }
91
- else if (trimmed) {
92
- // Non-empty line that couldn't be parsed
93
- errors.push({
94
- line: lineNum,
95
- message: 'Invalid line format'
96
- });
97
- }
98
- }
99
- if (errors.length > 0) {
100
- return { errors };
101
- }
102
- return {
103
- data: { package: project, uri, changes, tags },
104
- errors: []
105
- };
106
- }
107
- /**
108
- * Parse a single change line from a plan file
109
- * Format: change_name [deps] timestamp planner <email> # comment
110
- */
111
- function parseChangeLine(line) {
112
- // More flexible regex that handles various formats, including planner names with spaces
113
- // Format: change_name [deps] timestamp planner <email> # comment
114
- // The timestamp is required if planner/email/comment are present
115
- const regex = /^(\S+)(?:\s+\[([^\]]*)\])?(?:\s+(\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}Z)(?:\s+([^<]+?))?(?:\s+<([^>]+)>)?(?:\s+#\s+(.*))?)?$/;
116
- const match = line.match(regex);
117
- if (!match) {
118
- return null;
119
- }
120
- const [, name, depsStr, timestamp, planner, email, comment] = match;
121
- // Parse dependencies
122
- const dependencies = depsStr
123
- ? depsStr.split(/\s+/).filter(dep => dep.length > 0)
124
- : [];
125
- return {
126
- name,
127
- dependencies,
128
- timestamp,
129
- planner,
130
- email,
131
- comment
132
- };
133
- }
134
- /**
135
- * Parse a tag line from a plan file
136
- * Format: @tag_name change_name timestamp planner <email> # comment
137
- */
138
- function parseTagLine(line, lastChangeName) {
139
- // Tag lines start with @
140
- if (!line.startsWith('@')) {
141
- return null;
142
- }
143
- // Remove the @ and parse
144
- const tagContent = line.substring(1);
145
- // Two possible formats:
146
- // 1. tagname timestamp planner <email> # comment (tag for last change)
147
- // 2. tagname changename timestamp planner <email> # comment (tag for specific change)
148
- // First try to match with change name (format 2)
149
- const regexWithChange = /^(\S+)\s+(\S+)\s+(\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}Z)\s+(.+?)\s+<([^>]+)>(?:\s+#\s+(.*))?$/;
150
- let match = tagContent.match(regexWithChange);
151
- if (match) {
152
- const [, name, secondToken, timestamp, planner, email, comment] = match;
153
- // Check if the second token is a timestamp (format 1) or change name (format 2)
154
- if (/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}Z$/.test(secondToken)) {
155
- // Format 1: no change name specified
156
- return {
157
- name,
158
- change: lastChangeName || '', // Tag is associated with the last change
159
- timestamp: secondToken,
160
- planner: timestamp, // What we thought was timestamp is actually planner
161
- email: planner, // Shift everything
162
- comment: email || comment
163
- };
164
- }
165
- else {
166
- // Format 2: explicit change name
167
- return {
168
- name,
169
- change: secondToken,
170
- timestamp,
171
- planner,
172
- email,
173
- comment
174
- };
175
- }
176
- }
177
- // Try simple format without change name
178
- const regexSimple = /^(\S+)\s+(\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}Z)\s+(.+?)\s+<([^>]+)>(?:\s+#\s+(.*))?$/;
179
- match = tagContent.match(regexSimple);
180
- if (!match) {
181
- return null;
182
- }
183
- const [, name, timestamp, planner, email, comment] = match;
184
- return {
185
- name,
186
- change: lastChangeName || '', // Tag is associated with the last change
187
- timestamp,
188
- planner,
189
- email,
190
- comment
191
- };
192
- }
193
- /**
194
- * Resolve a reference within a plan file context
195
- * Handles symbolic references (HEAD, ROOT) and relative references
196
- */
197
- function resolveReference(ref, plan, currentPackage) {
198
- const parsed = (0, validators_1.parseReference)(ref);
199
- if (!parsed) {
200
- return { error: `Invalid reference: ${ref}` };
201
- }
202
- // Handle package qualifier
203
- const planPackage = currentPackage || plan.package;
204
- if (parsed.package && parsed.package !== planPackage) {
205
- // Cross-package reference - return as-is
206
- return { change: ref };
207
- }
208
- // Handle SHA1 references
209
- if (parsed.sha1) {
210
- // Would need to look up in database
211
- return { change: ref };
212
- }
213
- // Handle symbolic references
214
- if (parsed.symbolic === 'HEAD' && plan.changes.length > 0) {
215
- return { change: plan.changes[plan.changes.length - 1].name };
216
- }
217
- if (parsed.symbolic === 'ROOT' && plan.changes.length > 0) {
218
- return { change: plan.changes[0].name };
219
- }
220
- // Handle relative references
221
- if (parsed.relative) {
222
- const baseResolved = resolveReference(parsed.relative.base, plan, currentPackage);
223
- if (baseResolved.error) {
224
- return baseResolved;
225
- }
226
- // Get the change name from the resolved reference
227
- const baseChange = baseResolved.change;
228
- if (!baseChange) {
229
- return { error: `Cannot resolve base reference: ${parsed.relative.base}` };
230
- }
231
- const changeIndex = plan.changes.findIndex(c => c.name === baseChange);
232
- if (changeIndex === -1) {
233
- return { error: `Change not found: ${baseChange}` };
234
- }
235
- let targetIndex;
236
- if (parsed.relative.direction === '^') {
237
- // Go backwards
238
- targetIndex = changeIndex - parsed.relative.count;
239
- }
240
- else {
241
- // Go forwards
242
- targetIndex = changeIndex + parsed.relative.count;
243
- }
244
- if (targetIndex < 0 || targetIndex >= plan.changes.length) {
245
- return { error: `Relative reference out of bounds: ${ref}` };
246
- }
247
- return { change: plan.changes[targetIndex].name };
248
- }
249
- // Handle tag references
250
- if (parsed.tag) {
251
- const tag = plan.tags.find(t => t.name === parsed.tag);
252
- if (!tag) {
253
- return { error: `Tag not found: ${parsed.tag}` };
254
- }
255
- return { tag: parsed.tag, change: tag.change };
256
- }
257
- // Handle change@tag format
258
- if (parsed.change && parsed.tag) {
259
- return { change: parsed.change, tag: parsed.tag };
260
- }
261
- // Plain change reference
262
- if (parsed.change) {
263
- // Validate that the change exists in the plan
264
- const changeExists = plan.changes.some(c => c.name === parsed.change);
265
- if (!changeExists) {
266
- return { error: `Change not found: ${parsed.change}` };
267
- }
268
- return { change: parsed.change };
269
- }
270
- return { error: `Cannot resolve reference: ${ref}` };
271
- }
272
- /**
273
- * Simple plan file parser without validation.
274
- * Returns a PlanFile (without tags) for callers that don't need tag data.
275
- */
276
- function parsePlanFileSimple(planPath) {
277
- const result = parsePlanFile(planPath);
278
- if (result.data) {
279
- // Return without tags for simple format
280
- const { tags, ...planWithoutTags } = result.data;
281
- return planWithoutTags;
282
- }
283
- // If there are errors, throw with details
284
- if (result.errors && result.errors.length > 0) {
285
- const errorMessages = result.errors.map(e => `Line ${e.line}: ${e.message}`).join('\n');
286
- throw types_1.errors.PLAN_PARSE_ERROR({ planPath, errors: errorMessages });
287
- }
288
- // Return empty plan if no data and no errors
289
- return { package: '', uri: '', changes: [] };
290
- }
291
- /**
292
- * Get all change names from a plan file
293
- */
294
- function getChanges(planPath) {
295
- const plan = parsePlanFileSimple(planPath);
296
- return plan.changes.map(change => change.name);
297
- }
298
- /**
299
- * Get the latest (last) change from a plan file
300
- */
301
- function getLatestChange(planPath) {
302
- const changes = getChanges(planPath);
303
- return changes[changes.length - 1] || '';
304
- }
@@ -1,52 +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
- * Validate a change name according to Sqitch rules:
7
- * - ≥1 character
8
- * - Cannot contain whitespace
9
- * - First and last characters must not be punctuation (except _)
10
- * - Must not end in ~, ^, /, = or % followed by digits
11
- * - Disallowed characters anywhere: :, @, #, \
12
- */
13
- export declare function isValidChangeName(name: string): boolean;
14
- /**
15
- * Validate a tag name according to Sqitch rules:
16
- * - Same as change name rules
17
- * - Additionally, must not contain /
18
- */
19
- export declare function isValidTagName(name: string): boolean;
20
- /**
21
- * Parse a reference that might include project qualifier and/or tag
22
- * Examples:
23
- * - "users_table"
24
- * - "@v1.0"
25
- * - "users_table@v1.0"
26
- * - "otherproj:users_table"
27
- * - "otherproj:@v1.2"
28
- * - "otherproj:users_table@v1.2"
29
- * - "40763784148fa190d75bad036730ef44d1c2eac6"
30
- * - "project:40763784148fa190d75bad036730ef44d1c2eac6"
31
- */
32
- export interface ParsedReference {
33
- package?: string;
34
- change?: string;
35
- tag?: string;
36
- sha1?: string;
37
- symbolic?: 'HEAD' | 'ROOT';
38
- relative?: {
39
- base: string;
40
- direction: '^' | '~';
41
- count: number;
42
- };
43
- }
44
- /**
45
- * Parse a reference string into its components
46
- */
47
- export declare function parseReference(ref: string): ParsedReference | null;
48
- /**
49
- * Validate a dependency reference
50
- * Dependencies can be any valid reference format
51
- */
52
- export declare function isValidDependency(dep: string): boolean;