Tech Blog #5: Automatically sync multiple repositories versions using GitHub Actions

In Salable, we have a custom JavaScript library for our pricing table, at a high level this JS library is responsible for displaying a product’s various pricing tables on a user’s website. This library is versioned and published to our CDN for consumption in the various framework/library wrapper packages we maintain like our React one. These wrapper packages are then able to be used in users’ applications using that respective technology.

The wrapper packages are then individually versioned and published to NPM for people to install. To ensure the wrapper packages are always using the latest CDN version of the core library, we release a new version of each wrapper package each time the core library’s version is bumped to ensure they are aligned.

This isn’t too much hassle as we already have the semantic-release package configured on both our core library and our wrapper packages. semantic-release handles all of the versioning and release workflows for us; the only manual part we need to do is update the CDN version being consumed in the wrapper package and PR it into the main branch to trigger a new version wrapper package release using the latest CDN version.

Now, I say this isn’t too much hassle but we’re developers and developers are inherently lazy and like to automate things. And, since this situation was prime for automation, we decided to automate it. But laziness aside there are good reasons to automate this workflow.

  1. We want to expand the number of wrapper packages we have from one (React) to include the other large libraries/frameworks as well, so that manually updating all of these will take more and more time as we expand — which could be spent on other more valuable activities.
  2. Humans make mistakes, we might forget to bump the wrapper package in an update or miss one of the wrapper packages entirely when multiple are being maintained.

In short, the less human intervention in this type of work the better it is for everyone.

The Plan

I drew up a plan to use GitHub Actions to automatically check the latest released version of the core library on GitHub and compare it to the currently consumed version of the library on the main branch of the wrapper package the action is running within.

If the versions match, we do nothing. But, if the latest core library release version is higher than the version consumed on the latest wrapper package, then we need to update the wrapper package and trigger a new release to NPM.

Inside our wrapper packages, we have a root constant.ts file which contains the core library package version we consume. It’s kept in a seperate file to make it easier to edit with bash commands inside of GitHub Actions.

I realise that might be a bit wordy and convoluted so here is a flowchart explaining what is happening in the GitHub Action being run on a CRON job within each wrapper package repository.

wrapper package flow

Now, all we need to do is add our GitHub Action! Below is the entire code of our GitHub Action with some comments explaining what each step does. We add this code into the ./.github/workflows/update-version.yml file and PR it into the main branch.

name: Update Version

# Run this action every hour on the hour.
on:
  schedule:
    - cron: "0 * * * *"

jobs:
  update-version:
    name: Update Version
    runs-on: ubuntu-latest
    steps:
    
      # Check out the latest code on `main`
      - uses: actions/checkout@v2
        with:
          token: ${{ secrets.GH_TOKEN }}
          fetch-depth: 0

      # Fetch the latest GitHub release of the core library repository
      - name: Get latest release
        uses: rez0n/actions-github-release@main
        id: version
        env:
          token: ${{ secrets.GH_TOKEN }}
          repository: "Salable/pricing-table-js-lib"
          type: "latest"

      # Format the latest release version from above into 'x.x.x' format
      - name: Format latest version
        id: latest
        run: "version='${{ steps.version.outputs.release }}' && echo ::set-output name=version::${version/'v'/''} && mkdir params && echo ${version/'v'/''} > params/VERSION"

      # Get the current version used in the wrapper package from the root file containing it.
      - name: Read currentVersion from constants.ts file
        id: currentVersion
        run: echo "::set-output name=version::$(head -1 ./constants.ts | awk '{ print $5; }' | sed "s/'//g" | sed 's/.$//')"

      # Compare the versions and update the constants file to the new version if required.
      - name: Compare versions
        if: steps.latest.outputs.version > steps.currentVersion.outputs.version
        run: echo "$(sed -i -e 's/${{steps.currentVersion.outputs.version}}/${{steps.latest.outputs.version}}/g' ./constants.ts)"

      # Commit the change and push to `main` if version has changed.
      - name: Push changes
        if: steps.latest.outputs.version > steps.currentVersion.outputs.version
        run: |
          git config --global user.name 'github-actions'
          git config --global user.email 'github-actions@github.com'
          git commit -am "feat: bumped core library version to ${{steps.latest.outputs.version}}"
          git push


With the action now merged into the main branch, it will be triggered by a CRON job every hour on the hour, to check if the latest versions have deviated from each other and if so, update the wrapper package’s constant file to use the latest version of the core library.

Throughout this post, we’ve covered the reasoning why you might want to keep two repositories in sync using GitHub Actions as well as how to do just that with a custom GitHub Actions workflow. I hope you found this post helpful.

Related blogs

Tweet from Neal Riley saying "Commercial freedom and flexibility is a must for any digitally enabled business. @SalableApp gives you the tools to build your SaaS business."

Developers love how easy it is to grow a SaaS business with Salable

Learn more →
Tweet from Neal Riley saying "Commercial freedom and flexibility is a must for any digitally enabled business. @SalableApp gives you the tools to build your SaaS business."

Tech Blog #4: How to version all packages synchronously, in a monorepo using Lerna

Today we are setting up automatic versioning of packages inside of a monorepo using Lerna. There are many ways in which you can do this with different technologies...

Coner Murphy
22 Nov 2022

Tech Blog #4: How to version all packages synchronously, in a monorepo using Lerna

Today we are setting up automatic versioning of packages inside of a monorepo using Lerna. There are many ways in which you can do this with different technologies...

Coner Murphy
22 Nov 2022

Tech Blog #3: Using a private GitHub repository as an NPM package in another repository

Creating NPM packages is an easy and convenient way to share code across multiple projects. But, if you want to keep the package private and are unwilling to pay for the

Coner Murphy
07 Nov 2022

Tech Blog #3: Using a private GitHub repository as an NPM package in another repository

Creating NPM packages is an easy and convenient way to share code across multiple projects. But, if you want to keep the package private and are unwilling to pay for the

Coner Murphy
07 Nov 2022

Tech Blog #2 Salable Package Architecture

Over the last few months, we’ve been working on a new Global Design System (GDS) for Salable. But as part of this process, we’ve also had to make several decisions...

Tech Blog #2 Salable Package Architecture

Over the last few months, we’ve been working on a new Global Design System (GDS) for Salable. But as part of this process, we’ve also had to make several decisions...

Sign up to
our mailing list

Stay up to date with what we are currently building, plus new features, and chances to enter competitions.