grubble 5.1.0

Automatic semantic versioning based on conventional commits, optimized for AI-generated commit messages
grubble-5.1.0 is not a library.

Grubble

CI Version & Release Version Rust License: MIT

Automatic semantic versioning from conventional commits. Designed to be driven by AI-generated commit messages.

Why

Grubble reads your commit history, applies conventional commit rules, and bumps the version in one command:

  • feat: → minor, fix: → patch, ! / BREAKING CHANGE → major
  • Optionally writes the new version to Cargo.toml or package.json
  • Optionally generates a CHANGELOG.md (Keep a Changelog format)
  • Cuts and pushes tags, including floating v4 / v4.1 tags for GitHub Actions
  • Plays well with AI agents that emit conventional commits — see .github/prompts/sc.prompt.md

Quick Start

# Install
cargo install grubble

# Make some conventional commits
git commit -m "feat: add login"
git commit -m "fix: handle empty input"

# Preview the next version
grubble --dry-run

# Release
grubble --push --tag

For CI, jump to GitHub Actions.

Installation

Pre-built binaries

Download the latest release for your platform from GitHub Releases, or grab it directly:

# Linux x86_64
curl -L https://github.com/davegarvey/grubble/releases/latest/download/grubble-linux-x86_64.tar.gz | tar xz
sudo mv grubble /usr/local/bin/

# Linux ARM64
curl -L https://github.com/davegarvey/grubble/releases/latest/download/grubble-linux-aarch64.tar.gz | tar xz
sudo mv grubble /usr/local/bin/

# macOS Intel
curl -L https://github.com/davegarvey/grubble/releases/latest/download/grubble-macos-x86_64.tar.gz | tar xz
sudo mv grubble /usr/local/bin/

# macOS Apple Silicon
curl -L https://github.com/davegarvey/grubble/releases/latest/download/grubble-macos-aarch64.tar.gz | tar xz
sudo mv grubble /usr/local/bin/

# Windows (PowerShell)
Invoke-WebRequest -Uri https://github.com/davegarvey/grubble/releases/latest/download/grubble-windows-x86_64.zip -OutFile grubble.zip
Expand-Archive grubble.zip

Cargo

cargo install grubble

From source

git clone https://github.com/davegarvey/grubble.git
cd grubble
cargo build --release
# Binary at target/release/grubble

GitHub Action

- uses: davegarvey/grubble@v4

The Action exposes three outputs for downstream steps:

Output Description
version The new version (e.g. 1.2.3). If no bump was needed, this matches previous-version.
previous-version The version before the bump.
bump-type One of major, minor, patch, none.

When no bump is needed (e.g. only refactor: or chore: commits since the last tag), the Action exits cleanly with bump-type=none rather than failing.

Usage

grubble                      # bump based on commits since the last tag
grubble --push               # push the bump commit
grubble --tag                # create a git tag for the new version
grubble --changelog          # generate or update CHANGELOG.md
grubble --update-major-tag   # also maintain a floating v4 tag
grubble --raw                # print the new version, no changes
grubble --dry-run            # preview the bump without applying it
grubble --bump-type          # print major | minor | patch | none
grubble --output json        # emit machine-readable output (with --raw or --bump-type)
grubble --quiet              # suppress the commit list
grubble --help               # full flag reference

Every flag has a corresponding option in .versionrc.json (see Configuration). CLI flags override file values.

Configuration

Grubble reads .versionrc.json from the project root. Flags and file values are merged, with flags winning.

{
  "preset": "rust",
  "packageFiles": ["Cargo.toml"],
  "tagPrefix": "v",
  "commitPrefix": "chore(release): bump",
  "tag": true,
  "push": true,
  "changelog": true
}
Option CLI flag Default Description
preset --preset git Versioning strategy: git, rust, or node.
packageFiles --package-files [] Comma-separated files to update (for rust / node).
tagPrefix --tag-prefix v Prefix for git tags.
commitPrefix --commit-prefix chore: bump version Prefix for the bump commit message.
tag --tag false Create a git tag for the new version.
push --push false Push the commit (and tag) to the remote.
releaseNotes --release-notes (-r) false Include release notes in the tag annotation. Requires tag: true.
changelog --changelog false Generate or update CHANGELOG.md.
updateMajorTag --update-major-tag false Maintain a floating v4 tag pointing to the latest v4.x.x.
updateMinorTag --update-minor-tag false Maintain a floating v4.1 tag pointing to the latest v4.1.x.
gitUserName --git-user-name github-actions[bot] Identity used for the bump commit when no local git user is configured.
gitUserEmail --git-user-email 41898282+github-actions[bot]@users.noreply.github.com Email used for the bump commit when no local git user is configured.
types see Commit Types Per-type bump behavior. Valid values: major, minor, patch, none.

