versionary 0.6.0 → 0.7.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 +45 -24
- package/dist/app/release/pr.js +22 -1
- package/dist/config/schema.d.ts +1 -0
- package/dist/config/schema.js +15 -6
- package/dist/types/config.d.ts +1 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -125,6 +125,8 @@ Current rust auto-update behavior (phase scope):
|
|
|
125
125
|
- updates crate versions in each targeted crate `[package].version`
|
|
126
126
|
- updates internal workspace dependency versions when the dependency name
|
|
127
127
|
matches another targeted crate name
|
|
128
|
+
- refreshes `Cargo.lock` via `cargo generate-lockfile` when `Cargo.lock` exists
|
|
129
|
+
in repo root
|
|
128
130
|
- applies dependency version rewrites in:
|
|
129
131
|
- `[dependencies]`, `[dev-dependencies]`, `[build-dependencies]`
|
|
130
132
|
- `[target.*.dependencies]`, `[target.*.dev-dependencies]`,
|
|
@@ -137,6 +139,8 @@ Current rust non-goals/limits:
|
|
|
137
139
|
- does not add missing `version = ...` fields to dependency inline tables
|
|
138
140
|
- does not perform Cargo publish/release to crates.io
|
|
139
141
|
|
|
142
|
+
If `Cargo.lock` exists, `cargo` must be available in PATH during PR preparation.
|
|
143
|
+
|
|
140
144
|
### Monorepo release names and tag naming
|
|
141
145
|
|
|
142
146
|
For independent monorepo targets, Versionary derives release tags as:
|
|
@@ -11,25 +11,25 @@ const yaml_1 = __importDefault(require("yaml"));
|
|
|
11
11
|
function isRecord(value) {
|
|
12
12
|
return Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
|
13
13
|
}
|
|
14
|
-
function
|
|
15
|
-
if (!
|
|
16
|
-
throw new Error(`Invalid
|
|
14
|
+
function parseFieldPath(fieldPath) {
|
|
15
|
+
if (!fieldPath.startsWith("$")) {
|
|
16
|
+
throw new Error(`Invalid field-path "${fieldPath}". Must start with "$".`);
|
|
17
17
|
}
|
|
18
18
|
const tokens = [];
|
|
19
19
|
let index = 1;
|
|
20
|
-
while (index <
|
|
21
|
-
const current =
|
|
20
|
+
while (index < fieldPath.length) {
|
|
21
|
+
const current = fieldPath[index];
|
|
22
22
|
if (current === ".") {
|
|
23
|
-
const keyMatch =
|
|
23
|
+
const keyMatch = fieldPath.slice(index + 1).match(/^[A-Za-z0-9_-]+/u);
|
|
24
24
|
if (!keyMatch) {
|
|
25
|
-
throw new Error(`Invalid
|
|
25
|
+
throw new Error(`Invalid field-path "${fieldPath}" near index ${index}.`);
|
|
26
26
|
}
|
|
27
27
|
tokens.push(keyMatch[0]);
|
|
28
28
|
index += 1 + keyMatch[0].length;
|
|
29
29
|
continue;
|
|
30
30
|
}
|
|
31
31
|
if (current === "[") {
|
|
32
|
-
const rest =
|
|
32
|
+
const rest = fieldPath.slice(index + 1);
|
|
33
33
|
const numberMatch = rest.match(/^(\d+)\]/u);
|
|
34
34
|
if (numberMatch) {
|
|
35
35
|
tokens.push(Number(numberMatch[1]));
|
|
@@ -42,53 +42,60 @@ function parseJsonPath(jsonpath) {
|
|
|
42
42
|
index += 4 + keyMatch[1].length;
|
|
43
43
|
continue;
|
|
44
44
|
}
|
|
45
|
-
throw new Error(`Invalid
|
|
45
|
+
throw new Error(`Invalid field-path "${fieldPath}" near index ${index}.`);
|
|
46
46
|
}
|
|
47
|
-
throw new Error(`Invalid
|
|
47
|
+
throw new Error(`Invalid field-path "${fieldPath}" near index ${index}.`);
|
|
48
48
|
}
|
|
49
49
|
if (tokens.length === 0) {
|
|
50
|
-
throw new Error(`Invalid
|
|
50
|
+
throw new Error(`Invalid field-path "${fieldPath}". Path must target a field.`);
|
|
51
51
|
}
|
|
52
52
|
return tokens;
|
|
53
53
|
}
|
|
54
|
-
function setVersionAtJsonPath(document,
|
|
55
|
-
const tokens =
|
|
54
|
+
function setVersionAtJsonPath(document, fieldPath, version) {
|
|
55
|
+
const tokens = parseFieldPath(fieldPath);
|
|
56
56
|
let cursor = document;
|
|
57
57
|
for (let index = 0; index < tokens.length - 1; index += 1) {
|
|
58
58
|
const token = tokens[index];
|
|
59
59
|
if (typeof token === "number") {
|
|
60
60
|
if (!Array.isArray(cursor) || token >= cursor.length) {
|
|
61
|
-
throw new Error(`
|
|
61
|
+
throw new Error(`field-path "${fieldPath}" does not resolve to an existing field.`);
|
|
62
62
|
}
|
|
63
63
|
cursor = cursor[token];
|
|
64
64
|
continue;
|
|
65
65
|
}
|
|
66
66
|
if (!isRecord(cursor) || !(token in cursor)) {
|
|
67
|
-
throw new Error(`
|
|
67
|
+
throw new Error(`field-path "${fieldPath}" does not resolve to an existing field.`);
|
|
68
68
|
}
|
|
69
69
|
cursor = cursor[token];
|
|
70
70
|
}
|
|
71
71
|
const leaf = tokens.at(-1);
|
|
72
72
|
if (typeof leaf === "number") {
|
|
73
73
|
if (!Array.isArray(cursor) || leaf >= cursor.length) {
|
|
74
|
-
throw new Error(`
|
|
74
|
+
throw new Error(`field-path "${fieldPath}" does not resolve to an existing field.`);
|
|
75
75
|
}
|
|
76
76
|
const current = cursor[leaf];
|
|
77
77
|
if (typeof current !== "string" && typeof current !== "number") {
|
|
78
|
-
throw new Error(`
|
|
78
|
+
throw new Error(`field-path "${fieldPath}" must point to a string or number field for version updates.`);
|
|
79
79
|
}
|
|
80
80
|
cursor[leaf] = version;
|
|
81
81
|
return;
|
|
82
82
|
}
|
|
83
83
|
if (!isRecord(cursor) || !(leaf in cursor)) {
|
|
84
|
-
throw new Error(`
|
|
84
|
+
throw new Error(`field-path "${fieldPath}" does not resolve to an existing field.`);
|
|
85
85
|
}
|
|
86
86
|
const current = cursor[leaf];
|
|
87
87
|
if (typeof current !== "string" && typeof current !== "number") {
|
|
88
|
-
throw new Error(`
|
|
88
|
+
throw new Error(`field-path "${fieldPath}" must point to a string or number field for version updates.`);
|
|
89
89
|
}
|
|
90
90
|
cursor[leaf] = version;
|
|
91
91
|
}
|
|
92
|
+
function resolveFieldPath(rule) {
|
|
93
|
+
const fieldPath = rule["field-path"] ?? rule.jsonpath;
|
|
94
|
+
if (!fieldPath) {
|
|
95
|
+
throw new Error(`${rule.type} artifact rules require "field-path" (or deprecated "jsonpath").`);
|
|
96
|
+
}
|
|
97
|
+
return fieldPath;
|
|
98
|
+
}
|
|
92
99
|
function parseRegexPattern(pattern) {
|
|
93
100
|
const slashPattern = pattern.match(/^\/((?:\\\/|[^/])+)\/([a-z]*)$/u);
|
|
94
101
|
if (slashPattern) {
|
|
@@ -116,22 +123,36 @@ function applyRegexRule(content, pattern, version) {
|
|
|
116
123
|
const replacement = typeof groupOne === "string" ? full.replace(groupOne, version) : version;
|
|
117
124
|
return `${content.slice(0, start)}${replacement}${content.slice(start + full.length)}`;
|
|
118
125
|
}
|
|
126
|
+
function applyTomlRulePreservingFormatting(content, fieldPath, version) {
|
|
127
|
+
const simplePath = fieldPath.match(/^\$\.([A-Za-z0-9_-]+)$/u);
|
|
128
|
+
if (!simplePath) {
|
|
129
|
+
const parsed = toml_1.default.parse(content);
|
|
130
|
+
setVersionAtJsonPath(parsed, fieldPath, version);
|
|
131
|
+
return `${toml_1.default.stringify(parsed)}\n`;
|
|
132
|
+
}
|
|
133
|
+
const key = simplePath[1];
|
|
134
|
+
const linePattern = new RegExp(`^(\\s*${key}\\s*=\\s*)(["'])([^"']*)(\\2)(\\s*(?:#.*)?)$`, "mu");
|
|
135
|
+
const match = content.match(linePattern);
|
|
136
|
+
if (!match) {
|
|
137
|
+
throw new Error(`field-path "${fieldPath}" does not resolve to an existing field.`);
|
|
138
|
+
}
|
|
139
|
+
const [, prefix = "", quote = '"', , , suffix = ""] = match;
|
|
140
|
+
return content.replace(linePattern, `${prefix}${quote}${version}${quote}${suffix}`);
|
|
141
|
+
}
|
|
119
142
|
function applyArtifactRuleToContent(content, rule, version) {
|
|
120
143
|
if (rule.type === "regex") {
|
|
121
144
|
return applyRegexRule(content, rule.pattern, version);
|
|
122
145
|
}
|
|
123
146
|
if (rule.type === "json") {
|
|
124
147
|
const parsed = JSON.parse(content);
|
|
125
|
-
setVersionAtJsonPath(parsed, rule
|
|
148
|
+
setVersionAtJsonPath(parsed, resolveFieldPath(rule), version);
|
|
126
149
|
return `${JSON.stringify(parsed, null, 2)}\n`;
|
|
127
150
|
}
|
|
128
151
|
if (rule.type === "toml") {
|
|
129
|
-
|
|
130
|
-
setVersionAtJsonPath(parsed, rule.jsonpath, version);
|
|
131
|
-
return `${toml_1.default.stringify(parsed)}\n`;
|
|
152
|
+
return applyTomlRulePreservingFormatting(content, resolveFieldPath(rule), version);
|
|
132
153
|
}
|
|
133
154
|
const parsed = yaml_1.default.parse(content);
|
|
134
|
-
setVersionAtJsonPath(parsed, rule
|
|
155
|
+
setVersionAtJsonPath(parsed, resolveFieldPath(rule), version);
|
|
135
156
|
return `${yaml_1.default.stringify(parsed)}`;
|
|
136
157
|
}
|
|
137
158
|
function normalizeRelative(base, target) {
|
package/dist/app/release/pr.js
CHANGED
|
@@ -70,6 +70,25 @@ function ensureCleanWorktree(cwd, logger) {
|
|
|
70
70
|
logger?.warn(`Ignoring safe tracked changes before versionary pr:\n${ignored.join("\n")}`);
|
|
71
71
|
}
|
|
72
72
|
}
|
|
73
|
+
function ensureCargoLockUpToDate(cwd) {
|
|
74
|
+
const lockfilePath = node_path_1.default.join(cwd, "Cargo.lock");
|
|
75
|
+
if (!node_fs_1.default.existsSync(lockfilePath)) {
|
|
76
|
+
return [];
|
|
77
|
+
}
|
|
78
|
+
const before = node_fs_1.default.readFileSync(lockfilePath, "utf8");
|
|
79
|
+
try {
|
|
80
|
+
(0, node_child_process_1.execFileSync)("cargo", ["generate-lockfile"], {
|
|
81
|
+
cwd,
|
|
82
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
catch (error) {
|
|
86
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
87
|
+
throw new Error(`Failed to refresh Cargo.lock via "cargo generate-lockfile". Ensure cargo is installed and available in PATH. Details: ${message}`);
|
|
88
|
+
}
|
|
89
|
+
const after = node_fs_1.default.readFileSync(lockfilePath, "utf8");
|
|
90
|
+
return after !== before ? ["Cargo.lock"] : [];
|
|
91
|
+
}
|
|
73
92
|
function normalizeReleaseNameForTag(releaseName) {
|
|
74
93
|
return releaseName
|
|
75
94
|
.trim()
|
|
@@ -195,6 +214,7 @@ function prepareSimpleReleasePr(cwd = process.cwd(), options = {}) {
|
|
|
195
214
|
updatedVersionFiles.push(...strategy.writeVersion(cwd, loaded.config, plan.nextVersion));
|
|
196
215
|
}
|
|
197
216
|
const updatedArtifactFiles = (0, artifact_rules_js_1.applyConfiguredArtifactRules)(cwd, loaded.config, plan);
|
|
217
|
+
const updatedRustLockFiles = ensureCargoLockUpToDate(cwd);
|
|
198
218
|
const section = (0, changelog_js_1.renderSimpleChangelog)(plan);
|
|
199
219
|
(0, changelog_js_1.prependChangelog)(cwd, plan.changelogFile, section);
|
|
200
220
|
const releaseTargets = buildReleaseTargets(cwd, plan, loaded.config);
|
|
@@ -208,6 +228,7 @@ function prepareSimpleReleasePr(cwd = process.cwd(), options = {}) {
|
|
|
208
228
|
...new Set([
|
|
209
229
|
...updatedVersionFiles,
|
|
210
230
|
...updatedArtifactFiles,
|
|
231
|
+
...updatedRustLockFiles,
|
|
211
232
|
plan.changelogFile,
|
|
212
233
|
]),
|
|
213
234
|
];
|
|
@@ -307,5 +328,5 @@ function pushReleaseBranch(cwd, branch) {
|
|
|
307
328
|
});
|
|
308
329
|
}
|
|
309
330
|
function isReleaseCommitMessage(subject) {
|
|
310
|
-
return /^chore\(release\):\s+(?:v\d+\.\d+\.\d+|\S+-v\d+\.\d+\.\d+)(?:,\s+(?:v\d+\.\d+\.\d+|\S+-v\d+\.\d+\.\d+))
|
|
331
|
+
return /^chore\(release\):\s+(?:v\d+\.\d+\.\d+|\S+-v\d+\.\d+\.\d+)(?:,\s+(?:v\d+\.\d+\.\d+|\S+-v\d+\.\d+\.\d+))*(?:\s+\(#\d+\))?$/u.test(subject);
|
|
311
332
|
}
|
package/dist/config/schema.d.ts
CHANGED
package/dist/config/schema.js
CHANGED
|
@@ -6,16 +6,18 @@ const artifactRuleSchema = zod_1.z
|
|
|
6
6
|
.object({
|
|
7
7
|
type: zod_1.z.enum(["json", "toml", "yaml", "regex"]),
|
|
8
8
|
path: zod_1.z.string().min(1),
|
|
9
|
+
"field-path": zod_1.z.string().optional(),
|
|
9
10
|
jsonpath: zod_1.z.string().optional(),
|
|
10
11
|
pattern: zod_1.z.string().optional(),
|
|
11
12
|
})
|
|
12
13
|
.superRefine((value, ctx) => {
|
|
13
14
|
const needsJsonPath = value.type === "json" || value.type === "toml" || value.type === "yaml";
|
|
14
|
-
|
|
15
|
+
const hasFieldPath = Boolean(value["field-path"] ?? value.jsonpath);
|
|
16
|
+
if (needsJsonPath && !hasFieldPath) {
|
|
15
17
|
ctx.addIssue({
|
|
16
18
|
code: zod_1.z.ZodIssueCode.custom,
|
|
17
|
-
message: `${value.type} artifact rules require "jsonpath".`,
|
|
18
|
-
path: ["
|
|
19
|
+
message: `${value.type} artifact rules require "field-path" (or deprecated "jsonpath").`,
|
|
20
|
+
path: ["field-path"],
|
|
19
21
|
});
|
|
20
22
|
}
|
|
21
23
|
if (needsJsonPath && value.pattern) {
|
|
@@ -25,6 +27,13 @@ const artifactRuleSchema = zod_1.z
|
|
|
25
27
|
path: ["pattern"],
|
|
26
28
|
});
|
|
27
29
|
}
|
|
30
|
+
if (value["field-path"] && value.jsonpath) {
|
|
31
|
+
ctx.addIssue({
|
|
32
|
+
code: zod_1.z.ZodIssueCode.custom,
|
|
33
|
+
message: 'Specify only one of "field-path" or deprecated "jsonpath".',
|
|
34
|
+
path: ["field-path"],
|
|
35
|
+
});
|
|
36
|
+
}
|
|
28
37
|
if (value.type === "regex" && !value.pattern) {
|
|
29
38
|
ctx.addIssue({
|
|
30
39
|
code: zod_1.z.ZodIssueCode.custom,
|
|
@@ -32,11 +41,11 @@ const artifactRuleSchema = zod_1.z
|
|
|
32
41
|
path: ["pattern"],
|
|
33
42
|
});
|
|
34
43
|
}
|
|
35
|
-
if (value.type === "regex" && value.jsonpath) {
|
|
44
|
+
if (value.type === "regex" && (value["field-path"] || value.jsonpath)) {
|
|
36
45
|
ctx.addIssue({
|
|
37
46
|
code: zod_1.z.ZodIssueCode.custom,
|
|
38
|
-
message: 'regex artifact rules do not support "jsonpath".',
|
|
39
|
-
path: ["
|
|
47
|
+
message: 'regex artifact rules do not support "field-path" or deprecated "jsonpath".',
|
|
48
|
+
path: ["field-path"],
|
|
40
49
|
});
|
|
41
50
|
}
|
|
42
51
|
});
|
package/dist/types/config.d.ts
CHANGED