From df312e09715539b133c0c872a38285de70072805 Mon Sep 17 00:00:00 2001 From: Edgars Date: Thu, 23 Apr 2026 11:51:48 +0100 Subject: [PATCH] chore(release): cap auto-bumps at minor to avoid accidental majors MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Convert `.release-it.json` → `.release-it.cjs` so we can pass a custom `whatBump` function to the conventional-changelog plugin. The function caps the recommended bump at `minor`, even when a commit contains a BREAKING CHANGE footer or `!:` marker. Background: a stray "BREAKING CHANGE:" line in any commit body would trip `conventional-recommended-bump` into recommending a major release, which in this CLI's current 0.38.x state would instantly ship 1.0.0 without any intended stability declaration. Matching the fix applied to genlayer-js (#159) so the two release pipelines behave the same. Net effect: - BREAKING CHANGE footer / `!:` marker → minor bump (was: major) - feat: → minor (unchanged) - fix: / chore: / docs: / ci: / etc. → patch (unchanged) - `release-it major` still forces a major when genuinely needed All other release-it settings (git, github, npm, types, hooks) carried over verbatim from the JSON config. JSON deleted because it takes precedence over CJS in release-it's config resolution order. --- .release-it.cjs | 87 ++++++++++++++++++++++++++++++++++++++++++++++++ .release-it.json | 66 ------------------------------------ 2 files changed, 87 insertions(+), 66 deletions(-) create mode 100644 .release-it.cjs delete mode 100644 .release-it.json diff --git a/.release-it.cjs b/.release-it.cjs new file mode 100644 index 00000000..a8f89560 --- /dev/null +++ b/.release-it.cjs @@ -0,0 +1,87 @@ +/** + * release-it configuration. + * + * Converted from `.release-it.json` to `.cjs` so we can supply a custom + * `whatBump` function that caps the recommended bump at `minor`. Reason: + * + * `@release-it/conventional-changelog` forwards the default bump logic to + * `conventional-recommended-bump`, whose `conventionalcommits` preset always + * returns `major` when any commit's body contains a "BREAKING CHANGE" footer + * (case-insensitive) or a `!:` marker. That would silently jump this CLI + * from 0.x → 1.0.0 on a single stray commit, which is never what we want. + * + * This override forces the recommended bump to stop at `minor`. A genuine + * major release still requires an operator to pass `release-it major` + * explicitly, which is how most mature TS libraries handle it. + * + * `.release-it.json` takes precedence over `.release-it.cjs` in release-it's + * config resolution order, so the JSON file must not exist. + */ + +module.exports = { + git: { + commitMessage: "Release v${version} [skip ci]", + tagName: "v${version}", + requireCleanWorkingDir: false, + requireUpstream: false, + requireCommits: false, + push: true, + }, + github: { + release: true, + tokenRef: "GITHUB_TOKEN", + }, + npm: { + publish: true, + publishArgs: ["--provenance --access public"], + skipChecks: true, + }, + plugins: { + "@release-it/conventional-changelog": { + preset: "conventionalcommits", + infile: "CHANGELOG.md", + /** + * Cap automatic bumps at minor. Levels: 0 = major, 1 = minor, 2 = patch. + * BREAKING CHANGE footer or `!:` marker → minor (was: major) + * feat: → minor (unchanged) + * fix: / chore: / etc. → patch (unchanged) + * Run `release-it major` manually for a genuine major release. + */ + whatBump: (commits) => { + const hasBreaking = commits.some((c) => { + const bangMarker = typeof c.header === "string" && /^[^:!\s]+!:/.test(c.header); + const breakingFooter = + Array.isArray(c.notes) && c.notes.some((n) => /BREAKING[ -]CHANGE/i.test(n.title ?? "")); + return bangMarker || breakingFooter; + }); + const hasFeat = commits.some((c) => c.type === "feat"); + + if (hasBreaking) { + return { + level: 1, + reason: + "BREAKING CHANGE detected; auto-bump capped at minor. Run `release-it major` to force a major release.", + }; + } + if (hasFeat) { + return { level: 1, reason: "feat commits present → minor" }; + } + return { level: 2, reason: "no feat/breaking commits → patch" }; + }, + types: [ + { type: "feat", section: "Features" }, + { type: "fix", section: "Bug Fixes" }, + { type: "chore", section: "Improvement Tasks" }, + { type: "ci", section: "Continuous Integration" }, + { type: "docs", section: "Documentation" }, + { type: "style", section: "Styles" }, + { type: "refactor", section: "Refactor Tasks" }, + { type: "perf", section: "Performance Improvements" }, + { type: "test", section: "Testing" }, + ], + }, + }, + hooks: { + "after:bump": "npm run build", + }, +}; diff --git a/.release-it.json b/.release-it.json deleted file mode 100644 index 5ba0b6f0..00000000 --- a/.release-it.json +++ /dev/null @@ -1,66 +0,0 @@ -{ - "git": { - "commitMessage": "Release v${version} [skip ci]", - "tagName": "v${version}", - "requireCleanWorkingDir": false, - "requireUpstream": false, - "requireCommits": false, - "push": true - }, - "github": { - "release": true, - "tokenRef": "GITHUB_TOKEN" - }, - "npm": { - "publish": true, - "publishArgs": ["--provenance --access public"], - "skipChecks": true - }, - "plugins": { - "@release-it/conventional-changelog": { - "preset": "conventionalcommits", - "infile": "CHANGELOG.md", - "types": [ - { - "type": "feat", - "section": "Features" - }, - { - "type": "fix", - "section": "Bug Fixes" - }, - { - "type": "chore", - "section": "Improvement Tasks" - }, - { - "type": "ci", - "section": "Continuous Integration" - }, - { - "type": "docs", - "section": "Documentation" - }, - { - "type": "style", - "section": "Styles" - }, - { - "type": "refactor", - "section": "Refactor Tasks" - }, - { - "type": "perf", - "section": "Performance Improvements" - }, - { - "type": "test", - "section": "Testing" - } - ] - } - }, - "hooks": { - "after:bump": "npm run build" - } -}