Release Please for Ruby | GitHub Actions
Tags: tutorial, github, webdev, ci
Published: 2021-12-14 08:32:53

## release-please
[release-please](https://github.com/googleapis/release-please) is a project maintained by the [GoogleApi's team](https://github.com/googleapis) which automatically keeps a coherent changelog of your application as it grows as well as deploying your project with a new version tag.

## Why Use `release-please`
* Automatically generating CHANGELOGs.
* Automatically determining a semantic version bump (based on the types of commits landed).
* Communicating the nature of changes to teammates, the public, and other stakeholders.
* Triggering build and publish processes.
* Making it easier for people to contribute to your projects, by allowing them to explore a more structured commit history.

> Source: [conventionalcommits.org](https://www.conventionalcommits.org/en/v1.0.0/#why-use-conventional-commits)

This project not only keeps your changelog up to date by parsing your git commit history, but ensures that updates follow a common commit message convention.

**release-please** abides to the [Conventional Commit Messages](https://www.conventionalcommits.org/en/v1.0.0/), which allows commits to be read by human and machine in a cohesive fashion.

## Conventional Commits
> Source: [conventionalcommits.org](https://www.conventionalcommits.org/en/v1.0.0/#specification)

This aims to create a convention for committing to repositories, some of the most important keywords are:

### `fix:`

> A commit of the type fix patches a bug in your codebase (this correlates with PATCH in Semantic Versioning).

```Bash
$ git commit -m "fix(<category>): my new fix"
```

### `feat:`

> A commit of the type feat introduces a new feature to the codebase (this correlates with MINOR in Semantic Versioning).

```Bash
$ git commit -m "feat(<category>): my new feature"
```

### BREAKING CHANGE
> A commit that has a footer BREAKING CHANGE:, or appends a ! after the type/scope, introduces a breaking API change (correlating with MAJOR in Semantic Versioning). A BREAKING CHANGE can be part of commits of any type.

```BASH
# Breaking Fix
$ git commit -m "fix!(<category>): my breaking fix"

# Breaking Feat
$ git commit -m "feat!(<category>): my breaking feature"
```

### Extra
Types other than `fix:` and `feat:` are allowed, for example (based on the the [Angular Convention](https://github.com/conventional-changelog/commitlint/tree/master/%40commitlint/config-conventional)) recommends:
* `build:`
* `chore:`
* `ci:`
* `docs:`
* `style:`
* `refactor:`
* `perf:`
* `test:`
* and [more](https://github.com/conventional-changelog/commitlint/tree/master/%40commitlint).

## GitHub Action Setup

### Creating the Workflow
To get started using `release-please`, we must create a new `.yml` file in our `.github/workflows` directory of our repository.

We can call this file `release.yml`.

After creating our file, we can go ahead and name our new workflow.
```YAML
# /.github/workflows/release.yml

# The name of the Workflow.
name: release-please
```

### Choosing When to Run the Action
Since this project handles version releases and changelog updates, we only want this to run when pushes are made to our main or 'production' branch.

This ensures that this does not run on subsequent commits/pull requests to our repository.

The setup would look something like:

```YAML
# /.github/workflows/release.yml

name: release-please

# When to run this action.
on:
  # Run on push to our 'main' branch.
  push:
    branches: [ main ]
```

### Setting Up `release-please-action`
Now that we have our workflow set up to run on pushes to our main branch, we can configure **release-please** to our liking.

**release-please** requires a [personal access token](https://docs.github.com/en/authentication/keeping-your-account-and-data-secure/creating-a-personal-access-token) to be able to create pull-requests and modify the changelog in your repository.

Once you have generated your personal access token, [add it as a repository secret](https://docs.github.com/en/codespaces/managing-codespaces-for-your-organization/managing-encrypted-secrets-for-your-repository-and-organization-for-codespaces).

The secret can be named whatever you like, although must meet [a few criteria](https://docs.github.com/en/codespaces/managing-codespaces-for-your-organization/managing-encrypted-secrets-for-your-repository-and-organization-for-codespaces#naming-secrets).

For this example, lets call this secret: `GH_TOKEN`.

The default configuration would look something like:
```YAML
# /.github/workflows/release.yml

name: release-please

on:
  push:
    branches: [ master ]

jobs:
  release-please:
    runs-on: ubuntu-latest
    env:
      # The name of your gem/package.
      GEM_NAME: "GEM NAME HERE"

    steps:
      # Use release-please-action.
      - uses: GoogleCloudPlatform/release-please-action@v2

        # Assign an 'id' to this action
        id: release
        with:
          # Personal Access Token
          token: ${{ secrets.GH_TOKEN }}

          # Use release-please/strategies/ruby.ts Release Type
          release-type: ruby
          
          # The name of the gem.
          package-name: ${{ env.GEM_NAME }}
          
          # Location of the version.rb file to bump for new releases
          version-file: "lib/${{ env.GEM_NAME }}/version.rb"
```

### Checkout and Publish
If a release is found, we want to:

1. Checkout the repository.
2. Build the ruby gem.
3. Publish the gem to [rubygems.org](https://rubygems.org).

#### Checkout Repository
To checkout our repository, we can simply use the `actions/checkout` GitHub action.

```YAML
# /.github/workflows/release.yml

name: release-please

on:
  push:
    branches: [ master ]

jobs:
  release-please:
    runs-on: ubuntu-latest
    env:
      GEM_NAME: "GEM NAME HERE"
    steps:
      - uses: GoogleCloudPlatform/release-please-action@v2
        id: release
        with:
          token: ${{ secrets.GH_TOKEN }}
          release-type: ruby
          package-name: ${{ env.GEM_NAME }}
          version-file: "lib/${{ env.GEM_NAME }}/version.rb"

      # Checkout the Repository if a release has been created.  
      - uses: actions/checkout@v2
        if: ${{ steps.release.outputs.release_created }}
```

#### Setting Up Ruby
To be able to build our ruby gem, we first must install ruby onto our machine. We can achieve this by using the `ruby/setup-ruby` GitHub Action.

```YAML
# /.github/workflows/release.yml

name: release-please

on:
  push:
    branches: [ master ]

jobs:
  release-please:
    runs-on: ubuntu-latest
    env:
      GEM_NAME: "GEM NAME HERE"
    steps:
      - uses: GoogleCloudPlatform/release-please-action@v2
        id: release
        with:
          token: ${{ secrets.GH_TOKEN }}
          release-type: ruby
          package-name: ${{ env.GEM_NAME }}
          version-file: "lib/${{ env.GEM_NAME }}/version.rb"
        env:
          GEM_NAME: "GEM NAME HERE"
      - uses: actions/checkout@v2
        if: ${{ steps.release.outputs.release_created }}

      # Set up Ruby if a release can be created.
      - uses: ruby/setup-ruby@v1
        with:
          # Latest ruby version (remove to use .ruby-version).
          ruby-version: 3.0
          
        if: ${{ steps.release.outputs.release_created }}
```

#### Publish Gem
First to be able to push a new version, we must allow access via [rubygems.org](https://rubygems.org).

To create a new API key, head to [rubygems.org](https://rubygems.org/profile/api_keys). 

Next, create a new repository secret. 

> This can be named anything, although must be referenced correctly within the workflow file.

For this example, we will call this secret: `RUBYGEMS_API_KEY`

To publish our gem, we have a couple of tasks to complete.

Since our action will run in a new container every time it is called, we must create a `~/.gem/credentials` file containing an API Key from the account we would like to post on for [rubygems.org](https://rubygems.org).

> Manual installation of Bundler is required. Since we are releasing our gem from the repository it must be built and have packages updated on push. The default 'bundler-cache' will break this.

1. Install the bundler gem.
2. Unset the `BUNDLE_DEPLOYMENT` variable.
3. Run `bundle install`
3. Create `~/.gem/credentials`.
4. Add our `RUBYGEMS_API_KEY`.
5. Build our gem from the `*.gemspec` file.
6. Push the `*.gem` to the remote server.

We can simply run these commands from within our workflow:

```BASH
$ gem install bundler
$ bundle config unset deployment
$ bundle install
$ mkdir -p $HOME/.gem
$ touch $HOME/.gem/credentials
$ chmod 0600 $HOME/.gem/credentials
$ printf -- "---\n:rubygems_api_key: ${RUBYGEMS_API_KEY}\n" > $HOME/.gem/credentials
$ gem build *.gemspec
$ gem push *.gem
```

The end result would look like:

```YAML
# /.github/workflows/release.yml

name: release-please

on:
  push:
    branches: [ master ]

jobs:
  release-please:
    runs-on: ubuntu-latest
    env:
      GEM_NAME: "GEM NAME HERE"
      
      # Our Ruby Gems API key
      RUBYGEMS_API_KEY: ${{ secrets.RUBYGEMS_API_KEY }}

    steps:
      - uses: GoogleCloudPlatform/release-please-action@v2
        id: release
        with:
          token: ${{ secrets.GH_TOKEN }}
          release-type: ruby
          package-name: ${{ env.GEM_NAME }}
          version-file: "lib/${{ env.GEM_NAME }}/version.rb"
      - uses: actions/checkout@v2
        if: ${{ steps.release.outputs.release_created }}
      - uses: ruby/setup-ruby@v1
        with:
          ruby-version: 3.0
        if: ${{ steps.release.outputs.release_created }}
      
      # Release the gem to https://rubygems.org if a release has been created.
      - name: Release Gem
        run: |
          gem install bundler
          bundle config unset deployment
          bundle install
          mkdir -p $HOME/.gem
          touch $HOME/.gem/credentials
          chmod 0600 $HOME/.gem/credentials
          printf -- "---\n:rubygems_api_key: ${RUBYGEMS_API_KEY}\n" > $HOME/.gem/credentials
          gem build *.gemspec
          gem push *.gem
        if: ${{ steps.release.outputs.release_created }}
```

## Creating Your First Release
**release-please** will create a new pull-request automatically bumping the version of your project.

This will parse your projects git commit history to group together changes in a comprehensive fashion.

### Adding Some Changes
Some keywords like `ci:` will not be build a new release, as these do not change your application.

Only keywords such as: `fix:`, `feat:`, or any `<type>!:` (Breaking Change), will trigger a new release to be created.

> Changes will be grouped together, thus adding multiple commits of `fix:`, `feat:`, etc would be grouped into the same version release until the release pull request is merged into the main branch.

To generate a new changelog whenever we can force run our **release-please** action:
1. Navigate to your repository on GitHub.
2. Click on the **Actions** tab.
3. Select: **All Workflows** > **release-please** > **Run workflow**.

To generate a new release and changelog, we could:
* Add a new empty commit with a release tag.
```BASH
$ git commit --allow-empty -m "chore: release 1.0.0" -m "Release-As: 1.0.0" 
```
OR

* Start adding commits using *conventional commit messages*.
```BASH
$ git add .
$ git commit -m "feat: my new feature"
$ git push
```

Every time `feat:` or `fix:` is in the commit message, **release-please** will re-parse your git commit history, create a new pull-request with the bumped version and an up-to-date changelog.

## Conclusion
**release-please** adds convention to GitHub commits to allow changelogs and releases to be created without someone having to manually do so, as well as maintaining a specific committing convention.

Our final `release.yml` file may look like:
{% gist https://gist.github.com/aj-rom/7bda1f1c792e46658468f9e4b9056215 %}