yeet
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.
Install
brew install monkescience/tap/yeet
Or with Go:
go install github.com/monkescience/yeet/cmd/[email protected] # x-yeet-version
Or use the published container image:
docker run --rm ghcr.io/monkescience/yeet:v0.4.6 --help # x-yeet-version
Quick start
# 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-merge
Run yeet --help for the full list of commands and flags.
How it works
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 release updates the same release branch instead of
creating a second pending release.
- After the release PR/MR is merged, the next
yeet release run on the base branch
creates the tag/provider release from the latest changelog entry and flips the label to
autorelease: 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.
Configuration
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.
Repository targeting
yeet resolves the target repository from these sources, highest priority first:
- CLI flags (
--provider, --host, --owner, --repo, --project)
- explicit
.yeet.yaml values under repository:
- the configured
repository.remote
- the
origin remote
When yeet cannot classify a remote host automatically, set the provider and repository explicitly:
# 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/service
Monorepo targets
yeet 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.
targets:
api:
type: path
path: services/api
tag_prefix: api-v
web:
type: path
path: apps/web
tag_prefix: web-v
root:
type: derived
includes:
- api
- web
tag_prefix: v
Version file markers
yeet release updates only files listed in version_files. Each file must contain yeet markers.
# inline markers
VERSION = "0.4.6" # x-yeet-version
MAJOR = 0 # x-yeet-major
MINOR = 4 # x-yeet-minor
PATCH = 6 # x-yeet-patch
# block markers
# x-yeet-start-version
image: ghcr.io/acme/app:0.4.6
appVersion: "0.4.6"
# x-yeet-end
For calver repositories, yeet also supports aliases:
x-yeet-year (alias of x-yeet-major)
x-yeet-month (alias of x-yeet-minor)
x-yeet-micro (alias of x-yeet-patch)
x-yeet-start-year|month|micro for calver block markers
x-yeet-end closes the block
Changelog references
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.
Versioning strategies
Semantic Versioning (semver)
Follows semver with configurable pre-1.0 behavior.
For versions >= 1.0.0:
feat -> minor
fix, perf -> patch
- Breaking changes (
! or BREAKING CHANGE footer) -> major
For versions < 1.0.0 (default behavior with pre_major_breaking_bumps_minor: true and pre_major_features_bump_patch: true):
feat -> patch
fix, perf -> patch
- Breaking changes (
! or BREAKING CHANGE footer) -> minor
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.
Calendar Versioning (calver)
Uses YYYY.0M.MICRO format (e.g., 2026.02.1). The micro counter resets when the year/month changes.
Authentication
yeet needs a provider API token whenever it creates or updates PRs/MRs, applies release labels, or
publishes releases.
GitHub
Export either GITHUB_TOKEN or GH_TOKEN:
export GITHUB_TOKEN=ghp_xxx
yeet release --dry-run
For 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 release
The token needs contents: write, pull-requests: write, and issues: write permissions.
GitLab
Export either GITLAB_TOKEN or GL_TOKEN:
export GITLAB_TOKEN=glpat-xxx
yeet release --dry-run
For self-hosted GitLab, also set GITLAB_URL:
export GITLAB_TOKEN=glpat-xxx
export GITLAB_URL=https://gitlab.example.com/api/v4
yeet release
The token must be able to create merge requests, manage labels, and publish releases.
CI examples
GitHub Actions with a GitHub App
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
pull-requests: write
issues: write
concurrency:
group: yeet-release-${{ github.ref }}
cancel-in-progress: true
jobs:
release:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v6
with:
fetch-depth: 0
- name: Set up Go
uses: actions/setup-go@v6
with:
go-version: stable
- name: Generate GitHub App token
id: generate-token
uses: actions/create-github-app-token@v2
with:
app-id: ${{ vars.RELEASE_PLEASE_APP_ID }}
private-key: ${{ secrets.RELEASE_PLEASE_APP_PRIVATE_KEY }}
owner: ${{ github.repository_owner }}
- name: Run yeet release
run: |
go install github.com/monkescience/yeet/cmd/[email protected] # x-yeet-version
yeet release
env:
GITHUB_TOKEN: ${{ steps.generate-token.outputs.token }}
GitLab CI
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.4.6 # x-yeet-version
entrypoint: [""]
variables:
GIT_STRATEGY: fetch
GIT_DEPTH: "0"
script:
- yeet release
rules:
- if: '$CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH'
Troubleshooting
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.yaml with yeet init at the repo root or pass --config.
invalid configuration: fix invalid values in .yeet.yaml before rerunning.
repository resolution failed: set provider and/or repository explicitly when the remote
host is unsupported or auto-detection cannot classify it.
provider setup failed: export the required token (GITHUB_TOKEN/GH_TOKEN or
GITLAB_TOKEN/GL_TOKEN) and, for self-hosted providers, verify GITHUB_URL or GITLAB_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 stale
autorelease: pending entries until only one open release PR/MR remains for the base branch.
License
MIT