This GitHub action is designed to generate release notes between 2 tags and use them to create a GitHub release.
The notes open with a ## Changelog heading, then one line per commit in the
range:
## Changelog
- feat: add a widget ([#12](https://github.com/acme/widgets/pull/12)) (@octocat)
- fix: stop the thing exploding ([#34](https://github.com/acme/widgets/pull/34)) (@hubot)
- docs: tidy the readme ([ccccccc](https://github.com/acme/widgets/commit/ccccccc)) (Ada Lovelace)
**Full changelog**: https://github.com/acme/widgets/compare/v1.9.0...v2.0.0It is a composite action — no Node runtime, no bundled dist/. It calls the
GitHub API through the gh CLI that is preinstalled on GitHub-hosted runners.
The common case: tag a commit, push the tag, get a release.
name: Release
on:
push:
tags: ["v*"]
permissions:
contents: write
jobs:
release:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0 # so the previous tag can be detected
- uses: bobbymannino/github-release-action@v1With no inputs, the action releases the tag that triggered the workflow and works out which tag to compare against on its own.
- uses: bobbymannino/github-release-action@v1
with:
tag: v2.0.0
previous-tag: v1.9.0- uses: bobbymannino/github-release-action@v1
with:
tag: v2.0.0-rc.1
name: Release candidate 1
draft: true
prerelease: true
make-latest: false- uses: bobbymannino/github-release-action@v1
id: release
- run: gh release upload "${{ github.ref_name }}" ./dist/*.tar.gz
env:
GH_TOKEN: ${{ github.token }}
- run: echo "Released at ${{ steps.release.outputs.url }}"For each commit between the two tags, in order:
| Part | Where it comes from |
|---|---|
| title | The commit subject. A trailing (#12) — how GitHub writes squash merges — is stripped, and for a merge commit the merged branch's subject is used instead. |
| link | The pull request, when the commit references one (… (#12) or Merge pull request #34 from …). Otherwise the commit itself, linked by short SHA. |
| @username | The commit author's GitHub account. When GitHub cannot match the commit to an account, the raw commit author name is used instead, without the @. |
The body always starts with a ## Changelog heading and ends with a
Full changelog link to the diff between the two tags — or, when there is no
previous tag, to the tag's commit history.
Merge commits are included by default; set skip-merge-commits: true to leave
them out. If a range has no commits the notes read _No commits in this range._.
Ranges longer than 1000 commits are truncated, with a warning on the step.
previous-tag is used when you give one. Otherwise the action tries, in order:
git describe --tags --abbrev=0 <tag>^in the checked-out repository — the correct answer topologically, but it needsfetch-depth: 0on the checkout;- the tag of the latest published release;
- nothing — in which case the notes cover every commit up to the tag, which is what you want for a first release.
| Input | Default | Description |
|---|---|---|
tag |
the triggering tag | The tag to release. Required if the workflow was not triggered by a tag push. |
previous-tag |
detected | The tag to compare against. |
name |
the tag | Release title. |
skip-merge-commits |
false |
Leave merge commits out of the notes. |
target-commitish |
— | Branch or SHA the tag should point at. Only needed when the tag does not exist yet. |
draft |
false |
Create the release as a draft. |
prerelease |
false |
Mark the release as a prerelease. |
make-latest |
true |
Whether this release is the latest. One of true, false, legacy. |
overwrite |
false |
Delete an existing release for the tag instead of failing. |
token |
${{ github.token }} |
Token used for the API calls. Needs contents: write permission. |
Boolean inputs also accept yes/no and 1/0, and are case-insensitive.
Anything else fails the step rather than being silently treated as false.
| Output | Description |
|---|---|
id |
ID of the created release. |
url |
HTML URL of the created release. |
upload-url |
Asset upload URL of the created release. |
notes |
The generated release notes. |
previous-tag |
The tag the notes were generated against. Empty if there was none. |
title |
The release title that was used. |
The notes are also written to the job summary, so you can read them without leaving the workflow run.
The job needs contents: write. Creating a release for a tag that does not
exist yet also requires target-commitish.
The step fails, loudly, when:
- no
tagis given and the workflow was not triggered by a tag; tagandprevious-tagare the same;- a release already exists for the tag and
overwriteis nottrue; - a boolean input is not recognisable as a boolean;
- the GitHub API rejects the request — the API's error body is included in the message.
bash tests/run-tests.sh # runs the scripts against a stubbed gh CLI
shellcheck scripts/*.sh # lintThe scripts under scripts/ hold all the logic and read their inputs from
INPUT_* environment variables, which is what makes them testable outside of a
workflow. tests/stubs/gh records the requests the scripts make and replies
with canned API responses built from tests/fixtures/commits.json.
MIT. See LICENSE.