versionary 1.1.0 → 1.3.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.
@@ -48,6 +48,24 @@ function getRemoteRefSha(cwd, ref) {
48
48
  return null;
49
49
  }
50
50
  }
51
+ function hasVersionaryReleaseMarker(cwd, sha) {
52
+ try {
53
+ const message = runGit(cwd, ["show", "-s", "--format=%B", sha]);
54
+ return /^Versionary-Release:\s*true$/imu.test(message);
55
+ }
56
+ catch {
57
+ return false;
58
+ }
59
+ }
60
+ function isAncestor(cwd, ancestor, descendant) {
61
+ try {
62
+ runGit(cwd, ["merge-base", "--is-ancestor", ancestor, descendant]);
63
+ return true;
64
+ }
65
+ catch {
66
+ return false;
67
+ }
68
+ }
51
69
  function setOutput(name, value) {
52
70
  const outputPath = process.env.GITHUB_OUTPUT;
53
71
  if (!outputPath) {
@@ -98,26 +116,31 @@ function main() {
98
116
  hasOriginRemote(cwd)) {
99
117
  const remoteSha = getRemoteRefSha(cwd, ref);
100
118
  if (remoteSha && remoteSha !== sha) {
101
- const staleMessage = `Skipping stale push run for ${sha.slice(0, 7)}; ` +
102
- `${ref} now points to ${remoteSha.slice(0, 7)}.`;
103
- const stalePayload = {
104
- action: "stale-run-skipped",
105
- message: staleMessage,
106
- releaseCreated: false,
107
- tagNames: [],
108
- };
109
- process.stdout.write(`${JSON.stringify(stalePayload)}\n`);
110
- setOutput("action", stalePayload.action);
111
- setOutput("message", stalePayload.message);
112
- setOutput("release_created", "false");
113
- setOutput("tag_name", "");
114
- setOutput("tag_names", "[]");
115
- setOutput("release_targets", "[]");
116
- setOutput("review_url", "");
117
- setOutput("review_requests", "[]");
118
- setOutput("branch", "");
119
- setOutput("title", "");
120
- return;
119
+ // Descendant pushes must not turn an otherwise valid release into a
120
+ // recovery cycle.
121
+ const releaseCanPublish = hasVersionaryReleaseMarker(cwd, sha) && isAncestor(cwd, sha, remoteSha);
122
+ if (!releaseCanPublish) {
123
+ const staleMessage = `Skipping stale push run for ${sha.slice(0, 7)}; ` +
124
+ `${ref} now points to ${remoteSha.slice(0, 7)}.`;
125
+ const stalePayload = {
126
+ action: "stale-run-skipped",
127
+ message: staleMessage,
128
+ releaseCreated: false,
129
+ tagNames: [],
130
+ };
131
+ process.stdout.write(`${JSON.stringify(stalePayload)}\n`);
132
+ setOutput("action", stalePayload.action);
133
+ setOutput("message", stalePayload.message);
134
+ setOutput("release_created", "false");
135
+ setOutput("tag_name", "");
136
+ setOutput("tag_names", "[]");
137
+ setOutput("release_targets", "[]");
138
+ setOutput("review_url", "");
139
+ setOutput("review_requests", "[]");
140
+ setOutput("branch", "");
141
+ setOutput("title", "");
142
+ return;
143
+ }
121
144
  }
122
145
  }
123
146
  const raw = execFileSync("npx", ["--yes", `versionary@${versionaryVersion}`, "run", "--json"], {
@@ -39,6 +39,7 @@ export declare const configSchema: z.ZodObject<{
39
39
  "markdown-changelog": "markdown-changelog";
40
40
  "r-news": "r-news";
41
41
  }>>;
42
+ "bump-minor-pre-major": z.ZodOptional<z.ZodBoolean>;
42
43
  "allow-stable-major": z.ZodOptional<z.ZodBoolean>;
43
44
  "exclude-paths": z.ZodOptional<z.ZodArray<z.ZodString>>;
44
45
  "extra-files": z.ZodOptional<z.ZodArray<z.ZodObject<{
@@ -64,12 +64,23 @@ const packageSchema = z
64
64
  "package-name": z.string().optional(),
65
65
  "changelog-file": z.string().optional(),
66
66
  "changelog-format": z.enum(["markdown-changelog", "r-news"]).optional(),
67
+ "bump-minor-pre-major": z.boolean().optional(),
67
68
  "allow-stable-major": z.boolean().optional(),
68
69
  "exclude-paths": z.array(z.string()).optional(),
69
70
  "extra-files": z.array(artifactRuleSchema).optional(),
70
71
  follows: z.array(z.string().min(1)).optional(),
71
72
  })
72
- .strict();
73
+ .strict()
74
+ .superRefine((value, ctx) => {
75
+ if (value["bump-minor-pre-major"] !== undefined &&
76
+ value["allow-stable-major"] !== undefined) {
77
+ ctx.addIssue({
78
+ code: z.ZodIssueCode.custom,
79
+ message: '"bump-minor-pre-major" and "allow-stable-major" are inverse aliases; configure only one.',
80
+ path: ["bump-minor-pre-major"],
81
+ });
82
+ }
83
+ });
73
84
  export const configSchema = z
74
85
  .object({
75
86
  $schema: z.string().optional(),
@@ -98,6 +109,14 @@ export const configSchema = z
98
109
  })
99
110
  .strict()
100
111
  .superRefine((value, ctx) => {
112
+ if (value["bump-minor-pre-major"] !== undefined &&
113
+ value["allow-stable-major"] !== undefined) {
114
+ ctx.addIssue({
115
+ code: z.ZodIssueCode.custom,
116
+ message: '"bump-minor-pre-major" and "allow-stable-major" are inverse aliases; configure only one.',
117
+ path: ["bump-minor-pre-major"],
118
+ });
119
+ }
101
120
  const packages = value.packages;
102
121
  if (value["separate-release-prs"]) {
103
122
  if (!packages || Object.keys(packages).length === 0) {
@@ -291,7 +291,6 @@ export function prependChangelog(cwd, changelogFile, section, format = "markdown
291
291
  export function renderRNewsReleaseNotes(input) {
292
292
  const repoUrl = resolveRepositoryWebBaseUrl(input.cwd ?? process.cwd());
293
293
  const grouped = groupCommitLines(input.commits, repoUrl);
294
- const normalizedVersion = input.nextVersion.replace(/\.\d+$/u, "");
295
294
  const sections = [];
296
295
  const trimmedHighlights = input.highlights?.trim() ?? "";
297
296
  if (trimmedHighlights.length > 0) {
@@ -315,7 +314,7 @@ export function renderRNewsReleaseNotes(input) {
315
314
  if (needsOtherChangesFallback(grouped, trimmedHighlights.length > 0, 0)) {
316
315
  sections.push("## Other changes", "", ...grouped.other, "");
317
316
  }
318
- return [`# ${input.packageName} ${normalizedVersion}`, "", ...sections]
317
+ return [`# ${input.packageName} ${input.nextVersion}`, "", ...sections]
319
318
  .join("\n")
320
319
  .trimEnd();
321
320
  }
@@ -10,6 +10,21 @@ import { readBaselineSha, readReleaseTargets } from "./state.js";
10
10
  function getMode(configMode) {
11
11
  return configMode ?? "independent";
12
12
  }
13
+ function resolveBumpMinorPreMajor(config, packageConfig) {
14
+ if (packageConfig?.["bump-minor-pre-major"] !== undefined) {
15
+ return packageConfig["bump-minor-pre-major"];
16
+ }
17
+ if (packageConfig?.["allow-stable-major"] !== undefined) {
18
+ return !packageConfig["allow-stable-major"];
19
+ }
20
+ if (config["bump-minor-pre-major"] !== undefined) {
21
+ return config["bump-minor-pre-major"];
22
+ }
23
+ if (config["allow-stable-major"] !== undefined) {
24
+ return !config["allow-stable-major"];
25
+ }
26
+ return true;
27
+ }
13
28
  export function getChangelogDefaults(config) {
14
29
  const changelogFormat = config["changelog-format"] ??
15
30
  config.defaultChangelogFormat ??
@@ -50,9 +65,8 @@ export function createReleasePlan(cwd = process.cwd()) {
50
65
  const releaseBranchPrefix = loaded.config["release-branch"] ?? "versionary/release";
51
66
  const baselineSha = readBaselineSha(cwd) ?? loaded.config["bootstrap-sha"] ?? null;
52
67
  const releaseTargetByPath = new Map(readReleaseTargets(cwd).map((target) => [target.path, target]));
53
- const allowStableMajor = loaded.config["allow-stable-major"] ?? false;
54
- const allowStableMajorForPath = (packagePath) => loaded.config.packages?.[packagePath]?.["allow-stable-major"] ??
55
- allowStableMajor;
68
+ const bumpMinorPreMajor = resolveBumpMinorPreMajor(loaded.config);
69
+ const bumpMinorPreMajorForPath = (packagePath) => resolveBumpMinorPreMajor(loaded.config, loaded.config.packages?.[packagePath]);
56
70
  const monorepoMode = getMode(loaded.config["monorepo-mode"]);
57
71
  const buildPackagePlan = (pkg) => {
58
72
  const packageContext = resolvePackageStrategyContext(loaded.config, pkg.path, pkg.config);
@@ -95,7 +109,7 @@ export function createReleasePlan(cwd = process.cwd()) {
95
109
  releaseType = analyzedType;
96
110
  nextVersion = releaseType
97
111
  ? bumpVersion(packageCurrentVersion, releaseType, {
98
- allowStableMajor: allowStableMajorForPath(pkg.path),
112
+ bumpMinorPreMajor: bumpMinorPreMajorForPath(pkg.path),
99
113
  })
100
114
  : null;
101
115
  bumpReason = nextVersion ? "direct" : undefined;
@@ -170,7 +184,7 @@ export function createReleasePlan(cwd = process.cwd()) {
170
184
  const current = packageCurrentVersionByPath[target.path] ?? target.currentVersion;
171
185
  target.releaseType = "patch";
172
186
  target.nextVersion = bumpVersion(current, "patch", {
173
- allowStableMajor: allowStableMajorForPath(target.path),
187
+ bumpMinorPreMajor: bumpMinorPreMajorForPath(target.path),
174
188
  });
175
189
  target.bumpReason = reason;
176
190
  const strategyContext = strategyContextByPath.get(target.path);
@@ -203,7 +217,7 @@ export function createReleasePlan(cwd = process.cwd()) {
203
217
  }
204
218
  const hypotheticalVersion = strategyContext.nextVersion ??
205
219
  bumpVersion(packageCurrentVersionByPath[sourcePath] ??
206
- strategyContext.currentVersion, "patch", { allowStableMajor: allowStableMajorForPath(sourcePath) });
220
+ strategyContext.currentVersion, "patch", { bumpMinorPreMajor: bumpMinorPreMajorForPath(sourcePath) });
207
221
  return strategyGroup.strategy.propagateDependentPatchImpacts(cwd, strategyGroup.packages.map((pkg) => ({
208
222
  ...pkg,
209
223
  nextVersion: pkg.packagePath === sourcePath ? hypotheticalVersion : null,
@@ -387,7 +401,7 @@ export function createReleasePlan(cwd = process.cwd()) {
387
401
  const baseVersion = packageCurrentVersionByPath[pkgPlan.path] ?? pkgPlan.currentVersion;
388
402
  const nextVersion = combinedReleaseType
389
403
  ? bumpVersion(baseVersion, combinedReleaseType, {
390
- allowStableMajor: allowStableMajorForPath(pkgPlan.path),
404
+ bumpMinorPreMajor: bumpMinorPreMajorForPath(pkgPlan.path),
391
405
  })
392
406
  : null;
393
407
  return {
@@ -429,7 +443,9 @@ export function createReleasePlan(cwd = process.cwd()) {
429
443
  const fixedNextVersion = rootOverridden
430
444
  ? rootPackagePlan.nextVersion
431
445
  : analyzedFixedType
432
- ? bumpVersion(fixedBaseVersion, analyzedFixedType, { allowStableMajor })
446
+ ? bumpVersion(fixedBaseVersion, analyzedFixedType, {
447
+ bumpMinorPreMajor,
448
+ })
433
449
  : null;
434
450
  // Fixed mode can promote unchanged dependencies into the release, so the
435
451
  // final shared version set must drive dependency attribution.
@@ -466,7 +482,7 @@ export function createReleasePlan(cwd = process.cwd()) {
466
482
  ? rootPackagePlan.nextVersion
467
483
  : analyzedOverallType
468
484
  ? bumpVersion(overallBaseVersion, analyzedOverallType, {
469
- allowStableMajor,
485
+ bumpMinorPreMajor,
470
486
  })
471
487
  : null;
472
488
  return {
@@ -8,7 +8,7 @@ export interface ParsedVersion {
8
8
  build: string[];
9
9
  }
10
10
  export interface BumpVersionOptions {
11
- allowStableMajor?: boolean;
11
+ bumpMinorPreMajor?: boolean;
12
12
  }
13
13
  export declare function parseVersion(version: string): ParsedVersion;
14
14
  export declare function isValidVersion(version: string): boolean;
@@ -137,14 +137,11 @@ export function compareVersions(leftRaw, rightRaw) {
137
137
  }
138
138
  export function bumpVersion(current, releaseType, options = {}) {
139
139
  const parsed = parseVersion(current);
140
- const allowStableMajor = options.allowStableMajor ?? false;
140
+ const bumpMinorPreMajor = options.bumpMinorPreMajor ?? true;
141
141
  if (releaseType === "major") {
142
- if (parsed.major === 0 && !allowStableMajor) {
142
+ if (parsed.major === 0 && bumpMinorPreMajor) {
143
143
  return `0.${parsed.minor + 1}.0`;
144
144
  }
145
- if (parsed.major === 0 && allowStableMajor) {
146
- return "1.0.0";
147
- }
148
145
  return `${parsed.major + 1}.0.0`;
149
146
  }
150
147
  if (releaseType === "minor") {
@@ -14,6 +14,7 @@ export interface VersionaryPackage {
14
14
  "package-name"?: string;
15
15
  "changelog-file"?: string;
16
16
  "changelog-format"?: VersionaryChangelogFormat;
17
+ "bump-minor-pre-major"?: boolean;
17
18
  "allow-stable-major"?: boolean;
18
19
  "exclude-paths"?: string[];
19
20
  "extra-files"?: VersionaryArtifactRule[];
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "versionary",
3
- "version": "1.1.0",
3
+ "version": "1.3.0",
4
4
  "description": "Automatic release framework based on conventional commits and semantic versioning",
5
5
  "keywords": [
6
6
  "releasing",