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/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
- console.log("No releasable commits found. Nothing to do.");
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
- console.log(`Prepared release PR branch ${pr.branch}`);
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 Auto-dispatch release PR/update or release publish by context");
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");
@@ -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.object({
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("This PR was generated by Versionary.");
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"] ?? "version.txt";
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
- return node_fs_1.default.readFileSync(versionPath, "utf8").trim();
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.writeFileSync(versionPath, `${version}\n`, "utf8");
26
- const updatedFiles = [versionFile];
27
- const packageJsonPath = node_path_1.default.join(cwd, "package.json");
28
- if (node_fs_1.default.existsSync(packageJsonPath)) {
29
- const packageJsonRaw = node_fs_1.default.readFileSync(packageJsonPath, "utf8");
30
- const packageJson = JSON.parse(packageJsonRaw);
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,2 @@
1
+ import type { VersionStrategy } from "./types.js";
2
+ export declare const rVersionStrategy: VersionStrategy;
@@ -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,2 @@
1
+ import type { VersionStrategy } from "./types.js";
2
+ export declare const rustVersionStrategy: VersionStrategy;
@@ -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
+ };