versionary 0.10.0 → 0.11.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 CHANGED
@@ -49,7 +49,7 @@ release/tag.
49
49
 
50
50
  Current implementation focuses on:
51
51
 
52
- - strategy-based version updates (`simple`, `node`)
52
+ - strategy-based version updates (`simple`, `node`, `rust`, `r`)
53
53
  - release planning and changelog generation
54
54
  - review-mode vs direct-mode release flow
55
55
  - built-in GitHub SCM plugin capabilities
@@ -57,6 +57,40 @@ Current implementation focuses on:
57
57
  Planned/harder areas include deeper monorepo ergonomics, broader SCM coverage,
58
58
  and stronger failure recovery around release steps.
59
59
 
60
+ ## Adding a new release strategy
61
+
62
+ Versionary is set up so new language strategies can be added internally without
63
+ changing release orchestration. A new strategy should implement the
64
+ `VersionStrategy` contract in `src/domain/strategy/types.ts` and be wired in
65
+ `src/domain/strategy/resolve.ts`.
66
+
67
+ Checklist for new strategies (for example `python`):
68
+
69
+ - define strategy `name`
70
+ - define `getVersionFile(config)` defaults and config override behavior
71
+ - implement `readVersion(cwd, config)` with explicit malformed-file errors
72
+ - implement `writeVersion(cwd, config, version)` returning deterministic updated
73
+ file paths
74
+ - add release-name extraction support if package tags should derive from
75
+ language metadata (similar to Node/Rust/R)
76
+ - add focused strategy tests for ecosystem-specific behavior and edge cases
77
+ - add/extend strategy contract tests in `tests/strategy-contract.test.ts`
78
+ - update schema/docs for new `release-type` behavior and defaults
79
+
80
+ Current ecosystem policy defaults:
81
+
82
+ - changelog source for publish:
83
+ - root target uses root `changelog-file`
84
+ - package target uses `packages.<path>.changelog-file` when configured, else
85
+ root `changelog-file`
86
+ - lockfiles:
87
+ - Node strategy updates root `package-lock.json`/`npm-shrinkwrap.json` when
88
+ present
89
+ - Rust release PR prep refreshes all discovered `Cargo.lock` files
90
+ - workspace/inheritance:
91
+ - Rust supports `version.workspace = true` via `[workspace.package].version`
92
+ - other strategies should document equivalent inheritance behavior explicitly
93
+
60
94
  ## Architecture layout (current migration)
61
95
 
62
96
  The repository is moving to explicit layered modules:
@@ -177,7 +211,8 @@ Release planning is based on Conventional Commit parsing semantics:
177
211
  - recognizes breaking changes from `!` and `BREAKING CHANGE` / `BREAKING-CHANGE`
178
212
  footers
179
213
  - maps release impact as `feat => minor`, `fix|perf => patch`, breaking => major
180
- - treats `revert:` as non-releasable commits
214
+ - treats `revert:` commits as patch-releasable by default (and major if marked
215
+ breaking, e.g. `revert!:` or `BREAKING CHANGE`)
181
216
  - suppresses commits that are reverted within the analyzed release window so
182
217
  they do not affect bump/changelog output
183
218
  - emits parser diagnostics for malformed headers/footers/references and
@@ -1,4 +1,6 @@
1
+ import type { VersionaryConfig } from "../../types/config.js";
1
2
  import type { VersionaryPluginContext } from "../../types/plugins.js";
3
+ export declare function resolveTargetChangelogFile(config: VersionaryConfig, rootChangelogFile: string, targetPath: string): string;
2
4
  export declare function runSimpleRelease(cwd?: string): Promise<string>;
3
5
  export type SimpleRunReleaseResult = {
4
6
  action: "release-skipped";
@@ -3,6 +3,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
3
3
  return (mod && mod.__esModule) ? mod : { "default": mod };
4
4
  };
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.resolveTargetChangelogFile = resolveTargetChangelogFile;
6
7
  exports.runSimpleRelease = runSimpleRelease;
7
8
  exports.runSimpleReleaseDetailed = runSimpleReleaseDetailed;
8
9
  const node_child_process_1 = require("node:child_process");
@@ -46,6 +47,16 @@ function readReleaseNotes(cwd, version, changelogFile) {
46
47
  .trim();
47
48
  return notes.length > 0 ? notes : `Automated release for v${version}`;
48
49
  }
