release-tool manages releases from change files in .changes/. It computes the next semver, writes release.json, can generate markdown release notes, and creates the corresponding git tag.
Pick the binary for your platform from GitHub Releases.
Common installs:
curl -Lo /usr/local/bin/release-tool \
https://github.com/OpenShock/release-tool/releases/latest/download/release-tool-linux-amd64
chmod +x /usr/local/bin/release-toolcurl -Lo /usr/local/bin/release-tool \
https://github.com/OpenShock/release-tool/releases/latest/download/release-tool-darwin-arm64
chmod +x /usr/local/bin/release-toolcurl -Lo /usr/local/bin/release-tool \
https://github.com/OpenShock/release-tool/releases/latest/download/release-tool-darwin-amd64
chmod +x /usr/local/bin/release-toolInvoke-WebRequest `
-Uri https://github.com/OpenShock/release-tool/releases/latest/download/release-tool-windows-amd64.exe `
-OutFile $env:USERPROFILE\bin\release-tool.exeIf you use a different architecture, download the matching asset from Releases.
go install github.com/OpenShock/release-tool@latestIf release-tool is not found afterwards, add your Go bin directory to PATH:
export PATH="$(go env GOPATH)/bin:$PATH"release-tool --help- Initialise a repo once:
release-tool init- Add change files as work lands:
release-tool newOr non-interactively:
release-tool new "Add support for X" --type minor --categories api,cli- Inspect pending changes:
release-tool status- Cut a release:
release-tool stablestable:
- Computes the next version from pending
.changes/*.md - Writes
release.json - Optionally writes markdown notes via
--notes - Prepends a new entry to
CHANGELOG.md - Removes consumed change files
- Commits the changelog/update and creates a stable tag
For prereleases, use:
release-tool --prerelease-label beta rc
release-tool --prerelease-label develop --git-sha rc --allow-emptyrc does not consume .changes/ files or update CHANGELOG.md; it only writes release data and creates a prerelease tag.
---
type: minor # major | minor | patch
breaking: false # optional; major defaults to true
categories: [api] # optional
pr: 123 # optional; see below
---
Title shown in changelog
Optional extended body shown in the changelog.
## Release Note
Plain-language note for end users. Included in `release.json`, not in `CHANGELOG.md`.
## Notices
- warning: something users must know before upgrading
- info: optional migration or rollout notepr is tri-state:
- absent: the PR number is derived from git history (the PR that introduced the file)
- integer (
pr: 123): used verbatim, no derivation pr: null: suppresses the PR link entirely
Notice levels must be one of info, warning, or error, and each line must be
- level: message. Invalid levels or malformed lines fail validation (caught by status).
Special files in .changes/:
README.md: local format reference created byrelease-tool init_headline.md: optional markdown shown at the top of the generated changelog entryconfig.json: optional repo config
Example .changes/config.json:
{
"tag_prefix": "v",
"categories": ["api", "firmware", "frontend"]
}categories, when present, is an allowlist: change files declaring a category
outside the list fail validation. When omitted, any category string is accepted.
When the gh CLI is available and authenticated (GH_TOKEN), stable and rc
enrich the release with GitHub data:
- PR numbers are derived for change files that don't pin one (see
prabove). - Contributors — every commit author since the previous tag is recorded in
release.json(contributors), and the generated notes gain a### Contributorsfooter thanking them, excluding repo maintainers (admin/maintain collaborators) and*[bot]accounts.
Both require the checkout to include tags and history (fetch-depth: 0) so the
previous tag is a resolvable ref. Maintainer detection needs a token with push
access; without it, the footer simply thanks everyone. Enrichment is skipped
under --dry-run.
Global flags available to stable, rc, status, init, and new:
--dry-run: preview without writing files, committing, or tagging--output <path>: where to writerelease.json(defaultrelease.json)--notes <path>: write markdown release notes--prerelease-label <label>: prerelease label such asalpha,beta,rc, ordevelop--git-sha: append+g<sha>build metadata to prerelease tags--root <path>: operate on another repository root
The composite action wraps the CLI and exposes five modes:
- uses: OpenShock/release-tool@v1
with:
mode: stable # stable | beta | develop | status | check
dry-run: false
output: release.json
notes-output: release-notes.md
prerelease-label: beta # optional override for beta/develop
git-sha: false # append +g<sha> to prerelease tagsMode behavior:
stable: consumes pending change files and updatesCHANGELOG.mdbeta: creates prerelease tags from changes since the last beta or stable tagdevelop: creates prerelease tags from changes since the last develop, beta, or stable tag, always with git SHA metadata and allowing empty cutsstatus: validates pending changes and prints the next version without creating a tag (useful as a push-time check)check: validates the change files a pull request adds and writesrelease-check.json(see below)
Action outputs:
tag: created tag, empty when skippedprerelease:truefor prerelease tagsskip:truewhen no release was created
mode: check evaluates the change files a pull request adds relative to its base
branch and writes a verdict (ok, missing, invalid, or skip) to
release-check.json. The job exits non-zero on invalid, so it works as a merge
gate. The base branch is resolved through the branches map in
.changes/config.json, which is the single source of truth for which branches
are release branches:
{
"branches": { "master": "stable", "beta": "beta", "develop": "develop" }
}A pull request whose base is not listed yields skip (no gate, no comment).
To post the verdict as a sticky comment without exposing a write token to untrusted fork code, use a two-stage setup:
- A
pull_requestworkflow (permissions: contents: read, no secrets) runsmode: checkand uploadsrelease-check.jsonas an artifact. Its exit code is the gate, so it works for fork pull requests. - A
workflow_runworkflow (permissions: pull-requests: write) triggers on the first one's completion, downloads the artifact, and posts the comment. It never checks out or runs pull request code, so the write token never meets untrusted input.
See .github/workflows/pr-check.yml and pr-check-comment.yml in the
cicd-playground repo for a working example.