If your repo has a local user.name / user.email set, grubble uses those and ignores gitUserName / gitUserEmail. In CI, set these to match your bot user (e.g. github-actions[bot]).

Versioning Strategies

The preset option controls what files grubble writes.

  • git (default) — tracks versions via tags only. No files are modified. Use this for monorepos or projects with their own versioning scheme.
  • rust — updates the version field in Cargo.toml and refreshes Cargo.lock. Pairs with cargo publish.
  • node — updates the version field in package.json and package-lock.json. Pairs with npm publish.

When switching from git to a file-based preset, or when a package file is behind the latest tag, grubble first syncs the file to the tag (with a chore: sync package version to v... commit) and then proceeds with the normal bump.

Major / Minor Tag Tracking

When updateMajorTag is enabled, grubble maintains a lightweight tag that follows the latest release in its range:

  • v4 → latest v4.x.x
  • v4.1 → latest v4.1.x (additionally requires updateMinorTag)

This is the convention GitHub Actions use, so consumers can reference uses: owner/repo@v4 and automatically get the latest v4 release.

grubble --tag --push --update-major-tag --update-minor-tag

These tags are force-pushed. Anyone who has them checked out locally will need to re-fetch. Only enable this if you follow semver strictly — a breaking change bumps the major and consumers pinned to the old v4 will pick it up.

Changelog Generation

grubble --changelog writes a CHANGELOG.md in Keep a Changelog format:

  • feat:Added
  • fix:Fixed
  • perf:, refactor:Changed
  • revert:Removed
  • security:Security
  • Breaking changes → Changed with a BREAKING: prefix

The changelog is committed as part of the release.

GitHub Actions

The Action downloads a pre-built binary, runs the bump, and exposes the new version as outputs. It is the simplest way to wire grubble into your release flow.

name: Release
on:
  pull_request:
    types: [closed]
    branches: [main]

jobs:
  release:
    if: github.event.pull_request.merged == true
    runs-on: ubuntu-latest
    permissions:
      contents: write
      pull-requests: read
    steps:
      - uses: actions/checkout@v4
        with:
          fetch-depth: 0
      - uses: davegarvey/grubble@v4
        with:
          push: true
          tag: true
          update-major-tag: true

The davegarvey/grubble@v4 pin floats to the latest v4.x.x release. Pin to a specific tag (e.g. @v4.9.4) if you need the release frozen.

Manual setup

Use this when you want to run tests and linting before the bump, or when self-hosted runners can't reach GitHub Releases.

name: Release
on:
  pull_request:
    types: [closed]
    branches: [main]

jobs:
  release:
    if: github.event.pull_request.merged == true
    runs-on: ubuntu-latest
    permissions:
      contents: write
      pull-requests: read
    steps:
      - uses: actions/checkout@v4
        with:
          fetch-depth: 0
      - uses: actions-rust-lang/setup-rust-toolchain@v1
        with:
          toolchain: stable
      - run: cargo test
      - run: cargo clippy -- -D warnings
      - name: Bump version and release
        run: |
          grubble \
            --preset rust \
            --push \
            --tag \
            --update-major-tag \
            --git-user-name "github-actions[bot]" \
            --git-user-email "41898282+github-actions[bot]@users.noreply.github.com"

CI checklist:

  • Grant contents: write so the workflow can push commits and tags.
  • Use fetch-depth: 0 on actions/checkout so grubble can see the full history.
  • Pin the major version (@v4) unless you want the release frozen.
  • Use the GitHub Action unless you need custom logic between the lint and the bump.

CI/CD Patterns

Skip the bump when nothing changed

grubble --bump-type prints major, minor, patch, or none. Use it as a gate:

if [ "$(grubble --bump-type --preset rust)" != "none" ]; then
  grubble --push --tag --preset rust
fi

grubble --dry-run always exits 0 (success, including no-op). For the "would a bump happen?" signal, use --bump-type.

Read the bump type

grubble --bump-type prints major, minor, patch, or none. Use it to drive conditional logic:

case "$(grubble --bump-type)" in
  major) notify "breaking release" ;;
  minor) notify "feature release" ;;
  patch) : ;;                  # silent patch
  none)   exit 0 ;;
esac

Read the new version

grubble --raw prints the version that would be released without making changes. It honors --preset (e.g. --preset rust reads from Cargo.toml; --preset node reads from package.json; --preset git reads from the latest tag). Pair it with gh release create or a deploy step:

