How to automate releases in Gitlab using Standard Version and NPM
in Devops / Automation / Tools tagged Gitlab
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)
Challenges faced
- Gitlab would get stuck in an infinite loop when a releases
chore
commit is pushed back tomaster
anddevelop
, triggering further builds and releases. - Replacing static strings in files was difficult to make work with
standard-version
. I eventually compromised by having the aforementionedversion_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 togit 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.
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.
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.
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 auto-generated changelog when Merge Requests are merged onto master
. The image below shows the pipeline.
Change Log (Markdown rendered)
All notable changes to this project will be documented in this file. See standard-version for commit guidelines.
3.1.0 (2019-02-26)
Features
- blocked mode: add timeout to blocked mode such that the controller takes over after some time. (9160879)
3.0.1 (2019-02-26)
Bug Fixes
- tracker: update component name and location (91e4950)
3.0.0 (2019-02-26)
Chores
- rename component, migrate to new directory/file format (889d5cd)
BREAKING CHANGES
- component has been renamed to entity_controller and migrated to the new file/directory format. To update your configuration, hard-replace
lightingsm
withentity_controller
in your configuration files and Lovelace config. The directory/file format change may require you go into yourcustom_components
folder and manually remove thelightingsm.py
file and create the new directory structure.