@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.
- package/bundle/apply.d.ts +93 -0
- package/bundle/apply.js +137 -0
- package/bundle/index.d.ts +12 -0
- package/bundle/index.js +31 -0
- package/core/class/pgpm.d.ts +2 -2
- package/core/class/pgpm.js +6 -6
- package/esm/bundle/apply.js +131 -0
- package/esm/bundle/index.js +15 -0
- package/esm/core/class/pgpm.js +6 -6
- package/esm/index.js +5 -3
- package/esm/migrate/client.js +1 -1
- package/esm/migrate/utils/hash.js +3 -6
- package/esm/modules/modules.js +1 -1
- package/esm/packaging/package.js +1 -1
- package/esm/rebundle/module.js +1 -1
- package/esm/rebundle/rebundle.js +2 -2
- package/esm/rebundle/workspace.js +1 -1
- package/esm/refactor/rename.js +5 -27
- package/esm/resolution/deps.js +2 -2
- package/esm/resolution/resolve.js +2 -2
- package/esm/slice/slice.js +1 -1
- package/index.d.ts +4 -2
- package/index.js +6 -4
- package/migrate/client.js +1 -1
- package/migrate/utils/hash.d.ts +2 -4
- package/migrate/utils/hash.js +4 -8
- package/modules/modules.d.ts +1 -1
- package/modules/modules.js +1 -1
- package/package.json +4 -2
- package/packaging/package.js +1 -1
- package/rebundle/module.js +1 -1
- package/rebundle/rebundle.js +2 -2
- package/rebundle/types.d.ts +1 -1
- package/rebundle/workspace.js +1 -1
- package/refactor/rename.d.ts +2 -8
- package/refactor/rename.js +6 -29
- package/resolution/deps.d.ts +1 -1
- package/resolution/deps.js +2 -2
- package/resolution/resolve.js +2 -2
- package/slice/slice.d.ts +1 -1
- package/slice/slice.js +1 -1
- package/slice/types.d.ts +1 -1
- package/workspace/minimal.d.ts +1 -1
- package/esm/files/extension/index.js +0 -3
- package/esm/files/extension/reader.js +0 -79
- package/esm/files/extension/writer.js +0 -99
- package/esm/files/index.js +0 -6
- package/esm/files/plan/generator.js +0 -82
- package/esm/files/plan/index.js +0 -5
- package/esm/files/plan/parser.js +0 -297
- package/esm/files/plan/validators.js +0 -181
- package/esm/files/plan/writer.js +0 -142
- package/esm/files/sql/header.js +0 -259
- package/esm/files/sql/index.js +0 -2
- package/esm/files/sql/writer.js +0 -107
- package/esm/files/sql-scripts/index.js +0 -2
- package/esm/files/sql-scripts/reader.js +0 -19
- package/esm/files/types/index.js +0 -1
- package/esm/files/types/package.js +0 -1
- package/esm/utils/author.js +0 -11
- package/files/extension/index.d.ts +0 -2
- package/files/extension/index.js +0 -19
- package/files/extension/reader.d.ts +0 -24
- package/files/extension/reader.js +0 -86
- package/files/extension/writer.d.ts +0 -48
- package/files/extension/writer.js +0 -107
- package/files/index.d.ts +0 -5
- package/files/index.js +0 -22
- package/files/plan/generator.d.ts +0 -23
- package/files/plan/generator.js +0 -90
- package/files/plan/index.d.ts +0 -4
- package/files/plan/index.js +0 -21
- package/files/plan/parser.d.ts +0 -28
- package/files/plan/parser.js +0 -304
- package/files/plan/validators.d.ts +0 -52
- package/files/plan/validators.js +0 -187
- package/files/plan/writer.d.ts +0 -27
- package/files/plan/writer.js +0 -152
- package/files/sql/header.d.ts +0 -114
- package/files/sql/header.js +0 -266
- package/files/sql/index.d.ts +0 -2
- package/files/sql/index.js +0 -18
- package/files/sql/writer.d.ts +0 -12
- package/files/sql/writer.js +0 -114
- package/files/sql-scripts/index.d.ts +0 -1
- package/files/sql-scripts/index.js +0 -18
- package/files/sql-scripts/reader.d.ts +0 -8
- package/files/sql-scripts/reader.js +0 -23
- package/files/types/index.d.ts +0 -46
- package/files/types/index.js +0 -17
- package/files/types/package.d.ts +0 -20
- package/files/types/package.js +0 -2
- package/utils/author.d.ts +0 -5
- 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
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;
|
package/files/plan/generator.js
DELETED
|
@@ -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
|
-
}
|
package/files/plan/index.d.ts
DELETED
package/files/plan/index.js
DELETED
|
@@ -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);
|
package/files/plan/parser.d.ts
DELETED
|
@@ -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;
|
package/files/plan/parser.js
DELETED
|
@@ -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;
|