BLOG
ARTICLE

Version Management with Github Action

5 min read

Where I work, we were faced with a problem on how to automate our development workflow particularly in managing release builds for a small project that is meant to be published as a Chrome extension.

We have never tried to automate project versioning before so I thought it would be a nice challenge to try.

The Challenge

Here are the requirements for our project:

  1. We want to follow semantic versioning standard for Chrome manifest
  2. We need to find a way to automate project versioning in Github
  3. And while we're at it, create an elegant changelog for each version

When I researched this topic I stumbled across this very helpful article by Pusher. It's so good I ended up using their Github action as the base for the solution.

The Solution

TLDR - if you want to see the solution straight away, you can find it here.

Otherwise, I'd like to use this simple diagram to illustrate the solution step by step.

Tap to enlarge this image 👇
01.png

1. Manage pull request with labels

Triggering the execution of Github action via labeling is brilliant. It's something that I have not thought of before, and in our case, it fits the needs perfectly.

1
2
3
4
5
6
7
8
9
10
    # Associate each release label with semver
    - name: Set major release
      if: contains(github.event.pull_request.labels.*.name, 'release-major')
      run: echo "RELEASE=major" >> $GITHUB_ENV
    - name: Set minor release
      if: contains(github.event.pull_request.labels.*.name, 'release-minor')
      run: echo "RELEASE=minor" >> $GITHUB_ENV
    - name: Set patch release
      if: contains(github.event.pull_request.labels.*.name, 'release-patch')
      run: echo "RELEASE=patch" >> $GITHUB_ENV

The downside:

  • If no label is set, it's quite cumbersome to create another pull request
  • This will be explained later, but to create an orderly changelog, specific pull request rules are needed

2. Handle the versioning

To increase versioning for each pull request, we use a version file called __version to store the current application version. Then we fetch the version from that file and apply semver-tool to 'bump' the version.

1
2
3
4
5
6
7
8
9
10
    # Get CURRENT from __version file then increment new version
    - name: Bump version
      run: |
        export CURRENT=$(head -1 __version | xargs  )
        export NEW_VERSION=$(semver bump ${{ env.RELEASE }} $CURRENT)
        echo "VERSION=$NEW_VERSION" >> $GITHUB_ENV
    # Update __version
    - name: Prepare __version
      run: |
        echo "${{ env.VERSION }}" > "__version"

3. Update changelog

This step is not a must, but personally, I would like to see a neat changelog like React for our project if possible.

This is where strict pull requests rules are needed. The pull request to merge into master branch should have its own format so it can be used in changelog, or else the changelog will not be pretty.

I wish there is a way to automate multiple pull request templates for pull request like issue, but currently, it's not supported.

1
2
3
4
5
6
7
8
9
10
    # Update CHANGELOG with pull request contents
    - name: Prepare CHANGELOG
      run: |
        echo "${{ github.event.pull_request.body }}" | csplit -s - "/##/"
        echo "# Changelog
        ## ${{ env.VERSION }}
        " >> CHANGELOG.tmp
        grep "-" xx01 >> CHANGELOG.tmp
        grep -v "^# " CHANGELOG.md >> CHANGELOG.tmp
        cp CHANGELOG.tmp CHANGELOG.md

4. Create artifacts for subsequent job

Since we'll be using two jobs (explained later in step 6), we want the second job to be able to access information from the first job.

There is no way to share this information using environment variables since each job runner typically runs on a different server, so upload artifacts come to the rescue.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
    # Upload artifacts to be used in 'build'
    - run: echo "🥇 Update version is finished with artifacts."
    - uses: actions/upload-artifact@v2
      with:
        name: version_file
        path: __version
    - uses: actions/upload-artifact@v2
      with:
        name: changelog_file
        path: CHANGELOG.md
    - uses: actions/upload-artifact@v2
      with:
        name: manifest_file
        path: public/manifest.json

5. Retrieve artifacts

Continuation from step 4. Here we simply download the artifacts and store the information to be used later.

1
2
3
4
5
6
7
8
9
10
11
    # Download artifacts created in 'update-version'
    - uses: actions/download-artifact@v2
      with:
        name: version_file
    - uses: actions/download-artifact@v2
      with:
        name: changelog_file
    - uses: actions/download-artifact@v2
      with:
          name: manifest_file
          path: public

6. Git commit and push

This step is the reason why I'm using artifacts. I wanted the workflow to build and commit to the repository only if the version-update job is successful. Splitting the flow into two jobs requires managing dependent jobs, of which the documentation can be found here.

For pushing to a protected branch, this Github action makes it easy to do so.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
    # Git commit and push
    - name: Git commit build
      run: |
        export VERSION=$(head -1 __version | xargs)
        git config --local user.email <YOUR_EMAIL_HERE>
        git config --local user.name "github-actions[bot]"
        git add .
        git commit -m "[Build] Creating build files for $VERSION"
        git tag $VERSION
    # Push to protected branch
    - name: Push Changes
      uses: CasperWA/push-protected@v2
      with:
        # For protected branch needs to use Personal Access Token
        token: ${{ secrets.TOKEN }}
        tags: True
        branch: master

Et Voila! The Result!

Tap to enlarge this image 👇
02.png

OK, so now whenever a labeled pull request to master branch is approved and closed, the Github action kicks in and does version management automatically. Consider mission accomplished! 🥳

As an end-note, I realize this workflow can be improved more. Or perhaps we could find another way to automate release, such as letting CircleCI handle the flow.

Jerfareza Daviano

Hi, I'm Jerfareza
Daviano 👋🏼

Hi, I'm Jerfareza Daviano 👋🏼

I'm a Full Stack Developer from Indonesia currently based in Japan.

Passionate in software development, I write my thoughts and experiments into this personal blog.