How to automate releases in Gitlab using Standard Version and NPM

This post shows how to set up automated, tagged, semantically-versioned releases, changelog generation, and CI testing using a Gitlab pipeline, Standard Version and Conventional Commits.

In order to make this work, we need a few files:

  • A gitlab-ci.yml pipeline file with a correct configuration
  • A package.json file for the sake of pulling standard-version and tracking the current version number.
  • [Optional] A verion_bump.js file to replace static version strings within the codebase (if required)

MY MISSION

This blog started nearly 10 years ago to help me document my technical adventures in home automation and various side projects. Since then, my audience has grown significantly thanks to readers like you.

While blog content can be incredibly valuable to visitors, it’s difficult for bloggers to capture any of that value – and we still have to work for a living too. There are many ways to support my efforts should you choose to do so:

Consider joining my newsletter or shouting a coffee to help with research, drafting, crafting and publishing of new content or the costs of web hosting.

It would mean the world if gave my Android App a go or left a 5-star review on Google Play. You may also participate in feature voting to shape the apps future.

Alternatively, leave the gift of feedback, visit my Etsy Store or share a post you liked with someone who may be interested. All helps spread the word.

BTC network: 32jWFfkMQQ6o4dJMpiWVdZzSwjRsSUMCk6

Challenges faced

  • Gitlab would get stuck in an infinite loop when a releases chore commit is pushed back to master and develop, triggering further builds and releases.
  • Replacing static strings in files was difficult to make work with standard-version. I eventually compromised by having the aforementioned version_bump.js file output a commit message.
  • Having standard-version commit additional files was also difficult to figure out.

Caveats

  • Gitlab caches branches locally and uses git fetch. Change this to git clone if you are struggling with getting changes back into Git due to errors on push.
  • Rerunning the same Gitlab job reuses the same workspace, including any stale commits leftover from before a --force-push. Nasty.

For these reasons (and to document the process for myself) I wrote this post in the hope that it will save you some time.


Advertisement Begins

Advertisement End


Step 1. Add a .gitlab-ci.yml file

The pipeline configuration involves a job for testing (executed on all branches) and a release job which is executed exclusively on the master branch.

gitlab-ci.yml

variables:
    CI_NAME: "gitlab"
    CI_EMAIL: "gitlab-ci@example.com"
stages:
  - test
  - release
test:
  stage: test
  except:
    variables:
      - $GITLAB_USER_LOGIN == $CI_NAME

  script:
    - echo $GITLAB_USER_LOGIN
    - echo $CI_USER
    - echo $CI_NAME


release:
  stage: release
  when: on_success

  except:
    variables:
      - $GITLAB_USER_LOGIN == $CI_NAME

  tags: 
    - npm
  only:
    - master
  image: tarampampam/node:alpine
  script:
    - npm install
    - git config --global user.email $CI_EMAIL
    - git config --global user.name $CI_NAME
    - git config receive.advertisePushOptions true
    - git checkout -B "$CI_COMMIT_REF_NAME" "$CI_COMMIT_SHA"
    - npm run release
    - git push http://${CI_USER}:${CI_ACCESS_TOKEN}@REPO_URL --follow-tags master:master
    - git checkout develop
    - git merge master
    - git push http://${CI_USER}:${CI_ACCESS_TOKEN}@REPO_URL --follow-tags develop:develop

Replace REPO_URL with the URL to your repository. I had to include the port number (80 in my case) as well.


Affiliate Content Start

Affiliate Content End


Step 2: Add Project level CI variables in Gitlab

Add CI_ACCESS_TOKEN and CI_USER variables with an access token/user that has access to the project.

Step 3: Add package.json

The following package.json file contains the minimum configuration required to make standard-version work. (The replace-in-file dependency is only required if you need to replace the version string in some files.)

package.json

{
  "name": "jekyll-browser-startpage",
  "version": "0.1.0",
  "description": "A browser startpage.",
  "main": "index.js",
  "directories": {
    "test": "tests"
  },
  "scripts": {
    "release": "standard-version -a"
  },
  "repository": {
    "type": "git",
    "url": "https://github.com/danobot/jekyll-browser-start"
  },
  "author": "Daniel Mason",
  "license": "MIT",
  "devDependencies": {
    "semantic-release": "^15.13.3",
    "standard-version": "^4.4.0",
    "replace-in-file": "^3.4.3"
  },
  "standard-version": {
    "scripts": {
      "precommit": "node version_bump.js && git add startpage/_includes"
    }
  }
}

Notes:

  • In my experience, the precommit node script must reference a Javascript file in the root of the project directory.
  • If you have issues with node not running your script or Gitlab not committing the changed file, then copy the directory layout exactly. There are weird issues where subfolders cannot be found.

Step 4: Add a custom script to substitute version strings

This step is optional. if there are files where the version string is referenced (such as a HTML partial), then use the version+bump.js file below to regex replace the version string. The output of this file is used by standard-version as the commit message.


Kitchen Multi-Timer Pro

Now you’re cooking

Multi Timer Pro is your ultimate meal prep companion, keeping track of multiple cooking times and making adjustments on the fly. Give it a try today and become a better home cook!


version_bump.js

var v = require('./package.json').version
console.log(v)
const replace = require('replace-in-file');
const regex = new RegExp(/.*/, 'i');
const options = {
    files: 'startpage/_includes/version.html',
    from: regex,
    to: "v" + v,
};

var changes = replace.sync(options)

console.log("chore(release): " + v)

That’s it

You should now have a working pipeline that will run the test job on non-master branches and will prepare a release version with an auto-generated changelog when Merge Requests are merged onto master. The image below shows the pipeline.

Related posts

How to Optimize Docker Builds with Nexus OSS for Apt, Maven, Docker and NPM Dependencies

Docker could not find an available, non-overlapping IP address pool

Github Workflow for Electron React Boilerplate with Auto Updates

1 Comment

Max January 14, 2021 - 11:12 pm

Hey Daniel! Excellent article! Just a suggestion: I would add an explanation regarding in which part of the job you tell gitlab to avoid the infinite loop when pushing.
Thanks a lot for sharing! This saved me!

Add Comment

This website uses cookies to improve your experience. We'll assume you're ok with this, but you can opt-out if you wish. Read More