Creating releases with GitHub Actions

In October 2021, GitHub introduced a feature that enables maintainers to generate release notes for a release with the click of a button.

By default, the automatically generated release notes contain

  • a list of pull requests maintainers have merged since the last release
  • a list of first-time contributors to that release
  • a link to the diff, comparing the changes between this and the previous release

Additionally, you can configure filters and groups to fine-tune the content of these release notes, depending on the authors and labels attached to the pull requests.

While these automatically generated release notes may not be perfect, they can be a good companion for a curated, hand-crafted CHANGELOG.md as proposed by the keepachangelog form.

But - why bother clicking buttons when you could automatically create a release with release notes any time you push a tag to GitHub? In addition to the user interface, GitHub allows maintainers to create a release via their API. In combination with actions/github-script, you can quickly implement a GitHub Actions workflow that will create a release with generated release notes every time you push a tag!

Release workflow

Here is an example of a release workflow, placed in .github/workflows/release.yaml in the root of your repository:

# https://docs.github.com/en/actions

name: "Release"

on: # yamllint disable-line rule:truthy
  push:
    tags:
      - "**"

jobs:
  release:
    name: "Release"

    runs-on: "ubuntu-latest"

    steps:
      - name: "Determine tag"
        run: "echo \"RELEASE_TAG=${GITHUB_REF#refs/tags/}\" >> $GITHUB_ENV"

      - name: "Create release"
        uses: "actions/github-script@v6"
        with:
          github-token: "${{ secrets.GITHUB_TOKEN }}"
          script: |
            try {
              const response = await github.rest.repos.createRelease({
                draft: false,
                generate_release_notes: true,
                name: process.env.RELEASE_TAG,
                owner: context.repo.owner,
                prerelease: false,
                repo: context.repo.repo,
                tag_name: process.env.RELEASE_TAG,
              });

              core.exportVariable('RELEASE_ID', response.data.id);
              core.exportVariable('RELEASE_UPLOAD_URL', response.data.upload_url);
            } catch (error) {
              core.setFailed(error.message);
            }

With a release workflow in place, run

git tag -s x.y.z

followed by

git push --tags

to create a release with automatically generated release notes from the terminal.

Composite action

Since I have started to use this workflow in all of my open-source repositories on GitHub, I have extracted this workflow into a composite action in ergebnis/.github for easier reuse.

Here is what the composite action looks like:

# https://docs.github.com/en/actions/creating-actions/creating-a-composite-action
# https://docs.github.com/en/actions/creating-actions/metadata-syntax-for-github-actions#inputs
# https://docs.github.com/en/actions/creating-actions/metadata-syntax-for-github-actions#runs-for-composite-run-steps-actions
# https://docs.github.com/en/rest/reference/releases#create-a-release
# https://docs.github.com/en/developers/webhooks-and-events/webhooks/webhook-events-and-payloads#push

name: "Create a release"

description: "Creates a release"

inputs:
  github-token:
    description: "GitHub token of a user with permission to create a release"
    required: true

runs:
  using: "composite"

  steps:
    - name: "Determine tag"
      if: "${{ github.event_name }} == 'push' && ${{ github.ref_type }} == 'tag'"
      run: "echo \"RELEASE_TAG=${GITHUB_REF#refs/tags/}\" >> $GITHUB_ENV"
      shell: "bash"

    - name: "Create release"
      uses: "actions/github-script@v6.3.3"
      with:
        github-token: "${{ inputs.github-token }}"
        script: |
          if (!process.env.RELEASE_TAG) {
            core.setFailed("The environment variable RELEASE_TAG is not defined.")

            return;
          }

          try {
            const response = await github.rest.repos.createRelease({
              draft: false,
              generate_release_notes: true,
              name: process.env.RELEASE_TAG,
              owner: context.repo.owner,
              prerelease: false,
              repo: context.repo.repo,
              tag_name: process.env.RELEASE_TAG,
            });

            core.exportVariable('RELEASE_ID', response.data.id);
            core.exportVariable('RELEASE_UPLOAD_URL', response.data.upload_url);
          } catch (error) {
            core.setFailed(error.message);
          }

Release workflow using composite action

Thanks to the composite action above, a workflow for creating a release on GitHub with automatically generated release notes is as simple as this:

# https://docs.github.com/en/actions

name: "Release"

on: # yamllint disable-line rule:truthy
  push:
    tags:
      - "**"

jobs:
  release:
    name: "Release"

    runs-on: "ubuntu-latest"

    steps:
      - name: "Create release"
        uses: "ergebnis/.github/actions/github/release/create@1.8.0"
        with:
          github-token: "${{ secrets.GITHUB_TOKEN }}"

You can see an example of a release with release notes created by this workflow in ergebnis/data-provider:1.3.0.

Project on GitHub

ergebnis/.github

Integrate Workflow

❤️ Provides default community health files and composite actions for the @ergebnis organization.

Find out more at ergebnis/.github.

Do you find this article helpful?

Do you have feedback?

Do you need help with your PHP project?