NEW_VERSION=$(grubble --raw --preset rust)
gh release create "v$NEW_VERSION" --title "Release v$NEW_VERSION"

Machine-readable output

Both --bump-type and --raw accept --output json for stable, machine-readable output:

$ grubble --bump-type --output json
{
  "bump_type": "minor",
  "current_version": "1.2.3",
  "triggering_commits": ["Minor: feat: add login"],
  "unknown_commits": []
}

$ grubble --raw --preset rust --output json
{
  "version": "1.2.3",
  "preset": "rust"
}

--output json is rejected when combined with the normal run mode or --dry-run (those modes always print human-readable text). Use --output json from CI scripts that need to parse the result instead of shell-substring matching.

How It Works

  1. Sync package files to the latest tag (for rust / node presets).
  2. Analyze commits since the last tag using conventional commit rules.
  3. Pick the highest bump (major / minor / patch / none).
  4. Update package files and CHANGELOG.md if configured.
  5. Create the bump commit.
  6. Create tags, including v4 / v4.1 floating tags if enabled.
  7. Push to the remote if configured.

Commit Types

Default mappings (override with the types option in .versionrc.json):

Commit Bump
! or BREAKING CHANGE major
feat: minor
fix: patch
perf:, refactor: none by default; usually remapped to patch or minor
docs:, test:, chore:, ci:, build:, style: none

Custom mapping example:

{
  "types": {
    "perf": "minor",
    "revert": "none"
  }
}

Troubleshooting

"Author identity unknown"

Set a git identity in the workflow before running grubble:

- run: |
    git config user.name "github-actions[bot]"
    git config user.email "41898282+github-actions[bot]@users.noreply.github.com"

The --git-user-name and --git-user-email flags are ignored when a local user.name / user.email is already set.

"grubble: command not found"

Make sure grubble is on PATH. If you used cargo install --root <dir>, add <dir>/bin to PATH.

No version bump on merge

  • Confirm the merged commits include feat: / fix: (or another type mapped to a bump).
  • Confirm the workflow has contents: write.
  • Confirm actions/checkout uses fetch-depth: 0.

Invalid config file

Empty or invalid .versionrc.json falls back to defaults with a warning. Run grubble once to see the warning, then fix the JSON.

Package file not found

  • Check that --package-files points to files relative to the repo root.
  • For multi-package repos, pass each file: --package-files "Cargo.toml,client/Cargo.toml".

"Invalid format" in $GITHUB_OUTPUT

Fixed in v4.9.4 (see #54). Bump the Action to davegarvey/grubble@v4 (movable) to pick up the fix, or pin to a release ≥ v4.9.4 once you have a chance to verify.

Migration from v4

v5.0.0 is a breaking release for the CLI's exit-code contract. The GitHub Action and version.yml workflow are unchanged in behavior; the v5 contract is what they always wanted.

What changed

  • grubble exits 0 on success, including when no bump is needed. Previously, exit 1 meant "no bump" — now exit 1 means "error."
  • grubble --raw honors --preset. --raw --preset rust reads from Cargo.toml; --raw --preset node reads from package.json; --raw --preset git reads from the latest tag. Previously, --raw always read from git tags regardless of preset.
  • New --output text|json flag for --bump-type and --raw. Use this from CI scripts that need to parse the result.
  • Action requires release checksums (was warn-and-continue on missing .sha256).
  • The Action's "Get current version" step now uses a single ./grubble --raw --preset <preset> call instead of preset-specific shell branches.

If you script against the CLI

Replace exit-code gates with --bump-type checks:

# v4 (broken on v5)
if grubble --dry-run --preset rust; then
  grubble --push --tag --preset rust
fi

# v5
if [ "$(grubble --bump-type --preset rust)" != "none" ]; then
  grubble --push --tag --preset rust
fi

Replace --raw exit-code handling with output parsing:

# v4
NEW_VERSION=$(./grubble --raw --preset rust) || NEW_VERSION=$(./grubble --raw --preset rust || echo "0.0.0")

# v5 — exits 0 whenever a version is produced
NEW_VERSION=$(./grubble --raw --preset rust)

Staying on v4

@v4 (the floating major tag) and all @v4.x.x specific tags remain available indefinitely. Pin to @v4 or @v4.9.4 to stay on the v4 contract. The default floating tag shifts to @v5 once v5 ships.

Contributing

See CONTRIBUTING.md for development setup, commit guidelines, and the release process.

License

MIT.

For AI Users

This tool is built for AI-generated commits that follow the conventional commit format. See .github/prompts/sc.prompt.md for a prompt that produces commits compatible with grubble.