versionary 0.9.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
@@ -22,7 +22,7 @@ Versionary is being built to:
22
22
 
23
23
  - support both direct releases and release-PR-gated releases
24
24
  - work across repository types (Node, Rust, docs/LaTeX, etc.)
25
- - stay SCM-agnostic at the core, with integrations via plugin capabilities
25
+ - stay SCM-agnostic at the core with built-in integration adapters
26
26
  (GitHub first; GitLab/Codeberg later)
27
27
  - keep a small, stable core with explicit extension points
28
28
  - handle trunk-based development and monorepo workflows cleanly
@@ -40,6 +40,7 @@ Out of scope (intentional):
40
40
 
41
41
  - publishing artifacts to language registries
42
42
  - replacing package-specific publish tooling
43
+ - external/user-provided plugin loading
43
44
 
44
45
  Use your CI/CD platform for registry publishing, triggered from a created
45
46
  release/tag.
@@ -48,7 +49,7 @@ release/tag.
48
49
 
49
50
  Current implementation focuses on:
50
51
 
51
- - strategy-based version updates (`simple`, `node`)
52
+ - strategy-based version updates (`simple`, `node`, `rust`, `r`)
52
53
  - release planning and changelog generation
53
54
  - review-mode vs direct-mode release flow
54
55
  - built-in GitHub SCM plugin capabilities
@@ -56,6 +57,40 @@ Current implementation focuses on:
56
57
  Planned/harder areas include deeper monorepo ergonomics, broader SCM coverage,
57
58
  and stronger failure recovery around release steps.
58
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
+
59
94
  ## Architecture layout (current migration)
60
95
 
61
96
  The repository is moving to explicit layered modules:
@@ -95,7 +130,7 @@ For a quick trial, use:
95
130
  - pre-1.0 policy defaults to conservative major handling: for `0.y.z`, breaking
96
131
  changes bump to `0.(y+1).0`; set `allow-stable-major: true` to allow explicit
97
132
  auto-transition to `1.0.0` on a breaking release
