Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Introduction

Releasaurus 🦕 automates releases across multiple languages and Git forges. Point it at a repository and it analyzes your commit history, generates a changelog, and publishes a tagged release — no configuration required. Add a releasaurus.toml when you want version file updates, monorepo support, or custom changelog formatting.

# 1. Open a release PR (analyzes commits, writes the changelog)
releasaurus release-pr --repo "https://github.com/your-org/your-repo"

# 2. After merging the PR, tag and publish the release
releasaurus release --repo "https://github.com/your-org/your-repo"

That two-command loop — release-pr to prepare, release to publish — is the whole workflow. The pull request gives you a review step; Releasaurus handles the tedious version and changelog work.

Key Features

  • Zero config by default — changelog generation and tagging work immediately. Configure only when you need more.
  • Multi-forge — GitHub, GitLab, Gitea, Forgejo, and Azure DevOps (experimental), whether cloud-hosted or self-hosted.
  • Multi-language version updates — Rust, Node.js, Python, Java, PHP, Ruby, Go, and a generic regex-based updater for anything else.
  • Monorepo ready — multiple independently-versioned packages, with combined or separate release PRs.
  • Conventional-commit aware — version bumps follow conventional commits and semver.
  • Forge API native — runs entirely through forge APIs with no local clone required, ideal for CI/CD. An optional hybrid mode uses a local clone for git operations.
  • Command-line overrides — change branch, tag prefix, and prerelease settings per run without editing your config.

Optional Commands

  • releasaurus start-next — bump patch versions right after a release to start the next development cycle.
  • releasaurus get — query projected and published release data as JSON for automation, notifications, and debugging.

Where to Go Next

Credit and Inspiration

Releasaurus builds on the proven ideas of git-cliff, release-please, and release-plz, extending them to a broader set of languages, frameworks, and platforms.

Getting Started

Install Releasaurus and cut your first release in a few minutes.

Install

The fastest option, via cargo-binstall:

cargo binstall releasaurus

From crates.io

Compiles from source:

cargo install releasaurus

Docker

docker pull rgonnella/releasaurus:latest
docker run --rm rgonnella/releasaurus:latest --help

You can also download a binary directly from the releases page, or build from source — see Contributing. Confirm the install with releasaurus --version.

Preview Without Any Risk

Run Releasaurus against a local checkout to see what it would do — no token, no config, no changes:

cd /path/to/your/repo
releasaurus release-pr --forge local --repo "."

The output shows the next version and the generated changelog without touching your repository. See Local & Dry-Run Modes for more.

Cut Your First Release

1. Set an access token

Releasaurus picks the right variable from the --forge you use:

export GITHUB_TOKEN="ghp_your_token_here"    # GitHub
export GITLAB_TOKEN="glpat_your_token_here"  # GitLab
export GITEA_TOKEN="your_token_here"         # Gitea

Every token variable and its required scopes are listed in the Configuration Reference.

2. Open a release PR

releasaurus release-pr --repo "https://github.com/your-org/your-repo"

This analyzes your commits, picks the next version, generates a changelog, and opens a pull request. (--forge is inferred for known hosts like github.com; pass it explicitly for self-hosted instances.)

3. Merge, then publish

After reviewing and merging the PR:

releasaurus release --repo "https://github.com/your-org/your-repo"

This tags the release commit and publishes the release on your forge.

Add Version File Updates (Optional)

By default Releasaurus only writes changelogs and tags. To also bump versions in your manifests (package.json, Cargo.toml, etc.), add a releasaurus.toml at the repository root:

[[package]]
path = "."
release_type = "node"  # or rust, python, java, php, ruby, go, generic

See Configuration for monorepos, prereleases, changelog customization, and the full option list.

Next Steps

Commands

Releasaurus operates entirely through forge platform APIs — no local clone required — so every command can run from any machine with network access to your forge. An optional hybrid mode uses a local clone for git operations.

The core workflow is two commands:

# 1. Prepare: analyze commits, bump versions, write changelog, open a PR
releasaurus release-pr --repo "https://github.com/owner/repo"

# 2. Review and merge the PR in your forge's UI, then publish:
releasaurus release --repo "https://github.com/owner/repo"

start-next and get are optional helpers covered below.

release-pr

Analyzes commits since the last release, determines the version bump (patch/minor/major) from conventional commits, updates version files (if a release_type is configured), generates the changelog, and creates or updates a release pull request.

# All packages
releasaurus release-pr --repo "https://github.com/owner/repo"

# A single package in a monorepo
releasaurus release-pr --package my-pkg \
  --repo "https://github.com/owner/repo"

Supports prereleases, dry-run, and the overrides below.

release

Run after the release PR is merged. Validates the release commit, creates and pushes the git tag, and publishes the release on your forge. Reads the release notes directly from the merged PR body (see Editing Release Notes).

# All packages with merged release PRs
releasaurus release --repo "https://github.com/owner/repo"

# A single package
releasaurus release --package my-pkg \
  --repo "https://github.com/owner/repo"

start-next

Bumps the patch version for each previously-tagged package and commits the manifest changes directly to the base branch as a chore commit. It does not open PRs or create tags, and skips packages that have never been tagged. Use it right after a release to keep manifest versions ahead of the last release.

# All previously-tagged packages
releasaurus start-next --repo "https://github.com/owner/repo"

# Specific packages only
releasaurus start-next --repo "https://github.com/owner/repo" \
  --packages pkg-a,pkg-b

Note: This commits directly to your base branch. Ensure your branch protection rules permit it. It can also run automatically after release — see auto_start_next.

get

Queries release information as JSON without making any changes — useful for debugging version detection and for building custom notifications. (show is kept as an alias.)

get next-release

Projects the next release for each package as JSON.

releasaurus get next-release --repo "https://github.com/owner/repo"

# Single package, or write to a file
releasaurus get next-release --package my-pkg --out-file releases.json \
  --repo "https://github.com/owner/repo"

get current-release

Returns the most recent release for each package (packages without a release are omitted).

releasaurus get current-release --repo "https://github.com/owner/repo"

get release

