versionary 0.31.0 → 1.0.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 +93 -435
- package/dist/action/index.js +2 -2
- package/dist/cli/index.js +2 -7
- package/dist/config/schema.d.ts +5 -7
- package/dist/config/schema.js +11 -13
- package/dist/release/artifact-rules.d.ts +2 -2
- package/dist/release/artifact-rules.js +43 -8
- package/dist/release/changelog.d.ts +1 -3
- package/dist/release/changelog.js +29 -9
- package/dist/release/plan.d.ts +3 -5
- package/dist/release/plan.js +221 -65
- package/dist/release/pr.d.ts +7 -26
- package/dist/release/pr.js +13 -64
- package/dist/release/release-as.d.ts +29 -0
- package/dist/release/release-as.js +70 -0
- package/dist/release/release.d.ts +0 -8
- package/dist/release/release.js +0 -8
- package/dist/release/state.js +3 -12
- package/dist/strategy/composite.js +11 -0
- package/dist/strategy/node.js +15 -0
- package/dist/strategy/rust.js +50 -3
- package/dist/strategy/types.d.ts +11 -0
- package/dist/types/config.d.ts +2 -3
- package/package.json +10 -5
package/dist/config/schema.js
CHANGED
|
@@ -4,34 +4,33 @@ const artifactRuleSchema = z
|
|
|
4
4
|
type: z.enum(["json", "toml", "yaml", "nix", "regex"]),
|
|
5
5
|
path: z.string().min(1),
|
|
6
6
|
"field-path": z.string().optional(),
|
|
7
|
-
jsonpath: z.string().optional(),
|
|
8
7
|
pattern: z.string().optional(),
|
|
8
|
+
replacement: z.string().optional(),
|
|
9
9
|
})
|
|
10
10
|
.superRefine((value, ctx) => {
|
|
11
|
-
const
|
|
11
|
+
const needsFieldPath = value.type === "json" ||
|
|
12
12
|
value.type === "toml" ||
|
|
13
13
|
value.type === "yaml" ||
|
|
14
14
|
value.type === "nix";
|
|
15
|
-
|
|
16
|
-
if (needsJsonPath && !hasFieldPath) {
|
|
15
|
+
if (needsFieldPath && !value["field-path"]) {
|
|
17
16
|
ctx.addIssue({
|
|
18
17
|
code: z.ZodIssueCode.custom,
|
|
19
|
-
message: `${value.type} artifact rules require "field-path"
|
|
18
|
+
message: `${value.type} artifact rules require "field-path".`,
|
|
20
19
|
path: ["field-path"],
|
|
21
20
|
});
|
|
22
21
|
}
|
|
23
|
-
if (
|
|
22
|
+
if (needsFieldPath && value.pattern) {
|
|
24
23
|
ctx.addIssue({
|
|
25
24
|
code: z.ZodIssueCode.custom,
|
|
26
25
|
message: `${value.type} artifact rules do not support "pattern".`,
|
|
27
26
|
path: ["pattern"],
|
|
28
27
|
});
|
|
29
28
|
}
|
|
30
|
-
if (
|
|
29
|
+
if (needsFieldPath && value.replacement) {
|
|
31
30
|
ctx.addIssue({
|
|
32
31
|
code: z.ZodIssueCode.custom,
|
|
33
|
-
message:
|
|
34
|
-
path: ["
|
|
32
|
+
message: `${value.type} artifact rules do not support "replacement".`,
|
|
33
|
+
path: ["replacement"],
|
|
35
34
|
});
|
|
36
35
|
}
|
|
37
36
|
if (value.type === "regex" && !value.pattern) {
|
|
@@ -41,10 +40,10 @@ const artifactRuleSchema = z
|
|
|
41
40
|
path: ["pattern"],
|
|
42
41
|
});
|
|
43
42
|
}
|
|
44
|
-
if (value.type === "regex" &&
|
|
43
|
+
if (value.type === "regex" && value["field-path"]) {
|
|
45
44
|
ctx.addIssue({
|
|
46
45
|
code: z.ZodIssueCode.custom,
|
|
47
|
-
message: 'regex artifact rules do not support "field-path"
|
|
46
|
+
message: 'regex artifact rules do not support "field-path".',
|
|
48
47
|
path: ["field-path"],
|
|
49
48
|
});
|
|
50
49
|
}
|
|
@@ -67,7 +66,7 @@ export const configSchema = z
|
|
|
67
66
|
.object({
|
|
68
67
|
$schema: z.string().optional(),
|
|
69
68
|
version: z.literal(1),
|
|
70
|
-
"review-mode": z.enum(["direct", "pr"
|
|
69
|
+
"review-mode": z.enum(["direct", "pr"]).optional(),
|
|
71
70
|
"version-file": z.string().optional(),
|
|
72
71
|
"changelog-file": z.string().optional(),
|
|
73
72
|
"changelog-format": z.enum(["markdown-changelog", "r-news"]).optional(),
|
|
@@ -77,7 +76,6 @@ export const configSchema = z
|
|
|
77
76
|
.optional(),
|
|
78
77
|
"release-branch": z.string().optional(),
|
|
79
78
|
"baseline-file": z.string().optional(),
|
|
80
|
-
"next-release-file": z.string().optional(),
|
|
81
79
|
"bootstrap-sha": z.string().optional(),
|
|
82
80
|
"monorepo-mode": z.enum(["independent", "fixed"]).optional(),
|
|
83
81
|
"bump-minor-pre-major": z.boolean().optional(),
|
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
import type { VersionaryConfig } from "../types/config.js";
|
|
2
|
-
import type {
|
|
3
|
-
export declare function applyConfiguredArtifactRules(cwd: string, config: VersionaryConfig, plan:
|
|
2
|
+
import type { ReleasePlan } from "./plan.js";
|
|
3
|
+
export declare function applyConfiguredArtifactRules(cwd: string, config: VersionaryConfig, plan: ReleasePlan): string[];
|
|
@@ -2,6 +2,7 @@ import fs from "node:fs";
|
|
|
2
2
|
import path from "node:path";
|
|
3
3
|
import { parse as parseToml, stringify as stringifyToml } from "smol-toml";
|
|
4
4
|
import YAML from "yaml";
|
|
5
|
+
import { parseVersion } from "./semver.js";
|
|
5
6
|
const WILDCARD = Symbol("wildcard");
|
|
6
7
|
function isRecord(value) {
|
|
7
8
|
return Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
|
@@ -142,9 +143,9 @@ function setVersionAtJsonPath(document, fieldPath, version) {
|
|
|
142
143
|
}
|
|
143
144
|
}
|
|
144
145
|
function resolveFieldPath(rule) {
|
|
145
|
-
const fieldPath = rule["field-path"]
|
|
146
|
+
const fieldPath = rule["field-path"];
|
|
146
147
|
if (!fieldPath) {
|
|
147
|
-
throw new Error(`${rule.type} artifact rules require "field-path"
|
|
148
|
+
throw new Error(`${rule.type} artifact rules require "field-path".`);
|
|
148
149
|
}
|
|
149
150
|
return fieldPath;
|
|
150
151
|
}
|
|
@@ -160,12 +161,34 @@ function parseRegexPattern(pattern) {
|
|
|
160
161
|
}
|
|
161
162
|
return new RegExp(pattern, "m");
|
|
162
163
|
}
|
|
163
|
-
|
|
164
|
+
const REPLACEMENT_TOKEN_PATTERN = /\{\{\s*([A-Za-z]+)\s*\}\}/gu;
|
|
165
|
+
function renderReplacementTemplate(template, version) {
|
|
166
|
+
const parsed = parseVersion(version);
|
|
167
|
+
const tokens = {
|
|
168
|
+
version,
|
|
169
|
+
major: String(parsed.major),
|
|
170
|
+
minor: String(parsed.minor),
|
|
171
|
+
patch: String(parsed.patch),
|
|
172
|
+
prerelease: parsed.prerelease.join("."),
|
|
173
|
+
build: parsed.build.join("."),
|
|
174
|
+
};
|
|
175
|
+
return template.replace(REPLACEMENT_TOKEN_PATTERN, (_whole, name) => {
|
|
176
|
+
const key = name.toLowerCase();
|
|
177
|
+
const value = tokens[key];
|
|
178
|
+
if (value === undefined) {
|
|
179
|
+
throw new Error(`Unknown replacement token "{{${name}}}". Supported tokens: ${Object.keys(tokens)
|
|
180
|
+
.map((token) => `{{${token}}}`)
|
|
181
|
+
.join(", ")}.`);
|
|
182
|
+
}
|
|
183
|
+
return value;
|
|
184
|
+
});
|
|
185
|
+
}
|
|
186
|
+
function applyRegexRule(content, pattern, version, replacementTemplate) {
|
|
164
187
|
const regex = parseRegexPattern(pattern);
|
|
165
188
|
const matchFlags = regex.flags.includes("g")
|
|
166
189
|
? regex.flags
|
|
167
190
|
: `${regex.flags}g`;
|
|
168
|
-
const globalRegex = new RegExp(regex.source, matchFlags);
|
|
191
|
+
const globalRegex = new RegExp(regex.source, matchFlags.includes("d") ? matchFlags : `${matchFlags}d`);
|
|
169
192
|
const matches = [...content.matchAll(globalRegex)];
|
|
170
193
|
if (matches.length !== 1) {
|
|
171
194
|
throw new Error(`Regex pattern must match exactly one occurrence; matched ${matches.length}.`);
|
|
@@ -179,9 +202,21 @@ function applyRegexRule(content, pattern, version) {
|
|
|
179
202
|
throw new Error("Regex match did not include an index.");
|
|
180
203
|
}
|
|
181
204
|
const full = match[0];
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
205
|
+
// With a replacement template, render it and replace the entire match.
|
|
206
|
+
if (replacementTemplate !== undefined) {
|
|
207
|
+
const rendered = renderReplacementTemplate(replacementTemplate, version);
|
|
208
|
+
return `${content.slice(0, start)}${rendered}${content.slice(start + full.length)}`;
|
|
209
|
+
}
|
|
210
|
+
// Without a template: substitute the full version into the first capture
|
|
211
|
+
// group, leaving the rest of the match intact. Splice by group indices rather than
|
|
212
|
+
// `String.replace` so literal `$` sequences and repeated group content are
|
|
213
|
+
// handled correctly.
|
|
214
|
+
const groupIndices = match.indices?.[1];
|
|
215
|
+
if (!groupIndices) {
|
|
216
|
+
return `${content.slice(0, start)}${version}${content.slice(start + full.length)}`;
|
|
217
|
+
}
|
|
218
|
+
const [groupStart, groupEnd] = groupIndices;
|
|
219
|
+
return `${content.slice(0, groupStart)}${version}${content.slice(groupEnd)}`;
|
|
185
220
|
}
|
|
186
221
|
function applyTomlRulePreservingFormatting(content, fieldPath, version) {
|
|
187
222
|
const simplePath = fieldPath.match(/^\$\.([A-Za-z0-9_-]+)$/u);
|
|
@@ -368,7 +403,7 @@ function applyArtifactRuleToContent(content, rule, version) {
|
|
|
368
403
|
if (!rule.pattern) {
|
|
369
404
|
throw new Error('regex artifact rules require "pattern".');
|
|
370
405
|
}
|
|
371
|
-
return applyRegexRule(content, rule.pattern, version);
|
|
406
|
+
return applyRegexRule(content, rule.pattern, version, rule.replacement);
|
|
372
407
|
}
|
|
373
408
|
if (rule.type === "json") {
|
|
374
409
|
const parsed = JSON.parse(content);
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { type ParsedCommit } from "../git/commits.js";
|
|
2
2
|
import type { VersionaryChangelogFormat } from "../types/config.js";
|
|
3
|
-
import { type ReleasePlan
|
|
3
|
+
import { type ReleasePlan } from "./plan.js";
|
|
4
4
|
export declare function renderReleaseNotesSection(input: {
|
|
5
5
|
currentVersion: string;
|
|
6
6
|
nextVersion: string;
|
|
@@ -23,8 +23,6 @@ export declare function renderReleasePlanChangelog(plan: ReleasePlan, options?:
|
|
|
23
23
|
cwd?: string;
|
|
24
24
|
highlights?: string;
|
|
25
25
|
}): string;
|
|
26
|
-
/** @deprecated Use renderReleasePlanChangelog. */
|
|
27
|
-
export declare function renderSimpleChangelog(plan: SimplePlan): string;
|
|
28
26
|
/**
|
|
29
27
|
* Locate a manual-notes block at the top of an existing changelog and split it
|
|
30
28
|
* out. The notes block is the first release-level heading whose text is not a
|
|
@@ -2,7 +2,7 @@ import fs from "node:fs";
|
|
|
2
2
|
import path from "node:path";
|
|
3
3
|
import { applyRevertSuppression, inferReleaseTypeFromParsedCommit, parseConventionalCommitMessage, } from "../git/commits.js";
|
|
4
4
|
import { resolveRepositoryWebBaseUrl } from "../git/repo-url.js";
|
|
5
|
-
import { resolvePackageDependencies
|
|
5
|
+
import { resolvePackageDependencies } from "./plan.js";
|
|
6
6
|
import { isVersionHeading } from "./semver.js";
|
|
7
7
|
const REVIEW_REQUEST_FOOTER = "---\n\nThis PR was generated by [Versionary](https://github.com/jolars/versionary).";
|
|
8
8
|
function formatDate() {
|
|
@@ -68,6 +68,7 @@ function groupCommitLines(commits, repoUrl) {
|
|
|
68
68
|
const fixes = [];
|
|
69
69
|
const performance = [];
|
|
70
70
|
const reverts = [];
|
|
71
|
+
const other = [];
|
|
71
72
|
const effectiveCommits = applyRevertSuppression(commits);
|
|
72
73
|
const getRevertedSubject = (commit) => {
|
|
73
74
|
const normalizedDescription = (commit.description ?? "")
|
|
@@ -93,9 +94,6 @@ function groupCommitLines(commits, repoUrl) {
|
|
|
93
94
|
};
|
|
94
95
|
for (const commit of effectiveCommits) {
|
|
95
96
|
const type = inferReleaseTypeFromParsedCommit(commit);
|
|
96
|
-
if (!type) {
|
|
97
|
-
continue;
|
|
98
|
-
}
|
|
99
97
|
const { label, message } = formatCommitMessage(commit.subject);
|
|
100
98
|
const short = commit.hash.slice(0, 7);
|
|
101
99
|
const hashLabel = repoUrl
|
|
@@ -106,6 +104,10 @@ function groupCommitLines(commits, repoUrl) {
|
|
|
106
104
|
const line = `- ${label}${message} (${hashLabel})${referencesSuffix}`;
|
|
107
105
|
const commitType = (commit.type ?? "").toLowerCase();
|
|
108
106
|
const isBreaking = type === "major";
|
|
107
|
+
if (!type) {
|
|
108
|
+
other.push(line);
|
|
109
|
+
continue;
|
|
110
|
+
}
|
|
109
111
|
if (commit.isRevert) {
|
|
110
112
|
if (!shouldIncludeRevert(commit)) {
|
|
111
113
|
continue;
|
|
@@ -128,7 +130,23 @@ function groupCommitLines(commits, repoUrl) {
|
|
|
128
130
|
fixes.push(line);
|
|
129
131
|
}
|
|
130
132
|
}
|
|
131
|
-
return { breaking, features, fixes, performance, reverts };
|
|
133
|
+
return { breaking, features, fixes, performance, reverts, other };
|
|
134
|
+
}
|
|
135
|
+
/**
|
|
136
|
+
* A forced bump — a stale dependency, or a propagated requirement rewrite —
|
|
137
|
+
* can carry no release-worthy commits at all. Rather than publish a bare
|
|
138
|
+
* version heading, such a release falls back to listing whatever else is
|
|
139
|
+
* shipping under it.
|
|
140
|
+
*/
|
|
141
|
+
function needsOtherChangesFallback(grouped, hasHighlights, dependencyCount) {
|
|
142
|
+
if (hasHighlights || dependencyCount > 0 || grouped.other.length === 0) {
|
|
143
|
+
return false;
|
|
144
|
+
}
|
|
145
|
+
return (grouped.breaking.length === 0 &&
|
|
146
|
+
grouped.features.length === 0 &&
|
|
147
|
+
grouped.fixes.length === 0 &&
|
|
148
|
+
grouped.performance.length === 0 &&
|
|
149
|
+
grouped.reverts.length === 0);
|
|
132
150
|
}
|
|
133
151
|
export function renderReleaseNotesSection(input, options = {}) {
|
|
134
152
|
const repoUrl = resolveRepositoryWebBaseUrl(input.cwd ?? process.cwd());
|
|
@@ -161,6 +179,9 @@ export function renderReleaseNotesSection(input, options = {}) {
|
|
|
161
179
|
if (input.dependencies && input.dependencies.length > 0) {
|
|
162
180
|
sections.push("### Dependencies", ...input.dependencies.map((dependency) => `- updated ${dependency.name} to v${dependency.version}`), "");
|
|
163
181
|
}
|
|
182
|
+
if (needsOtherChangesFallback(grouped, trimmedHighlights.length > 0, input.dependencies?.length ?? 0)) {
|
|
183
|
+
sections.push("### Other changes", ...grouped.other, "");
|
|
184
|
+
}
|
|
164
185
|
const body = [header, "", ...sections].join("\n").trimEnd();
|
|
165
186
|
if (options.includeFooter) {
|
|
166
187
|
return `${body}\n\n${renderReviewRequestFooter()}`;
|
|
@@ -198,10 +219,6 @@ export function renderReleasePlanChangelog(plan, options = {}) {
|
|
|
198
219
|
includeFooter: options.includeFooter,
|
|
199
220
|
});
|
|
200
221
|
}
|
|
201
|
-
/** @deprecated Use renderReleasePlanChangelog. */
|
|
202
|
-
export function renderSimpleChangelog(plan) {
|
|
203
|
-
return renderReleasePlanChangelog(plan);
|
|
204
|
-
}
|
|
205
222
|
/**
|
|
206
223
|
* Locate a manual-notes block at the top of an existing changelog and split it
|
|
207
224
|
* out. The notes block is the first release-level heading whose text is not a
|
|
@@ -292,6 +309,9 @@ export function renderRNewsReleaseNotes(input) {
|
|
|
292
309
|
if (grouped.reverts.length > 0) {
|
|
293
310
|
sections.push("## Reverts", "", ...grouped.reverts, "");
|
|
294
311
|
}
|
|
312
|
+
if (needsOtherChangesFallback(grouped, trimmedHighlights.length > 0, 0)) {
|
|
313
|
+
sections.push("## Other changes", "", ...grouped.other, "");
|
|
314
|
+
}
|
|
295
315
|
return [`# ${input.packageName} ${normalizedVersion}`, "", ...sections]
|
|
296
316
|
.join("\n")
|
|
297
317
|
.trimEnd();
|
package/dist/release/plan.d.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { type ParsedCommit } from "../git/commits.js";
|
|
2
2
|
import type { VersionaryChangelogFormat, VersionaryConfig } from "../types/config.js";
|
|
3
3
|
import { type ReleaseType } from "./semver.js";
|
|
4
|
+
type BumpReason = "direct" | "dependency-propagation" | "stale-dependency" | "follows" | "release-as";
|
|
4
5
|
export interface ReleasePlan {
|
|
5
6
|
mode: "simple";
|
|
6
7
|
releaseType: ReleaseType;
|
|
@@ -18,13 +19,11 @@ export interface ReleasePlan {
|
|
|
18
19
|
releaseType: ReleaseType;
|
|
19
20
|
currentVersion: string;
|
|
20
21
|
nextVersion: string | null;
|
|
21
|
-
bumpReason?:
|
|
22
|
+
bumpReason?: BumpReason;
|
|
22
23
|
dependencySourcePaths?: string[];
|
|
23
24
|
commits: ParsedCommit[];
|
|
24
25
|
}>;
|
|
25
26
|
}
|
|
26
|
-
/** @deprecated Use ReleasePlan. */
|
|
27
|
-
export type SimplePlan = ReleasePlan;
|
|
28
27
|
export declare function getChangelogDefaults(config: {
|
|
29
28
|
"release-type"?: VersionaryConfig["release-type"];
|
|
30
29
|
"changelog-file"?: VersionaryConfig["changelog-file"];
|
|
@@ -35,9 +34,8 @@ export declare function getChangelogDefaults(config: {
|
|
|
35
34
|
changelogFormat: VersionaryChangelogFormat;
|
|
36
35
|
};
|
|
37
36
|
export declare function createReleasePlan(cwd?: string): ReleasePlan;
|
|
38
|
-
/** @deprecated Use createReleasePlan. */
|
|
39
|
-
export declare function createSimplePlan(cwd?: string): ReleasePlan;
|
|
40
37
|
export declare function resolvePackageDependencies(plan: ReleasePlan, packagePath: string): Array<{
|
|
41
38
|
name: string;
|
|
42
39
|
version: string;
|
|
43
40
|
}>;
|
|
41
|
+
export {};
|