Automate your release workflow from the command line.
release-cli detects your project type, analyzes commits, bumps versions, generates changelogs, creates tags, and publishes GitHub Releases -- all from a single command.
- Monorepo support -- Hierarchical multi-package releases with cascading, batched commits, and namespaced tags
- Multi-ecosystem support -- Go, Node.js, Python, Rust, Java (Gradle/Maven), Dart, Helm
- Commit analysis -- Conventional Commits, Angular, or custom conventions
- Semantic versioning -- Automatic bump calculation from commit history
- Changelog generation -- Grouped or flat changelogs with custom template support
- Version propagation -- Update version across Dockerfiles, config files, and more
- GitHub Releases -- Publish releases with artifact uploads
- Lifecycle hooks -- Run scripts at pre-bump, post-bump, pre-publish, post-publish
- Dry-run mode -- Preview the entire release without executing
go install github.com/0x1306e6d/release-cli/cmd/release-cli@latestgit clone https://github.com/0x1306e6d/release-cli.git
cd release-cli
make installInitialize a config file in your project:
release-cli initThis creates .release.yaml with auto-detected project type and sensible defaults.
Preview what will happen:
release-cli statusRun the release:
release-cli releaseGenerates a .release.yaml config file with auto-detected project type.
Shows the current version, last release tag, and commits since last release. In monorepo mode, displays a summary table for all packages.
| Flag | Description |
|---|---|
--package |
Show status for a specific package (monorepo) |
Executes the full release pipeline: bump version, generate changelog, commit, tag, push, and publish.
| Flag | Description |
|---|---|
--bump |
Override bump level (major, minor, or patch) |
--package |
Release specific package(s) in monorepo mode |
--all |
Release all packages (monorepo mode) |
| Flag | Description |
|---|---|
--dry-run |
Preview actions without executing |
--verbose |
Enable verbose output |
--version |
Show version |
| Type | Detection File | Version Source |
|---|---|---|
go |
go.mod |
Git tags |
node |
package.json |
version field |
python |
pyproject.toml |
version field |
rust |
Cargo.toml |
version field |
java-gradle |
build.gradle / build.gradle.kts |
gradle.properties |
java-maven |
pom.xml |
<version> element |
dart |
pubspec.yaml |
version field |
helm |
Chart.yaml |
version field |
release-cli is configured via .release.yaml in the project root. Only the project field is required (optional for monorepo container roots; see Monorepo section).
project: goproject: go # Required: go, node, python, rust, java-gradle, java-maven, dart, helm
name: my-project # Tag prefix (required when modules is declared, see Monorepo section)
modules: # Child package paths (see Monorepo section)
- cli
- lib
version:
scheme: semver # Version scheme (default: semver)
snapshot: false # Enable snapshot versions (e.g., -SNAPSHOT for Java, .dev0 for Python)
manifest: go.mod # Override manifest file
field: version # Override version field name
pattern: 'version = "(.+)"' # Override version regex pattern
changes:
commits:
convention: custom # Convention: conventional, angular, custom
types: # Custom type mappings (only for custom convention)
major: [breaking]
minor: [feat, feature]
patch: [fix, bugfix]
changelog:
enabled: true # Generate changelog (default: true)
file: CHANGELOG.md # Changelog file path (default: CHANGELOG.md)
template: | # Custom Go template (optional)
## {{ .Version }} ({{ .Date }})
{{ range .Groups }}...
propagate: # Propagate version to other files
- file: Dockerfile
type: docker-label # Built-in type
- file: config.json
field: app.version # JSON/YAML field path
- file: version.txt
pattern: "VERSION=(.+)" # Regex with capture group
hooks:
pre-bump: ./scripts/validate.sh
post-bump: ./scripts/build.sh
pre-publish: ./scripts/docs.sh
post-publish: ./scripts/notify.sh
publish:
github:
enabled: true # Default: true
draft: false # Create as draft release
artifacts: # Glob patterns for upload
- dist/*.tar.gzEnvironment variables can be referenced with ${VAR_NAME} syntax in string values.
When the changes section is omitted, all commits are accepted as patch-level changes and the changelog is rendered as a flat list. This is useful for projects that don't follow a structured commit convention.
feat(auth): add OAuth2 support
fix: resolve null pointer in parser
feat!: redesign API response format
feat-> minorfix-> patch!orBREAKING CHANGEfooter -> major
feat(core): add new endpoint
fix(auth): handle expired tokens
perf(db): optimize query execution
feat-> minorfix,perf-> patch- Breaking changes -> major
Define your own type-to-bump mappings:
changes:
commits:
convention: custom
types:
major: [breaking]
minor: [feat, feature]
patch: [fix, bugfix]release-cli supports hierarchical multi-package repositories out of the box. Each package gets independent versioning, changelogs, and releases -- with optional cascading when releasing a parent.
Each package gets its own .release.yaml. A parent declares its children via the modules field.
# .release.yaml (root)
name: my-project
project: go
modules:
- cli
- workflow# cli/.release.yaml
project: node# workflow/.release.yaml
project: go
modules:
- sub# workflow/sub/.release.yaml
project: goIf the root (or any intermediate node) is just a collection of sub-projects and not itself a releasable unit, omit project to make it a container:
# .release.yaml (container root)
modules:
- cli
- workflowKey rules:
nameis required whenmodulesandprojectare both declared -- it becomes the git tag prefix (e.g.,my-project/v1.2.0)- A config with
modulesbut noprojectis a container: it groups sub-projects but is never released or tagged itself - Child packages use their relative path as the tag prefix (e.g.,
cli/v1.0.0,workflow/sub/v0.3.0) - Each
.release.yamlis fully independent -- no config inheritance between parent and child - Selecting a parent for release cascades to all its descendants (containers are skipped)
release-cli release --package cli # Release a single package
release-cli release --package cli --package workflow # Release multiple packages
release-cli release --all # Release the entire treeWhen a parent is selected, all descendants are force-released (even those without new commits get a patch bump). A cascading release produces one batched commit with one tag per package:
commit: "Release my-project 0.3.0, cli 0.3.0, workflow 0.1.5"
-> tag: my-project/v0.3.0
-> tag: cli/v0.3.0
-> tag: workflow/v0.1.5
Commit analysis is path-scoped: only commits touching files under a package's directory contribute to its bump and changelog. Parent packages see all commits under their path, including children's.
Pull requests against main and pushes to main are validated by .github/workflows/ci.yml, which runs make vet, make test, make build, golangci-lint, and a go mod tidy drift check.
name: Release
on:
push:
tags:
- "v*"
workflow_dispatch:
inputs:
bump:
description: "Version bump level"
required: false
type: choice
options:
- ""
- patch
- minor
- major
permissions:
contents: write
jobs:
release:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- uses: actions/setup-go@v5
with:
go-version-file: go.mod
- name: Install release-cli
run: go install github.com/0x1306e6d/release-cli/cmd/release-cli@latest
- name: Configure git identity
run: |
git config user.name "github-actions[bot]"
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
- name: Release
run: release-cli release ${{ inputs.bump && format('--bump {0}', inputs.bump) || '' }}
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}name: Release Package
on:
workflow_dispatch:
inputs:
package:
description: "Package to release (or 'all')"
required: true
type: string
bump:
description: "Version bump level"
required: false
type: choice
options:
- ""
- patch
- minor
- major
permissions:
contents: write
jobs:
release:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- uses: actions/setup-go@v5
with:
go-version-file: go.mod
- name: Install release-cli
run: go install github.com/0x1306e6d/release-cli/cmd/release-cli@latest
- name: Configure git identity
run: |
git config user.name "github-actions[bot]"
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
- name: Release
run: |
if [ "${{ inputs.package }}" = "all" ]; then
release-cli release --all ${{ inputs.bump && format('--bump {0}', inputs.bump) || '' }}
else
release-cli release --package ${{ inputs.package }} ${{ inputs.bump && format('--bump {0}', inputs.bump) || '' }}
fi
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}Hooks run shell commands at key stages of the release pipeline. The following environment variables are injected:
| Variable | Description |
|---|---|
RELEASE_VERSION |
New version being released |
RELEASE_PREV_VERSION |
Previous version |
RELEASE_PROJECT |
Project type |
RELEASE_PACKAGE |
Package name (monorepo only) |
RELEASE_PACKAGE_PATH |
Package path relative to repo root (monorepo only) |
A non-zero exit code from any hook aborts the release.
| Variable | Description |
|---|---|
GITHUB_TOKEN |
Required for GitHub Release publishing |