Automate Versioning Using Bitbucket Pipelines (NodeJS)

Shane Fast
BACIC
Published in
4 min readApr 23, 2017
Reject modernity, Return to Monkey

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…), and remembering to update all of these locations is a strain on any developer’s memory

Remembering to update the version number at all often falls through the cracks. After this issue constantly hindered our deployment strategy at a previous project, I decided it was time to craft a lasting process.

To see a working example go to https://bitbucket.org/Scfast/pipelines-autoversion or https://github.com/scfast/autoversion

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.

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

My team at the time used 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

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

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 in 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: Set a versioning strategy that resembles your team’s workflow.

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

We decided to automate the patch number with every merged pull request.

The minor and major updates are more subjective and are manually changed when development progress justifies it. This was a success with our trunk-based development style but had to be revisited when we moved into using git-flow.

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 of notes:

  • This example uses HTTPS. Using SSH is preferable. If using HTTPS, ensure the user has write access to the master branch.
  • 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 to 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, static analysis, patch notes, formatting, etc…

Refining these business logic systems as code configurations will not only relieve developer headaches but also better manage long-term complexity and help mature your code base!

If you found this valuable or entertaining, please follow the blog, and I’ll continue to post more tech goodness. Thanks for reading!

--

--

Shane Fast
BACIC
Editor for

Interested in building things and building teams.