RodarRelease

Version management and release utilities for Rodar projects. Automates semantic version bumping, changelog updates, git commits/tags.

Installation

Add rodar_release to your list of dependencies in mix.exs:

def deps do
  [
    {:rodar_release, "~> 0.1.0"}
  ]
end

With Igniter

If your project uses Igniter, you can install with automatic setup:

mix igniter.install rodar_release

This creates a CHANGELOG.md with the standard Keep a Changelog structure if one doesn't exist. To also configure a custom AI CLI:

mix igniter.install rodar_release --ai-cmd gemini

Without Igniter

If your project does not use Igniter, follow these steps after adding the dependency:

  1. Create CHANGELOG.md in your project root with the Keep a Changelog structure:

     # Changelog
    
     All notable changes to this project will be documented in this file.
    
     The format is based on [Keep a Changelog](https://keepachangelog.com),
     and this project adheres to [Semantic Versioning](https://semver.org).
    
     ## [Unreleased]
    
     ## [0.1.0] - 2026-01-01
    
     ### Added
    
     - Initial release

    Replace 0.1.0 with your current version and 2026-01-01 with today's date.

  2. (Optional) Configure AI CLI for changelog generation in config/config.exs:

     config :rodar_release, :ai_cmd, {"claude", ["-p"]}   # default (Claude Code)
     config :rodar_release, :ai_cmd, {"gemini", ["-p"]}   # Gemini CLI
     config :rodar_release, :ai_cmd, {"codex", ["e"]}     # OpenAI Codex
     config :rodar_release, :ai_cmd, {"gh", ["-p"]}       # GitHub Copilot

    If omitted, Claude Code is used by default.

  3. (Recommended) Install the changelog skill for AI-assisted dev tools:

     npx skills add rodar-project/rodar_skills --skill changelog

Semantic Versioning

This tool follows Semantic Versioning (MAJOR.MINOR.PATCH):

Pre-release versions

Pre-release identifiers (e.g. 1.2.0-rc.1) are supported and automatically inferred from the current git branch:

Branch Suffix Example
main / master (none) 1.2.0
develop / dev-dev.N1.2.0-dev.1
release/* / rc/*-rc.N1.2.0-rc.1
beta/*-beta.N1.2.0-beta.1
alpha/*-alpha.N1.2.0-alpha.1

The full lifecycle looks like:

main:    1.1.0 (stable)
              ↓ branch develop
develop: 1.2.0-dev.1 → 1.2.0-dev.2 → 1.2.0-dev.3
                                       ↓ branch release/1.2.0
release: 1.2.0-rc.1  → 1.2.0-rc.2
                         ↓ merge to main
main:    1.2.0 (stable)

Rules:

Custom branch mappings can be configured in config.exs:

config :rodar_release, :branch_pre, %{
  "staging" => "rc",
  ~r/^preview\// => "beta"
}

Build metadata (e.g. 1.0.0+build.42) is part of the semver spec but is not currently supported by this tool.

Usage

mix rodar_release              # list available commands
mix help rodar_release.patch   # help for a specific command

Commands

Release

mix rodar_release.patch              # bug fix:         1.0.8 -> 1.0.9
mix rodar_release.minor              # new feature:     1.0.8 -> 1.1.0
mix rodar_release.major              # breaking change: 1.0.8 -> 2.0.0
mix rodar_release.minor --pre rc     # pre-release:     1.1.0 -> 1.2.0-rc.1
mix rodar_release.minor --dry-run    # preview changes
  1. Resolves the pre-release suffix from the current branch (or --pre flag)
  2. Validates the git working directory is clean
  3. If [Unreleased] in CHANGELOG.md is empty, offers to generate an entry using Claude Code (requires claude CLI)
  4. Bumps the version in mix.exs
  5. Updates CHANGELOG.md with the release date and comparison links
  6. Commits changes with message release: vX.Y.Z
  7. Creates an annotated git tag vX.Y.Z

AI-generated changelog

When releasing with an empty [Unreleased] section, the tool gathers the git log and diff since the last tag and asks an AI CLI to suggest a changelog entry using Keep a Changelog headings (### Added, ### Changed, ### Fixed, ### Removed). You are prompted to confirm before anything is written.

By default it uses Claude Code. To use a different AI CLI, configure the command and args in your config.exs:

config :rodar_release, :ai_cmd, {"claude", ["-p"]}   # default
config :rodar_release, :ai_cmd, {"gemini", ["-p"]}   # Gemini CLI
config :rodar_release, :ai_cmd, {"codex", ["e"]}     # OpenAI Codex

The prompt is appended as the last argument.

Merge (post-merge promotion)

mix rodar_release.merge              # 1.5.1-dev.3 -> 1.5.1
mix rodar_release.merge minor        # 1.5.1-dev.3 -> 1.6.0
mix rodar_release.merge major        # 1.5.1-dev.3 -> 2.0.0
mix rodar_release.merge --dry-run    # preview changes

Promotes a pre-release version to stable after merging a development branch into main/master. Without arguments, strips the pre-release suffix. Pass a segment to bump higher.

If you run mix rodar_release.patch (or minor/major) on a stable branch and the current version has a pre-release suffix, the task will detect this and suggest using merge instead.

Rollback

mix rodar_release.rollback           # undo last release (soft reset)
mix rodar_release.rollback --hard    # undo and discard changes
mix rodar_release.rollback --dry-run # preview rollback

Undoes the last release by deleting its tag and resetting the release commit. Requires the latest commit to be a release commit (release: vX.Y.Z).

Amend

mix rodar_release.amend              # fold changes into release commit
mix rodar_release.amend --dry-run    # preview amend

Amends the last release commit with any current changes and re-tags. Useful for fixing a typo or adding a missing file right after releasing.

Options

Option Applies to Description
--dry-run all commands Preview changes without applying them
--pre LABEL patch, minor, major Create a pre-release version (e.g., --pre rc, --pre beta)
--hard rollback Discard release changes entirely

Programmatic API

RodarRelease.read_version()
#=> "1.2.0"

RodarRelease.bump("1.2.3", :minor)
#=> "1.3.0"

RodarRelease.bump("1.1.0", :minor, "rc")
#=> "1.2.0-rc.1"

RodarRelease.bump("1.2.0-rc.1", :patch, "rc")
#=> "1.2.0-rc.2"

RodarRelease.bump("1.2.0-rc.2", :patch)
#=> "1.2.0"

RodarRelease.promote("1.5.1-dev.3", :minor)
#=> "1.6.0"

RodarRelease.has_pre?("1.2.0-rc.1")
#=> true

RodarRelease.write_version("1.3.0")