versionary 0.3.0 → 0.4.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/README.md +71 -28
- package/dist/app/release/artifact-rules.d.ts +3 -0
- package/dist/app/release/artifact-rules.js +186 -0
- package/dist/app/release/pr.d.ts +7 -2
- package/dist/app/release/pr.js +15 -19
- package/dist/app/release/recovery.d.ts +3 -0
- package/dist/app/release/recovery.js +1 -1
- package/dist/app/release/release.d.ts +18 -0
- package/dist/app/release/release.js +28 -7
- package/dist/app/release/state.d.ts +0 -1
- package/dist/app/release/state.js +36 -2
- package/dist/cli/index.js +60 -6
- package/dist/config/schema.js +33 -1
- package/dist/domain/release/changelog.js +1 -1
- package/dist/domain/strategy/node.js +41 -11
- package/dist/domain/strategy/r.d.ts +2 -0
- package/dist/domain/strategy/r.js +46 -0
- package/dist/domain/strategy/resolve.js +8 -0
- package/dist/domain/strategy/rust.d.ts +2 -0
- package/dist/domain/strategy/rust.js +369 -0
- package/package.json +3 -2
package/dist/cli/index.js
CHANGED
|
@@ -20,27 +20,81 @@ function printVerifyResult() {
|
|
|
20
20
|
}
|
|
21
21
|
return result.ok ? 0 : 1;
|
|
22
22
|
}
|
|
23
|
+
function parseFlags(args) {
|
|
24
|
+
return {
|
|
25
|
+
json: args.includes("--json"),
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
function emitJson(payload) {
|
|
29
|
+
process.stdout.write(`${JSON.stringify(payload)}\n`);
|
|
30
|
+
}
|
|
23
31
|
async function main() {
|
|
24
32
|
const [, , command, ...args] = process.argv;
|
|
33
|
+
const flags = parseFlags(args);
|
|
34
|
+
const logger = flags.json ? undefined : console;
|
|
25
35
|
if (!command || command === "run") {
|
|
26
36
|
const subject = (0, node_child_process_1.execFileSync)("git", ["log", "-1", "--pretty=%s"], {
|
|
27
37
|
encoding: "utf8",
|
|
28
38
|
stdio: ["ignore", "pipe", "ignore"],
|
|
29
39
|
}).trim();
|
|
30
40
|
if ((0, pr_js_1.isReleaseCommitMessage)(subject)) {
|
|
41
|
+
if (flags.json) {
|
|
42
|
+
const release = await (0, release_js_1.runSimpleReleaseDetailed)(process.cwd(), {
|
|
43
|
+
logger,
|
|
44
|
+
});
|
|
45
|
+
if (release.action === "release-skipped") {
|
|
46
|
+
emitJson({
|
|
47
|
+
action: "release-skipped",
|
|
48
|
+
message: release.reason,
|
|
49
|
+
releaseCreated: false,
|
|
50
|
+
tagNames: [],
|
|
51
|
+
});
|
|
52
|
+
return 0;
|
|
53
|
+
}
|
|
54
|
+
emitJson({
|
|
55
|
+
action: "release-published",
|
|
56
|
+
message: release.message,
|
|
57
|
+
releaseCreated: release.releases.length > 0,
|
|
58
|
+
tagNames: release.releases.map((target) => target.tag),
|
|
59
|
+
});
|
|
60
|
+
return 0;
|
|
61
|
+
}
|
|
31
62
|
const message = await (0, release_js_1.runSimpleRelease)(process.cwd());
|
|
32
63
|
console.log(message);
|
|
33
64
|
return 0;
|
|
34
65
|
}
|
|
35
66
|
const plan = (0, plan_js_1.createSimplePlan)();
|
|
36
67
|
if (!plan.nextVersion) {
|
|
37
|
-
|
|
68
|
+
const message = "No releasable commits found. Nothing to do.";
|
|
69
|
+
if (flags.json) {
|
|
70
|
+
emitJson({
|
|
71
|
+
action: "noop",
|
|
72
|
+
message,
|
|
73
|
+
releaseCreated: false,
|
|
74
|
+
tagNames: [],
|
|
75
|
+
});
|
|
76
|
+
return 0;
|
|
77
|
+
}
|
|
78
|
+
console.log(message);
|
|
38
79
|
return 0;
|
|
39
80
|
}
|
|
40
|
-
const pr = (0, pr_js_1.prepareSimpleReleasePr)();
|
|
81
|
+
const pr = (0, pr_js_1.prepareSimpleReleasePr)(process.cwd(), { logger });
|
|
41
82
|
(0, pr_js_1.pushReleaseBranch)(process.cwd(), pr.branch);
|
|
42
|
-
const reviewResult = await (0, pr_js_1.openOrUpdateSimpleReviewRequest)(process.cwd(), pr.branch, pr.title, pr.version, pr.previousVersion, pr.commits, pr.plan);
|
|
43
|
-
|
|
83
|
+
const reviewResult = await (0, pr_js_1.openOrUpdateSimpleReviewRequest)(process.cwd(), pr.branch, pr.title, pr.version, pr.previousVersion, pr.commits, pr.plan, { logger });
|
|
84
|
+
const message = `Prepared release PR branch ${pr.branch}`;
|
|
85
|
+
if (flags.json) {
|
|
86
|
+
emitJson({
|
|
87
|
+
action: "pr-prepared",
|
|
88
|
+
message,
|
|
89
|
+
releaseCreated: false,
|
|
90
|
+
tagNames: [],
|
|
91
|
+
reviewUrl: reviewResult,
|
|
92
|
+
branch: pr.branch,
|
|
93
|
+
title: pr.title,
|
|
94
|
+
});
|
|
95
|
+
return 0;
|
|
96
|
+
}
|
|
97
|
+
console.log(message);
|
|
44
98
|
console.log(`Title: ${pr.title}`);
|
|
45
99
|
console.log(reviewResult);
|
|
46
100
|
return 0;
|
|
@@ -76,7 +130,7 @@ async function main() {
|
|
|
76
130
|
return 0;
|
|
77
131
|
}
|
|
78
132
|
if (command === "pr") {
|
|
79
|
-
const pr = (0, pr_js_1.prepareSimpleReleasePr)();
|
|
133
|
+
const pr = (0, pr_js_1.prepareSimpleReleasePr)(process.cwd(), { logger: console });
|
|
80
134
|
(0, pr_js_1.pushReleaseBranch)(process.cwd(), pr.branch);
|
|
81
135
|
const reviewResult = await (0, pr_js_1.openOrUpdateSimpleReviewRequest)(process.cwd(), pr.branch, pr.title, pr.version, pr.previousVersion, pr.commits, pr.plan);
|
|
82
136
|
console.log(`Prepared release PR branch ${pr.branch}`);
|
|
@@ -91,7 +145,7 @@ async function main() {
|
|
|
91
145
|
}
|
|
92
146
|
console.log("Usage: versionary <command>");
|
|
93
147
|
console.log("Commands:");
|
|
94
|
-
console.log(" run
|
|
148
|
+
console.log(" run [--json] Auto-dispatch release PR/update or release publish by context");
|
|
95
149
|
console.log(" verify Validate config and basic repository shape");
|
|
96
150
|
console.log(" plan Print release plan (simple mode)");
|
|
97
151
|
console.log(" changelog [--write] Print or write changelog section");
|
package/dist/config/schema.js
CHANGED
|
@@ -2,11 +2,43 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.configSchema = void 0;
|
|
4
4
|
const zod_1 = require("zod");
|
|
5
|
-
const artifactRuleSchema = zod_1.z
|
|
5
|
+
const artifactRuleSchema = zod_1.z
|
|
6
|
+
.object({
|
|
6
7
|
type: zod_1.z.enum(["json", "toml", "yaml", "regex"]),
|
|
7
8
|
path: zod_1.z.string().min(1),
|
|
8
9
|
jsonpath: zod_1.z.string().optional(),
|
|
9
10
|
pattern: zod_1.z.string().optional(),
|
|
11
|
+
})
|
|
12
|
+
.superRefine((value, ctx) => {
|
|
13
|
+
const needsJsonPath = value.type === "json" || value.type === "toml" || value.type === "yaml";
|
|
14
|
+
if (needsJsonPath && !value.jsonpath) {
|
|
15
|
+
ctx.addIssue({
|
|
16
|
+
code: zod_1.z.ZodIssueCode.custom,
|
|
17
|
+
message: `${value.type} artifact rules require "jsonpath".`,
|
|
18
|
+
path: ["jsonpath"],
|
|
19
|
+
});
|
|
20
|
+
}
|
|
21
|
+
if (needsJsonPath && value.pattern) {
|
|
22
|
+
ctx.addIssue({
|
|
23
|
+
code: zod_1.z.ZodIssueCode.custom,
|
|
24
|
+
message: `${value.type} artifact rules do not support "pattern".`,
|
|
25
|
+
path: ["pattern"],
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
if (value.type === "regex" && !value.pattern) {
|
|
29
|
+
ctx.addIssue({
|
|
30
|
+
code: zod_1.z.ZodIssueCode.custom,
|
|
31
|
+
message: 'regex artifact rules require "pattern".',
|
|
32
|
+
path: ["pattern"],
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
if (value.type === "regex" && value.jsonpath) {
|
|
36
|
+
ctx.addIssue({
|
|
37
|
+
code: zod_1.z.ZodIssueCode.custom,
|
|
38
|
+
message: 'regex artifact rules do not support "jsonpath".',
|
|
39
|
+
path: ["jsonpath"],
|
|
40
|
+
});
|
|
41
|
+
}
|
|
10
42
|
});
|
|
11
43
|
const packageSchema = zod_1.z.object({
|
|
12
44
|
"release-type": zod_1.z.string().optional(),
|
|
@@ -95,7 +95,7 @@ function renderSimpleReleaseNotes(input, options = {}) {
|
|
|
95
95
|
}
|
|
96
96
|
const lines = [header, "", ...sections];
|
|
97
97
|
if (options.includeFooter) {
|
|
98
|
-
lines.push("
|
|
98
|
+
lines.push("\n---\n\nThis PR was generated by [Versionary](https://github.com/jolars/versionary).");
|
|
99
99
|
}
|
|
100
100
|
return lines.join("\n");
|
|
101
101
|
}
|
|
@@ -6,10 +6,33 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
6
6
|
exports.nodeVersionStrategy = void 0;
|
|
7
7
|
const node_fs_1 = __importDefault(require("node:fs"));
|
|
8
8
|
const node_path_1 = __importDefault(require("node:path"));
|
|
9
|
+
function readJsonFile(targetPath) {
|
|
10
|
+
return JSON.parse(node_fs_1.default.readFileSync(targetPath, "utf8"));
|
|
11
|
+
}
|
|
12
|
+
function writeJsonFile(targetPath, value) {
|
|
13
|
+
node_fs_1.default.writeFileSync(targetPath, `${JSON.stringify(value, null, 2)}\n`, "utf8");
|
|
14
|
+
}
|
|
15
|
+
function updateNodeLockfileVersion(cwd, version) {
|
|
16
|
+
const updated = [];
|
|
17
|
+
for (const lockfile of ["package-lock.json", "npm-shrinkwrap.json"]) {
|
|
18
|
+
const lockfilePath = node_path_1.default.join(cwd, lockfile);
|
|
19
|
+
if (!node_fs_1.default.existsSync(lockfilePath)) {
|
|
20
|
+
continue;
|
|
21
|
+
}
|
|
22
|
+
const parsed = readJsonFile(lockfilePath);
|
|
23
|
+
parsed.version = version;
|
|
24
|
+
if (parsed.packages?.[""]) {
|
|
25
|
+
parsed.packages[""].version = version;
|
|
26
|
+
}
|
|
27
|
+
writeJsonFile(lockfilePath, parsed);
|
|
28
|
+
updated.push(lockfile);
|
|
29
|
+
}
|
|
30
|
+
return updated;
|
|
31
|
+
}
|
|
9
32
|
exports.nodeVersionStrategy = {
|
|
10
33
|
name: "node",
|
|
11
34
|
getVersionFile(config) {
|
|
12
|
-
return config["version-file"] ?? "
|
|
35
|
+
return config["version-file"] ?? "package.json";
|
|
13
36
|
},
|
|
14
37
|
readVersion(cwd, config) {
|
|
15
38
|
const versionFile = this.getVersionFile(config);
|
|
@@ -17,21 +40,28 @@ exports.nodeVersionStrategy = {
|
|
|
17
40
|
if (!node_fs_1.default.existsSync(versionPath)) {
|
|
18
41
|
throw new Error(`Versionary requires ${versionFile} to exist.`);
|
|
19
42
|
}
|
|
20
|
-
|
|
43
|
+
const packageJson = readJsonFile(versionPath);
|
|
44
|
+
if (!packageJson.version || typeof packageJson.version !== "string") {
|
|
45
|
+
throw new Error(`${versionFile} is missing a valid "version" field required by release-type "node".`);
|
|
46
|
+
}
|
|
47
|
+
return packageJson.version.trim();
|
|
21
48
|
},
|
|
22
49
|
writeVersion(cwd, config, version) {
|
|
23
50
|
const versionFile = this.getVersionFile(config);
|
|
24
51
|
const versionPath = node_path_1.default.join(cwd, versionFile);
|
|
25
|
-
node_fs_1.default.
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
packageJson.version = version;
|
|
32
|
-
node_fs_1.default.writeFileSync(packageJsonPath, `${JSON.stringify(packageJson, null, 2)}\n`, "utf8");
|
|
33
|
-
updatedFiles.push("package.json");
|
|
52
|
+
if (!node_fs_1.default.existsSync(versionPath)) {
|
|
53
|
+
throw new Error(`Versionary requires ${versionFile} to exist.`);
|
|
54
|
+
}
|
|
55
|
+
const packageJson = readJsonFile(versionPath);
|
|
56
|
+
if (!packageJson.version || typeof packageJson.version !== "string") {
|
|
57
|
+
throw new Error(`${versionFile} is missing a valid "version" field required by release-type "node".`);
|
|
34
58
|
}
|
|
59
|
+
packageJson.version = version;
|
|
60
|
+
writeJsonFile(versionPath, packageJson);
|
|
61
|
+
const updatedFiles = [
|
|
62
|
+
versionFile,
|
|
63
|
+
...updateNodeLockfileVersion(cwd, version),
|
|
64
|
+
];
|
|
35
65
|
return updatedFiles;
|
|
36
66
|
},
|
|
37
67
|
};
|
|
@@ -0,0 +1,46 @@
|
|
|
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.rVersionStrategy = void 0;
|
|
7
|
+
const node_fs_1 = __importDefault(require("node:fs"));
|
|
8
|
+
const node_path_1 = __importDefault(require("node:path"));
|
|
9
|
+
function readDescriptionVersion(content, versionFile) {
|
|
10
|
+
const match = content.match(/^Version:\s*(.+)\s*$/mu);
|
|
11
|
+
if (!match || !match[1]) {
|
|
12
|
+
throw new Error(`${versionFile} is missing a valid "Version:" field required by release-type "r".`);
|
|
13
|
+
}
|
|
14
|
+
return match[1].trim();
|
|
15
|
+
}
|
|
16
|
+
function writeDescriptionVersion(content, versionFile, version) {
|
|
17
|
+
if (!/^Version:\s*/mu.test(content)) {
|
|
18
|
+
throw new Error(`${versionFile} is missing a valid "Version:" field required by release-type "r".`);
|
|
19
|
+
}
|
|
20
|
+
return content.replace(/^Version:\s*.*$/mu, `Version: ${version}`);
|
|
21
|
+
}
|
|
22
|
+
exports.rVersionStrategy = {
|
|
23
|
+
name: "r",
|
|
24
|
+
getVersionFile(config) {
|
|
25
|
+
return config["version-file"] ?? "DESCRIPTION";
|
|
26
|
+
},
|
|
27
|
+
readVersion(cwd, config) {
|
|
28
|
+
const versionFile = this.getVersionFile(config);
|
|
29
|
+
const versionPath = node_path_1.default.join(cwd, versionFile);
|
|
30
|
+
if (!node_fs_1.default.existsSync(versionPath)) {
|
|
31
|
+
throw new Error(`Versionary requires ${versionFile} to exist.`);
|
|
32
|
+
}
|
|
33
|
+
return readDescriptionVersion(node_fs_1.default.readFileSync(versionPath, "utf8"), versionFile);
|
|
34
|
+
},
|
|
35
|
+
writeVersion(cwd, config, version) {
|
|
36
|
+
const versionFile = this.getVersionFile(config);
|
|
37
|
+
const versionPath = node_path_1.default.join(cwd, versionFile);
|
|
38
|
+
if (!node_fs_1.default.existsSync(versionPath)) {
|
|
39
|
+
throw new Error(`Versionary requires ${versionFile} to exist.`);
|
|
40
|
+
}
|
|
41
|
+
const existing = node_fs_1.default.readFileSync(versionPath, "utf8");
|
|
42
|
+
const updated = writeDescriptionVersion(existing, versionFile, version);
|
|
43
|
+
node_fs_1.default.writeFileSync(versionPath, updated, "utf8");
|
|
44
|
+
return [versionFile];
|
|
45
|
+
},
|
|
46
|
+
};
|
|
@@ -2,10 +2,18 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.resolveVersionStrategy = resolveVersionStrategy;
|
|
4
4
|
const node_js_1 = require("./node.js");
|
|
5
|
+
const r_js_1 = require("./r.js");
|
|
6
|
+
const rust_js_1 = require("./rust.js");
|
|
5
7
|
const simple_js_1 = require("./simple.js");
|
|
6
8
|
function resolveVersionStrategy(config) {
|
|
7
9
|
if (config["release-type"] === "node") {
|
|
8
10
|
return node_js_1.nodeVersionStrategy;
|
|
9
11
|
}
|
|
12
|
+
if (config["release-type"] === "rust") {
|
|
13
|
+
return rust_js_1.rustVersionStrategy;
|
|
14
|
+
}
|
|
15
|
+
if (config["release-type"] === "r") {
|
|
16
|
+
return r_js_1.rVersionStrategy;
|
|
17
|
+
}
|
|
10
18
|
return simple_js_1.simpleVersionStrategy;
|
|
11
19
|
}
|
|
@@ -0,0 +1,369 @@
|
|
|
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.rustVersionStrategy = void 0;
|
|
7
|
+
const node_fs_1 = __importDefault(require("node:fs"));
|
|
8
|
+
const node_path_1 = __importDefault(require("node:path"));
|
|
9
|
+
const toml_1 = __importDefault(require("@iarna/toml"));
|
|
10
|
+
const ROOT_DEPENDENCY_SECTIONS = new Set([
|
|
11
|
+
"dependencies",
|
|
12
|
+
"dev-dependencies",
|
|
13
|
+
"build-dependencies",
|
|
14
|
+
]);
|
|
15
|
+
function normalizeSlashPath(input) {
|
|
16
|
+
return input.replaceAll("\\", "/");
|
|
17
|
+
}
|
|
18
|
+
function hasGlobPattern(pattern) {
|
|
19
|
+
return /[*?]/u.test(pattern);
|
|
20
|
+
}
|
|
21
|
+
function escapeRegex(input) {
|
|
22
|
+
return input.replace(/[.+^${}()|\\]/gu, "\\$&");
|
|
23
|
+
}
|
|
24
|
+
function globToRegex(pattern) {
|
|
25
|
+
const normalized = normalizeSlashPath(pattern);
|
|
26
|
+
let source = "^";
|
|
27
|
+
for (let index = 0; index < normalized.length; index += 1) {
|
|
28
|
+
const current = normalized[index] ?? "";
|
|
29
|
+
const next = normalized[index + 1] ?? "";
|
|
30
|
+
if (current === "*" && next === "*") {
|
|
31
|
+
source += ".*";
|
|
32
|
+
index += 1;
|
|
33
|
+
continue;
|
|
34
|
+
}
|
|
35
|
+
if (current === "*") {
|
|
36
|
+
source += "[^/]*";
|
|
37
|
+
continue;
|
|
38
|
+
}
|
|
39
|
+
if (current === "?") {
|
|
40
|
+
source += "[^/]";
|
|
41
|
+
continue;
|
|
42
|
+
}
|
|
43
|
+
source += escapeRegex(current);
|
|
44
|
+
}
|
|
45
|
+
source += "$";
|
|
46
|
+
return new RegExp(source, "u");
|
|
47
|
+
}
|
|
48
|
+
function findCargoManifestDirectories(rootDir) {
|
|
49
|
+
const results = new Set();
|
|
50
|
+
const queue = [rootDir];
|
|
51
|
+
while (queue.length > 0) {
|
|
52
|
+
const currentDir = queue.shift();
|
|
53
|
+
if (!currentDir) {
|
|
54
|
+
continue;
|
|
55
|
+
}
|
|
56
|
+
const entries = node_fs_1.default.readdirSync(currentDir, {
|
|
57
|
+
withFileTypes: true,
|
|
58
|
+
});
|
|
59
|
+
let hasCargoManifest = false;
|
|
60
|
+
for (const entry of entries) {
|
|
61
|
+
const fullPath = node_path_1.default.join(currentDir, entry.name);
|
|
62
|
+
if (entry.isFile() && entry.name === "Cargo.toml") {
|
|
63
|
+
hasCargoManifest = true;
|
|
64
|
+
continue;
|
|
65
|
+
}
|
|
66
|
+
if (entry.isDirectory()) {
|
|
67
|
+
queue.push(fullPath);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
if (hasCargoManifest) {
|
|
71
|
+
const rel = normalizeSlashPath(node_path_1.default.relative(rootDir, currentDir));
|
|
72
|
+
results.add(rel === "" ? "." : rel);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
return [...results].sort((a, b) => a.localeCompare(b));
|
|
76
|
+
}
|
|
77
|
+
function parseCargoManifest(versionFile, cargoTomlRaw) {
|
|
78
|
+
let parsed;
|
|
79
|
+
try {
|
|
80
|
+
parsed = toml_1.default.parse(cargoTomlRaw);
|
|
81
|
+
}
|
|
82
|
+
catch (error) {
|
|
83
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
84
|
+
throw new Error(`Failed to parse ${versionFile}: ${message}`);
|
|
85
|
+
}
|
|
86
|
+
const parsedRecord = parsed;
|
|
87
|
+
const packageTable = parsedRecord.package && typeof parsedRecord.package === "object"
|
|
88
|
+
? parsedRecord.package
|
|
89
|
+
: null;
|
|
90
|
+
const workspaceTable = parsedRecord.workspace && typeof parsedRecord.workspace === "object"
|
|
91
|
+
? parsedRecord.workspace
|
|
92
|
+
: null;
|
|
93
|
+
return { packageTable, workspaceTable };
|
|
94
|
+
}
|
|
95
|
+
function isCrateManifest(versionFile, cargoTomlRaw) {
|
|
96
|
+
const parsed = parseCargoManifest(versionFile, cargoTomlRaw);
|
|
97
|
+
return parsed.packageTable !== null;
|
|
98
|
+
}
|
|
99
|
+
function readWorkspaceMemberPatterns(workspaceTable) {
|
|
100
|
+
if (!workspaceTable) {
|
|
101
|
+
return [];
|
|
102
|
+
}
|
|
103
|
+
const members = workspaceTable.members;
|
|
104
|
+
if (!Array.isArray(members)) {
|
|
105
|
+
return [];
|
|
106
|
+
}
|
|
107
|
+
return members
|
|
108
|
+
.filter((member) => typeof member === "string")
|
|
109
|
+
.map((member) => normalizeSlashPath(member.trim()))
|
|
110
|
+
.filter((member) => member.length > 0);
|
|
111
|
+
}
|
|
112
|
+
function readWorkspaceExcludes(workspaceTable) {
|
|
113
|
+
if (!workspaceTable) {
|
|
114
|
+
return [];
|
|
115
|
+
}
|
|
116
|
+
const excludes = workspaceTable.exclude;
|
|
117
|
+
if (!Array.isArray(excludes)) {
|
|
118
|
+
return [];
|
|
119
|
+
}
|
|
120
|
+
return excludes
|
|
121
|
+
.filter((entry) => typeof entry === "string")
|
|
122
|
+
.map((entry) => normalizeSlashPath(entry.trim()))
|
|
123
|
+
.filter((entry) => entry.length > 0);
|
|
124
|
+
}
|
|
125
|
+
function resolveWorkspaceMemberManifests(rootDir, workspaceTable) {
|
|
126
|
+
const memberPatterns = readWorkspaceMemberPatterns(workspaceTable);
|
|
127
|
+
if (memberPatterns.length === 0) {
|
|
128
|
+
return [];
|
|
129
|
+
}
|
|
130
|
+
const excludes = readWorkspaceExcludes(workspaceTable);
|
|
131
|
+
const excludeRegexes = excludes.map((pattern) => globToRegex(pattern));
|
|
132
|
+
const cargoDirs = findCargoManifestDirectories(rootDir);
|
|
133
|
+
const manifests = new Set();
|
|
134
|
+
for (const pattern of memberPatterns) {
|
|
135
|
+
if (hasGlobPattern(pattern)) {
|
|
136
|
+
const regex = globToRegex(pattern);
|
|
137
|
+
for (const dir of cargoDirs) {
|
|
138
|
+
if (regex.test(dir)) {
|
|
139
|
+
manifests.add(normalizeSlashPath(node_path_1.default.posix.join(dir, "Cargo.toml")));
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
continue;
|
|
143
|
+
}
|
|
144
|
+
const candidateDir = normalizeSlashPath(pattern);
|
|
145
|
+
if (excludeRegexes.some((regex) => regex.test(candidateDir))) {
|
|
146
|
+
continue;
|
|
147
|
+
}
|
|
148
|
+
const cargoPath = node_path_1.default.join(rootDir, candidateDir, "Cargo.toml");
|
|
149
|
+
if (node_fs_1.default.existsSync(cargoPath)) {
|
|
150
|
+
manifests.add(normalizeSlashPath(node_path_1.default.posix.join(candidateDir, "Cargo.toml")));
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
const filtered = [...manifests].filter((manifestPath) => {
|
|
154
|
+
const dir = normalizeSlashPath(node_path_1.default.posix.dirname(manifestPath));
|
|
155
|
+
return !excludeRegexes.some((regex) => regex.test(dir));
|
|
156
|
+
});
|
|
157
|
+
return filtered.sort((a, b) => a.localeCompare(b));
|
|
158
|
+
}
|
|
159
|
+
function collectRustTargetManifests(cwd, versionFile) {
|
|
160
|
+
if (node_path_1.default.basename(versionFile) !== "Cargo.toml") {
|
|
161
|
+
throw new Error(`Rust strategy requires "version-file" to point to a Cargo.toml manifest (received "${versionFile}").`);
|
|
162
|
+
}
|
|
163
|
+
const rootManifestPath = node_path_1.default.join(cwd, versionFile);
|
|
164
|
+
if (!node_fs_1.default.existsSync(rootManifestPath)) {
|
|
165
|
+
throw new Error(`Versionary requires ${versionFile} to exist.`);
|
|
166
|
+
}
|
|
167
|
+
const rootRaw = node_fs_1.default.readFileSync(rootManifestPath, "utf8");
|
|
168
|
+
const parsedRoot = parseCargoManifest(versionFile, rootRaw);
|
|
169
|
+
const rootIsCrate = parsedRoot.packageTable !== null;
|
|
170
|
+
const rootDir = node_path_1.default.dirname(rootManifestPath);
|
|
171
|
+
const workspaceMembers = resolveWorkspaceMemberManifests(rootDir, parsedRoot.workspaceTable);
|
|
172
|
+
if (rootIsCrate) {
|
|
173
|
+
const relRoot = normalizeSlashPath(node_path_1.default.relative(cwd, rootManifestPath));
|
|
174
|
+
return [...new Set([relRoot, ...workspaceMembers])].sort((a, b) => a.localeCompare(b));
|
|
175
|
+
}
|
|
176
|
+
if (workspaceMembers.length > 0) {
|
|
177
|
+
return workspaceMembers;
|
|
178
|
+
}
|
|
179
|
+
throw new Error(`Configured rust target "${versionFile}" is not a Rust crate manifest. Expected [package].version or [workspace].members with crate Cargo.toml files.`);
|
|
180
|
+
}
|
|
181
|
+
function readCargoVersion(cargoTomlRaw, versionFile) {
|
|
182
|
+
const { packageTable } = parseCargoManifest(versionFile, cargoTomlRaw);
|
|
183
|
+
if (!packageTable || typeof packageTable !== "object") {
|
|
184
|
+
throw new Error(`${versionFile} is missing [package].version. Add [package] with a SemVer version.`);
|
|
185
|
+
}
|
|
186
|
+
const rawVersion = packageTable.version;
|
|
187
|
+
if (rawVersion === undefined) {
|
|
188
|
+
throw new Error(`${versionFile} is missing [package].version. Add [package] with a SemVer version.`);
|
|
189
|
+
}
|
|
190
|
+
if (typeof rawVersion !== "string" || rawVersion.trim().length === 0) {
|
|
191
|
+
throw new Error(`${versionFile} has invalid [package].version. Expected a non-empty SemVer string.`);
|
|
192
|
+
}
|
|
193
|
+
return rawVersion.trim();
|
|
194
|
+
}
|
|
195
|
+
function readCargoPackageName(cargoTomlRaw, versionFile) {
|
|
196
|
+
const { packageTable } = parseCargoManifest(versionFile, cargoTomlRaw);
|
|
197
|
+
if (!packageTable || typeof packageTable !== "object") {
|
|
198
|
+
throw new Error(`${versionFile} is missing [package].name. Add [package] with a crate name.`);
|
|
199
|
+
}
|
|
200
|
+
const rawName = packageTable.name;
|
|
201
|
+
if (typeof rawName !== "string" || rawName.trim().length === 0) {
|
|
202
|
+
throw new Error(`${versionFile} has invalid [package].name. Expected a non-empty crate name.`);
|
|
203
|
+
}
|
|
204
|
+
return rawName.trim();
|
|
205
|
+
}
|
|
206
|
+
function writeCargoVersion(cargoTomlRaw, versionFile, version) {
|
|
207
|
+
const lineEnding = cargoTomlRaw.includes("\r\n") ? "\r\n" : "\n";
|
|
208
|
+
const hasFinalLineEnding = cargoTomlRaw.endsWith("\n") || cargoTomlRaw.endsWith("\r\n");
|
|
209
|
+
const lines = cargoTomlRaw.split(/\r?\n/u);
|
|
210
|
+
let inPackageSection = false;
|
|
211
|
+
let foundPackageSection = false;
|
|
212
|
+
let replacedVersion = false;
|
|
213
|
+
for (let index = 0; index < lines.length; index += 1) {
|
|
214
|
+
const line = lines[index] ?? "";
|
|
215
|
+
const sectionMatch = line.match(/^\s*\[([^\]]+)\]\s*(?:#.*)?$/u);
|
|
216
|
+
if (sectionMatch) {
|
|
217
|
+
const section = sectionMatch[1]?.trim();
|
|
218
|
+
inPackageSection = section === "package";
|
|
219
|
+
if (inPackageSection) {
|
|
220
|
+
foundPackageSection = true;
|
|
221
|
+
}
|
|
222
|
+
continue;
|
|
223
|
+
}
|
|
224
|
+
if (!inPackageSection) {
|
|
225
|
+
continue;
|
|
226
|
+
}
|
|
227
|
+
const versionMatch = line.match(/^(\s*version\s*=\s*)(["'])([^"']*)(\2)(\s*(?:#.*)?)?$/u);
|
|
228
|
+
if (!versionMatch) {
|
|
229
|
+
continue;
|
|
230
|
+
}
|
|
231
|
+
const [, prefix = "", quote = '"', , , suffix = ""] = versionMatch;
|
|
232
|
+
lines[index] = `${prefix}${quote}${version}${quote}${suffix}`;
|
|
233
|
+
replacedVersion = true;
|
|
234
|
+
break;
|
|
235
|
+
}
|
|
236
|
+
if (!foundPackageSection) {
|
|
237
|
+
throw new Error(`${versionFile} is missing [package].version. Add [package] with a SemVer version.`);
|
|
238
|
+
}
|
|
239
|
+
if (!replacedVersion) {
|
|
240
|
+
throw new Error(`${versionFile} is missing [package].version. Add [package] with a SemVer version.`);
|
|
241
|
+
}
|
|
242
|
+
let updated = lines.join(lineEnding);
|
|
243
|
+
if (hasFinalLineEnding && !updated.endsWith(lineEnding)) {
|
|
244
|
+
updated += lineEnding;
|
|
245
|
+
}
|
|
246
|
+
if (!hasFinalLineEnding && updated.endsWith(lineEnding)) {
|
|
247
|
+
updated = updated.slice(0, -lineEnding.length);
|
|
248
|
+
}
|
|
249
|
+
return updated;
|
|
250
|
+
}
|
|
251
|
+
function isDependencySection(section) {
|
|
252
|
+
if (ROOT_DEPENDENCY_SECTIONS.has(section)) {
|
|
253
|
+
return true;
|
|
254
|
+
}
|
|
255
|
+
if (!section.startsWith("target.")) {
|
|
256
|
+
return false;
|
|
257
|
+
}
|
|
258
|
+
return (section.endsWith(".dependencies") ||
|
|
259
|
+
section.endsWith(".dev-dependencies") ||
|
|
260
|
+
section.endsWith(".build-dependencies"));
|
|
261
|
+
}
|
|
262
|
+
function parseDependencyName(line) {
|
|
263
|
+
const match = line.match(/^\s*(?:"([^"]+)"|'([^']+)'|([A-Za-z0-9_-]+))\s*=/u);
|
|
264
|
+
return match?.[1] ?? match?.[2] ?? match?.[3] ?? null;
|
|
265
|
+
}
|
|
266
|
+
function writeInternalDependencyVersionInLine(line, dependencyName, internalCrates, version) {
|
|
267
|
+
if (!internalCrates.has(dependencyName)) {
|
|
268
|
+
return line;
|
|
269
|
+
}
|
|
270
|
+
const stringVersionMatch = line.match(/^(\s*(?:"[^"]+"|'[^']+'|[A-Za-z0-9_-]+)\s*=\s*)(["'])([^"']*)(\2)(\s*(?:#.*)?)?$/u);
|
|
271
|
+
if (stringVersionMatch) {
|
|
272
|
+
const [, prefix = "", quote = '"', , , suffix = ""] = stringVersionMatch;
|
|
273
|
+
return `${prefix}${quote}${version}${quote}${suffix}`;
|
|
274
|
+
}
|
|
275
|
+
const inlineTableMatch = line.match(/^(\s*(?:"[^"]+"|'[^']+'|[A-Za-z0-9_-]+)\s*=\s*\{)(.*)(\}\s*(?:#.*)?)$/u);
|
|
276
|
+
if (!inlineTableMatch) {
|
|
277
|
+
return line;
|
|
278
|
+
}
|
|
279
|
+
const [, prefix = "", tableBody = "", suffix = ""] = inlineTableMatch;
|
|
280
|
+
const updatedTableBody = tableBody.replace(/(\bversion\s*=\s*)(["'])([^"']*)(\2)/u, `$1$2${version}$4`);
|
|
281
|
+
if (updatedTableBody === tableBody) {
|
|
282
|
+
return line;
|
|
283
|
+
}
|
|
284
|
+
return `${prefix}${updatedTableBody}${suffix}`;
|
|
285
|
+
}
|
|
286
|
+
function writeInternalDependencyVersions(cargoTomlRaw, internalCrates, version) {
|
|
287
|
+
if (internalCrates.size === 0) {
|
|
288
|
+
return cargoTomlRaw;
|
|
289
|
+
}
|
|
290
|
+
const lineEnding = cargoTomlRaw.includes("\r\n") ? "\r\n" : "\n";
|
|
291
|
+
const hasFinalLineEnding = cargoTomlRaw.endsWith("\n") || cargoTomlRaw.endsWith("\r\n");
|
|
292
|
+
const lines = cargoTomlRaw.split(/\r?\n/u);
|
|
293
|
+
let currentSection = null;
|
|
294
|
+
for (let index = 0; index < lines.length; index += 1) {
|
|
295
|
+
const line = lines[index] ?? "";
|
|
296
|
+
const sectionMatch = line.match(/^\s*\[([^\]]+)\]\s*(?:#.*)?$/u);
|
|
297
|
+
if (sectionMatch) {
|
|
298
|
+
currentSection = sectionMatch[1]?.trim() ?? null;
|
|
299
|
+
continue;
|
|
300
|
+
}
|
|
301
|
+
if (!currentSection || !isDependencySection(currentSection)) {
|
|
302
|
+
continue;
|
|
303
|
+
}
|
|
304
|
+
const dependencyName = parseDependencyName(line);
|
|
305
|
+
if (!dependencyName) {
|
|
306
|
+
continue;
|
|
307
|
+
}
|
|
308
|
+
lines[index] = writeInternalDependencyVersionInLine(line, dependencyName, internalCrates, version);
|
|
309
|
+
}
|
|
310
|
+
let updated = lines.join(lineEnding);
|
|
311
|
+
if (hasFinalLineEnding && !updated.endsWith(lineEnding)) {
|
|
312
|
+
updated += lineEnding;
|
|
313
|
+
}
|
|
314
|
+
if (!hasFinalLineEnding && updated.endsWith(lineEnding)) {
|
|
315
|
+
updated = updated.slice(0, -lineEnding.length);
|
|
316
|
+
}
|
|
317
|
+
return updated;
|
|
318
|
+
}
|
|
319
|
+
exports.rustVersionStrategy = {
|
|
320
|
+
name: "rust",
|
|
321
|
+
getVersionFile(config) {
|
|
322
|
+
return config["version-file"] ?? "Cargo.toml";
|
|
323
|
+
},
|
|
324
|
+
readVersion(cwd, config) {
|
|
325
|
+
const versionFile = this.getVersionFile(config);
|
|
326
|
+
const manifests = collectRustTargetManifests(cwd, versionFile);
|
|
327
|
+
const selectedManifest = manifests[0];
|
|
328
|
+
if (!selectedManifest) {
|
|
329
|
+
throw new Error(`Configured rust target "${versionFile}" did not resolve to a Rust crate manifest.`);
|
|
330
|
+
}
|
|
331
|
+
const cargoTomlRaw = node_fs_1.default.readFileSync(node_path_1.default.join(cwd, selectedManifest), "utf8");
|
|
332
|
+
return readCargoVersion(cargoTomlRaw, selectedManifest);
|
|
333
|
+
},
|
|
334
|
+
writeVersion(cwd, config, version) {
|
|
335
|
+
const versionFile = this.getVersionFile(config);
|
|
336
|
+
const manifests = collectRustTargetManifests(cwd, versionFile);
|
|
337
|
+
const updatedFiles = [];
|
|
338
|
+
const internalCrates = new Set();
|
|
339
|
+
for (const manifest of manifests) {
|
|
340
|
+
const versionPath = node_path_1.default.join(cwd, manifest);
|
|
341
|
+
if (!node_fs_1.default.existsSync(versionPath)) {
|
|
342
|
+
continue;
|
|
343
|
+
}
|
|
344
|
+
const cargoTomlRaw = node_fs_1.default.readFileSync(versionPath, "utf8");
|
|
345
|
+
if (!isCrateManifest(manifest, cargoTomlRaw)) {
|
|
346
|
+
continue;
|
|
347
|
+
}
|
|
348
|
+
internalCrates.add(readCargoPackageName(cargoTomlRaw, manifest));
|
|
349
|
+
}
|
|
350
|
+
for (const manifest of manifests) {
|
|
351
|
+
const versionPath = node_path_1.default.join(cwd, manifest);
|
|
352
|
+
if (!node_fs_1.default.existsSync(versionPath)) {
|
|
353
|
+
continue;
|
|
354
|
+
}
|
|
355
|
+
const cargoTomlRaw = node_fs_1.default.readFileSync(versionPath, "utf8");
|
|
356
|
+
if (!isCrateManifest(manifest, cargoTomlRaw)) {
|
|
357
|
+
continue;
|
|
358
|
+
}
|
|
359
|
+
readCargoVersion(cargoTomlRaw, manifest);
|
|
360
|
+
const updatedCargoToml = writeInternalDependencyVersions(writeCargoVersion(cargoTomlRaw, manifest, version), internalCrates, version);
|
|
361
|
+
node_fs_1.default.writeFileSync(versionPath, updatedCargoToml, "utf8");
|
|
362
|
+
updatedFiles.push(manifest);
|
|
363
|
+
}
|
|
364
|
+
if (updatedFiles.length === 0) {
|
|
365
|
+
throw new Error(`Configured rust target "${versionFile}" did not resolve to a Rust crate manifest to update.`);
|
|
366
|
+
}
|
|
367
|
+
return updatedFiles.sort((a, b) => a.localeCompare(b));
|
|
368
|
+
},
|
|
369
|
+
};
|