Returns the data for an existing tag — tag, sha, and notes.

releasaurus get release --tag v1.0.0 \
  --repo "https://github.com/owner/repo"

get notes

Re-renders release notes from a get next-release JSON file using your configured Tera template. This lets you transform the data (for example, replacing author names with Slack IDs) before producing final notes. (recompiled-notes is kept as an alias.)

# 1. Capture release data
releasaurus get next-release --out-file releases.json \
  --repo "https://github.com/owner/repo"

# 2. Transform it however you like (custom script), then re-render:
releasaurus get notes --file releases.json \
  --repo "https://github.com/owner/repo"

Output is a JSON array of { name, notes } objects.

Global Options & Forge Selection

These apply to every command:

FlagEnv fallbackDescription
--repo <url>RELEASAURUS_REPORepository URL
--forge <forge>RELEASAURUS_FORGEForge type (see below)
--token <token>GITHUB_TOKEN, etc.Auth token
--local-path <path>RELEASAURUS_LOCAL_PATHLocal clone for hybrid mode
--base-branch <branch>Override the base branch
--debugRELEASAURUS_DEBUGVerbose logging

Available forge types: github, gitlab, gitea, forgejo, azure-devops (experimental), and local (testing). For the full list of token variables and required scopes, see the Configuration Reference.

Automatic forge inference

When --repo points at a recognized cloud host, --forge can be omitted:

HostInferred forge
github.comgithub
gitlab.comgitlab
gitea.comgitea
codeberg.orgforgejo
dev.azure.comazure-devops

