Skip to content

Releases

Every ModernLeft repository releases the same way. Versioning is driven entirely by Conventional Commits and commitizen, following the release architecture commitizen-tools uses for commitizen itself. There is no release button and no release branch. Landing an eligible commit on main is the release decision.

What cuts a release

cz bump runs after every push to main and inspects the commits since the last tag. A release happens only when at least one bump-eligible commit landed.

Commit type Effect on the version
BREAKING CHANGE footer or ! major bump
feat minor bump
fix, refactor, perf patch bump
docs, chore, ci, build, test, style none

Inert commits accumulate silently and ship as part of the next eligible release. Renovate commits its dependency updates as ci(deps) (workflow and action updates) or build(deps) (everything else), so automated dependency merges never cut a release on their own.

Developer workflow

  • Branch off main and work normally. Commit messages follow Conventional Commits. Nothing you do locally can cut a release.
  • Open a pull request. CI runs the quality checks and tests, and the bump preview bot posts a sticky comment stating exactly which release a merge would produce. The comment updates as you push.
  • The commit types are the release lever. If the PR should ship immediately, make sure an eligible type (feat, fix, refactor, perf) lands with it. If it should stay silent, keep everything docs/chore/ci/build/ test.
  • Merge (or push an eligible commit straight to main). That is the entire release ceremony: tests run and the bump commit and tag are pushed. The publish workflow fires from the tag. There is no second approval step.
  • If nothing eligible landed, the bump is a no-op and your changes ride along with the next eligible release.
  • If a publish run fails, re-run it for that tag via workflow_dispatch. Do not create a new version to retry a publish.

The pipeline

Three workflows per repository, chained by events rather than by jobs:

  1. Test & Version (build-version.yaml) runs on every push to main. The test job is the post-merge CI check. When it is green, the reusable bump workflow from commitizen-ci runs cz bump. If a release is due, it writes the changelog and creates a signed bump commit plus an annotated tag, then pushes both atomically with git push --follow-tags. If nothing is eligible, the run ends as a no-op.
  2. Publish (publish.yaml) triggers on the tag push. Container image repos build and push the image with the version tag and latest. Library repos upload the package to the Forgejo package registry (PyPI or npm).
  3. PR bump preview (pr-bump-preview.yaml) keeps one sticky comment on every pull request stating exactly which release a merge would produce, so the consequence is visible before the merge button is clicked.

The bump commit itself starts with bump: and is skipped by every workflow, which prevents release loops.

Versioning mechanics

  • Versions are semver. Tags are the bare version (1.4.0, no v prefix).
  • The version source of truth is the repo's native metadata: pyproject.toml for Python (version_provider = "uv") or package.json for TypeScript ("npm"). Repos without package metadata use git tags alone ("scm").
  • Bump commits and tags are SSH-signed by the commitizen-bot account. The signing key is pulled from Infisical at run time, so no per-repo secret setup is needed for org repositories.
  • The bump workflow pushes the commit and the created tag in one atomic push, so a partial release (commit without tag) cannot happen. Tags are annotated (annotated_tag = true in the cz config templates) by convention, but the workflow does not require any particular tag settings.

Republishing a failed release

Publish workflows accept a workflow_dispatch with a tag name. This rebuilds and republishes that exact tag without creating a new version and without moving latest. Use it when the original tag-triggered run failed for environmental reasons.

Adopting the system in a repository

The repo needs the workflow callers and a commitizen config with annotated_tag = true. Conventional commit discipline covers the rest. The commitizen-ci README documents the reusable workflows, the caller snippets, and the setup for repositories outside the org (bring your own signing key). Org repositories get the Infisical credentials automatically.