Automate releases based on conventional commits. Analyzes commit history, calculates the next version, generates changelogs, creates release PRs/MRs, and finalizes merged releases on GitHub or GitLab.
Inspired by release-please.
brew install monkescience/tap/yeetOr on Windows with Scoop:
scoop bucket add monkescience https://github.com/monkescience/scoop-bucket
scoop install yeetOr with Go:
go install github.com/monkescience/yeet/cmd/yeet@v0.5.1 # x-yeet-versionOr use the published container image:
docker run --rm ghcr.io/monkescience/yeet:v0.5.1 --help # x-yeet-version# Initialize config in your repo
yeet init
# Preview what the next release would look like
yeet release --dry-run
# Create a release PR/MR
yeet release
# Auto-merge and finalize in the same run
yeet release --auto-mergeRun yeet --help for the full list of commands and flags.
yeet release does slightly different work depending on repository state:
- Before a release PR/MR exists, it scans conventional commits, calculates the next version,
updates the changelog/version files, and opens a release PR/MR labeled
autorelease: pending. - While that PR/MR is open, rerunning
yeet releaseupdates the same release branch instead of creating a second pending release. - After the release PR/MR is merged, the next
yeet releaserun on the base branch creates the tag/provider release from the latest changelog entry and flips the label toautorelease: tagged.
That label lifecycle is operational, not decorative: yeet uses autorelease: pending to discover
merged releases that still need tagging, and it expects only one open pending release PR/MR per
base branch. If multiple pending PRs/MRs exist, yeet release fails and prints the conflicting
URLs so you can close or relabel stale entries.
When auto-merge is enabled (--auto-merge or release.auto_merge in config), yeet merges the
release PR/MR and finalizes the release in the same run. Force mode (--auto-merge-force) skips
yeet's own readiness gates but does not bypass provider branch protections, required checks,
approvals, or missing permissions.
Follows semver with configurable pre-1.0 behavior.
For versions >= 1.0.0:
feat-> minorfix,perf-> patch- Breaking changes (
!orBREAKING CHANGEfooter) -> major
For versions < 1.0.0 (default behavior with pre_major_breaking_bumps_minor: true and pre_major_features_bump_patch: true):
feat-> patchfix,perf-> patch- Breaking changes (
!orBREAKING CHANGEfooter) -> minor
These type-to-bump defaults are configurable via bump_types (see Bump types).
This keeps pre-1.0 breaking changes from automatically jumping to 1.0.0.
Set pre_major_breaking_bumps_minor: false to let breaking changes bump major (triggering 1.0.0),
or pre_major_features_bump_patch: false to let features bump minor as they do post-1.0.
These options can also be overridden per target in monorepo configurations.
Release-As commit footers (for example Release-As: 1.0.0) override automatic semver bumping.
The value must be a stable semver version greater than the current version. Release-As is
case-insensitive and applies only to semver repositories; calver repositories ignore it.
Uses YYYY.0M.MICRO format (e.g., 2026.02.1). The micro counter resets when the year/month changes.
yeet reads the nearest ancestor .yeet.yaml by default. Run yeet init to generate one with sensible defaults, or pass --config to write to a custom path. The generated file includes a YAML language server schema modeline for editor validation and autocomplete.
All available options, defaults, and descriptions are defined in the JSON schema. YAML-aware editors that support # yaml-language-server: $schema=... modelines will provide validation and autocomplete automatically. You can pin the schema URL to a release tag for stricter reproducibility.
yeet resolves the target repository from these sources, highest priority first:
- CLI flags (
--provider,--host,--owner,--repo,--project) - explicit
.yeet.yamlvalues underrepository: - the configured
repository.remote - the
originremote
Automatic provider detection intentionally only classifies the public hosts github.com and
gitlab.com. For custom or enterprise domains, set the provider and repository explicitly; this
avoids sending provider tokens to an arbitrary host based only on hostname text:
# GitHub Enterprise
provider: github
repository:
host: github.company.com
owner: platform
repo: yeet# GitLab subgroup
provider: gitlab
repository:
host: gitlab.company.com
project: group/subgroup/serviceyeet plans releases per target and creates one combined release PR/MR per base branch.
PR workflow settings remain top-level under release: and apply to the combined PR/MR, not individual targets.
Use --target to limit yeet release to specific targets (repeatable).
targets:
api:
type: path
path: services/api
tag_prefix: api-v
exclude_paths:
- services/api/testdata
web:
type: path
path: apps/web
tag_prefix: web-v
root:
type: derived
includes:
- api
- web
path: . # optional: also matches commits at repo root
tag_prefix: vPath targets support exclude_paths to ignore commits under specific subdirectories.
Derived targets aggregate included path targets and optionally match direct commits via path.
By default, feat commits bump minor and fix/perf commits bump patch. Override this mapping with bump_types:
bump_types:
minor:
- feat
- improvement
patch:
- fix
- perf
- depsTypes not listed produce no version bump. Breaking changes always bump major regardless of this mapping.
yeet release updates only files listed in version_files. Each file must contain yeet markers.
# inline markers
VERSION = "0.5.1" # x-yeet-version
MAJOR = 0 # x-yeet-major
MINOR = 5 # x-yeet-minor
PATCH = 1 # x-yeet-patch
# block markers
# x-yeet-start-version
image: ghcr.io/acme/app:0.5.1
appVersion: "0.5.1"
# x-yeet-endFor calver repositories, yeet also supports aliases:
x-yeet-year(alias ofx-yeet-major)x-yeet-month(alias ofx-yeet-minor)x-yeet-micro(alias ofx-yeet-patch)x-yeet-start-year|month|microfor calver block markersx-yeet-endcloses the block
yeet generates a changelog from conventional commits. Configure which commit types appear and how sections are labeled:
changelog:
file: CHANGELOG.md
include:
- feat
- fix
- perf
- revert
sections:
feat: Features
fix: Bug Fixes
perf: Performance Improvements
revert: RevertsOnly types listed in include appear in the changelog. The sections map controls their heading text.
yeet can link issue tracker references in generated changelogs. References are extracted from two sources: inline patterns matched in commit descriptions, and conventional commit footers.
changelog:
references:
patterns:
- pattern: "JIRA-\\d+"
url: "https://jira.example.com/browse/{value}"
- pattern: "#\\d+"
url: "" # plain text, GitHub auto-links these
footers:
Refs: "https://jira.example.com/browse/{value}"
Closes: ""Inline patterns match against the commit description using regex and replace matches with links. A commit like feat: add OAuth2 support JIRA-123 produces:
- add OAuth2 support [JIRA-123](https://jira.example.com/browse/JIRA-123) (abc1234)
Footer references extract values from conventional commit footers and append them after the commit hash. A commit with a Refs: JIRA-456 footer produces:
- add OAuth2 support (abc1234) ([JIRA-456](https://jira.example.com/browse/JIRA-456))
Use {value} as the placeholder in URL templates. An empty URL string renders the reference as plain text without linking. Both patterns and footers can be configured per target in monorepo setups.
If a merged PR/MR has a vague squash or merge commit message, edit that source PR/MR body to add override entries:
BEGIN_COMMIT_OVERRIDE
feat(auth): add OAuth token refresh
fix(api): return 401 for expired sessions
END_COMMIT_OVERRIDEWhen yeet analyzes the merge/squash commit, those conventional commit messages replace the commit message for version bumping and changelog generation. The generated changelog still links to the original commit hash.
This can split one merged commit into multiple release notes, or introduce a breaking change:
BEGIN_COMMIT_OVERRIDE
feat(auth)!: replace session cookie format
BREAKING CHANGE: existing session cookies are invalid after upgrade
END_COMMIT_OVERRIDECommit overrides are read from the original merged PR/MR body. Manual edits to the generated release PR/MR body may be overwritten the next time yeet updates the release branch. Rebase-merged PRs are not overridden because one PR/MR can produce many commits and the association is ambiguous.
release:
subject_include_branch: true # include target branch in PR/MR subject
pr_body_header: "## Release" # markdown before changelog in PR/MR body
pr_body_footer: "_Automated._" # markdown after changelog in PR/MR bodyyeet needs a provider API token whenever it creates or updates PRs/MRs, applies release labels, or publishes releases.
Export either GITHUB_TOKEN or GH_TOKEN:
export GITHUB_TOKEN=ghp_xxx
yeet release --dry-runFor GitHub Enterprise, also set GITHUB_URL to the API base URL or let yeet derive it from the
configured repository host:
export GITHUB_TOKEN=ghp_xxx
export GITHUB_URL=https://github.example.com/api/v3/
yeet releaseThe token needs contents: write, pull-requests: write, and issues: write permissions.
Export either GITLAB_TOKEN or GL_TOKEN:
export GITLAB_TOKEN=glpat-xxx
yeet release --dry-runFor self-hosted GitLab, also set GITLAB_URL:
export GITLAB_TOKEN=glpat-xxx
export GITLAB_URL=https://gitlab.example.com/api/v4
yeet releaseThe token must be able to create merge requests, manage labels, and publish releases.
This example uses a GitHub App installation token instead of the default GITHUB_TOKEN.
The app needs contents: write, pull-requests: write, and issues: write repository permissions.
Store the app ID as a repository variable and the private key as a repository secret.
name: Release
on:
push:
branches: [main]
workflow_dispatch:
permissions:
contents: write
issues: write
pull-requests: write
concurrency:
group: release-${{ github.ref }}
cancel-in-progress: false
jobs:
release:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
with:
fetch-depth: 0
- name: Generate GitHub App token
id: generate-token
uses: actions/create-github-app-token@1b10c78c7865c340bc4f6099eb2f838309f1e8c3 # v3
with:
app-id: ${{ vars.YEET_APP_ID }}
private-key: ${{ secrets.YEET_APP_PRIVATE_KEY }}
owner: ${{ github.repository_owner }}
- name: Run yeet
uses: docker://ghcr.io/monkescience/yeet:v0.5.1 # x-yeet-version
with:
args: release
env:
GITHUB_TOKEN: ${{ steps.generate-token.outputs.token }}Set GITLAB_TOKEN as a masked CI/CD variable. The entrypoint: [""] override is required so
GitLab runs the job script with sh instead of the image's default yeet entrypoint.
release:
stage: release
image:
name: ghcr.io/monkescience/yeet:v0.5.1 # x-yeet-version
entrypoint: [""]
variables:
GIT_STRATEGY: fetch
GIT_DEPTH: "0"
script:
- yeet release
rules:
- if: '$CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH'yeet release keeps wrapped errors for debugging, but the top-level message points at the failure
category so you can pick the next fix quickly:
configuration file not found: create.yeet.yamlwithyeet initat the repo root or pass--config.invalid configuration: fix invalid values in.yeet.yamlbefore rerunning.repository resolution failed: setproviderand/orrepositoryexplicitly when the remote host is unsupported or auto-detection cannot classify it.provider setup failed: export the required token (GITHUB_TOKEN/GH_TOKENorGITLAB_TOKEN/GL_TOKEN) and, for self-hosted providers, verifyGITHUB_URLorGITLAB_URL.release execution failed: merge blocked: the release PR/MR is still draft, has conflicts, lacks required approvals/checks, or requests a merge method the provider settings do not allow.release execution failed: multiple pending release PRs/MRs found: close or relabel staleautorelease: pendingentries until only one open release PR/MR remains for the base branch.