A GitHub Action that creates GitHub Releases from your Git tags. Does what you probably wish GitHub would just do without the need to use GitHub Actions.
This action creates releases by sourcing the release data from the place where it makes the most sense to keep it โ your Git tags. By harnessing SemVer to determine pre-release status, and Markdown for formatting, your GitHub Releases become a natural extension of your Git tags.
In addition, this action has been designed to feel like it could be a first-party GitHub feature. Its feature set closely mirrors what you have access to when you publish a GitHub Release manually.
- Minimal configuration, or often zero configuration
- SemVer stability determines pre-release status
- Markdown support in tag annotation messages
- Asset uploading with support for labels
- Automated release notes support
- Release discussion creation
- Releases can be created as drafts
- Creation of initial ๐ reactions โค๏ธ to promote engagement
- Creation of job summaries for the Actions run summary page
- Works with GitHub Enterprise Server (GHES)
Most of the time you will want tag pushes to trigger release publishing:
# .github/workflows/publish-release.yml
name: Publish release
on:
push:
tags:
- "*"
jobs:
publish:
runs-on: ubuntu-latest
name: Publish release
steps:
- name: Checkout
uses: actions/checkout@v3
- name: Publish release
uses: eloquent/github-release-action@v3
It's also possible to use if
conditionals to restrict the release publishing
step inside a multi-purpose workflow, so that it only runs on tag pushes:
- name: Publish release
uses: eloquent/github-release-action@v3
if: github.ref_type == 'tag'
You may also want to be able to manually publish releases for a specific tag. This allows you to remedy failed publish attempts, or publish tags that were created before automated release publishing was set up:
# .github/workflows/publish-release-manual.yml
name: Publish release (manual)
on:
workflow_dispatch:
inputs:
tag:
description: The tag to publish
required: true
jobs:
publish:
runs-on: ubuntu-latest
name: Publish release
steps:
- name: Checkout
uses: actions/checkout@v3
with:
ref: refs/tags/${{ github.event.inputs.tag }}
- name: Publish release
uses: eloquent/github-release-action@v3
Generally speaking, you do not need to supply this action with a custom GitHub token unless you run into some kind of permissions issue, which should not happen under normal operation.
By default, this action uses automatic token authentication to obtain the token used to manage releases. If for some reason you want to supply a different token, you can do so via action inputs:
# In your workflow:
- uses: eloquent/github-release-action@v3
with:
token: ${{ secrets.CUSTOM_GITHUB_TOKEN }}
This action uses SemVer rules to determine whether a tag should be published as a pre-release, or a stable release. The decision is made as follows:
- If the tag name is a "stable" SemVer version, it's considered a stable release.
- If the tag name is an "unstable" SemVer version, it's considered a pre-release.
- If the tag name is not a valid SemVer version, it's considered a pre-release.
The standard SemVer rules are relaxed a bit to allow for tag names with a v
prefix (e.g. v1.2.3
), as well as major/minor version tag names (e.g. v1
,
v1.2
) as per GitHub's recommendations for action versioning.
It's also possible to configure an override for this behavior, and force a release to be published as either a pre-release or stable release. This can be done via the configuration file, or via action inputs:
# In .github/release.eloquent.yml:
prerelease: true # or false
# In your workflow:
- uses: eloquent/github-release-action@v3
with:
prerelease: "true" # or "false"
Tag name | Is SemVer? | Release stability |
---|---|---|
1 / v1 |
no | stable release |
1.2 / v1.2 |
no | stable release |
1.2.3 / v1.2.3 |
yes | stable release |
1.2.3+21AF26D3 / v1.2.3+21AF26D3 |
yes | stable release |
0.1.0 / v0.1.0 |
yes | pre-release |
1.2.3-alpha / v1.2.3-alpha |
yes | pre-release |
0 / v0 |
no | pre-release |
0.1 / v0.1 |
no | pre-release |
something-else |
no | pre-release |
This action can be configured to create draft releases. These draft releases can then be published manually at some later time via GitHub. You can enable this feature via the configuration file, or via action inputs:
# In .github/release.eloquent.yml:
draft: true
# In your workflow:
- uses: eloquent/github-release-action@v3
with:
draft: "true"
This action generates a release name and body from your tag annotation message. Git already breaks your tag annotation messages into two parts that line up with each part of a GitHub release:
- The tag annotation subject becomes the release name.
- The tag annotation body is rendered as Markdown, and used as the release body.
The tag annotation "subject" is just the first paragraph of the message. The "body" is everything that follows:
This is part of the subject.
This is also considered part of the subject.
This is the beginning of the body.
This is also part of the body.
The body can have multiple paragraphs.
For the most part, Markdown "just works" how you would expect. You can write Markdown in the "body" portion of your tag annotations, and it will be rendered in the body of the releases published by this action.
When authoring tag annotation messages, you might run into the issue that
Markdown headings are lines that start with a #
character, which Git
interprets as a comment. You can get around this limitation by using the
following Git command to create the tag:
git tag --annotate --cleanup=whitespace --edit --message "" 1.0.0
You might want to add a Git alias to make it easier to remember the command:
git config --global alias.tag-md 'tag --annotate --cleanup=whitespace --edit --message ""'
With the above alias configured, you can then use git tag-md
to create tags
with Markdown tag annotation bodies:
git tag-md 1.0.0
It's common for tag annotation messages to be "wrapped" at a fixed column width, for readability when viewed as plain text:
1.0.0
This provides an example of a Git tag annotation body that has been
"hard wrapped". This is a very common practice.
If we were to copy the body from the tag annotation message above directly into the GitHub release, the line breaks would be interpreted as explicit line breaks in the final HTML, like so:
This provides an example of a Git tag annotation body that has been
"hard wrapped". This is a very common practice.
Most people would probably consider this an undesirable result, and would rather
that the above two lines be combined into a single line in the resulting HTML,
similar to how GitHub behaves when rendering README.md
files.
To avoid this issue, line breaks that are not followed by another line break (also known as "soft" line breaks) are transformed into spaces before they are used in GitHub release bodies. Meaning the above tag annotation body would be rendered like so:
This provides an example of a Git tag annotation body that has been "hard wrapped". This is a very common practice.
This action supports GitHub's automatically generated release notes feature. You can enable this feature via the configuration file, or via action inputs:
# In .github/release.eloquent.yml:
generateReleaseNotes: true
# In your workflow:
- uses: eloquent/github-release-action@v3
with:
generateReleaseNotes: "true"
When enabled, automated release notes will be generated and appended to each release body. The release notes are based off of pull requests, and can be configured to customize how they are generated.
Example automated release notes
- Add feature A by @ezzatron in #5
- Add feature B by @ezzatron in #6
- Fix bug A by @ezzatron in #7
- Fix bug B by @ezzatron in #8
- Add docs for feature A by @ezzatron in #9
- Add docs for feature B by @ezzatron in #10
Full Changelog: https://github.com/owner/repo/commits/1.0.0
This action supports uploading release assets โ files that are associated with a release, and made available for download from GitHub. Release assets must exist before this action is run, and can be specified via the configuration file, or via action inputs:
# In .github/release.eloquent.yml:
assets:
- path: path/to/asset-a
- path: path/to/asset-b-*
# In your workflow:
- uses: eloquent/github-release-action@v3
with:
# Note the "|" character - this example uses a YAML multiline string.
assets: |
- path: path/to/asset-d
โ ๏ธ Warning: This action will overwrite existing release assets if their names match the assets configured for upload. Assets other than the ones specified in configuration or action inputs will not be modified or removed.
Note: Unlike other action inputs, which typically override their equivalent configuration file options, assets specified via action inputs are merged with those specified in the configuration file.
Each asset must have a path
property, which is a file glob pattern supported
by @actions/glob
. If no matching file is found when the action is run, the
workflow step will fail (unless the asset is configured to be optional).
If multiple files match the path
glob pattern, each file will be uploaded
individually. This action does not handle archiving multiple assets for you.
If you want to upload a .zip
(or similar) file composed of multiple files, you
must build the archive yourself prior to running this action.
If a single file matches the path
glob pattern, you can additionally
specify a custom name
and/or label
for the asset:
# In .github/release.eloquent.yml:
assets:
- path: path/to/asset-a.yaml
name: custom-name.yml
label: Labels can have spaces
# In your workflow (using YAML):
- uses: eloquent/github-release-action@v3
with:
# Note the "|" character - this example uses a YAML multiline string.
assets: |
- path: path/to/asset-a.yaml
name: custom-name.yml
label: Labels can have spaces
# In your workflow (using JSON):
- uses: eloquent/github-release-action@v3
with:
# Note the "|" character - this example uses a YAML multiline string.
assets: |
[{
"path": "path/to/asset-a.yaml",
"name: "custom-name.yml",
"label": "Labels can have spaces"
}]
The name
property overrides the file name that will be used when the file is
uploaded (and hence the filename seen by users who download the asset). The
label
property is a text field that gets used by GitHub when viewing a
release's assets.
Assets can be made "optional" โ that is, they will simply be skipped if the
path
file glob pattern does not match any files. You can enable this behavior
by setting the optional
property to true
:
# In .github/release.eloquent.yml:
assets:
- path: path/to/assets/*
optional: true
# In your workflow:
- uses: eloquent/github-release-action@v3
with:
# Note the "|" character - this example uses a YAML multiline string.
assets: |
- path: path/to/assets/*
optional: true
If you need to dynamically specify a list of assets to upload, you can use the
assets
action input with generated JSON (or YAML). How you generate the
value for this input is up to you, but any value from a context (e.g.
an output from another step) can be used, for example:
# Executing a script that outputs JSON describing the assets to upload.
- id: listAssets
run: echo "assets=$(bash list-assets.sh)" >> $GITHUB_OUTPUT
- uses: eloquent/github-release-action@v3
with:
assets: ${{ steps.listAssets.outputs.assets }}
This action supports creating GitHub Discussions for releases. You can enable this feature via the configuration file, or via action inputs:
# In .github/release.eloquent.yml:
discussion:
category: Announcements
# In your workflow:
- uses: eloquent/github-release-action@v3
with:
discussionCategory: Announcements
When enabled, discussions will automatically be created and linked to each published release. The discussion title and body will match the release name and body. The specified discussion category must already exist in the repo.
In order to promote engagement with your releases, this action can create reactions like ๐, ๐, ๐, โค๏ธ, ๐, and ๐.
A typical user is more likely to add their own reaction if they can simply click on an existing one โ rather than be the first one to add a particular reaction, which takes more effort. You can enable this feature via the configuration file, or via action inputs:
# In .github/release.eloquent.yml:
reactions: ["+1", laugh, hooray, heart, rocket, eyes]
# In your workflow:
- uses: eloquent/github-release-action@v3
with:
reactions: +1,laugh,hooray,heart,rocket,eyes
If you've enabled release discussion creation, reactions can also be created for release discussions (which support a couple of additional reactions like ๐ and ๐):
# In .github/release.eloquent.yml:
discussion:
category: Announcements
reactions: ["+1", "-1", laugh, hooray, confused, heart, rocket, eyes]
# In your workflow:
- uses: eloquent/github-release-action@v3
with:
discussionCategory: Announcements
discussionReactions: +1,-1,laugh,hooray,confused,heart,rocket,eyes
When a release is created or updated, a summary containing useful information and links will be displayed on the Actions run summary page:
You can disable this feature via the configuration file, or via action inputs:
# In .github/release.eloquent.yml:
summary:
enabled: false
# In your workflow:
- uses: eloquent/github-release-action@v3
with:
summaryEnabled: "false"
Tip: Try to use as little configuration as possible. Everything here is optional, and the less configuration the better.
This action supports an optional YAML configuration file, with options for affecting how releases are published:
Note: These options can also be specified by action inputs. A JSON Schema definition is also available.
# .github/release.eloquent.yml
# Set to true to produce releases in a draft state.
draft: true
# Set to true to append automatically generated release notes to the release
# body.
generateReleaseNotes: true
# Set to true or false to override the automatic tag name based pre-release
# detection.
prerelease: false
# Reactions to create for releases.
reactions: ["+1", laugh, hooray, heart, rocket, eyes]
assets:
# A path is required for each asset.
- path: assets/text/file-a.txt
# The "optional" flag, name, and label are optional.
- path: assets/json/file-b.json
optional: true
name: custom-name-b.json
label: Label for file-b.json
discussion:
# The category to use when creating the discussion.
category: category-a
# Reactions to create for discussions linked to releases.
reactions: ["+1", "-1", laugh, hooray, confused, heart, rocket, eyes]
summary:
# Set to false to disable GitHub Actions job summary creation.
enabled: false
This action supports optional inputs for affecting how releases are published:
Note: With the exception of
assets
, these inputs take precedence over any equivalent options specified in the configuration file. The action metadata file contains the actual definitions for these inputs.
- uses: eloquent/github-release-action@v3
with:
# Set to "true" to produce releases in a draft state.
draft: "true"
# Set to "true" to append automatically generated release notes to the
# release body.
generateReleaseNotes: "true"
# Set to "true" or "false" to override the automatic tag name based
# pre-release detection.
prerelease: "false"
# Reactions to create for releases.
reactions: +1,laugh,hooray,heart,rocket,eyes
# Assets to be associated with releases, specified as YAML (or JSON), and
# merged with assets specified elsewhere. If you need a dynamic list, this
# input can be useful. See the section titled "Dynamic release assets".
assets: |
- path: assets/text/file-a.txt
- path: assets/json/file-b.json
optional: true
name: custom-name-b.json
label: Label for file-b.json
# Use a custom GitHub token.
token: ${{ secrets.CUSTOM_GITHUB_TOKEN }}
# The category to use when creating the discussion.
discussionCategory: category-a
# Reactions to create for discussions linked to releases.
discussionReactions: +1,-1,laugh,hooray,confused,heart,rocket,eyes
# Set to "false" to disable GitHub Actions job summary creation.
summaryEnabled: "false"
This action makes a number of outputs available:
Note: The action metadata file contains the actual definitions for these outputs. The example below should give you some idea what each output looks like. The outputs aren't actually YAML of course, it's just for explanatory purposes.
# The ID of the published release.
releaseId: "68429422"
# The URL of the published release.
releaseUrl: https://github.com/owner/repo/releases/tag/1.0.0
# The asset upload URL for the published release (as an RFC 6570 URI Template).
releaseUploadUrl: https://uploads.github.com/repos/owner/repo/releases/68429422/assets{?name,label}
# Contains "true" if a new release was created.
releaseWasCreated: "true"
# The name of the published release.
releaseName: 1.0.0 Leopard Venom ๐
# The body of the published release.
releaseBody: |
This is the first _stable_ release ๐
## What's Changed ...
# The avatar URL of the GitHub user who created the tag.
taggerAvatarUrl: https://avatars.githubusercontent.com/u/100152?u=2d625417e12ad2b9cf55a3897e9a36b1bc145133&v=4
# The username of the GitHub user who created the tag.
taggerLogin: ezzatron
# The name of the tag that caused the release.
tagName: 1.0.0
# Contains "true" for any tag considered "stable".
tagIsStable: "true"
# Contains "true" for SemVer version tags.
tagIsSemVer: "true"
# The "subject" portion of the tag annotation.
tagSubject: |
1.0.0
Leopard Venom ๐
# The "body" portion of the tag annotation.
tagBody: |
This is the first
_stable_
release ๐
# The "body" portion of the tag annotation, rendered as Markdown. This
# represents the Markdown after it has been "processed", and may differ greatly
# from the original input Markdown.
tagBodyRendered: This is the first _stable_ release ๐
# The generated release notes produced by GitHub. See "Automated release notes".
generatedReleaseNotes: "## What's Changed ..."
# The ID of the release discussion.
discussionId: D_kwDOG4Ywls4APsqF
# The unique number of the release discussion.
discussionNumber: "93"
# The URL of the release discussion.
discussionUrl: https://github.com/owner/repo/discussions/93
# A JSON array of objects describing the release assets.
assets: |
[
{
"apiUrl": "https://api.github.com/repos/owner/repo/releases/assets/67328106",
"downloadUrl": "https://github.com/owner/repo/releases/download/1.0.0/file.txt",
"id": 67328106,
"nodeId": "RA_kwDOG4Ywls4EA1hq",
"name": "file.txt",
"label": "Label for file.txt",
"state": "uploaded",
"contentType": "application/json",
"size": 16,
"downloadCount": 0,
"createdAt": "2022-06-02T09:37:56Z",
"updatedAt": "2022-06-02T09:37:56Z"
},
...
]
Action outputs can be used to integrate with other steps in a workflow. Simply
add an id
to the step that uses this action, and reference the output you
need as demonstrated below:
- uses: eloquent/github-release-action@v3
id: publishRelease
- env:
RELEASE_URL: ${{ steps.publishRelease.outputs.releaseUrl }}
run: echo Released to $RELEASE_URL
The assets
output is a JSON array, and needs to be decoded before its contents
can be accessed:
Note: The assets are ordered by their
name
property.
- uses: eloquent/github-release-action@v3
id: publishRelease
- env:
DOWNLOAD_URL: ${{ fromJSON(steps.publishRelease.outputs.assets)[0].downloadUrl }}
run: echo Download the first asset from $DOWNLOAD_URL
This action works with GitHub Enterprise Server (GHES). Depending on how your enterprise is configured, you may have to work with an administrator to either:
Feature support on GitHub Enterprise Server often lags behind other versions of GitHub. This action may not work correctly if you try to use features on an enterprise that does not have support for those features.
Here are some key features that can be used by this action, and which version of GitHub Enterprise Server introduced support:
Feature | 3.1 | 3.2 | 3.3 | 3.4 | 3.5 | 3.6 |
---|---|---|---|---|---|---|
Release reactions | โ | โ | โ | โ | โ | โ |
Generated release notes | โ | โ | โ | โ | โ | โ |
Discussions | โ | โ | โ | โ | โ | โ |
Job summaries | โ | โ | โ | โ | โ | โ |
I recommend following Keep a Changelog. When it's time to release, just grab the content from the Unreleased section and paste it into your tag annotation message.
Does this action work with Semantic Release / Release Please?
Technically yes, but it's not recommended. These tools have their own solutions for publishing GitHub releases which are better suited.