Add internal/pyproject package with ReadVersion and WriteVersion for pyproject.toml files. Reads [project].version (PEP 621) first, then falls back to [tool.poetry].version. Section boundaries are detected via TOML's rule that headers always start at the beginning of a line (\n[ pattern), so inline arrays with [ characters don't interfere. Config follows the established multi-value pattern: - python.pyproject_toml: single path (opt-in, no default) - python.pyproject_tomls: list for monorepos (overrides single) - --pyproject flag overrides pyproject_toml and clears pyproject_tomls 100% per-package statement coverage maintained across all 14 packages; FuzzReadVersion and FuzzWriteVersion added per fuzzing guidelines. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
releaser
A CI-friendly release automation tool for GitFlow workflows using Conventional Commits.
Problem
Standard tools like semantic-release are designed for trunk-based development. In a GitFlow setup with versioned release branches (release/1.1, release/1.2), they either fail to respect the branch's version range or require brittle configuration.
releaser is built for this exact workflow: it reads the branch name to pin the major.minor, parses Conventional Commits to determine the patch increment, and handles everything from pom.xml update to GitLab/GitHub tag+release creation.
How it works
release/1.2 branch
└─ last tag: 1.2.3 (or none → start at 1.2.0)
└─ commits since tag → Conventional Commits analysis
└─ next version: 1.2.4
- Branch parsing — extracts
major.minorfrom branch name (e.g.release/1.2→1.2) - Tag discovery — finds the latest tag matching
major.minor.*on the current branch - Commit analysis — parses Conventional Commits between last tag and HEAD
- Version bump — increments patch (the minor is owned by the branch)
- Release — updates
pom.xml/package.json/build.gradle, commits, tags, creates GitLab or GitHub release
Version bump rules
By default, all releasable commits bump the patch component (minor is pinned to the branch). You can override this per commit type via git.bump_rules in .releaser.yml:
| Commit type | Default | Configurable via bump_rules |
|---|---|---|
fix: |
patch | fix: "minor" to bump minor instead |
feat: |
patch | feat: "minor" to bump minor instead |
feat!: / BREAKING CHANGE |
patch | breaking: "minor" to bump minor |
chore:, docs:, etc. |
none | — |
| unparseable msg | none | non-strict mode: silently ignored |
Usage
# Scaffold a default .releaser.yml in the current repository
releaser --init
# Simulate next version (no side effects)
releaser --dry-run
# Full release: update pom.xml + CHANGELOG.md, commit, tag, push, create release
releaser
# Commit and tag locally — skip push and release creation
releaser --no-push
# Push commit and tag but skip creating the release
releaser --no-release
# Update files but stop before committing (review first)
releaser --no-commit
# … then commit manually and re-run:
releaser --tag-only
# Explicitly target a branch (useful in detached HEAD CI)
releaser --branch release/1.2
# Write changelog to a custom file
releaser --changelog-file CHANGES.md
# Show configuration sources, commit list, and version decision
releaser --verbose --dry-run
# Target a specific pom.xml
releaser --pom path/to/pom.xml
# Override tag prefix from CLI (empty = no prefix)
releaser --tag-prefix ""
# Override branch pattern (e.g. also match hotfix/ branches)
releaser --branch-pattern "^(?:.*/)?(?:release|hotfix)/(\d+)\.(\d+)$"
# Write dotenv artifact to a custom path (or "" to disable)
releaser --release-env-file deploy/version.env
Configuration
releaser reads .releaser.yml from the repository root. All fields are optional — missing values fall back to the defaults shown below.
git:
tag_prefix: "" # default: no prefix; set to "v" for v-prefixed tags
branch_pattern: "^(?:.*/)?release/(\\d+)\\.(\\d+)$" # two capture groups: major, minor
commit_message: "chore(release): {version} [skip ci]"
author_name: "" # defaults to git config user.name
author_email: "" # defaults to git config user.email
releasable_types: # default: all three
- fix
- feat
- breaking
bump_rules: # which version component each type bumps
breaking: "patch" # "minor" to bump minor on breaking changes
feat: "patch"
fix: "patch"
maven:
pom_path: "pom.xml" # single pom.xml, relative to repo root
# pom_paths: # multi-module: list overrides pom_path
# - "pom.xml"
# - "module-a/pom.xml"
# - "module-b/pom.xml"
node: # opt-in — no default; omit to skip
# package_json: "package.json" # single path
# package_jsons: # monorepo: list overrides package_json
# - "packages/frontend/package.json"
# - "packages/backend/package.json"
gradle: # opt-in — no default; omit to skip
# build_file: "build.gradle" # Groovy or Kotlin DSL; single path
# build_files: # multi-module: list overrides build_file
# - "build.gradle"
# - "module-a/build.gradle"
python: # opt-in — no default; omit to skip
# pyproject_toml: "pyproject.toml" # PEP 621 [project] or [tool.poetry]
# pyproject_tomls: # monorepo: list overrides pyproject_toml
# - "pyproject.toml"
# - "packages/cli/pyproject.toml"
gitlab:
url: "https://gitlab.example.com" # or env CI_SERVER_URL
token: "" # env GITLAB_TOKEN (never commit this)
project: "" # env CI_PROJECT_ID or CI_PROJECT_PATH
github:
token: "" # env GITHUB_TOKEN (never commit this)
repo: "" # "owner/repo" format
Environment variables
| Variable | Used for |
|---|---|
GITLAB_TOKEN |
GitLab API auth + HTTPS push auth |
CI_SERVER_URL |
GitLab instance URL |
CI_PROJECT_ID |
GitLab project identifier (numeric) |
CI_PROJECT_PATH |
GitLab project identifier (fallback) |
GITHUB_TOKEN |
GitHub API auth |
When both github.* and gitlab.* are configured, GitHub takes precedence.
CI integration (GitLab CI example)
release:
stage: release
image: registry.example.com/releaser:latest
rules:
- if: $CI_COMMIT_BRANCH =~ /^release\/.+$/
variables:
GITLAB_TOKEN: $RELEASE_TOKEN # project/group CI variable with api + write_repository scope
script:
- releaser
artifacts:
reports:
dotenv: release.env # exposes NEXT_VERSION to downstream jobs