Automate Versioning Using Bitbucket Pipelines (NodeJS)

Athennian
4 min readApr 23, 2017

--

While the concept of semantic versioning is widely known and used across the software industry — its execution can quickly become a tangled mess. The version number gets declared in multiple locations (package.json, environment variables, version tags, CI configurations, etc.). Remembering to update all of these locations is a strain on developer’s memory, and often remembering to update the version number at all often fall through the cracks. After having this issue constantly hinder our deployment strategy at ContractClub we decided it was time to craft a lasting process. To see working example go to https://bitbucket.org/Scfast/pipelines-autoversion or https://github.com/scfast/autoversion

Here’s how to set-up automated version pipelines:

Step 1: Reduce all version number references to a single spot.

Straight forward — search your code base and CI tools for references to the version number and make sure they all reference one and only one spot. As per tradition we went with the default version field in package.json. To reference this value in any javascript file use the following declaration:

const currentVersion = require(‘./package.json’).version;

Purposely using ‘const’ to keep the version number immutable during run time.

To reference the version number in bash our team used the CLI tool ‘jq’ (https://stedolan.github.io/jq/download/). More about this below.

Step 2: Set up automatic Git tagging during CI process.

Part of our team’s CI uses Bitbucket Pipelines. The commands used to capture the version number and automatically tag the repository push that triggered the build are:

- declare -x VERSION=$(jq -r '.version' package.json)- echo $VERSION- git tag $VERSION- git push origin --tags

The only magic here might be the first command. Essentially, the CLI tool ‘jq’ searches the package.json file for a field called ‘version’. jq then returns the value of ‘version’ (say “1.2.3”). The ‘-r’ flag strips the quotes off of the returned value (now only 1.2.3). The value gets stored into the variable VERSION which gets created as a tag and pushed to the branch that triggered the build. You can tag with quotes, but after personally struggling with it I recommend not having tags with quotes for reasons that will take too long to explain here.

Step 3: Determine a versioning strategy that more closely resembles your team’s branch workflow.

This is where things get more opinionated. I will describe what our team did with full awareness that this may not work/be applicable for other team’s workflows.

For Contract Club, we decided to automate the patch number with every merged pull request to master. The minor and major updates are more subjective and are manually changed when development progress justifies it. This has been a successful with our trunk based development style.

Going back to Bitbucket pipelines, first implement the branch workflow strategy (see https://confluence.atlassian.com/bitbucket/branch-workflows-856697482.html). Second, create a script to update the patch number in package.json. We just used a gulp task called ‘autoversion’ to accomplish this:

const gulp = require('gulp');
const runSequence = require('run-sequence'); // Run tasks sequentially
const jsonModify = require('gulp-json-modify');
gulp.task('upversion', function () {
let ver = require('./package.json').version; //version defined in the package.json file
console.log('current version: ', ver)
let splitString = ver.split('.', 3)
let patchVersion = splitString[2].split('"',1)let patchNumber = Number(patchVersion[0])
patchNumber++
splitString[2] = String(patchNumber);
process.env.VERSION = splitString.join('.');
console.log(process.env.VERSION)
})
gulp.task('saveversion', function () {
return gulp.src(['./package.json'])
.pipe(jsonModify({
key: 'version',
value: process.env.VERSION
}))
.pipe(gulp.dest('./'))
})
gulp.task('autoversion', function () {
runSequence('upversion','saveversion');
})

Finally, apply this task to the pipeline for master and have the pipeline commit this change back into master. All put together, the pipeline steps will look like this:

pipelines:
default:
- step:
script:
# Regular CI process for feature branches
branches:
master:
- step:
script:
# PACKAGE INSTALLATIONS
- npm install
- npm install -g gulp
# AUTO VERSIONING
- git config remote.origin.url https://<URL to repository here>
- gulp autoversion
- git init
- git config user.name "<your username>"
- git config user.email "<your email>"
- git add package.json
- git commit -m "[skip CI]"
- git push
- declare -x VERSION=$(jq -r '.version' package.json)
- echo $VERSION
- git tag $VERSION
- git remote -v
- git push origin --tags

A couple notes:

  • This example uses HTTPS. Using SSH is preferable. If using HTTPS make sure the user has write access to master.
  • The commit message “[skip CI]” is necessary to avoid re-triggering the build. Without it an endless cascade of builds will trigger.

Conclusion:

At this point you may be asking yourself why bother to automate changing a single digit? First, because we can. Second, it relieves developers from needing to remember to update it with each pull request and to be checking for it while reviewing pull requests. Third, the repository is automatically better organized, making operations more predictable and more automated. Fourth, and most importantly, a feedback loop is created between the CI process to the version control process.

We can train pipelines to automate routine changes to our repository such as controlled node package handling, usage based code removal, formatting, etc… Being able to refine these business logic systems as code configurations will not only be able to relieve developer headaches, but also better manage long term complexity and help the business win!

--

--