Automatic semantic release
Stephdotnet
Posted on May 22, 2023
Goal
In this post I will describe a way to automate the process of :
- Creating a tag based on the changes you've made on your repo
- Publishing a release based on a specific event (eg: push on master)
Context
If you’re in software development, you might be familiar with semantic versioning. To summarize, it allows you to:
- Keep track of the lifecycle of your app and indicate to users what changes were made.
- Allow users of your package to safely update or upgrade newly published versions.
By following semantic versioning conventions (as indicated on https://semver.org), you’ll be able to indicate whether you are publishing a fix, a minor change, or a breaking change.
Semantic versioning is a series of numbers that looks like X.Y.Z, where each number has a meaning:
- X: Breaking change introduced
- Y: A minor change or major change with backward compatibility
- Z: A fix or a patch on your product
By following this convention, you’ll allow users to safely require your package by following version constraints of your dependency manager (such as composer or npm): https://getcomposer.org/doc/articles/versions.md#writing-version-constraints
Why Automation?
While it’s easy to choose which version tag you’ll link to a specific commit (usually on your main branch) by using git tag X.Y.Z && git push --tags
, you might be looking for a way to make this process automatic. Although automation comes with risks, and here we don’t want to mess with our version number.
To make sure that your changes are recognizable by automation, we are going to follow a convention that helps us identify what type of changes were introduced.
Conventional Commits
If you’re using a VCS to push your code, you’re familiar with the commit history. Conventional naming of your commits is a convention that we’ll follow to help us detect what sort of changes were introduced to our app.
You can check this website to have a look at what are conventional commits: https://www.conventionalcommits.org/en/v1.0.0/
What you just need to know is that by prefixing your commits (or your pull requests name, if you use squash merging), you’ll be able to automate the tag creation and the release of your changes:
-
fix: [description of the change]
will indicate a fix or a patch (as well as therefactor
prefix) -
feat: [description of the feature]
is equivalent to a minor change -
feat!: [description of the change]
will help us identify breaking changes
While conventional commits have a wider variety of syntax, this is basically what you need to know.
YAC: Yet Another Convention?!
Since following another convention might be a frustrating for you and your team, I’ll give you a tip to help you naming your commits: https://github.com/Everduin94/better-commits
This CLI tool assists you when committing your changes with a set of prompts that will write your commit message for you.
Also, if you’re using GitHub pull requests (with a squash merge strategy), you might have noticed that your pull requests are named after your first commit name. That way, nothing retains you to WIP your next commits, just make sure the first one reflects the nature of your changes.
This tool will assist you in writing you conventional commits
Let Your CI Do the Rest
If you’re familiar with GitHub Actions, you might just grab the following gist and go. It’s basically all you need to make everything work in automation.
If not, you just need to create a .github/workflows
folder at the root of your GitHub project and to create (or paste) the following GitHub action files. Everything will now be automated when pushing
Also, create your first tag of main manually if you want the automation to work:
git checkout origin/main && git tag 0.1.0 && git push --tags
Check out this gist for the full code.
name: Semantic Release
on:
push:
branches:
- main
concurrency:
group: ${{ github.ref }}
cancel-in-progress: true
jobs:
release:
permissions:
contents: write # to create a release (ncipollo/release-action)
runs-on: ubuntu-latest
steps:
- name: Checkout Code
uses: actions/checkout@v2
- name: Get Next Version
id: semver
uses: ietf-tools/semver-action@v1
with:
token: ${{ secrets.GITHUB_TOKEN }}
branch: main
noVersionBumpBehavior: 'silent'
- name: Create Release
if: steps.semver.outputs.nextStrict != ''
uses: ncipollo/release-action@v1.12.0
with:
allowUpdates: true
draft: false
makeLatest: true
tag: ${{ steps.semver.outputs.nextStrict }}
name: ${{ steps.semver.outputs.nextStrict }}
generateReleaseNotes: true
token: ${{ secrets.GITHUB_TOKEN }}
Unlike some CI workflows, this one is easy to read:
-
on
: Defines the conditions that will run this workflow. Here, we want to trigger this workflow whenever we push code onmain
(eg: merging a pull request) -
concurrency
: It indicates that we only want one workflow at the time. This can be removed if you have multiple teams working on the same repository with continuous delivery. -
jobs
: Is where we define the actions. -
checkout
: Is just how GitHub retrieves the code for your action. -
get next version
: This GitHub action checks your commit names since the previous tag and creates the new tag based on the changes described by your conventional commits. Check the repository if you would like to learn more about it. This action is flexible and will allow you to customize the behavior of the version detection. -
create release
: This will create a release automatically after tag creation. You can have a look at the repository to know more about this action and the configurations that you can tweak.
Create a Release automatically
GitHub releases come with a "generate release notes" feature that will create a summary based on the pull requests merged since the creation of the tag. It can be interesting to have a summary of what's being shipped and to act as a changelog.
I think that something interesting in that action is that you can generate this release summary thanks to this option generateReleaseNotes: true
.
Github automatic releases are neat
Diving deeper
Although this workflow suits my needs, and fits well for a small project, you might need to dig for more examples. There is one that you might like from Tamagui.dev :
The actions named changelog.yml
and changelog-prerelease.yml
you might find interesting ways to generate and update a release by using a latest
tag
Conclusion
Using github actions to automate your processes is something that is affordable, free and easy if you start with something like automatic releases. I hope you learned something and that you'll enjoy looking at your automatic semantic versionning while having a cup of tea.
Cheers ☕
Posted on May 22, 2023
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.