[ JagoPG Site ]
This publication is more than a year old. The information can be out-dated.

Software SemVer with GitHub Actions

I have thought on different ways of versioning the applications I own, but I reached to some points that I was not really happy with the usual solutions I found online.

I manage different type of applications, some are only an API which is a standalone application. But with others like a web application I have the server side and the front side, which I work as different project but in the same repository, because one depends strongly on the other - talking about small projects, for medium-huge projects it's more easy to treat as different projects.

I also like that a new generated version won't potentially have any bug - you cannot assure this with unit testing - And to execute integration testing in big environments, you need to deploy first to a staging environment.

So, with those considerations I found problems when the version depends on a file inside the project, or part of a file. In both cases you need:

  1. To update before merging: in the case the integration tests fails, you will have to skip versions, or delete artifacts that already are stored in other repositories.

  2. To commit the new version after merging: this causes that you have to make two commits, one with the merge and another one to update the file version.

  • To test on a integration environment before merging: if you work generate deployable artifacts with key names as SNAPSHOTor beta, alpha... this could be a good solution, but if you are working in a team that several versions are rolled out during the day can cause problems if you don't sync.

Normally, I work with one Git repository per deployed artifact. So, when having several related closely related projects, I use a monorepo strategy to gather there projects. But each reapository will have its own version. So I found that I am more confortable managing the versions with the Git tag command.

Take into account that:

  • You need to annotate when tagging a new version: done with git tag <NAME> -m <COMMENT>
  • I am using only feature branches: so I make new branches with one new feature or patch, and then merge in the main branch.

I wrote a GitHub Action that solves the versioning problem, but take into account that if you work differently you may have to write you own, or search among the existing ones. Most of the actions that generate a new version expect that you work with a develop branch.

The script expects that in the merging commit the first word matching some keywords (breaking, feature, patch...) indicates the script which version I want to generate:

name: Tag version

on:
    workflow_call:

jobs:
    tag:
        runs-on: ubuntu-latest
        steps:
            - name: Checkout repository
              uses: actions/checkout@v3
              with:
                  ref: '${{ inputs.ref }}'
            - name: Set up GitHub Credentials
              run: git config --global user.email "robot@stubhubinternational.com" && git config --global user.name "CI/CD"

            - name: Get latest annotated tag
              id: last-tag
              run: git fetch --unshallow && echo "LAST_TAG=$(git describe --tags $(git rev-list --tags --max-count=1))" >> $GITHUB_OUTPUT

            - name: Calculate next tag -> parse change
              id: parse-change
              run: echo "CHANGE=$(PAGER="" git log HEAD~1..HEAD | grep -oE "(patch|feature|major)" | head -n1)" >> $GITHUB_OUTPUT

            - name: Generate next version
              id: next-version
              uses: 'WyriHaximus/github-action-next-semvers@v1'
              with:
                  version: ${{ steps.last-tag.outputs.LAST_TAG }}

            - name: Create tag and push
              shell: bash
              run: |
                  if [ "${{ steps.parse-change.outputs.CHANGE }}" == "major" ] || [ "${{ steps.parse-change.outputs.CHANGE }}" == "breaking" ]; then
                    VERSION=${{ steps.next-version.outputs.major }}
                  elif [ "${{ steps.parse-change.outputs.CHANGE }}" == "minor" ] || [ "${{ steps.parse-change.outputs.CHANGE }}" == "feature" ]; then
                    VERSION=${{ steps.next-version.outputs.minor }}
                  else
                    VERSION=${{ steps.next-version.outputs.patch }}
                  fi
                  git tag $VERSION -m "Version $VERSION" && git push origin $VERSION