Automating Ruby Gem Package Releases With Github Actions
In the final part of the Ruby Gem series, we will be automating the deployment of our recently created gem using GitHub Actions.
On this page
In the series so far, we have created a gem to test locally before pushing to the remote RubyGems repository.
This time, we will be setting up an automated configuration in order to automate the process of release based on conventional commits.
It will work off the code written in the previous part "Deploying Your Gem To RubyGems".
Source code can be found here.
Prerequisites
- Understanding how Conventional Commits works. Not much will be covered.
- Catch up with the previous posts on Series: Releasing your own Ruby Gem.
Getting started
We will clone the project rspec-github-actions (if you haven't been keeping up with the previous work):
$ git clone https://github.com/okeeffed/rspec-github-actions.git
$ cd rspec-github-actions
# Checkout the starting point
$ git checkout 3-gem-deployed
At this stage, our project is now ready to continue working on.
Updating our GitHub Action for release
This post will really be a recap of the great work done on the blog post "Automating Ruby Gem Releases with GitHub Actions" at andrewm.codes.
Since that was such a straight-forward post, this will be more on the adjustments made to my own project to also automate the Ruby Gem release.
The method we are going to take makes use of Google's GitHub Action release-please-action. This action takes your commit history to help automate the package semantic version.
If you are not familiar with conventional commits, the quick summary gives a good overview of how it works.
Essentially, you prefix your git commits with a specified "type" and "optional scope" <type>[optional scope]: <description> and based on the type, it will have a different weight for the impact on the versioning.
The types fix and feat are associated with PATCH and MINOR updates respectively, while a BREAKING CHANGE or bang following the type <type>! will result in a major change.
If you look at the history of the logs I have committed for this project, you will see the following (using git log --pretty=oneline):
$ git log --pretty=oneline
0e0977aa (HEAD -> main, origin/main, origin/3-gem-deployed, 3-gem-deployed) feat: add the example folder
012bdeb7 (origin/2-gemspec, 2-gemspec) feat: add in gemspec file
3e0f797b (origin/1-rspec-with-github-actions, 1-rspec-with-github-actions) fix: README
d5fd59c2 fix: remove lcov
f639002d docs: add README
016d5ae7 feat: first attempt at post
Luckily, this bodes well for our GitHub Action.
Updating our GitHub Workflows
Let's make some adjustments to our .github/workflows/rspec.yml workflow:
name: Run RSpec tests
on:
push:
branches:
- "**" # matches every branch
- "!main" # excludes main
jobs:
run-rspec-tests:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Set up Ruby
uses: ruby/setup-ruby@v1
with:
# Not needed with a .ruby-version file
ruby-version: 2.7
# runs 'bundle install' and caches installed gems automatically
bundler-cache: true
- name: Run tests
run: |
bundle exec rspec
This will not run the RSpec tests action on the main branch, which we will set up a new action for.
Next, let's create a new workflow. Create a new file with touch .github/workflows/release.yml and then add the following code:
name: Release
on:
push:
branches:
- "main" # main only
jobs:
release:
runs-on: ubuntu-latest
steps:
- uses: GoogleCloudPlatform/release-please-action@v2
id: release
with:
release-type: ruby
package-name: contrived_math
bump-minor-pre-major: true
version-file: "lib/release/contrived_math/version.rb"
# Checkout code if release was created
- uses: actions/checkout@v2
if: ${{ steps.release.outputs.release_created }}
# Setup ruby if a release was created
- uses: ruby/setup-ruby@v1
with:
# Not needed with a .ruby-version file
ruby-version: 2.7
# runs 'bundle install' and caches installed gems automatically
bundler-cache: true
if: ${{ steps.release.outputs.release_created }}
- name: Run tests
run: |
bundle exec rspec
if: ${{ steps.release.outputs.release_created }}
# Publish
- name: publish gem
run: |
mkdir -p $HOME/.gem
touch $HOME/.gem/credentials
chmod 0600 $HOME/.gem/credentials
printf -- "---\n:rubygems_api_key: ${GEM_HOST_API_KEY}\n" > $HOME/.gem/credentials
gem build *.gemspec
gem push *.gem
env:
# Make sure to update the secret name
# if yours isn't named RUBYGEMS_AUTH_TOKEN
GEM_HOST_API_KEY: "${{secrets.RUBYGEMS_AUTH_TOKEN}}"
if: ${{ steps.release.outputs.release_created }}
This new action will run on main and will release our Ruby Gem if both testing and the release-action-please succeed.
This example is contrived and will only run things on the main branch, but adjust this for the release workflow you actually want in your own work.
Note: you will need to create a new RubyGems API key and add that to the repo as the secret named
RUBYGEMS_AUTH_TOKEN.
Configuring our version for our gemspec file
Update contrived_math.gemspec to the following:
require_relative 'lib/release/contrived_math/version'
Gem::Specification.new do |s|
s.name = 'contrived_math'
s.version = ContrivedMath::VERSION
s.summary = 'Hello, World!'
s.description = 'A simple hello world gem'
s.authors = ["Dennis O'Keeffe"]
s.email = 'hello@dennisokeeffe.com'
s.files = ['lib/contrived_math.rb']
s.homepage =
'https://rubygems.org/gems/contrived_math'
s.license = 'MIT'
end
Now we will grab the version from the file lib/release/contrived_math/version.rb.
Let's make that file and add in some content now:
module ContrivedMath
VERSION = '0.0.0'.freeze
end
The file content comes from Google's example.
The GitHub Action at work
Assuming you already set up the secret for the RubyGems, we can push main and see the action at work.

The first time we run the action, it will note that it needs to create a release PR for us. We can see that in the action logs.

This release will create some release notes and a PR. The notes will followed the conventional commit types.


Next, we can approve and merge the PR.

Once merged, the bot will link the release notes for us.

The merge of the pull request will kick off another invocation of the release action. This time, the action will release the gem.
Note: The 0.1.0 release failed because I was a goose, so the following two images come from the 0.1.1 release which followed all the same previous steps after I implemented the fix.

Finally, a successful release will also show up on our live Ruby Gem.

Success! The release schedule has been automated through GitHub Actions.
Summary
Today's post demonstrated how to take our Ruby Gem repo and automate the release process based on changes noted in git history using Conventional Commits.
It is worth noting that these flows outlined today may not be appropriate to your own needs (I am only running release on the main branch will generally is not what you will want since main would be protected), but take the workflows from today and apply the changes for your own work as required.
Resources and further reading
- Automating Ruby Gem Releases with GitHub Actions - andrewm.codes
- Conventional Commits
- Source code
- Series: Releasing your own Ruby Gem
Photo credit: pawel_czerwinski