versionary 0.8.1 → 0.9.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 +4 -0
- package/dist/app/release/artifact-rules.js +28 -4
- package/dist/app/release/pr.js +92 -15
- package/dist/app/release/release.js +5 -1
- package/dist/cli/index.js +1 -1
- package/dist/config/schema.d.ts +1 -0
- package/dist/config/schema.js +1 -0
- package/dist/domain/release/changelog.d.ts +7 -0
- package/dist/domain/release/changelog.js +19 -0
- package/dist/domain/strategy/package-context.js +14 -2
- package/dist/domain/strategy/r.js +1 -1
- package/dist/domain/strategy/rust.js +138 -12
- package/dist/types/config.d.ts +1 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -101,6 +101,8 @@ For a quick trial, use:
|
|
|
101
101
|
- `independent` computes package bumps per path
|
|
102
102
|
- `fixed` computes one shared bump across configured package paths
|
|
103
103
|
- per-package `package-name` can override release identity (labels + tag base)
|
|
104
|
+
- per-package `changelog-file` writes package release notes to
|
|
105
|
+
`<package-path>/<changelog-file>`
|
|
104
106
|
|
|
105
107
|
Rust strategy examples:
|
|
106
108
|
|
|
@@ -123,6 +125,8 @@ Rust strategy examples:
|
|
|
123
125
|
Current rust auto-update behavior (phase scope):
|
|
124
126
|
|
|
125
127
|
- updates crate versions in each targeted crate `[package].version`
|
|
128
|
+
- supports targeted crates using `version.workspace = true` by updating
|
|
129
|
+
`[workspace.package].version` in the owning workspace manifest
|
|
126
130
|
- updates internal workspace dependency versions when the dependency name
|
|
127
131
|
matches another targeted crate name
|
|
128
132
|
- refreshes `Cargo.lock` via `cargo generate-lockfile` when `Cargo.lock` exists
|
|
@@ -33,13 +33,17 @@ function parseFieldPath(fieldPath) {
|
|
|
33
33
|
const numberMatch = rest.match(/^(\d+)\]/u);
|
|
34
34
|
if (numberMatch) {
|
|
35
35
|
tokens.push(Number(numberMatch[1]));
|
|
36
|
-
index += 2 + numberMatch[1]
|
|
36
|
+
index += 2 + numberMatch[1]?.length;
|
|
37
37
|
continue;
|
|
38
38
|
}
|
|
39
39
|
const keyMatch = rest.match(/^"([^"]+)"\]/u);
|
|
40
40
|
if (keyMatch) {
|
|
41
|
-
|
|
42
|
-
|
|
41
|
+
const key = keyMatch[1];
|
|
42
|
+
if (!key) {
|
|
43
|
+
throw new Error(`Invalid field-path "${fieldPath}" near index ${index}.`);
|
|
44
|
+
}
|
|
45
|
+
tokens.push(key);
|
|
46
|
+
index += 4 + keyMatch[1]?.length;
|
|
43
47
|
continue;
|
|
44
48
|
}
|
|
45
49
|
throw new Error(`Invalid field-path "${fieldPath}" near index ${index}.`);
|
|
@@ -56,6 +60,9 @@ function setVersionAtJsonPath(document, fieldPath, version) {
|
|
|
56
60
|
let cursor = document;
|
|
57
61
|
for (let index = 0; index < tokens.length - 1; index += 1) {
|
|
58
62
|
const token = tokens[index];
|
|
63
|
+
if (token === undefined) {
|
|
64
|
+
throw new Error(`field-path "${fieldPath}" does not resolve to an existing field.`);
|
|
65
|
+
}
|
|
59
66
|
if (typeof token === "number") {
|
|
60
67
|
if (!Array.isArray(cursor) || token >= cursor.length) {
|
|
61
68
|
throw new Error(`field-path "${fieldPath}" does not resolve to an existing field.`);
|
|
@@ -69,6 +76,9 @@ function setVersionAtJsonPath(document, fieldPath, version) {
|
|
|
69
76
|
cursor = cursor[token];
|
|
70
77
|
}
|
|
71
78
|
const leaf = tokens.at(-1);
|
|
79
|
+
if (leaf === undefined) {
|
|
80
|
+
throw new Error(`field-path "${fieldPath}" does not resolve to an existing field.`);
|
|
81
|
+
}
|
|
72
82
|
if (typeof leaf === "number") {
|
|
73
83
|
if (!Array.isArray(cursor) || leaf >= cursor.length) {
|
|
74
84
|
throw new Error(`field-path "${fieldPath}" does not resolve to an existing field.`);
|
|
@@ -99,7 +109,12 @@ function resolveFieldPath(rule) {
|
|
|
99
109
|
function parseRegexPattern(pattern) {
|
|
100
110
|
const slashPattern = pattern.match(/^\/((?:\\\/|[^/])+)\/([a-z]*)$/u);
|
|
101
111
|
if (slashPattern) {
|
|
102
|
-
|
|
112
|
+
const source = slashPattern[1];
|
|
113
|
+
const flags = slashPattern[2];
|
|
114
|
+
if (!source || flags === undefined) {
|
|
115
|
+
throw new Error(`Invalid regex pattern "${pattern}".`);
|
|
116
|
+
}
|
|
117
|
+
return new RegExp(source, flags);
|
|
103
118
|
}
|
|
104
119
|
return new RegExp(pattern, "m");
|
|
105
120
|
}
|
|
@@ -114,6 +129,9 @@ function applyRegexRule(content, pattern, version) {
|
|
|
114
129
|
throw new Error(`Regex pattern must match exactly one occurrence; matched ${matches.length}.`);
|
|
115
130
|
}
|
|
116
131
|
const match = matches[0];
|
|
132
|
+
if (!match) {
|
|
133
|
+
throw new Error("Regex match result missing.");
|
|
134
|
+
}
|
|
117
135
|
const start = match.index;
|
|
118
136
|
if (start === undefined) {
|
|
119
137
|
throw new Error("Regex match did not include an index.");
|
|
@@ -131,6 +149,9 @@ function applyTomlRulePreservingFormatting(content, fieldPath, version) {
|
|
|
131
149
|
return `${toml_1.default.stringify(parsed)}\n`;
|
|
132
150
|
}
|
|
133
151
|
const key = simplePath[1];
|
|
152
|
+
if (!key) {
|
|
153
|
+
throw new Error(`field-path "${fieldPath}" does not resolve to an existing field.`);
|
|
154
|
+
}
|
|
134
155
|
const linePattern = new RegExp(`^(\\s*${key}\\s*=\\s*)(["'])([^"']*)(\\2)(\\s*(?:#.*)?)$`, "mu");
|
|
135
156
|
const match = content.match(linePattern);
|
|
136
157
|
if (!match) {
|
|
@@ -141,6 +162,9 @@ function applyTomlRulePreservingFormatting(content, fieldPath, version) {
|
|
|
141
162
|
}
|
|
142
163
|
function applyArtifactRuleToContent(content, rule, version) {
|
|
143
164
|
if (rule.type === "regex") {
|
|
165
|
+
if (!rule.pattern) {
|
|
166
|
+
throw new Error('regex artifact rules require "pattern".');
|
|
167
|
+
}
|
|
144
168
|
return applyRegexRule(content, rule.pattern, version);
|
|
145
169
|
}
|
|
146
170
|
if (rule.type === "json") {
|
package/dist/app/release/pr.js
CHANGED
|
@@ -71,24 +71,60 @@ function ensureCleanWorktree(cwd, logger) {
|
|
|
71
71
|
logger?.warn(`Ignoring safe tracked changes before versionary pr:\n${ignored.join("\n")}`);
|
|
72
72
|
}
|
|
73
73
|
}
|
|
74
|
+
function normalizeSlashPath(input) {
|
|
75
|
+
return input.replaceAll("\\", "/");
|
|
76
|
+
}
|
|
77
|
+
function listCargoLockFiles(cwd) {
|
|
78
|
+
const lockfiles = [];
|
|
79
|
+
const queue = [cwd];
|
|
80
|
+
while (queue.length > 0) {
|
|
81
|
+
const currentDir = queue.shift();
|
|
82
|
+
if (!currentDir) {
|
|
83
|
+
continue;
|
|
84
|
+
}
|
|
85
|
+
const entries = node_fs_1.default.readdirSync(currentDir, { withFileTypes: true });
|
|
86
|
+
for (const entry of entries) {
|
|
87
|
+
if (entry.name === ".git") {
|
|
88
|
+
continue;
|
|
89
|
+
}
|
|
90
|
+
const fullPath = node_path_1.default.join(currentDir, entry.name);
|
|
91
|
+
if (entry.isDirectory()) {
|
|
92
|
+
queue.push(fullPath);
|
|
93
|
+
continue;
|
|
94
|
+
}
|
|
95
|
+
if (!entry.isFile() || entry.name !== "Cargo.lock") {
|
|
96
|
+
continue;
|
|
97
|
+
}
|
|
98
|
+
lockfiles.push(normalizeSlashPath(node_path_1.default.relative(cwd, fullPath)));
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
return lockfiles.sort((a, b) => a.localeCompare(b));
|
|
102
|
+
}
|
|
74
103
|
function ensureCargoLockUpToDate(cwd) {
|
|
75
|
-
const
|
|
76
|
-
if (
|
|
104
|
+
const lockfiles = listCargoLockFiles(cwd);
|
|
105
|
+
if (lockfiles.length === 0) {
|
|
77
106
|
return [];
|
|
78
107
|
}
|
|
79
|
-
const
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
108
|
+
const updatedLockfiles = [];
|
|
109
|
+
for (const lockfile of lockfiles) {
|
|
110
|
+
const lockfilePath = node_path_1.default.join(cwd, lockfile);
|
|
111
|
+
const before = node_fs_1.default.readFileSync(lockfilePath, "utf8");
|
|
112
|
+
try {
|
|
113
|
+
(0, node_child_process_1.execFileSync)("cargo", ["generate-lockfile"], {
|
|
114
|
+
cwd: node_path_1.default.dirname(lockfilePath),
|
|
115
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
116
|
+
});
|
|
117
|
+
}
|
|
118
|
+
catch (error) {
|
|
119
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
120
|
+
throw new Error(`Failed to refresh ${lockfile} via "cargo generate-lockfile". Ensure cargo is installed and available in PATH. Details: ${message}`);
|
|
121
|
+
}
|
|
122
|
+
const after = node_fs_1.default.readFileSync(lockfilePath, "utf8");
|
|
123
|
+
if (after !== before) {
|
|
124
|
+
updatedLockfiles.push(lockfile);
|
|
125
|
+
}
|
|
89
126
|
}
|
|
90
|
-
|
|
91
|
-
return after !== before ? ["Cargo.lock"] : [];
|
|
127
|
+
return updatedLockfiles;
|
|
92
128
|
}
|
|
93
129
|
function normalizeReleaseNameForTag(releaseName) {
|
|
94
130
|
return releaseName
|
|
@@ -178,6 +214,22 @@ function buildReleaseTargets(cwd, plan, loadedConfig) {
|
|
|
178
214
|
}
|
|
179
215
|
return releaseTargets;
|
|
180
216
|
}
|
|
217
|
+
function buildPackageReleaseMetadata(cwd, plan, loadedConfig) {
|
|
218
|
+
const metadataByPath = {};
|
|
219
|
+
for (const pkg of plan.packages ?? []) {
|
|
220
|
+
if (!pkg.nextVersion || pkg.path === ".") {
|
|
221
|
+
continue;
|
|
222
|
+
}
|
|
223
|
+
const packageConfig = loadedConfig.packages?.[pkg.path] ?? {};
|
|
224
|
+
const packageContext = (0, package_context_js_1.resolvePackageStrategyContext)(loadedConfig, pkg.path, packageConfig);
|
|
225
|
+
const releaseName = resolveReleaseName(cwd, pkg.path, packageConfig, packageContext.strategy.name, packageContext.versionFile);
|
|
226
|
+
metadataByPath[pkg.path] = {
|
|
227
|
+
releaseName,
|
|
228
|
+
tagPrefix: normalizeReleaseNameForTag(releaseName),
|
|
229
|
+
};
|
|
230
|
+
}
|
|
231
|
+
return metadataByPath;
|
|
232
|
+
}
|
|
181
233
|
function formatReleaseCommitTitle(releaseTargets) {
|
|
182
234
|
if (releaseTargets.length === 0) {
|
|
183
235
|
return "chore(release): v0.0.0";
|
|
@@ -216,8 +268,33 @@ function prepareSimpleReleasePr(cwd = process.cwd(), options = {}) {
|
|
|
216
268
|
}
|
|
217
269
|
const updatedArtifactFiles = (0, artifact_rules_js_1.applyConfiguredArtifactRules)(cwd, loaded.config, plan);
|
|
218
270
|
const updatedRustLockFiles = ensureCargoLockUpToDate(cwd);
|
|
271
|
+
const packageReleaseMetadata = buildPackageReleaseMetadata(cwd, plan, loaded.config);
|
|
219
272
|
const section = (0, changelog_js_1.renderSimpleChangelog)(plan);
|
|
220
273
|
(0, changelog_js_1.prependChangelog)(cwd, plan.changelogFile, section);
|
|
274
|
+
const updatedChangelogFiles = [plan.changelogFile];
|
|
275
|
+
for (const packagePlan of plan.packages ?? []) {
|
|
276
|
+
if (!packagePlan.nextVersion || packagePlan.path === ".") {
|
|
277
|
+
continue;
|
|
278
|
+
}
|
|
279
|
+
const packageConfig = loaded.config.packages?.[packagePlan.path] ?? {};
|
|
280
|
+
const packageChangelogFile = packageConfig["changelog-file"];
|
|
281
|
+
if (!packageChangelogFile) {
|
|
282
|
+
continue;
|
|
283
|
+
}
|
|
284
|
+
const packageMetadata = packageReleaseMetadata[packagePlan.path];
|
|
285
|
+
if (!packageMetadata) {
|
|
286
|
+
continue;
|
|
287
|
+
}
|
|
288
|
+
const packageSection = (0, changelog_js_1.renderPackageChangelogSection)({
|
|
289
|
+
currentVersion: packagePlan.currentVersion,
|
|
290
|
+
nextVersion: packagePlan.nextVersion,
|
|
291
|
+
commits: packagePlan.commits,
|
|
292
|
+
tagPrefix: packageMetadata.tagPrefix,
|
|
293
|
+
cwd,
|
|
294
|
+
});
|
|
295
|
+
(0, changelog_js_1.prependChangelog)(cwd, node_path_1.default.posix.join(packagePlan.path, packageChangelogFile), packageSection);
|
|
296
|
+
updatedChangelogFiles.push(node_path_1.default.posix.join(packagePlan.path, packageChangelogFile));
|
|
297
|
+
}
|
|
221
298
|
const releaseTargets = buildReleaseTargets(cwd, plan, loaded.config);
|
|
222
299
|
const branch = plan.releaseBranchPrefix;
|
|
223
300
|
const title = formatReleaseCommitTitle(releaseTargets);
|
|
@@ -230,7 +307,7 @@ function prepareSimpleReleasePr(cwd = process.cwd(), options = {}) {
|
|
|
230
307
|
...updatedVersionFiles,
|
|
231
308
|
...updatedArtifactFiles,
|
|
232
309
|
...updatedRustLockFiles,
|
|
233
|
-
|
|
310
|
+
...updatedChangelogFiles,
|
|
234
311
|
]),
|
|
235
312
|
];
|
|
236
313
|
(0, node_child_process_1.execFileSync)("git", ["add", ...filesToAdd], {
|
|
@@ -75,6 +75,7 @@ async function runSimpleReleaseDetailed(cwd = process.cwd(), options = {}) {
|
|
|
75
75
|
if (!plugin?.createReleaseMetadata) {
|
|
76
76
|
throw new Error(`Plugin "${plugin?.name ?? "unknown"}" does not implement createReleaseMetadata.`);
|
|
77
77
|
}
|
|
78
|
+
const createReleaseMetadata = plugin.createReleaseMetadata;
|
|
78
79
|
const releaseTargets = (0, state_js_1.readReleaseTargets)(cwd);
|
|
79
80
|
const targets = releaseTargets.length > 0
|
|
80
81
|
? releaseTargets
|
|
@@ -92,7 +93,10 @@ async function runSimpleReleaseDetailed(cwd = process.cwd(), options = {}) {
|
|
|
92
93
|
version: target.version,
|
|
93
94
|
notes: readReleaseNotes(cwd, target.version, changelogFile),
|
|
94
95
|
}, {
|
|
95
|
-
createReleaseMetadata: (input) =>
|
|
96
|
+
createReleaseMetadata: (input) => createReleaseMetadata(input, {
|
|
97
|
+
cwd,
|
|
98
|
+
logger: options.logger,
|
|
99
|
+
}),
|
|
96
100
|
logger: options.logger,
|
|
97
101
|
});
|
|
98
102
|
releases.push({
|
package/dist/cli/index.js
CHANGED
|
@@ -125,7 +125,7 @@ async function main() {
|
|
|
125
125
|
: "";
|
|
126
126
|
const heading = "# Changelog\n\n";
|
|
127
127
|
const body = existing.replace(/^# Changelog\s*/u, "");
|
|
128
|
-
node_fs_1.default.writeFileSync(changelogPath, `${heading}${section}\n${body}`.trimEnd()
|
|
128
|
+
node_fs_1.default.writeFileSync(changelogPath, `${`${heading}${section}\n${body}`.trimEnd()}\n`, "utf8");
|
|
129
129
|
console.log(`Updated ${plan.changelogFile}`);
|
|
130
130
|
return 0;
|
|
131
131
|
}
|
package/dist/config/schema.d.ts
CHANGED
|
@@ -22,6 +22,7 @@ export declare const configSchema: z.ZodObject<{
|
|
|
22
22
|
packages: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodObject<{
|
|
23
23
|
"release-type": z.ZodOptional<z.ZodString>;
|
|
24
24
|
"package-name": z.ZodOptional<z.ZodString>;
|
|
25
|
+
"changelog-file": z.ZodOptional<z.ZodString>;
|
|
25
26
|
"exclude-paths": z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
26
27
|
"extra-files": z.ZodOptional<z.ZodArray<z.ZodObject<{
|
|
27
28
|
type: z.ZodEnum<{
|
package/dist/config/schema.js
CHANGED
|
@@ -53,6 +53,7 @@ const packageSchema = zod_1.z
|
|
|
53
53
|
.object({
|
|
54
54
|
"release-type": zod_1.z.string().optional(),
|
|
55
55
|
"package-name": zod_1.z.string().optional(),
|
|
56
|
+
"changelog-file": zod_1.z.string().optional(),
|
|
56
57
|
"exclude-paths": zod_1.z.array(zod_1.z.string()).optional(),
|
|
57
58
|
"extra-files": zod_1.z.array(artifactRuleSchema).optional(),
|
|
58
59
|
})
|
|
@@ -9,4 +9,11 @@ export declare function renderSimpleReleaseNotes(input: {
|
|
|
9
9
|
includeFooter?: boolean;
|
|
10
10
|
}): string;
|
|
11
11
|
export declare function renderSimpleChangelog(plan: SimplePlan): string;
|
|
12
|
+
export declare function renderPackageChangelogSection(input: {
|
|
13
|
+
currentVersion: string;
|
|
14
|
+
nextVersion: string;
|
|
15
|
+
commits: ParsedCommit[];
|
|
16
|
+
tagPrefix: string;
|
|
17
|
+
cwd?: string;
|
|
18
|
+
}): string;
|
|
12
19
|
export declare function prependChangelog(cwd: string, changelogFile: string, section: string): void;
|
|
@@ -5,6 +5,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
6
|
exports.renderSimpleReleaseNotes = renderSimpleReleaseNotes;
|
|
7
7
|
exports.renderSimpleChangelog = renderSimpleChangelog;
|
|
8
|
+
exports.renderPackageChangelogSection = renderPackageChangelogSection;
|
|
8
9
|
exports.prependChangelog = prependChangelog;
|
|
9
10
|
const node_fs_1 = __importDefault(require("node:fs"));
|
|
10
11
|
const node_path_1 = __importDefault(require("node:path"));
|
|
@@ -110,6 +111,24 @@ function renderSimpleChangelog(plan) {
|
|
|
110
111
|
cwd: process.cwd(),
|
|
111
112
|
});
|
|
112
113
|
}
|
|
114
|
+
function renderPackageChangelogSection(input) {
|
|
115
|
+
const repoUrl = (0, repo_url_js_1.resolveRepositoryWebBaseUrl)(input.cwd ?? process.cwd());
|
|
116
|
+
const header = repoUrl
|
|
117
|
+
? `## [${input.nextVersion}](${repoUrl}/compare/${input.tagPrefix}-v${input.currentVersion}...${input.tagPrefix}-v${input.nextVersion}) (${formatDate()})`
|
|
118
|
+
: `## ${input.nextVersion} (${formatDate()})`;
|
|
119
|
+
const grouped = groupCommitLines(input.commits, repoUrl);
|
|
120
|
+
const sections = [];
|
|
121
|
+
if (grouped.breaking.length > 0) {
|
|
122
|
+
sections.push("### Breaking changes", ...grouped.breaking, "");
|
|
123
|
+
}
|
|
124
|
+
if (grouped.features.length > 0) {
|
|
125
|
+
sections.push("### Features", ...grouped.features, "");
|
|
126
|
+
}
|
|
127
|
+
if (grouped.fixes.length > 0) {
|
|
128
|
+
sections.push("### Bug Fixes", ...grouped.fixes, "");
|
|
129
|
+
}
|
|
130
|
+
return [header, "", ...sections].join("\n");
|
|
131
|
+
}
|
|
113
132
|
function prependChangelog(cwd, changelogFile, section) {
|
|
114
133
|
const changelogPath = node_path_1.default.join(cwd, changelogFile);
|
|
115
134
|
const existing = node_fs_1.default.existsSync(changelogPath)
|
|
@@ -22,8 +22,20 @@ function resolvePackageStrategyContext(rootConfig, packagePath, packageConfig) {
|
|
|
22
22
|
: { ...rootConfig };
|
|
23
23
|
const baseStrategy = (0, resolve_js_1.resolveVersionStrategy)(baseConfig);
|
|
24
24
|
if (!packageReleaseType) {
|
|
25
|
-
const versionFile =
|
|
26
|
-
|
|
25
|
+
const versionFile = packagePath === "."
|
|
26
|
+
? (baseConfig["version-file"] ??
|
|
27
|
+
baseStrategy.getVersionFile(baseConfig))
|
|
28
|
+
: baseStrategy.name === "simple"
|
|
29
|
+
? (baseConfig["version-file"] ??
|
|
30
|
+
baseStrategy.getVersionFile(baseConfig))
|
|
31
|
+
: node_path_1.default.posix.join(packagePath, baseStrategy.getVersionFile({
|
|
32
|
+
...baseConfig,
|
|
33
|
+
"version-file": undefined,
|
|
34
|
+
}));
|
|
35
|
+
const config = withVersionFile({
|
|
36
|
+
...baseConfig,
|
|
37
|
+
packages: undefined,
|
|
38
|
+
}, versionFile);
|
|
27
39
|
return {
|
|
28
40
|
strategy: baseStrategy,
|
|
29
41
|
config,
|
|
@@ -8,7 +8,7 @@ const node_fs_1 = __importDefault(require("node:fs"));
|
|
|
8
8
|
const node_path_1 = __importDefault(require("node:path"));
|
|
9
9
|
function readDescriptionVersion(content, versionFile) {
|
|
10
10
|
const match = content.match(/^Version:\s*(.+)\s*$/mu);
|
|
11
|
-
if (!match
|
|
11
|
+
if (!match?.[1]) {
|
|
12
12
|
throw new Error(`${versionFile} is missing a valid "Version:" field required by release-type "r".`);
|
|
13
13
|
}
|
|
14
14
|
return match[1].trim();
|
|
@@ -202,19 +202,73 @@ function collectRustTargetManifests(cwd, versionFile, includeWorkspaceMembers) {
|
|
|
202
202
|
}
|
|
203
203
|
throw new Error(`Configured rust target "${versionFile}" is not a Rust crate manifest. Expected [package].version or [workspace].members with crate Cargo.toml files.`);
|
|
204
204
|
}
|
|
205
|
-
function
|
|
206
|
-
|
|
205
|
+
function isWorkspaceInheritedVersion(rawVersion) {
|
|
206
|
+
if (!rawVersion || typeof rawVersion !== "object") {
|
|
207
|
+
return false;
|
|
208
|
+
}
|
|
209
|
+
const versionRecord = rawVersion;
|
|
210
|
+
return versionRecord.workspace === true;
|
|
211
|
+
}
|
|
212
|
+
function readWorkspacePackageVersion(cargoTomlRaw, versionFile) {
|
|
213
|
+
const { workspaceTable } = parseCargoManifest(versionFile, cargoTomlRaw);
|
|
214
|
+
if (!workspaceTable || typeof workspaceTable !== "object") {
|
|
215
|
+
throw new Error(`${versionFile} is missing [workspace.package].version required by members using version.workspace = true.`);
|
|
216
|
+
}
|
|
217
|
+
const workspacePackage = workspaceTable.package && typeof workspaceTable.package === "object"
|
|
218
|
+
? workspaceTable.package
|
|
219
|
+
: null;
|
|
220
|
+
const rawVersion = workspacePackage?.version;
|
|
221
|
+
if (typeof rawVersion !== "string" || rawVersion.trim().length === 0) {
|
|
222
|
+
throw new Error(`${versionFile} is missing [workspace.package].version required by members using version.workspace = true.`);
|
|
223
|
+
}
|
|
224
|
+
return rawVersion.trim();
|
|
225
|
+
}
|
|
226
|
+
function findWorkspaceManifestForMember(cwd, memberManifest) {
|
|
227
|
+
const cwdAbs = node_path_1.default.resolve(cwd);
|
|
228
|
+
let currentDir = node_path_1.default.resolve(cwd, node_path_1.default.dirname(memberManifest));
|
|
229
|
+
while (true) {
|
|
230
|
+
const relativeDir = node_path_1.default.relative(cwdAbs, currentDir);
|
|
231
|
+
if (relativeDir.startsWith("..")) {
|
|
232
|
+
break;
|
|
233
|
+
}
|
|
234
|
+
const candidatePath = node_path_1.default.join(currentDir, "Cargo.toml");
|
|
235
|
+
if (node_fs_1.default.existsSync(candidatePath)) {
|
|
236
|
+
const relativeManifest = normalizeSlashPath(node_path_1.default.relative(cwdAbs, candidatePath));
|
|
237
|
+
const cargoTomlRaw = node_fs_1.default.readFileSync(candidatePath, "utf8");
|
|
238
|
+
const parsed = parseCargoManifest(relativeManifest, cargoTomlRaw);
|
|
239
|
+
if (parsed.workspaceTable) {
|
|
240
|
+
return relativeManifest;
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
if (currentDir === cwdAbs) {
|
|
244
|
+
break;
|
|
245
|
+
}
|
|
246
|
+
const parentDir = node_path_1.default.dirname(currentDir);
|
|
247
|
+
if (parentDir === currentDir) {
|
|
248
|
+
break;
|
|
249
|
+
}
|
|
250
|
+
currentDir = parentDir;
|
|
251
|
+
}
|
|
252
|
+
throw new Error(`${memberManifest} uses version.workspace = true, but no workspace Cargo.toml with [workspace.package].version was found between that crate and repository root.`);
|
|
253
|
+
}
|
|
254
|
+
function readResolvedCargoVersion(cwd, manifest, cargoTomlRaw) {
|
|
255
|
+
const { packageTable } = parseCargoManifest(manifest, cargoTomlRaw);
|
|
207
256
|
if (!packageTable || typeof packageTable !== "object") {
|
|
208
|
-
throw new Error(`${
|
|
257
|
+
throw new Error(`${manifest} is missing [package].version. Add [package] with a SemVer version.`);
|
|
209
258
|
}
|
|
210
259
|
const rawVersion = packageTable.version;
|
|
211
260
|
if (rawVersion === undefined) {
|
|
212
|
-
throw new Error(`${
|
|
261
|
+
throw new Error(`${manifest} is missing [package].version. Add [package] with a SemVer version.`);
|
|
213
262
|
}
|
|
214
|
-
if (typeof rawVersion
|
|
215
|
-
|
|
263
|
+
if (typeof rawVersion === "string" && rawVersion.trim().length > 0) {
|
|
264
|
+
return rawVersion.trim();
|
|
216
265
|
}
|
|
217
|
-
|
|
266
|
+
if (!isWorkspaceInheritedVersion(rawVersion)) {
|
|
267
|
+
throw new Error(`${manifest} has invalid [package].version. Expected a non-empty SemVer string or version.workspace = true.`);
|
|
268
|
+
}
|
|
269
|
+
const workspaceManifest = findWorkspaceManifestForMember(cwd, manifest);
|
|
270
|
+
const workspaceRaw = node_fs_1.default.readFileSync(node_path_1.default.join(cwd, workspaceManifest), "utf8");
|
|
271
|
+
return readWorkspacePackageVersion(workspaceRaw, workspaceManifest);
|
|
218
272
|
}
|
|
219
273
|
function readCargoPackageName(cargoTomlRaw, versionFile) {
|
|
220
274
|
const { packageTable } = parseCargoManifest(versionFile, cargoTomlRaw);
|
|
@@ -272,6 +326,56 @@ function writeCargoVersion(cargoTomlRaw, versionFile, version) {
|
|
|
272
326
|
}
|
|
273
327
|
return updated;
|
|
274
328
|
}
|
|
329
|
+
function writeWorkspacePackageVersion(cargoTomlRaw, versionFile, version) {
|
|
330
|
+
const lineEnding = cargoTomlRaw.includes("\r\n") ? "\r\n" : "\n";
|
|
331
|
+
const hasFinalLineEnding = cargoTomlRaw.endsWith("\n") || cargoTomlRaw.endsWith("\r\n");
|
|
332
|
+
const lines = cargoTomlRaw.split(/\r?\n/u);
|
|
333
|
+
let inWorkspacePackageSection = false;
|
|
334
|
+
let foundWorkspacePackageSection = false;
|
|
335
|
+
let replacedVersion = false;
|
|
336
|
+
for (let index = 0; index < lines.length; index += 1) {
|
|
337
|
+
const line = lines[index] ?? "";
|
|
338
|
+
const sectionMatch = line.match(/^\s*\[([^\]]+)\]\s*(?:#.*)?$/u);
|
|
339
|
+
if (sectionMatch) {
|
|
340
|
+
const section = sectionMatch[1]?.trim();
|
|
341
|
+
inWorkspacePackageSection = section === "workspace.package";
|
|
342
|
+
if (inWorkspacePackageSection) {
|
|
343
|
+
foundWorkspacePackageSection = true;
|
|
344
|
+
}
|
|
345
|
+
continue;
|
|
346
|
+
}
|
|
347
|
+
if (!inWorkspacePackageSection) {
|
|
348
|
+
continue;
|
|
349
|
+
}
|
|
350
|
+
const versionMatch = line.match(/^(\s*version\s*=\s*)(["'])([^"']*)(\2)(\s*(?:#.*)?)?$/u);
|
|
351
|
+
if (!versionMatch) {
|
|
352
|
+
continue;
|
|
353
|
+
}
|
|
354
|
+
const [, prefix = "", quote = '"', , , suffix = ""] = versionMatch;
|
|
355
|
+
lines[index] = `${prefix}${quote}${version}${quote}${suffix}`;
|
|
356
|
+
replacedVersion = true;
|
|
357
|
+
break;
|
|
358
|
+
}
|
|
359
|
+
if (!foundWorkspacePackageSection || !replacedVersion) {
|
|
360
|
+
throw new Error(`${versionFile} is missing [workspace.package].version required by members using version.workspace = true.`);
|
|
361
|
+
}
|
|
362
|
+
let updated = lines.join(lineEnding);
|
|
363
|
+
if (hasFinalLineEnding && !updated.endsWith(lineEnding)) {
|
|
364
|
+
updated += lineEnding;
|
|
365
|
+
}
|
|
366
|
+
if (!hasFinalLineEnding && updated.endsWith(lineEnding)) {
|
|
367
|
+
updated = updated.slice(0, -lineEnding.length);
|
|
368
|
+
}
|
|
369
|
+
return updated;
|
|
370
|
+
}
|
|
371
|
+
function usesWorkspaceInheritedVersion(cargoTomlRaw, versionFile) {
|
|
372
|
+
const { packageTable } = parseCargoManifest(versionFile, cargoTomlRaw);
|
|
373
|
+
if (!packageTable || typeof packageTable !== "object") {
|
|
374
|
+
return false;
|
|
375
|
+
}
|
|
376
|
+
const rawVersion = packageTable.version;
|
|
377
|
+
return isWorkspaceInheritedVersion(rawVersion);
|
|
378
|
+
}
|
|
275
379
|
function isDependencySection(section) {
|
|
276
380
|
if (ROOT_DEPENDENCY_SECTIONS.has(section)) {
|
|
277
381
|
return true;
|
|
@@ -450,13 +554,14 @@ exports.rustVersionStrategy = {
|
|
|
450
554
|
throw new Error(`Configured rust target "${versionFile}" did not resolve to a Rust crate manifest.`);
|
|
451
555
|
}
|
|
452
556
|
const cargoTomlRaw = node_fs_1.default.readFileSync(node_path_1.default.join(cwd, selectedManifest), "utf8");
|
|
453
|
-
return
|
|
557
|
+
return readResolvedCargoVersion(cwd, selectedManifest, cargoTomlRaw);
|
|
454
558
|
},
|
|
455
559
|
writeVersion(cwd, config, version) {
|
|
456
560
|
const versionFile = this.getVersionFile(config);
|
|
457
561
|
const manifests = collectRustTargetManifests(cwd, versionFile, !config.packages);
|
|
458
562
|
const updatedFiles = [];
|
|
459
563
|
const internalCrates = new Set();
|
|
564
|
+
const workspaceManifestsToUpdate = new Set();
|
|
460
565
|
for (const manifest of manifests) {
|
|
461
566
|
const versionPath = node_path_1.default.join(cwd, manifest);
|
|
462
567
|
if (!node_fs_1.default.existsSync(versionPath)) {
|
|
@@ -467,6 +572,7 @@ exports.rustVersionStrategy = {
|
|
|
467
572
|
continue;
|
|
468
573
|
}
|
|
469
574
|
internalCrates.add(readCargoPackageName(cargoTomlRaw, manifest));
|
|
575
|
+
readResolvedCargoVersion(cwd, manifest, cargoTomlRaw);
|
|
470
576
|
}
|
|
471
577
|
for (const manifest of manifests) {
|
|
472
578
|
const versionPath = node_path_1.default.join(cwd, manifest);
|
|
@@ -477,10 +583,30 @@ exports.rustVersionStrategy = {
|
|
|
477
583
|
if (!isCrateManifest(manifest, cargoTomlRaw)) {
|
|
478
584
|
continue;
|
|
479
585
|
}
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
586
|
+
let updatedCargoToml = cargoTomlRaw;
|
|
587
|
+
if (usesWorkspaceInheritedVersion(cargoTomlRaw, manifest)) {
|
|
588
|
+
workspaceManifestsToUpdate.add(findWorkspaceManifestForMember(cwd, manifest));
|
|
589
|
+
}
|
|
590
|
+
else {
|
|
591
|
+
updatedCargoToml = writeCargoVersion(updatedCargoToml, manifest, version);
|
|
592
|
+
}
|
|
593
|
+
updatedCargoToml = writeInternalDependencyVersions(updatedCargoToml, internalCrates, version);
|
|
594
|
+
if (updatedCargoToml !== cargoTomlRaw) {
|
|
595
|
+
node_fs_1.default.writeFileSync(versionPath, updatedCargoToml, "utf8");
|
|
596
|
+
updatedFiles.push(manifest);
|
|
597
|
+
}
|
|
598
|
+
}
|
|
599
|
+
for (const workspaceManifest of workspaceManifestsToUpdate) {
|
|
600
|
+
const workspacePath = node_path_1.default.join(cwd, workspaceManifest);
|
|
601
|
+
if (!node_fs_1.default.existsSync(workspacePath)) {
|
|
602
|
+
continue;
|
|
603
|
+
}
|
|
604
|
+
const workspaceRaw = node_fs_1.default.readFileSync(workspacePath, "utf8");
|
|
605
|
+
const updatedWorkspaceToml = writeInternalDependencyVersions(writeWorkspacePackageVersion(workspaceRaw, workspaceManifest, version), internalCrates, version);
|
|
606
|
+
if (updatedWorkspaceToml !== workspaceRaw) {
|
|
607
|
+
node_fs_1.default.writeFileSync(workspacePath, updatedWorkspaceToml, "utf8");
|
|
608
|
+
updatedFiles.push(workspaceManifest);
|
|
609
|
+
}
|
|
484
610
|
}
|
|
485
611
|
if (updatedFiles.length === 0) {
|
|
486
612
|
throw new Error(`Configured rust target "${versionFile}" did not resolve to a Rust crate manifest to update.`);
|
package/dist/types/config.d.ts
CHANGED