Automating Versioning and Releases Using Semantic Release

Moving away from a manual (semi-automated), repetitive, error-prone process.

Yasser Shaikh
Agoda Engineering & Design

--

Over the last few years with Agoda, I have been working with the SEO team of our Frontend Department. The SEO team is responsible for building APIs & libraries that are used by other teams and many small to medium sized services that talk to each other.

With so many existing projects and new ones coming up all the time, we ended up having multiple ways of versioning and releasing them. Eventually more than often we found ourselves unable to answer the following questions

  • Did we pick the right version number for our new release?
  • Did we git tag correctly? Hopefully, using the same version number.
  • Did we write a correct changelog? and so on…

It had been a manual (semi-automated), repetitive, error-prone process that one had to do every time there is a release in any of these projects and libraries maintained by us.

So last year we started looking into unifying and automating our release process and stumbled upon Semantic Release.

We had a checklist, that we wanted to automate and Semantic Release seemed to fit the bill. Our checklist —

  • Calculate the new version number based on the commit messages
  • Create git tags for every release
  • Publish a release to GitHub releases & NuGet (package manager for .NET)
  • Create release notes & changelogs automatically
  • Notify maintainers and users of new releases

In this post, I would like to discuss our experiences with trying out Semantic Release and how it has helped us better manage our versioning and release process.

Semantic Versioning & Conventional Commits

Now before we jump into Semantic Release, there are 2 important things to know

  1. Semantic Versioning
  2. Conventional Commits

Let’s briefly understand what these two are here,

Semantic Versioning

Semantic Versioning (also called SemVer) is a specification, a simple set of rules that dictates how version numbers of your projects are assigned and incremented.

As per this specification, any version number should be complying with this structure — MAJOR.MINOR.PATCH where,

  1. MAJOR version is incremented when you make any breaking change
  2. MINOR version is incremented when you add a new feature/functionality
  3. PATCH version is incremented when you make bug fixes

To sum up what Semantic versioning does in one line-

Conventional Commits

Conventional commit is a specification, a set of rules that have to be followed when writing commit messages. It is heavily inspired by Angular commit guidelines, and follows this structure —

Only type and description are mandatory, rest everything is optional. The value of type is important and needs to be only one of the following —

  • feat: A new feature
  • fix: A bug fix
  • docs: Documentation only changes
  • style: Changes that do not affect the meaning of the code (white-space, formatting, missing semi-colons, etc)
  • refactor: A code change that neither fixes a bug nor adds a feature
  • perf: A code change that improves performance
  • test: Adding missing or correcting existing tests
  • chore: Changes to the build process or auxiliary tools and libraries such as documentation generation

Below are some sample commits made using this specification —

feat(logging): added logs for failed signups
fix(homepage): fixed image gallery
test(homepage): updated tests
docs(readme): added new logging table information

There are tools like commitlint and commitizen to help enforce these conventions in your projects. If you are interested, I have written a more in-depth post about it here — Automate and enforce conventional commits for .NET based projects

Semantic Release

Now that we know what SemVer & Conventional commits are, I am sure most of you already have got a hint of what semantic-release does with these two —

semantic-release uses the commit messages to determine the type of changes in the codebase. Following formalized conventions for commit messages, semantic-release automatically determines the next semantic version number, generates a changelog and publishes the release.

Below, in yellow are different commit message, pay close attention to the type and body of the commit messages and based on them either MAJOR/MINOR/PATCH version is incremented.

  • If the body contains the text “BREAKING CHANGE” then MAJOR version is incremented.
  • If the type contains feat/feature, then MINOR version is incremented.
  • If the type contains fix, then PATCH version is incremented.
  • And finally, if the type contains refactor/style/perf/doc/test/chore, then nothing is increment and no release is made.

You can find more details about semantic release here — https://github.com/semantic-release/semantic-release

Enough with the theory now, next, we’ll dive in to see of all this in “action” 😄.

Configuring Semantic Release with GitHub actions.

Configuring semantic-release with GitHub actions is pretty straightforward, I am using cycjimmy/semantic-release-action@v2 action which supports a wide variety of optional features via its plugins — git, exec, and changelog.

publish:
needs: [ build ]
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v1
- name: Semantic Release
uses: cycjimmy/semantic-release-action@v2
with:
extra_plugins: |
@semantic-release/git
@semantic-release/exec
@semantic-release/changelog

The semantic-release/exec plugin allows us to execute any custom shell commands at the different stages of semantic release i.e prepare ,publish , notify etc.

module.exports = {
branches: ['main'],
"verifyConditions": ["@semantic-release/github"],
"prepare": // INSERT-CUSTOM-COMMANDS-HERE-TO-RUN-ON-PREPARE,
"publish": // INSERT-CUSTOM-COMMANDS-HERE-TO-RUN-ON-PUBLISH,
"notify": // INSERT-CUSTOM-COMMANDS-HERE-TO-RUN-ON-NOTIFY
}

Here is where I use we use the familiar dotnet build, pack, and push commands to push new releases to NuGet (package manager for .NET), the generated version number and other release details are made available and easy to use under ${nextRelease.xxx}. This section can be used for doing so much more, the possibilities are limitless here.

Revisiting our checklist

Now that we have managed to set up semantic release, we can now go back to our original checklist and see how we fared —

✅ Calculate the new version number based on the commit messages

✅ Create git tags for every release

✅ Publish a release to GitHub releases & NuGet (package manager for .NET)

✅ Create release notes & changelogs automatically

✅ Notify maintainers and users of new releases

Summary

We have been using Semantic Release for over a year now and actively adopting the same for our other projects. With few easy steps, we can automate the entire release process and focus more on the things that matter i.e code.

I would strongly recommend giving semantic-release a try, there are loads of both official and community-built plugins, my personal favorite is this one — semantic-release-slack-bot which sends a slack message whenever a new release is made. Now, how cool is that! 😎

References

Big thanks to Agodans, who helped review this article: Vlad Batushkov, Olivier Grenado, Max Panasenkov, Akshesh Doshi, and Jonah Periera

Join the team

careersatagoda.com

--

--

Yasser Shaikh
Agoda Engineering & Design

Lead @ Agoda.com — Full stack Engineering. Gamer, Footballer, Bollywood Buff, Software Engineer, and @stackoverflow contributor. Mumbaikar.