50
+ function resolveTargetChangelogFile(config, rootChangelogFile, targetPath) {
51
+ if (targetPath === ".") {
52
+ return rootChangelogFile;
53
+ }
54
+ const packageChangelogFile = config.packages?.[targetPath]?.["changelog-file"];
55
+ if (!packageChangelogFile) {
56
+ return rootChangelogFile;
57
+ }
58
+ return node_path_1.default.posix.join(targetPath, packageChangelogFile);
59
+ }
49
60
  async function runSimpleRelease(cwd = process.cwd()) {
50
61
  const result = await runSimpleReleaseDetailed(cwd, { logger: console });
51
62
  if (result.action === "release-skipped") {
@@ -99,10 +110,11 @@ async function runSimpleReleaseDetailed(cwd = process.cwd(), options = {}) {
99
110
  const createReleaseMetadata = plugin.createReleaseMetadata;
100
111
  const releases = [];
101
112
  for (const target of targets) {
113
+ const targetChangelogFile = resolveTargetChangelogFile(loaded.config, changelogFile, target.path);
102
114
  const outcome = await (0, recovery_js_1.executeIdempotentReleaseTarget)(cwd, {
103
115
  tag: target.tag,
104
116
  version: target.version,
105
- notes: readReleaseNotes(cwd, target.version, changelogFile),
117
+ notes: readReleaseNotes(cwd, target.version, targetChangelogFile),
106
118
  }, {
107
119
  createReleaseMetadata: (input) => createReleaseMetadata(input, {
108
120
  cwd,
@@ -48,6 +48,7 @@ function groupCommitLines(commits, repoUrl) {
48
48
  const breaking = [];
49
49
  const features = [];
50
50
  const fixes = [];
51
+ const reverts = [];
51
52
  for (const commit of commits) {
52
53
  const type = (0, commits_js_1.inferReleaseTypeFromParsedCommit)(commit);
53
54
  if (!type) {
@@ -63,6 +64,10 @@ function groupCommitLines(commits, repoUrl) {
63
64
  const line = `- ${label}${message} (${hashLabel})${referencesSuffix}`;
64
65
  const commitType = (commit.type ?? "").toLowerCase();
65
66
  const isBreaking = type === "major";
67
+ if (commit.isRevert) {
68
+ reverts.push(line);
69
+ continue;
70
+ }
66
71
  if (isBreaking) {
67
72
  breaking.push(line);
68
73
  }
@@ -76,7 +81,7 @@ function groupCommitLines(commits, repoUrl) {
76
81
  fixes.push(line);
77
82
  }
78
83
  }
79
- return { breaking, features, fixes };
84
+ return { breaking, features, fixes, reverts };
80
85
  }
81
86
  function renderSimpleReleaseNotes(input, options = {}) {
82
87
  const repoUrl = (0, repo_url_js_1.resolveRepositoryWebBaseUrl)(input.cwd ?? process.cwd());
@@ -94,6 +99,9 @@ function renderSimpleReleaseNotes(input, options = {}) {
94
99
  if (grouped.fixes.length > 0) {
95
100
  sections.push("### Bug Fixes", ...grouped.fixes, "");
96
101
  }
102
+ if (grouped.reverts.length > 0) {
103
+ sections.push("### Reverts", ...grouped.reverts, "");
104
+ }
97
105
  const lines = [header, "", ...sections];
98
106
  if (options.includeFooter) {
99
107
  lines.push("\n---\n\nThis PR was generated by [Versionary](https://github.com/jolars/versionary).");
@@ -127,6 +135,9 @@ function renderPackageChangelogSection(input) {
127
135
  if (grouped.fixes.length > 0) {
128
136
  sections.push("### Bug Fixes", ...grouped.fixes, "");
129
137
  }
138
+ if (grouped.reverts.length > 0) {
139
+ sections.push("### Reverts", ...grouped.reverts, "");
140
+ }
130
141
  return [header, "", ...sections].join("\n");
131
142
  }
132
143
  function prependChangelog(cwd, changelogFile, section) {
@@ -351,12 +351,12 @@ function getParsedCommitsForPath(cwd = process.cwd(), baselineSha, packagePath =
351
351
  return readGitLogFull(cwd, range, [normalizedPackagePath, ...excludes]);
352
352
  }
353
353
  function inferReleaseTypeFromParsedCommit(commit) {
354
- if (commit.isRevert) {
355
- return null;
356
- }
357
354
  if (commit.isBreaking) {
358
355
  return "major";
359
356
  }
357
+ if (commit.isRevert) {
358
+ return "patch";
359
+ }
360
360
  const type = commit.type?.toLowerCase() ?? "";
361
361
  if (type === "feat") {
362
362
  return "minor";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "versionary",
3
- "version": "0.10.0",
3
+ "version": "0.11.0",
4
4
  "description": "Automatic release framework based on conventional commits and semantic versioning",
5
5
  "keywords": [
6
6
  "releasing",