98
- - review mode (`review-mode`): `review` (PR/MR style) or `direct` (no review
133
+ - review mode (`review-mode`): `pr` (PR/MR style) or `direct` (no review
99
134
  request)
100
135
  - optional monorepo planning with `monorepo-mode` and `packages`:
101
136
  - `independent` computes package bumps per path
@@ -176,7 +211,8 @@ Release planning is based on Conventional Commit parsing semantics:
176
211
  - recognizes breaking changes from `!` and `BREAKING CHANGE` / `BREAKING-CHANGE`
177
212
  footers
178
213
  - maps release impact as `feat => minor`, `fix|perf => patch`, breaking => major
179
- - 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`)
180
216
  - suppresses commits that are reverted within the analyzed release window so
181
217
  they do not affect bump/changelog output
182
218
  - emits parser diagnostics for malformed headers/footers/references and
@@ -239,7 +275,8 @@ Minimum GitHub token/repo permissions for Versionary-managed metadata:
239
275
 
240
276
  `review-mode` behavior:
241
277
 
242
- - `review`: `pnpm run run` prepares/updates the release branch and creates or
278
+ - `pr` (preferred; `review` is a backward-compatible alias): `pnpm run run`
279
+ prepares/updates the release branch and creates or
243
280
  updates a release PR
244
281
  - `direct`: `pnpm run run` prepares/updates the release branch and skips review
245
282
  request creation
@@ -235,7 +235,10 @@ function formatReleaseCommitTitle(releaseTargets) {
235
235
  return "chore(release): v0.0.0";
236
236
  }
237
237
  const tags = releaseTargets.map((target) => target.tag);
238
- return `chore(release): ${tags.join(", ")}`;
238
+ if (tags.length === 1) {
239
+ return `chore(release): ${tags[0]}`;
240
+ }
241
+ return `chore(release): ${tags[0]} (+${tags.length - 1} more)`;
239
242
  }
240
243
  function prepareSimpleReleasePr(cwd = process.cwd(), options = {}) {
241
244
  const plan = (0, plan_js_1.createSimplePlan)(cwd);
@@ -374,14 +377,14 @@ function renderSimpleReviewRequestBody(version, previousVersion, commits, plan =
374
377
  }
375
378
  async function openOrUpdateSimpleReviewRequest(cwd, branch, title, version, previousVersion, commits, plan = null, options = {}) {
376
379
  const loaded = (0, load_config_js_1.loadConfig)(cwd);
377
- const releaseFlow = loaded.config["review-mode"] ?? "direct";
378
- if (releaseFlow !== "review") {
380
+ const releaseFlow = loaded.config["review-mode"] ?? "pr";
381
+ if (releaseFlow === "direct") {
379
382
  return "Release flow mode is direct; skipping review request creation.";
380
383
  }
381
384
  const plugins = (0, runtime_js_1.loadRuntimePlugins)();
382
385
  const scmPlugins = (0, capabilities_js_1.findPluginsByCapability)(plugins, "scm.reviewRequest");
383
386
  if (scmPlugins.length === 0) {
384
- throw new Error("review-mode is review but no scm.reviewRequest plugin is available.");
387
+ throw new Error("review-mode is pr but no scm.reviewRequest plugin is available.");
385
388
  }
386
389
  const plugin = scmPlugins[0];
387
390
  if (!plugin?.createOrUpdateReviewRequest) {
@@ -410,5 +413,5 @@ function isReleaseCommitMessage(commitMessage) {
410
413
  return true;
411
414
  }
412
415
  const subject = commitMessage.split("\n")[0]?.trim() ?? "";
413
- 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);
416
+ return /^chore\(release\):\s+(?:(?:v\d+\.\d+\.\d+|\S+-v\d+\.\d+\.\d+)(?:,\s+(?:v\d+\.\d+\.\d+|\S+-v\d+\.\d+\.\d+))*|(?:v\d+\.\d+\.\d+|\S+-v\d+\.\d+\.\d+)\s+\(\+\d+\s+more\))(?:\s+\(#\d+\))?$/u.test(subject);
414
417
  }
@@ -1,8 +1,17 @@
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";
5
7
  reason: string;
8
+ } | {
9
+ action: "release-dry-run";
10
+ message: string;
11
+ targets: {
12
+ tag: string;
13
+ version: string;
14
+ }[];
6
15
  } | {
7
16
  action: "release-published";
8
17
  message: string;
@@ -15,5 +24,6 @@ export type SimpleRunReleaseResult = {
15
24
  };
16
25
  export interface RunSimpleReleaseOptions {
17
26
  logger?: VersionaryPluginContext["logger"];
27
+ "dry-run"?: boolean;
18
28
  }
19
29
  export declare function runSimpleReleaseDetailed(cwd?: string, options?: RunSimpleReleaseOptions): Promise<SimpleRunReleaseResult>;
@@ -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") {
@@ -66,16 +77,6 @@ async function runSimpleReleaseDetailed(cwd = process.cwd(), options = {}) {
66
77
  const changelogFile = loaded.config["changelog-file"] ?? "CHANGELOG.md";
67
78
  const version = strategy.readVersion(cwd, loaded.config);
68
79
  const defaultTag = `v${version}`;
69
- const plugins = (0, runtime_js_1.loadRuntimePlugins)();
70
- const scmPlugins = (0, capabilities_js_1.findPluginsByCapability)(plugins, "scm.releaseMetadata");
71
- if (scmPlugins.length === 0) {
72
- throw new Error("No scm.releaseMetadata plugin is available.");
73
- }
74
- const plugin = scmPlugins[0];
75
- if (!plugin?.createReleaseMetadata) {
76
- throw new Error(`Plugin "${plugin?.name ?? "unknown"}" does not implement createReleaseMetadata.`);
77
- }
78
- const createReleaseMetadata = plugin.createReleaseMetadata;
79
80
  const releaseTargets = (0, state_js_1.readReleaseTargets)(cwd);
80
81
  const targets = releaseTargets.length > 0
81
82
  ? releaseTargets
@@ -86,12 +87,34 @@ async function runSimpleReleaseDetailed(cwd = process.cwd(), options = {}) {
86
87
  tag: defaultTag,
87
88
  },
88
89
  ];
90
+ if (options["dry-run"]) {
91
+ const targetList = targets.map((target) => `${target.tag} (${target.version})`);
92
+ return {
93
+ action: "release-dry-run",
94
+ targets: targets.map((target) => ({
95
+ tag: target.tag,
96
+ version: target.version,
97
+ })),
98
+ message: `Dry run: would publish releases ${targetList.join(", ")}`,
99
+ };
100
+ }
101
+ const plugins = (0, runtime_js_1.loadRuntimePlugins)();
102
+ const scmPlugins = (0, capabilities_js_1.findPluginsByCapability)(plugins, "scm.releaseMetadata");
103
+ if (scmPlugins.length === 0) {
104
+ throw new Error("No scm.releaseMetadata plugin is available.");
105
+ }
106
+ const plugin = scmPlugins[0];
107
+ if (!plugin?.createReleaseMetadata) {
108
+ throw new Error(`Plugin "${plugin?.name ?? "unknown"}" does not implement createReleaseMetadata.`);
109
+ }
110
+ const createReleaseMetadata = plugin.createReleaseMetadata;
89
111
  const releases = [];
90
112
  for (const target of targets) {
113
+ const targetChangelogFile = resolveTargetChangelogFile(loaded.config, changelogFile, target.path);
91
114
  const outcome = await (0, recovery_js_1.executeIdempotentReleaseTarget)(cwd, {
92
115
  tag: target.tag,
93
116
  version: target.version,
94
- notes: readReleaseNotes(cwd, target.version, changelogFile),
117
+ notes: readReleaseNotes(cwd, target.version, targetChangelogFile),
95
118
  }, {
96
119
  createReleaseMetadata: (input) => createReleaseMetadata(input, {
97
120
  cwd,
@@ -79,7 +79,7 @@ function readReleaseTargets(cwd = process.cwd()) {
79
79
  const parsed = parseStateFile(node_fs_1.default.readFileSync(filePath, "utf8"), filePath);
80
80
  return parsed[RELEASE_TARGETS_KEY] ?? [];
81
81
  }
82
- function writeBaselineSha(cwd = process.cwd(), sha, releaseTargets = []) {
82
+ function writeBaselineSha(cwd = process.cwd(), sha, releaseTargets) {
83
83
  const baselineShaValue = sha ??
84
84
  (0, node_child_process_1.execFileSync)("git", ["rev-parse", "HEAD"], {
85
85
  cwd,
@@ -87,10 +87,22 @@ function writeBaselineSha(cwd = process.cwd(), sha, releaseTargets = []) {
87
87
  stdio: ["ignore", "pipe", "ignore"],
88
88
  }).trim();
89
89
  const filePath = getBaselineStatePath(cwd);
90
+ const existing = node_fs_1.default.existsSync(filePath)
91
+ ? parseStateFile(node_fs_1.default.readFileSync(filePath, "utf8"), filePath)
92
+ : {};
93
+ const existingTargets = existing[RELEASE_TARGETS_KEY] ?? [];
94
+ const nextTargets = releaseTargets === undefined
95
+ ? existingTargets
96
+ : [
97
+ ...new Map([...existingTargets, ...releaseTargets].map((target) => [
98
+ target.path,
99
+ target,
100
+ ])).values(),
101
+ ].sort((a, b) => a.path.localeCompare(b.path));
90
102
  const next = {
91
103
  [MANIFEST_VERSION_KEY]: 1,
92
104
  [BASELINE_SHA_KEY]: baselineShaValue,
93
- [RELEASE_TARGETS_KEY]: releaseTargets,
105
+ [RELEASE_TARGETS_KEY]: nextTargets,
94
106
  };
95
107
  node_fs_1.default.writeFileSync(filePath, `${JSON.stringify(next, null, 2)}\n`, "utf8");
96
108
  }
package/dist/cli/index.js CHANGED
@@ -14,15 +14,33 @@ const changelog_js_1 = require("../domain/release/changelog.js");
14
14
  const plan_js_1 = require("../domain/release/plan.js");
15
15
  function printVerifyResult() {
16
16
  const result = (0, verify_js_1.verifyProject)();
17
- for (const check of result.checks) {
18
- const status = check.ok ? "OK" : "FAIL";
19
- console.log(`[${status}] ${check.name} - ${check.details}`);
17
+ const categories = [
18
+ { key: "config", title: "Config" },
19
+ { key: "paths", title: "Paths" },
20
+ { key: "version-files", title: "Version files" },
21
+ ];
22
+ for (const category of categories) {
23
+ const checks = result.checks.filter((check) => check.category === category.key);
24
+ if (checks.length === 0) {
25
+ continue;
26
+ }
27
+ console.log(`${category.title}:`);
28
+ for (const check of checks) {
29
+ const status = check.ok ? "OK" : "FAIL";
30
+ console.log(` [${status}] ${check.name} - ${check.details}`);
31
+ if (!check.ok && check.remediation) {
32
+ console.log(` Fix: ${check.remediation}`);
33
+ }
34
+ }
35
+ console.log("");
20
36
  }
37
+ console.log(result.ok ? "Summary: all checks passed." : "Summary: checks failed.");
21
38
  return result.ok ? 0 : 1;
22
39
  }
23
40
  function parseFlags(args) {
24
41
  return {
25
42
  json: args.includes("--json"),
43
+ "dry-run": args.includes("--dry-run"),
26
44
  };
27
45
  }
28
46
  function emitJson(payload) {
@@ -38,9 +56,20 @@ async function main() {
38
56
  stdio: ["ignore", "pipe", "ignore"],
39
57
  }).trim();
40
58
  if ((0, pr_js_1.isReleaseCommitMessage)(commitMessage)) {
59
+ if (flags["dry-run"] && !flags.json) {
60
+ const release = await (0, release_js_1.runSimpleReleaseDetailed)(process.cwd(), {
61
+ logger,
62
+ "dry-run": true,
63
+ });
64
+ if (release.action === "release-dry-run") {
65
+ console.log(release.message);
66
+ return 0;
67
+ }
68
+ }
41
69
  if (flags.json) {
42
70
  const release = await (0, release_js_1.runSimpleReleaseDetailed)(process.cwd(), {
43
71
  logger,
72
+ "dry-run": flags["dry-run"],
44
73
  });
45
74
  if (release.action === "release-skipped") {
46
75
  emitJson({
@@ -51,6 +80,16 @@ async function main() {
51
80
  });
52
81
  return 0;
53
82
  }
83
+ if (release.action === "release-dry-run") {
84
+ emitJson({
85
+ action: "release-dry-run",
86
+ message: release.message,
87
+ releaseCreated: false,
88
+ tagNames: release.targets.map((target) => target.tag),
89
+ targets: release.targets,
90
+ });
91
+ return 0;
92
+ }
54
93
  emitJson({
55
94
  action: "release-published",
56
95
  message: release.message,
@@ -78,6 +117,34 @@ async function main() {
78
117
  console.log(message);
79
118
  return 0;
80
119
  }
120
+ if (flags["dry-run"]) {
121
+ const dryRunMessage = `Dry run: would prepare release PR branch ${plan.releaseBranchPrefix} for ${plan.nextVersion}`;
122
+ if (flags.json) {
123
+ emitJson({
124
+ action: "pr-dry-run",
125
+ message: dryRunMessage,
126
+ releaseCreated: false,
127
+ tagNames: [],
128
+ branch: plan.releaseBranchPrefix,
129
+ targets: plan.packages
130
+ ?.filter((pkg) => pkg.nextVersion)
131
+ .map((pkg) => ({
132
+ tag: pkg.path === "."
133
+ ? `v${pkg.nextVersion ?? ""}`
134
+ : `${pkg.path}-v${pkg.nextVersion ?? ""}`,
135
+ version: pkg.nextVersion ?? "",
136
+ })) ?? [
137
+ {
138
+ tag: `v${plan.nextVersion}`,
139
+ version: plan.nextVersion,
140
+ },
141
+ ],
142
+ });
143
+ return 0;
144
+ }
145
+ console.log(dryRunMessage);
146
+ return 0;
147
+ }
81
148
  const pr = (0, pr_js_1.prepareSimpleReleasePr)(process.cwd(), { logger });
82
149
  (0, pr_js_1.pushReleaseBranch)(process.cwd(), pr.branch);
83
150
  const reviewResult = await (0, pr_js_1.openOrUpdateSimpleReviewRequest)(process.cwd(), pr.branch, pr.title, pr.version, pr.previousVersion, pr.commits, pr.plan, { logger });
@@ -130,6 +197,15 @@ async function main() {
130
197
  return 0;
131
198
  }
132
199
  if (command === "pr") {
200
+ if (flags["dry-run"]) {
201
+ const plan = (0, plan_js_1.createSimplePlan)();
202
+ if (!plan.nextVersion) {
203
+ console.log("No releasable commits found. Nothing to do.");
204
+ return 0;
205
+ }
206
+ console.log(`Dry run: would prepare release PR branch ${plan.releaseBranchPrefix} for ${plan.nextVersion}`);
207
+ return 0;
208
+ }
133
209
  const pr = (0, pr_js_1.prepareSimpleReleasePr)(process.cwd(), { logger: console });
134
210
  (0, pr_js_1.pushReleaseBranch)(process.cwd(), pr.branch);
135
211
  const reviewResult = await (0, pr_js_1.openOrUpdateSimpleReviewRequest)(process.cwd(), pr.branch, pr.title, pr.version, pr.previousVersion, pr.commits, pr.plan);
@@ -139,18 +215,34 @@ async function main() {
139
215
  return 0;
140
216
  }
141
217
  if (command === "release") {
218
+ if (flags["dry-run"]) {
219
+ const result = await (0, release_js_1.runSimpleReleaseDetailed)(process.cwd(), {
220
+ logger,
221
+ "dry-run": true,
222
+ });
223
+ if (result.action === "release-dry-run") {
224
+ console.log(result.message);
225
+ }
226
+ else if (result.action === "release-skipped") {
227
+ console.log(result.reason);
228
+ }
229
+ else {
230
+ console.log(result.message);
231
+ }
232
+ return 0;
233
+ }
142
234
  const message = await (0, release_js_1.runSimpleRelease)(process.cwd());
143
235
  console.log(message);
144
236
  return 0;
145
237
  }
146
238
  console.log("Usage: versionary <command>");
147
239
  console.log("Commands:");
148
- console.log(" run [--json] Auto-dispatch release PR/update or release publish by context");
240
+ console.log(" run [--json] [--dry-run] Auto-dispatch release PR/update or release publish by context");
149
241
  console.log(" verify Validate config and basic repository shape");
150
242
  console.log(" plan Print release plan (simple mode)");
151
243
  console.log(" changelog [--write] Print or write changelog section");
152
- console.log(" pr Prepare release PR commit and branch");
153
- console.log(" release Publish release metadata for release commit context");
244
+ console.log(" pr [--dry-run] Prepare release PR commit and branch");
245
+ console.log(" release [--dry-run] Publish release metadata for release commit context");
154
246
  return 1;
155
247
  }
156
248
  main()
@@ -41,6 +41,9 @@ function loadConfig(cwd = process.cwd()) {
41
41
  if (!isRecord(parsed)) {
42
42
  throw new Error("Invalid config: expected an object at the root.");
43
43
  }
44
+ if (Object.hasOwn(parsed, "plugins")) {
45
+ throw new Error('The "plugins" config key is no longer supported. Versionary uses built-in integrations only.');
46
+ }
44
47
  const validated = schema_js_1.configSchema.parse(parsed);
45
48
  return {
46
49
  path: found.path,
@@ -4,6 +4,7 @@ export declare const configSchema: z.ZodObject<{
4
4
  version: z.ZodLiteral<1>;
5
5
  "review-mode": z.ZodOptional<z.ZodEnum<{
6
6
  direct: "direct";
7
+ pr: "pr";
7
8
  review: "review";
8
9
  }>>;
9
10
  "version-file": z.ZodOptional<z.ZodString>;
@@ -37,6 +38,5 @@ export declare const configSchema: z.ZodObject<{
37
38
  pattern: z.ZodOptional<z.ZodString>;
38
39
  }, z.core.$strip>>>;
39
40
  }, z.core.$strict>>>;
40
- plugins: z.ZodOptional<z.ZodArray<z.ZodString>>;
41
41
  }, z.core.$strict>;
42
42
  export type ConfigSchema = z.infer<typeof configSchema>;
@@ -62,7 +62,7 @@ exports.configSchema = zod_1.z
62
62
  .object({
63
63
  $schema: zod_1.z.string().optional(),
64
64
  version: zod_1.z.literal(1),
65
- "review-mode": zod_1.z.enum(["direct", "review"]).optional(),
65
+ "review-mode": zod_1.z.enum(["direct", "pr", "review"]).optional(),
66
66
  "version-file": zod_1.z.string().optional(),
67
67
  "changelog-file": zod_1.z.string().optional(),
68
68
  "release-branch": zod_1.z.string().optional(),
@@ -74,6 +74,5 @@ exports.configSchema = zod_1.z
74
74
  "include-commit-authors": zod_1.z.boolean().optional(),
75
75
  "release-type": zod_1.z.string().optional(),
76
76
  packages: zod_1.z.record(zod_1.z.string().min(1), packageSchema).optional(),
77
- plugins: zod_1.z.array(zod_1.z.string().min(1)).optional(),
78
77
  })
79
78
  .strict();
@@ -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";
@@ -1,2 +1,2 @@
1
1
  import type { VersionaryPluginRuntime } from "../../types/plugins.js";
2
- export declare function loadRuntimePlugins(cwd?: string): VersionaryPluginRuntime[];
2
+ export declare function loadRuntimePlugins(): VersionaryPluginRuntime[];
@@ -1,24 +1,7 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.loadRuntimePlugins = loadRuntimePlugins;
4
- const load_config_js_1 = require("../../config/load-config.js");
5
4
  const plugin_js_1 = require("./github/plugin.js");
6
- const BUILTIN_PLUGIN_FACTORIES = {
7
- github: plugin_js_1.createGitHubPlugin,
8
- };
9
- function loadRuntimePlugins(cwd = process.cwd()) {
10
- const loaded = (0, load_config_js_1.loadConfig)(cwd);
11
- const configured = loaded.config.plugins ?? [];
12
- const plugins = [(0, plugin_js_1.createGitHubPlugin)()];
13
- const seen = new Set(plugins.map((plugin) => plugin.name));
14
- for (const name of configured) {
15
- const factory = BUILTIN_PLUGIN_FACTORIES[name];
16
- if (!factory || seen.has(name)) {
17
- continue;
18
- }
19
- const plugin = factory();
20
- plugins.push(plugin);
21
- seen.add(plugin.name);
22
- }
23
- return plugins;
5
+ function loadRuntimePlugins() {
6
+ return [(0, plugin_js_1.createGitHubPlugin)()];
24
7
  }
@@ -15,7 +15,7 @@ export interface VersionaryPackage {
15
15
  }
16
16
  export interface VersionaryConfig {
17
17
  version: 1;
18
- "review-mode"?: "direct" | "review";
18
+ "review-mode"?: "direct" | "pr" | "review";
19
19
  "version-file"?: string;
20
20
  "changelog-file"?: string;
21
21
  "release-branch"?: string;
@@ -27,7 +27,6 @@ export interface VersionaryConfig {
27
27
  "include-commit-authors"?: boolean;
28
28
  "release-type"?: string;
29
29
  packages?: Record<string, VersionaryPackage>;
30
- plugins?: string[];
31
30
  }
32
31
  export interface LoadedConfig {
33
32
  path: string;
@@ -4,6 +4,8 @@ export interface VerifyResult {
4
4
  name: string;
5
5
  ok: boolean;
6
6
  details: string;
7
+ category: "config" | "paths" | "version-files";
8
+ remediation?: string;
7
9
  }>;
8
10
  }
9
11
  export declare function verifyProject(cwd?: string): VerifyResult;
@@ -16,6 +16,7 @@ function verifyProject(cwd = process.cwd()) {
16
16
  name: "config-load",
17
17
  ok: true,
18
18
  details: `Loaded ${node_path_1.default.basename(config.path)} (${config.format})`,
19
+ category: "config",
19
20
  });
20
21
  const strategy = (0, resolve_js_1.resolveVersionStrategy)(config.config);
21
22
  const versionFile = strategy.getVersionFile(config.config);
@@ -24,6 +25,10 @@ function verifyProject(cwd = process.cwd()) {
24
25
  name: `version-file:${versionFile}`,
25
26
  ok: exists,
26
27
  details: exists ? "Version file exists" : `Missing ${versionFile}`,
28
+ category: "version-files",
29
+ remediation: exists
30
+ ? undefined
31
+ : `Create ${versionFile} or set "version-file" to the correct path for your release strategy.`,
27
32
  });
28
33
  if (config.config.packages) {
29
34
  for (const [pkgPathRaw, packageConfig] of Object.entries(config.config.packages)) {
@@ -33,6 +38,10 @@ function verifyProject(cwd = process.cwd()) {
33
38
  name: `package-path:${pkgPathRaw}`,
34
39
  ok: exists,
35
40
  details: exists ? "Path exists" : `Missing path: ${pkgPathRaw}`,
41
+ category: "paths",
42
+ remediation: exists
43
+ ? undefined
44
+ : `Create ${pkgPathRaw} or remove/rename this entry under "packages" in versionary config.`,
36
45
  });
37
46
  if (exists) {
38
47
  const packageContext = (0, package_context_js_1.resolvePackageStrategyContext)(config.config, pkgPathRaw, packageConfig);
@@ -44,6 +53,10 @@ function verifyProject(cwd = process.cwd()) {
44
53
  details: packageVersionExists
45
54
  ? "Version file exists"
46
55
  : `Missing ${packageVersionFile}`,
56
+ category: "version-files",
57
+ remediation: packageVersionExists
58
+ ? undefined
59
+ : `Create ${packageVersionFile} or adjust package release settings ("release-type"/"version-file") for ${pkgPathRaw}.`,
47
60
  });
48
61
  }
49
62
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "versionary",
3
- "version": "0.9.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",