Automatic semantic release
Tags: programming, github, tutorial, cicd
Published: 2023-05-22 17:43:02


## 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](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](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/](https://www.conventionalcommits.org/en/v1.0.0/)**

![Overview of conventional commits website](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/xgri7zbtbfbx1hodj81e.jpg)

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 the **`refactor`** 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](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.

![Preview of the better commits CLI tool](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/1mg7lgzmwz4usydandkb.jpg)
> 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](https://gist.github.com/stephdotnet/ae0c2cc504d40df60ddd04762d0f621c)** 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 on **`main`** (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](https://github.com/ietf-tools/semver-action)** 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](https://github.com/ncipollo/release-action)** 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`**.

![A github auto generated release](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/8752gf3rxxjv2wdn8lu1.png)
> 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 : 
  - [https://github.com/tamagui/tamagui/tree/master/.github/workflows](https://github.com/tamagui/tamagui/tree/master/.github/workflows)

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 ☕