Skip to main content

conventional-release

PyPI Python CI Coverage License PRs welcome

A CHANGELOG.md in standard-version's exact format, generated from Conventional Commits. Releases go through a pull request, and CI tags the merge commit. This works on a protected main, needs no Node, and bumps the version in any language's version file.

$ uvx conventional-release release          # version inferred from the commits
release 1.3.0 -> 1.4.0
https://github.com/you/project/pull/57       # "chore(release): 1.4.0", squash-merge it
                                             # CI then tags v1.4.0 and publishes the release

Why another one

standard-version is deprecated. Here is how it compares with the alternatives:

changelog format release on a protected branch needs
commit-and-tag-version standard-version's (it is the maintained drop-in fork) tags locally, then pushes the tag Node
release-please its own a bot keeps a rolling release PR open GitHub App / Action
git-cliff whatever you template changelog only; no version files, no PR, no tag —
conventional-release standard-version's you open the release PR when you want a release; CI tags on merge Python ≥ 3.11 (git-cliff comes with it)

If you want a drop-in replacement for npx standard-version and have Node, use commit-and-tag-version. This project is for repos where main only accepts PRs, and for ecosystems where adding Node just to write a changelog isn't worth it.

How a release works

  1. Every PR title is a conventional commit (feat(cli): add --json). A squash merge turns the title into the commit subject, and GitHub appends (#57). That subject becomes the changelog line, with the PR and commit linked. The pr-title workflow below enforces this.
  2. conventional-release release creates release/vX.Y.Z from your HEAD. It adds the new section to CHANGELOG.md, bumps the version files, commits chore(release): X.Y.Z, pushes, and opens the PR. --dry-run prints the version and the section and changes nothing.
  3. You squash-merge the PR with its title unchanged.
  4. CI (the tag workflow) sees the chore(release): X.Y.Z commit on main. It checks that the version files agree with the subject, creates an annotated tag vX.Y.Z on that commit, and publishes a GitHub Release whose notes are the version's changelog section. Your own jobs (publish to PyPI or npm, build images) run after it in the same workflow.

The tag is never created on the release branch: after a squash merge, that commit never lands on main.

Install

uv tool install conventional-release     # or: pipx install conventional-release
uvx conventional-release --help          # or run it without installing

The command is conventional-release, with crel as a short alias. git-cliff is a dependency and ships as a wheel, so there's nothing else to install. gh is needed only to open the PR (--no-pr skips it).

Commands

command does
release [major|minor|patch|X.Y.Z] [--dry-run] [--no-push] [--no-pr] the local half, above
next [level|X.Y.Z] print the version the next release would get
current print the current version
notes X.Y.Z print that version's CHANGELOG section (the release notes)
check-title "<title>" validate a PR title / commit subject; exits 1 with the reason
detect [--github-output] CI: is HEAD a release commit that still needs its tag? prints released=, version=, tag=
tag X.Y.Z [--push] CI: create the annotated tag on HEAD

Version inference follows SemVer: feat bumps minor, fix and everything else bump patch, and a ! or a BREAKING CHANGE: footer bumps major. feat and breaking changes bump minor and major even before 1.0.0.

GitHub Actions

Both workflows are reusable. Call them from your repo:

# .github/workflows/release.yml
name: release
on:
  push:
    branches: [main]
permissions:
  contents: write
jobs:
  tag:
    uses: MdaaaaO/conventional-release/.github/workflows/tag.yml@v0
  publish:                        # optional: your own release jobs
    needs: tag
    if: needs.tag.outputs.released == 'true'
    runs-on: ubuntu-latest
    environment: pypi
    permissions:
      id-token: write             # PyPI trusted publishing
    steps:
      - uses: actions/checkout@v7
        with:
          ref: ${{ needs.tag.outputs.tag }}
      - uses: astral-sh/setup-uv@v10.2.0
      - run: uv build
      - uses: pypa/gh-action-pypi-publish@release/v1
# .github/workflows/pr-title.yml — make this check required on main
name: pr-title
on:
  pull_request:
    types: [opened, edited, synchronize, reopened]
jobs:
  pr-title:
    uses: MdaaaaO/conventional-release/.github/workflows/pr-title.yml@v0

Why publishing runs in your workflow, after tag, and not on on: push: tags: a tag pushed with the workflow's GITHUB_TOKEN doesn't trigger other workflows, so a separate tag-triggered workflow would never start. Also, PyPI trusted publishing checks which workflow file publishes, and that must be yours.

Configuration

Every key is optional. Put them in .conventional-release.toml, or in [tool.conventional-release] in pyproject.toml:

version-source = "file"          # "file": bump version files; "tag": the git tag is the version
                                 # (hatch-vcs, setuptools-scm) and the release PR only changes the changelog
version-files = ["pyproject.toml"]   # default: first of pyproject.toml, package.json, Cargo.toml,
                                     # VERSION, version that has a static version
changelog = "CHANGELOG.md"
changelog-mode = "prepend"       # "prepend": insert the new section, keep everything below it
                                 # "regenerate": rewrite the whole file from git history
tag-prefix = "v"
base-branch = "main"
branch-prefix = "release/"
initial-version = "0.1.0"        # the first release, when there is no tag yet
subject-lowercase = true         # check-title: "feat: add x", not "feat: Add x"
# repo-url = "https://github.com/o/r"   # default: derived from the origin remote
# cliff-config = "cliff.toml"           # use your own git-cliff config verbatim instead

# Types and their changelog sections, in display order. Default: the Angular set below.
[[types]]            # in pyproject.toml: [[tool.conventional-release.types]]
type = "feat"
section = "Features"
[[types]]
type = "fix"
section = "Bug Fixes"
[[types]]
type = "chore"
section = "Chore"
hidden = true        # still a valid title, just not listed in the changelog

The default types are feat fix docs style refactor perf test build ci chore revert, all listed. chore(release) commits are never listed.

Version files are edited in place, one line each, and nothing else in the file changes. The supported files are pyproject.toml ([project] or [tool.poetry]), Cargo.toml ([package] or [workspace.package]), any *.json with a top-level "version" (package.json), and plain one-line files (VERSION). Lock files are not touched.

Coming from standard-version? Keep your CHANGELOG.md. The default prepend mode adds new sections above the old ones in the same format. Map your .versionrc types to [[types]], and your bumpFiles to version-files.

Commit convention

Only Conventional Commits 1.0 is supported. It grew out of the Angular convention and is what git-cliff, release-please, semantic-release and commitlint all use by default. This project's own history follows it, and the project releases itself with itself.

Lineage

This was extracted from the release tooling of a private project, which reproduced the standard-version changelog of atlassian-labs/observe on git-cliff after standard-version was deprecated.

License

Apache-2.0

Release files for conventional-release 0.1.1

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for conventional-release 0.1.1
File Size Uploaded
conventional_release-0.1.1.tar.gz 76.0 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for conventional-release 0.1.1
File Interpreter ABI Platform
conventional_release-0.1.1-py3-none-any.whl Python 3 none any Details

Total release size: 99.2 kB

Release files / conventional_release-0.1.1.tar.gz

Download URL conventional_release-0.1.1.tar.gz
Size 76.0 kB
Tags Source
SHA-256 checksum
How to use checksums
8b5f78fd603a522e85fea9864a590b7b11485c5eee655323603ae60a16eba31c
BLAKE2b-256 checksum
How to use checksums
4d78994e84b70ac4c849a5d262fb2c31d35739a64c2a44b91359830504d8ff70
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Sep 25, 2026.

Transparency log

Release files / conventional_release-0.1.1-py3-none-any.whl

Download URL conventional_release-0.1.1-py3-none-any.whl
Size 23.2 kB
Tags Python 3
SHA-256 checksum
How to use checksums
2ae5eca8797ef6b5ed6e9a5fab291ba8f45f3f2b3db2765ccc6c5ec68c7077f8
BLAKE2b-256 checksum
How to use checksums
0abe3eda05379048636b22093687832791b2436fea768d2355041838c9657c91
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Sep 25, 2026.

Transparency log

Release history Release notifications | RSS feed

0.3.0

2 release files

0.2.0

2 release files

0.1.2

2 release files

This release

0.1.1 This release

2 release files

0.1.0

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page