Self-hosted instances (e.g. https://gitlab.company.com/...) and --forge local always require the flag, since the host alone can’t identify the forge software.

Testing Modes

Three ways to run safely or against a local checkout.

Dry-Run Mode

Performs all analysis and validation and logs exactly what would happen, but makes no changes — no branches, PRs, tags, or releases. Dry-run automatically enables debug logging (output is prefixed dry_run:).

releasaurus release-pr --dry-run --repo "https://github.com/owner/repo"

# Or via environment variable
export RELEASAURUS_DRY_RUN=true

Local Repository Mode

--forge local reads commits, tags, and files from your working directory and never contacts a remote forge — ideal for validating a releasaurus.toml change before pushing. No token required.

releasaurus release-pr --forge local --repo "."

# Or from a specific path
releasaurus release-pr --forge local --repo "/path/to/repo"

Hybrid Mode (Local Git + Remote Forge)

--local-path performs git operations (reading commits/tags/files, creating branches, committing, pushing) against a local clone, while still creating real PRs and releases via the forge API. Use it when you already have a checkout and want to avoid repeated API calls for data gathering. A forge token is still required.

releasaurus release-pr \
  --repo "https://github.com/owner/repo" \
  --token "$GITHUB_TOKEN" \
  --local-path /path/to/checkout

CI fetch depth: in hybrid mode the local checkout must include full history and all tags back to the previous release. Most CI systems shallow-clone by default — set fetch-depth: 0 (GitHub/Gitea Actions) or GIT_DEPTH: 0 (GitLab CI), or run git fetch --unshallow. See CI/CD Integration for per-platform setup.

Configuration Overrides

Override config from the command line without editing releasaurus.toml — handy for testing, one-off releases, and per-branch CI settings.

FlagEffect
--base-branch <branch>Override the base branch
--tag-prefix <prefix>Global tag prefix for all packages
--prerelease-suffix <suffix>Global prerelease suffix (empty "" disables)
--prerelease-strategy <versioned|static>Global prerelease strategy
--skip-sha <sha>Skip a commit by SHA prefix (repeatable)
--reword <sha>=<message>Rewrite a commit message (repeatable)
--set-package <pkg>.<property>=<value>Per-package override (repeatable)

--set-package takes precedence over all other overrides and config. Supported properties: tag_prefix, prerelease.suffix, prerelease.strategy. Setting an unsupported property prints an error listing valid values.

Precedence (highest to lowest): --set-package → global CLI overrides → package config → global config → defaults.

# Override base branch and global prerelease suffix
releasaurus release-pr --base-branch develop --prerelease-suffix beta \
  --repo "https://github.com/owner/repo"

# Per-package override (e.g. only the frontend gets a beta suffix)
releasaurus release-pr --set-package frontend.prerelease.suffix=beta \
  --repo "https://github.com/owner/repo"

# Skip one commit and reword another
releasaurus release-pr --skip-sha abc123d \
  --reword "def456e=feat: improved authentication" \
  --repo "https://github.com/owner/repo"

See Configuration for what these settings mean.

Known Limitations

Forgejo: Closed Release PRs on Repeated Runs

Forgejo’s API does not support force-pushing a branch. As a workaround, Releasaurus deletes and re-creates the release branch on each run; Forgejo auto-closes the PR targeting the deleted branch, so each run leaves a closed PR behind. Use hybrid mode (--local-path) to avoid this. A patch to Forgejo to allow force pushing the release branch has been accepted and will be available in v16. https://codeberg.org/forgejo/forgejo/pulls/12663

Azure DevOps: Release Branch Requires “Allow rewriting history”

When updating a release PR, Releasaurus resets the release branch to the tip of the base branch and replays the changelog commit. If the existing release branch has diverged, this is a non-fast-forward update that Azure DevOps rejects unless Allow rewriting history is granted on the release branch (typically releasaurus-release-*).

Grant it under Project Settings → Repositories → {repo} → Security → Branches → {release branch}, setting Allow rewriting history to Allow for the identity holding the PAT. Azure DevOps release also only pushes the git tag — there is no native release object, so no release notes page is published.

Getting Help

releasaurus --help          # general help
releasaurus <cmd> --help    # command-specific help
releasaurus --version       # version information

Configuration

Releasaurus works with zero configuration for changelog generation and tagging. Add an optional releasaurus.toml at your repository root when you need more. This page covers the common cases; for the exhaustive option list see the Configuration Reference.

Do You Need a Config File?

You don’t need one if you only want changelog generation and tagging with the default format and the default v tag prefix.

You do need one to:

  • update version files (set a release_type)
  • manage multiple packages (monorepo)
  • create prereleases (alpha/beta/rc/snapshot)
  • customize the changelog or use custom tag prefixes

Place the file at the repository root:

my-project/
├── releasaurus.toml
├── src/
└── README.md

Single Package

The most common setup — bump versions in one package’s manifests:

[[package]]
path = "."
release_type = "node"  # or rust, python, java, php, ruby, go, generic

release_type selects which manifest and lock files are updated. See Supported Languages for the file list per language.

Monorepos

Define one [[package]] per independently-versioned package. Each gets its own version, tag prefix, and manifest updates.

[[package]]
path = "./frontend"
release_type = "node"
tag_prefix = "frontend-v"

[[package]]
path = "./backend"
release_type = "rust"
tag_prefix = "backend-v"

Tag prefix defaults to v for a root package (path = ".") and <name>-v for nested packages.

Combined vs. Separate PRs

By default all packages with changes are released in a single PR. Set separate_pull_requests = true to give each package its own PR (branches like releasaurus-release-main-frontend):

separate_pull_requests = true

[[package]]
path = "./frontend"
release_type = "node"
tag_prefix = "frontend-v"

[[package]]
path = "./backend"
release_type = "rust"
tag_prefix = "backend-v"
  • Combined (default) — best for tightly-coupled packages that release together and a single, atomic review.
  • Separate — best for large monorepos and independently-versioned packages with different release cadences or owners.

In either mode, target one package with --package <name> on release-pr and release.

Tracking Shared Code

Use additional_paths so a package also releases when shared directories change:

[[package]]
path = "./apps/web"
release_type = "node"
tag_prefix = "web-v"
additional_paths = ["shared/types", "shared/utils"]

Workspaces in a Subdirectory

When a workspace isn’t at the repo root, set workspace_root so lock files resolve correctly:

[[package]]
name = "api-server"
workspace_root = "backend"
path = "services/api"
release_type = "rust"
tag_prefix = "api-v"

This updates backend/services/api/Cargo.toml and the workspace backend/Cargo.lock.

Naming & Path Rules

  • Names must be unique across all packages. If omitted, the name is derived from the last path component. Match the manifest’s name field where one exists (package.json, Cargo.toml, etc.).
  • The full path (workspace_root + path) must be unique. Two packages may share a path only if their workspace_root differs.

Grouped Releases (Sub-Packages)

Use sub_packages to release several packages under one shared tag, changelog, and release, while each sub-package still gets its own manifest updates based on its release_type. A sub-package does not produce its own tag.

[[package]]
name = "platform"
workspace_root = "."
path = "."
tag_prefix = "v"
sub_packages = [
    { name = "web", path = "packages/web", release_type = "node" },
    { name = "cli", path = "packages/cli", release_type = "rust" },
]

Result: one tag (v1.0.0), one changelog covering everything, one release — with package.json (web) and Cargo.toml (cli) updated independently. Reach for this when a group of packages must always ship together with the same version.

Sub-packages vs. separate packages: separate [[package]] entries are versioned and tagged independently; sub_packages share the parent’s single tag and changelog.

Prereleases

Publish alpha/beta/rc/snapshot versions before a stable release. Configure globally with [prerelease] or per-package with a prerelease table.

[prerelease]
suffix = "alpha"
strategy = "versioned"  # or "static"

[[package]]
path = "."
release_type = "node"

Strategies

  • versioned (default) — appends an incrementing counter: 1.1.0-alpha.1, 1.1.0-alpha.2, …
  • static — appends the suffix as-is, with no counter: 1.0.1-SNAPSHOT (common in Java).

Lifecycle

Change behavior by editing the config and opening a new release PR:

FromConfig changeResult
v1.0.0suffix = "alpha" (+ feature commit)v1.1.0-alpha.1
v1.1.0-alpha.1unchanged (+ fix commit)v1.1.0-alpha.2
v1.0.0-alpha.3suffix = "beta" (+ feature)v1.1.0-beta.1
v1.0.0-alpha.5remove [prerelease] (or suffix = "")v1.0.0

Switching the suffix recalculates the base version and resets the counter. Removing the prerelease config graduates to a stable release.

Per-Package Overrides

[prerelease]
suffix = "beta"
strategy = "versioned"

[[package]]
path = "./stable"
release_type = "rust"
# inherits the global beta prerelease

[[package]]
path = "./experimental"
release_type = "rust"
prerelease = { suffix = "alpha", strategy = "versioned" }

Aggregating Prerelease Notes

When graduating to stable, include the changelog entries from every prior prerelease:

[changelog]
aggregate_prereleases = true

You can also override prerelease settings per run without editing the config — see Configuration Overrides (--prerelease-suffix, --prerelease-strategy, --set-package).

Testing Your Configuration

Validate any config change locally before pushing — no token, no remote changes:

releasaurus release-pr --forge local --repo "."

Check that packages are detected, tag prefixes match, and the combined/separate PR strategy behaves as expected. See Local Repository Mode.

Next Steps

Changelog Customization

Releasaurus generates changelogs from conventional commits. Control what appears with the filtering options, and how it’s formatted with a Tera template — both in the [changelog] section of releasaurus.toml.

Filtering Commits

Each skip_* flag drops a category of commit from the changelog; the remaining flags adjust what’s shown. Set them in [changelog]:

[changelog]
skip_ci = true
skip_chore = true
skip_miscellaneous = true
include_author = true

[[package]]
path = "."
release_type = "node"
OptionDefaultEffect
skip_cifalseExcludes ci: commits (e.g. ci: update workflow)
skip_chorefalseExcludes chore: commits (e.g. chore: update deps)
skip_docfalseExcludes docs: commits
skip_testfalseExcludes test: commits
skip_stylefalseExcludes style: commits
skip_refactorfalseExcludes refactor: commits
skip_perffalseExcludes perf: commits
skip_revertfalseExcludes revert: commits
skip_miscellaneousfalseExcludes non-conventional commits (no recognized type prefix)
skip_merge_commitstrueExcludes merge commits
include_authorfalseAdds the commit author’s name to each entry
aggregate_prereleasesfalseWhen graduating a prerelease to stable, folds in the changelog entries from all prior prereleases (see Prereleases)

Dropping or rewriting individual commits

skip_shas removes specific commits by SHA prefix (use 7+ characters). Handy for commits that shouldn’t affect versioning or appear in the changelog:

[changelog]
skip_shas = ["abc123d", "def456e"]

reword rewrites a commit’s message in the changelog. The new message affects both the changelog text and the version bump — changing fix: to feat:, for example, bumps minor instead of patch:

[[changelog.reword]]
sha = "abc123d"
message = "feat: added user authentication"

Both have CLI equivalents for one-off runs: --skip-sha <sha> and --reword <sha>=<message>. The Configuration Reference lists these options again in terse lookup form.

The body Template

body is a Tera template rendered once per release. The default groups commits by type, links each commit, and highlights breaking changes:

[changelog]
body = """# [{{ version  }}]{% if tag_compare_link %}({{ tag_compare_link }}){% else %}({{ link }}){% endif %} - {{ timestamp | date(format="%Y-%m-%d") }}
{% for group, commits in commits | filter(attribute="merge_commit", value=false) | sort(attribute="group") | group_by(attribute="group") %}
### {{ group | striptags | trim }}
{% for commit in commits %}
{% if commit.breaking -%}
{% if commit.scope %}_({{ commit.scope }})_ {% endif -%}[**breaking**]: {{ commit.title }} [_({{ commit.short_id }})_]({{ commit.link }}){% if include_author %} ({{ commit.author_name }}){% endif %}
{% if commit.body -%}
> {{ commit.body }}
{% endif -%}
{% if commit.breaking_description -%}
> {{ commit.breaking_description }}
{% endif -%}
{% else -%}
- {% if commit.scope %}_({{ commit.scope }})_ {% endif %}{{ commit.title }} [_({{ commit.short_id }})_]({{ commit.link }}){% if include_author %} ({{ commit.author_name }}){% endif %}
{% endif -%}
{% endfor %}
{% endfor %}"""

A simpler custom template:

[changelog]
body = """## Release v{{ version }} — {{ timestamp | date(format="%Y-%m-%d") }}

{% for group, commits in commits | group_by(attribute="group") %}
### {{ group }}
{% for commit in commits %}
- {{ commit.title }} ({{ commit.short_id }}){% if include_author %} by {{ commit.author_name }}{% endif %}
{% endfor %}
{% endfor %}"""

Template Variables

Release

VariableDescription
versionSemantic version (e.g. 1.2.3)
tag_nameFull tag including prefix/suffix
linkURL to the release
tag_compare_linkDiff vs. previous tag (empty for first release)
sha_compare_linkDiff vs. previous tag, by commit SHA (empty for first release)
shaRelease commit SHA
timestampUnix timestamp
include_authorWhether author display is enabled

Commit (each item in commits)

VariableDescription
id / short_idFull / abbreviated SHA
groupCategory (Features, Bug Fixes, …)
scopeOptional conventional-commit scope
titleMessage without type/scope
bodyOptional extended description
linkURL to the commit
breaking / breaking_descriptionBreaking-change flag and details
merge_commitWhether it’s a merge commit
timestampCommit timestamp
author_name / author_emailCommit author
raw_title / raw_messageOriginal unprocessed title / message

Tips

Filter merge commits and conditionally show authors:

{% for commit in commits | filter(attribute="merge_commit", value=false) %}
- {{ commit.title }}{% if include_author %} <{{ commit.author_name }}>{% endif %}
{% endfor %}

Test any template change locally before committing it:

releasaurus release-pr --forge local --repo "."

See the Tera documentation for advanced filtering and formatting.

Editing Release Notes

Releasaurus lets you customize the release notes for a specific release directly in the pull request body — without touching releasaurus.toml or the CHANGELOG.md.

How It Works

When release-pr creates or updates a release PR, it renders the PR body with a structured layout per package:

<details open>
  <summary>v1.2.3</summary>
  <div id="my-package-header"></div>
  <div id="my-package" data-tag="v1.2.3">
    <!--{"metadata":{"sha_compare_link":"...","tag_compare_link":"..."}}-->

    ## [v1.2.3](...) - 2026-04-10 ### Features - feat: some new feature
    (abc1234)
  </div>
  <div id="my-package-footer"></div>
</details>

At release time, releasaurus release reads the notes directly from the PR body rather than regenerating them from commit history. This means any edits you make before merging are reflected in the published forge release.

Note: Edits to the PR body affect only the forge release notes. CHANGELOG.md is generated from commit history and is not affected.

Editing the Release Notes

Open the PR body and edit the text inside the notes <div>. The metadata comment (<!--{...}-->) must be left intact — it carries the tag and link information needed at publish time. For example, adding a summary paragraph above the generated entries:

<div id="my-package" data-tag="v1.2.3">
  <!--{"metadata":{"sha_compare_link":"...","tag_compare_link":"..."}}-->

  ## [v1.2.3](...) - 2026-04-10 This release improves startup performance and
  fixes a crash on empty input. See the [migration guide](https://example.com)
  for details. ### Features - feat: some new feature (abc1234)
</div>

For content that should survive re-runs of release-pr (for example, if you run the command again after new commits land), place it in the dedicated header and footer <div>s.

<div id="my-package-header">
  ## Highlights This is a major stability release. All users on v1.x are
  encouraged to upgrade.
</div>

<div id="my-package-footer">
  Full migration guide: https://example.com/migrate
</div>

When release-pr regenerates the PR body, it reads back the content of these <div>s and re-embeds it. The header is prepended and the footer is appended to the final release notes at publish time.

Tip: Leave the header and footer <div>s empty (the default) if you have nothing to add. They will not appear in the published release notes.

Monorepo: Multiple Packages

In a monorepo, each package gets its own set of sections. The id attributes are derived from the package name with any characters outside [a-zA-Z0-9-_] replaced by -.

For example, a package named @scope/my-pkg gets:

  • <div id="-scope-my-pkg"> — notes
  • <div id="-scope-my-pkg-header"> — header
  • <div id="-scope-my-pkg-footer"> — footer

Edit each package’s sections independently.

Backward Compatibility

PRs created by an older version of Releasaurus use a different body format. The release command detects the format automatically and falls back to reading release notes from the hidden metadata for those PRs — no manual migration required.

Limits

  • The metadata comment (<!--{...}-->) inside the notes <div> must not be removed or modified.
  • Do not write a literal </div> anywhere inside the notes, header, or footer sections. Releasaurus parses the PR body as HTML and a bare </div> will close the section early, truncating any content that follows.
  • Header and footer content is preserved verbatim. Markdown is supported by all major forge platforms.
  • Re-running release-pr regenerates the notes from commit history and overwrites any direct edits to the notes <div>. Use the header/footer sections for content you want to survive re-runs.

CI/CD Integration

Releasaurus provides official integrations for GitHub Actions, Gitea Actions, and Forgejo Actions. For GitLab CI and Azure Pipelines, use the Docker image directly.

Note on fetch depth: When using --local-path (hybrid mode), Releasaurus reads commit history and tags directly from the local clone. Most CI systems shallow-clone by default, which will cause missing commits or tags. Configure your CI checkout for full depth when using --local-path. Platform-specific instructions are in each section below.

Required token scopes

ForgeScopes / permissions
GitHub (classic)repo
GitHub (fine-grained)Contents, Issues, Pull requests — all read & write. Add Actions/Workflows read & write only if using the Action to modify workflow files.
GitLabapi, write_repository
Gitearepository (read/write), issue (read/write), misc (read/write) management
Forgejorepository (read/write), issue (read/write), misc (read/write) management
Azure DevOpsCode: Read & Write, Pull Request Threads: Read & Write

GitHub Actions, Gitea Actions & Forgejo Actions

A single action works for GitHub Actions, Gitea Actions, and Forgejo Actions workflows. See the action README for inputs, usage examples, and fetch depth configuration for --local-path.

GitLab CI

Use the Releasaurus Docker image directly in your .gitlab-ci.yml. You may provide an authentication token either by specifying a CI/CD variable named GITLAB_TOKEN, or by directly passing the --token option with a reference to your defined variable, e.g. --token $RELEASE_TOKEN.

Required Scopes:

  • api (full API access)
  • write_repository (repository write access)

Run both commands in a single job so they execute sequentially: release first (it tags any merged release PR), then release-pr (it opens or updates the next one). This matches the order used by the GitHub, Gitea, and Forgejo action. Defining them as two separate jobs with the same rules: lets GitLab schedule them in the same stage concurrently, which races: release-pr may observe a merged but not-yet-tagged release PR and abort with must finish previous release first.

Example

releasaurus:
  image:
    name: rgonnella/releasaurus:vX.X.X
    entrypoint: [""]
  script:
    # Assumes a CI/CD variable named $GITLAB_TOKEN for authentication.
    # Alternatively, pass `--token $RELEASE_TOKEN` to each command.
    #
    # Run `release` BEFORE `release-pr`: `release` tags any merged
    # release PR, then `release-pr` opens/updates the next one. The
    # reverse order (or two parallel jobs) lets `release-pr` see a
    # merged-but-untagged release PR and abort with
    # "must finish previous release first".
    - releasaurus release --forge gitlab --repo $CI_PROJECT_URL
    - releasaurus release-pr --forge gitlab --repo $CI_PROJECT_URL
  rules:
    - if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH

Using --local-path

When using --local-path, Releasaurus reads commit history and tags from the local clone and requires a full checkout. Configure GIT_DEPTH: 0 to ensure a full clone when the runner starts fresh:

variables:
  GIT_DEPTH: 0

If the runner reuses an existing workspace from a prior job (i.e. GIT_STRATEGY: fetch), GIT_DEPTH has no effect on the already-shallow repository. Unshallow explicitly in before_script:

before_script:
  - git fetch --unshallow || true # no-op if already full-depth

Using both together is safe and covers all runner states.

Azure Pipelines (EXPERIMENTAL)

Azure DevOps support is experimental. No first-party Azure Pipelines task is provided — use the Releasaurus Docker image directly in your pipeline. Note that the release command only pushes the git tag (the changelog commit lands when the release PR is merged); Azure DevOps Git has no native release object, so no release notes page is created.

Provide a PAT via the AZURE_DEVOPS_TOKEN pipeline secret variable. The PAT needs Code: Read & Write and Pull Request Threads: Read & Write scopes.

The release branch (typically releasaurus-release-*) must have Allow rewriting history enabled for the build service identity — releasaurus performs a non-fast-forward reset to the base branch when updating an existing release PR. See the Azure DevOps known limitation for the exact setting.

Run release first (it tags any merged release PR), then release-pr (it opens or updates the next one). This matches the order used by the GitHub, Gitea, and Forgejo action. Running release-pr first may observe a merged but not-yet-tagged release PR and abort with must finish previous release first.

trigger:
  branches:
    include:
      - main

pool:
  vmImage: ubuntu-latest

container: rgonnella/releasaurus:vX.X.X

steps:
  - checkout: self
    fetchDepth: 0 # required if you also pass --local-path

  - script: |
      releasaurus release \
        --forge azure-devops \
        --repo "$(Build.Repository.Uri)"
    env:
      AZURE_DEVOPS_TOKEN: $(AZURE_DEVOPS_TOKEN)

  - script: |
      releasaurus release-pr \
        --forge azure-devops \
        --repo "$(Build.Repository.Uri)"
    env:
      AZURE_DEVOPS_TOKEN: $(AZURE_DEVOPS_TOKEN)

Troubleshooting

Common issues and how to diagnose them. If your problem isn’t covered here, check the GitHub issues.

Inspect Before You Run

get next-release shows exactly what Releasaurus would do — version, included commits, and release notes — without making any changes. It’s the fastest way to debug version detection, tag matching, and config:

releasaurus get next-release --repo "https://github.com/owner/repo"

# Or fully offline against a local checkout
releasaurus get next-release --forge local --repo "."

For deeper diagnostics, add --debug (or --dry-run, which also enables debug). See Testing Modes.

Releasaurus doesn’t find existing tags

Usually a tag prefix mismatch — the package’s tag_prefix must match your existing tags:

[[package]]
path = "."
tag_prefix = "v"   # for v1.0.0; use "api-v" for api-v1.0.0; "" for 1.0.0

If no matching tag exists, Releasaurus treats it as a first release and analyzes up to first_release_search_depth commits (default 400). Raise it for a fuller first changelog, or lower it for speed. This affects only the first release — once a matching tag exists, all commits back to that tag are analyzed. To control how many tags are fetched while searching, use tag_search_depth.

“Authentication failed” / 401 Unauthorized

  1. Confirm the token is set for the forge you’re targeting (echo $GITHUB_TOKEN), or pass --token explicitly.
  2. Check the scopes — see required token scopes.
  3. Check expiration — regenerate if expired.

“Repository not found” with a valid repo

The token lacks access, or the URL is wrong.

  1. Verify the URL format, e.g. --repo "https://github.com/owner/repository".

  2. Confirm the token’s account has access to the repository.

  3. Reproduce offline to rule out config issues:

    git clone https://github.com/owner/repo && cd repo
    releasaurus get next-release --forge local --repo "."
    

“must finish previous release first”

release-pr found a merged-but-not-yet-tagged release PR. Run release first to tag it, then release-pr. In CI, always order the two commands releaserelease-pr in a single job — see CI/CD Integration.

Getting Help

When opening an issue, include: debug output (with secrets removed), your repository structure, the exact command, expected vs. actual behavior, your OS, releasaurus --version, and your forge platform and hosting type.

Configuration Reference

Complete reference for releasaurus.toml, environment variables, and supported languages. For guidance and examples, see Configuration.

Global Settings

Top-level keys, all optional:

KeyTypeDefaultDescription
base_branchstringrepo defaultBranch targeted for PRs, tagging, and releases. Override: --base-branch.
first_release_search_depthinteger400Commits to analyze for the first release (when no matching tag exists).
tag_search_depthinteger100Max tags fetched when searching for a previous release. 0 = all tags.
separate_pull_requestsboolfalseOne PR per package (true) vs. a single combined PR (false).
auto_start_nextboolfalseBump patch versions automatically after a release (see start-next).
breaking_always_increment_majorbooltrueBreaking changes (feat!:, BREAKING CHANGE:) bump major.
features_always_increment_minorbooltruefeat: commits bump minor.
custom_major_increment_regexstringnoneAdditional regex that triggers a major bump.
custom_minor_increment_regexstringnoneAdditional regex that triggers a minor bump.

Custom increment regexes

custom_major_increment_regex and custom_minor_increment_regex are additive — breaking changes always bump major and feat: always bumps minor regardless. The pattern is matched against the full commit message. In TOML double-quoted strings, escape backslashes (\\):

custom_major_increment_regex = "\\[MAJOR\\]"   # matches "[MAJOR]"
custom_minor_increment_regex = "FEATURE"        # no escaping needed

[prerelease]

Global prerelease config; can be overridden per package via a package prerelease table. See Prereleases.

KeyTypeDefaultDescription
suffixstringnone (stable)Identifier such as alpha, beta, rc, SNAPSHOT. Override: --prerelease-suffix.
strategystringversionedversioned (adds .1, .2, …) or static (suffix as-is). Override: --prerelease-strategy.
[prerelease]
suffix = "beta"
strategy = "versioned"

[changelog]

Controls changelog generation. See Changelog Customization for the template and variables.

KeyTypeDefaultDescription
skip_ciboolfalseExclude ci: commits.
skip_choreboolfalseExclude chore: commits.
skip_docboolfalseExclude docs: commits.
skip_testboolfalseExclude test: commits.
skip_styleboolfalseExclude style: commits.
skip_refactorboolfalseExclude refactor: commits.
skip_perfboolfalseExclude perf: commits.
skip_revertboolfalseExclude revert: commits.
skip_miscellaneousboolfalseExclude non-conventional commits.
skip_merge_commitsbooltrueExclude merge commits.
include_authorboolfalseInclude commit author names.
aggregate_prereleasesboolfalseOn graduation, fold prior prerelease notes into the stable release.
skip_shasstring[]noneSkip commits by SHA prefix (7+ chars). CLI: --skip-sha.
rewordobject[]noneRewrite commit messages (affects changelog and version bump). CLI: --reword.
bodystringstandard templateTera template for the changelog body.
[changelog]
skip_ci = true
skip_chore = true

[[changelog.reword]]
sha = "abc123d"
message = "fix: corrected description"

[[package]]

One entry per package; repeatable.

KeyTypeDefaultDescription
pathstring.Package directory, relative to workspace_root.
workspace_rootstring.Workspace root, relative to repo root.
namestringderived from pathExplicit package name; must be unique.
release_typestringnoneLanguage for version updates (see Supported Languages). Omit for changelog/tagging only.
tag_prefixstringv (root) / <name>-v (nested)Git tag prefix. Override: --tag-prefix or --set-package <name>.tag_prefix=.
prereleasetableinherits globalPer-package prerelease override. Override: --set-package <name>.prerelease.suffix=.
sub_packagesobject[]noneGroup packages under one shared tag/changelog (see Grouped Releases).
additional_pathsstring[]noneExtra directories whose changes trigger a release for this package.
additional_manifest_filesstring[] / object[]noneExtra files to version-bump (see below).
auto_start_nextboolinherits globalPer-package auto_start_next override.
breaking_always_increment_majorboolinherits globalPer-package override.
features_always_increment_minorboolinherits globalPer-package override.
custom_major_increment_regexstringinherits globalPer-package override.
custom_minor_increment_regexstringinherits globalPer-package override.

sub_packages entries take name, path, and release_type.

additional_manifest_files

Extra files whose version strings should be kept in sync — custom VERSION files, docs, config, etc. Accepts plain string paths (using a default regex) or objects with a custom version_regex. All paths are relative to the package path.

[[package]]
path = "."
release_type = "rust"
additional_manifest_files = [
    "VERSION",                    # default regex
    "README.md",                  # default regex
    { path = "helm/Chart.yaml", version_regex = "appVersion:\\s*\"?(?<version>\\d+\\.\\d+\\.\\d+)\"?" },
]

The default regex matches common forms like version = "1.0.0", version: "1.0.0", VERSION='1.0.0', and "version": "1.0.0". A custom version_regex must include a named capture group (?<version>...); only that group is replaced. Files without a match are skipped; an invalid regex errors during config resolution.

Complete Example

# Global settings
base_branch = "main"
first_release_search_depth = 400
separate_pull_requests = false
auto_start_next = false
breaking_always_increment_major = true
features_always_increment_minor = true

[prerelease]
suffix = "beta"
strategy = "versioned"

[changelog]
skip_ci = true
skip_chore = true
include_author = false

[[package]]
name = "frontend"
path = "./apps/web"
release_type = "node"
tag_prefix = "web-v"

[[package]]
name = "backend"
path = "./services/api"
release_type = "rust"
tag_prefix = "api-v"
prerelease = { suffix = "alpha", strategy = "versioned" }

Environment Variables

Releasaurus selects the auth token automatically from the --forge type; --token overrides it. The RELEASAURUS_* variables are fallbacks for their matching CLI flags, and flags always win.

VariablePurpose
GITHUB_TOKENGitHub auth token
GITLAB_TOKENGitLab auth token
GITEA_TOKENGitea auth token
FORGEJO_TOKENForgejo auth token
AZURE_DEVOPS_TOKENAzure DevOps PAT (experimental)
RELEASAURUS_FORGEDefault --forge
RELEASAURUS_REPODefault --repo
RELEASAURUS_LOCAL_PATHDefault --local-path (hybrid mode)
RELEASAURUS_DEBUGEnable debug logging when set to any non-empty value
RELEASAURUS_DRY_RUNEnable dry-run (auto-enables debug) when set to any non-empty value

Required token scopes

ForgeScopes / permissions
GitHub (classic)repo
GitHub (fine-grained)Contents, Issues, Pull requests — all read & write. Add Actions/Workflows read & write only if using the Action to modify workflow files.
GitLabapi, write_repository
Gitearepository (read/write), issue (read/write), misc (read/write) management
Forgejorepository (read/write), issue (read/write), misc (read/write) management
Azure DevOpsCode: Read & Write, Pull Request Threads: Read & Write

RELEASAURUS_DEBUG and RELEASAURUS_DRY_RUN are enabled by any non-empty value (including false or 0); unset or empty to disable. The --debug / --dry-run flags always enable regardless of the variable.

Supported Languages

Set release_type on a package and Releasaurus updates the matching manifest and lock files. Lock files are updated when present, and all languages support workspace/monorepo layouts.

release_typeFiles updated
genericCustom files via additional_manifest_files
goversion.go, version/version.go, internal/version.go, internal/version/version.go
javapom.xml, build.gradle, build.gradle.kts, gradle.properties, gradle/libs.versions.toml
nodepackage.json, package-lock.json, yarn.lock
phpcomposer.json, composer.lock
pythonpyproject.toml, setup.py, setup.cfg
ruby*.gemspec, Gemfile, Gemfile.lock
rustCargo.toml, Cargo.lock

Library API

The releasaurus-core crate exposes the full release pipeline as a public Rust API — use it to embed release automation in your own tooling instead of shelling out to the CLI. (For CI/CD and simple automation, the CLI is the better choice.)

Adding the Dependency

[dependencies]
releasaurus-core = "0.14"
tokio = { version = "1", features = ["full"] }  # async-first, built on Tokio

Architecture

Orchestrator            (pipeline entry point)
  └─ ResolvedConfig     (merged settings)
  └─ ResolvedPackageHash (resolved package configs)
  └─ ForgeManager       (caching + dry-run wrapper)
       └─ Forge         (GitHub / GitLab / Gitea / Local)

All operations go through Orchestrator, which needs three pieces:

  1. A ForgeManager wrapping a concrete Forge.
  2. A ResolvedConfig — built by Resolver::builder() from the loaded TOML plus any runtime overrides.
  3. A ResolvedPackageHash — the resolved packages, produced alongside ResolvedConfig by Resolver::resolve().

See the crate-level quick start on docs.rs for the full builder chain with per-step comments.

Internally each call drives packages through typed stages — ResolvedPackage → PreparedPackage → AnalyzedPackage → ReleasablePackage → ReleasePRPackage. The stage name appears in most error contexts, which helps when reading errors.

Constructing a RepoUrl

Forge constructors (Github::new, Gitlab::new, Gitea::new) take a RepoUrl defined in this crate rather than a third-party URL type, so your dependency tree stays stable. Build it from your parsed URL’s components:

#![allow(unused)]
fn main() {
use releasaurus_core::forge::{RepoUrl, config::Scheme};

let url = RepoUrl {
    scheme: Scheme::Https,
    host: "github.com".into(),
    owner: "my-org".into(),
    name: "my-repo".into(),
    // Full project path — nested GitLab groups may be "group/subgroup/repo"
    path: "my-org/my-repo".into(),
    port: None,
    token: None,
};
}

Set token only when the credential is embedded in the URL (https://TOKEN@host/...); otherwise leave it None and pass the token as Option<secrecy::SecretString> to the forge constructor (add secrecy to construct one).

The Forge Trait

Forge is the extension point for platform support. The crate ships four implementations:

TypeWhen to use
forge::github::GithubGitHub (cloud or Enterprise)
forge::gitlab::GitlabGitLab (cloud or self-hosted)
forge::gitea::GiteaGitea self-hosted
forge::local::LocalRepoLocal git2 operations (testing)

To target a custom platform, implement Forge from releasaurus_core::forge::traits and pass it to ForgeManager::new(Box::new(my_forge), ...):

#![allow(unused)]
fn main() {
use async_trait::async_trait;
use releasaurus_core::{
    config::Config,
    forge::{
        request::Tag,
        request::{
            Commit, CreateCommitRequest, CreatePrRequest,
            CreateReleaseBranchRequest, ForgeCommit,
            GetFileContentRequest, GetPrRequest, PrLabelsRequest,
            PullRequest, ReleaseByTagResponse, UpdatePrRequest,
        },
        traits::Forge,
    },
    result::Result,
};
use std::any::Any;
use url::Url;

pub struct MyForge { /* ... */ }

#[async_trait]
impl Forge for MyForge {
    fn repo_name(&self) -> String { todo!() }
    fn release_link_base_url(&self) -> Url { todo!() }
    fn compare_link_base_url(&self) -> Url { todo!() }
    fn default_branch(&self) -> String { todo!() }

    async fn load_config(
        &self,
        branch: Option<String>,
    ) -> Result<Config> { todo!() }

    async fn get_file_content(
        &self,
        req: GetFileContentRequest,
    ) -> Result<Option<String>> { todo!() }

    // ... remaining trait methods (see docs.rs for the full list)
    async fn get_release_by_tag(&self, _: &str)
        -> Result<ReleaseByTagResponse> { todo!() }
    async fn create_release_branch(&self, _: CreateReleaseBranchRequest)
        -> Result<Commit> { todo!() }
    async fn create_commit(&self, _: CreateCommitRequest)
        -> Result<Commit> { todo!() }
    async fn tag_commit(&self, _: &str, _: &str)
        -> Result<()> { todo!() }
    async fn get_latest_tags_for_prefix(&self, _: &str, _: &str)
        -> Result<Vec<Tag>> { todo!() }
    async fn get_commits(&self, _: Option<String>, _: Option<String>)
        -> Result<Vec<ForgeCommit>> { todo!() }
    async fn get_open_release_pr(&self, _: GetPrRequest)
        -> Result<Option<PullRequest>> { todo!() }
    async fn get_merged_release_pr(&self, _: GetPrRequest)
        -> Result<Option<PullRequest>> { todo!() }
    async fn create_pr(&self, _: CreatePrRequest)
        -> Result<PullRequest> { todo!() }
    async fn update_pr(&self, _: UpdatePrRequest)
        -> Result<()> { todo!() }
    async fn replace_pr_labels(&self, _: PrLabelsRequest)
        -> Result<()> { todo!() }
    async fn create_release(&self, _: &str, _: &str, _: &str)
        -> Result<()> { todo!() }
}
}

Dry-Run & Testing

Pass ForgeOptions { dry_run: true } to ForgeManager::new to skip all write operations (logged at WARN) while read operations proceed normally. For tests, LocalRepo runs everything against a local git2 repository; the Forge trait is also #[cfg_attr(test, automock)], so mockall’s MockForge is available under #[cfg(test)].

Contributing

Thanks for your interest in contributing to Releasaurus! Bug reports, feature requests, code, docs, tests, and community support are all welcome. Bugs and feature requests go through GitHub Issues; general questions through GitHub Discussions.

Development Setup

Prerequisites: Rust 1.92+ (rustup), Git, and a GitHub/GitLab/Gitea account for testing.

This project uses Mise to manage the Rust version and dev tools (see mise.toml). After installing and activating mise:

git clone https://github.com/your-username/releasaurus.git
cd releasaurus
mise trust && mise install

This installs the correct Rust toolchain and tools (including just), switches to them whenever you cd into the repo, and auto-loads any variables from a local .env.

A Justfile provides common recipes:

just build              # build (add --release for a release build)
just run --help         # = cargo run -p releasaurus -- --help
just help               # list all recipes

To build and install from source directly:

cargo install --path crates/cli

Running Tests

There are two kinds of tests:

Unit tests use mocks and never touch a real forge:

just test           # run unit tests
just test-cov       # with coverage

Integration tests run against real forges and require per-forge environment variables (*_TEST_REPO, *_TEST_TOKEN, *_RESET_SHA for GITHUB, GITLAB, GITEA, FORGEJO, and AZURE_DEVOPS). You can put them in .env for mise to load automatically.

⚠️ The configured test repositories WILL be overwritten. All PRs, tags, releases, and branches are deleted and the repo is hard-reset to the configured reset SHA at the start of the suite. Use dedicated, disposable repositories with minimal-permission tokens.

just test-all                      # all tests, including integration
just test-github-integration       # a single forge's integration tests
# (also: gitlab, gitea, forgejo, azure-devops)

Azure DevOps test setup: the test repo’s default branch must have no branch policies (the reset routine force-resets history via a temporary branch swap). The PAT needs Code: Read & Write and Pull Request Threads: Read & Write.

Coding Standards

  • Format with cargo fmt and lint with cargo clippy.
  • Write documentation comments for public APIs.
  • Test outcomes, not implementation; keep tests minimal and use the existing test_helpers.rs patterns; name tests descriptively (returns_all_manifest_targets, not test_1).

Adding a New Language Updater

Each language updater lives under crates/core/src/updater/<lang>/ and consists of a ReleaseType variant, a manifests module, an updater module, file parsers, tests, and docs. To add one:

  1. Add the ReleaseType variant in crates/core/src/config/release_type.rs.
  2. Create the manifests module (crates/core/src/updater/<lang>/manifests.rs, implementing ManifestTargets) and register it in crates/core/src/updater/manager.rs under release_type_manifest_targets().
  3. Create the updater (crates/core/src/updater/<lang>/updater.rs implementing PackageUpdater, plus per-format file parsers), declare the module in crates/core/src/updater.rs, and register it in the updater() function in manager.rs.
  4. Add tests for manifest generation, updater integration, and each file parser.
  5. Update the docs — add the language to the Supported Languages table in book/src/configuration-reference.md.

Reference implementations: PHP and Python are good simple starting points; Node, Rust, and Java show workspace support, lock files, and multiple build tools. Verify your work end-to-end with the local and hybrid modes:

just run release-pr --forge local --repo "/path/to/test/project" --debug

Code of Conduct

This project follows the Rust Code of Conduct. Report unacceptable behavior to the project maintainers.

Thank you for contributing to Releasaurus!