Skip to content

Automating Semantic Versioning for any Project

· 7 min

Semantic versioning is the best known and most widely adopted convention for versioning software. If you aren’t familiar with it, read up on it before continuing.

Once you start implementing semantic versioning for a project, you realize that you have just given yourself a small check list of items to go through for every release:

Let’s have a look how we can avoid having to go through a check list for every release, by handling this automatically in our Continuous Integration tool.

📢 Want to know when more posts like this come out? Follow me on Twitter: @MattHodge 📢

Automating using semantic-release#

Semantic-release is a tool which can help us automate our whole release workflow, including determining the next version number, generating the release notes and publishing the package.

It is a Node.js command line tool, but it can be used to automate the semantic versioning of projects of any language.

It does so with the use of several plugins.

Plugin - commit-analyzer#

The commit-analyzer plugin parses commit messages and determines if a new release should be made, and whether it should be a Major, Feature or Patch release.

The default configuration is to use the AngularJS commit message format. Let’s have a look at some example commit messages:

fix: fix timeout when creating environments

The above commit message would be a Patch Release, for example 1.0.1.

feat(resource): add new argument to environment resource

The above commit message would be a Feature Release, for example 1.1.0.

You can also include a optional section of the code you changed in parentheses, in this case I am changing a resource.

refactor(resource): rename useguidedfailure argument on environment resource
BREAKING CHANGE: The useguidedfailure argument has been renamed use_guided_failure in the octopusdeploy_environment resource.

The above commit message would be a Major Release as it has a breaking change, for example 2.0.0.

If you like, you can also implement your own commit parsing methods.

Plugin - release-notes-generator, changelog & git#

The release-notes-generator will generate a changelog based on the commit messages. You can then use the changelog plugin to create or update a CHANGELOG.md file.

Using the refactor commit above as an example, the following changelog would be generated.

CHANGELOG.md Example

CHANGELOG.md Example

This generated CHANGELOG.md needs to be checked back into the git repository, which is where the git plugin comes in.

Plugin - github#

The github plugin can create GitHub releases and comment on issues the release closes them.

Using the refactor commit above as an example, the following GitHub release would be created.

GitHub Release example

GitHub Release example

Plugin - exec#

The exec plugin allows you to execute any arbitrary command at the different stages of a release.

This allows you to use semantic-release with any repository or language you wish.

For example, you could use the prepareCmd to generate release artifacts and a publishCmd to push them to an artifact repository like NuGet.

You also get access to variables like ${nextRelease.version} which you can pass to your scripts / commands. You can find all of the available variables in the JavaScript Developers Guide.

Installation#

Now that you have an idea of how semantic-release and its plugins work, its time to set them up on our repository.

Most of the documentation for semantic-release is very specific to NPM packages, but it can work with any type of repository and software.

For the rest of this guide I will assume you have some familiarity with Node.js and have it at least version 8.3 installed.

Install semantic-release and the plugins we need via NPM. We will install them globally.

Terminal window
npm install -g semantic-release \
@semantic-release/changelog \
@semantic-release/commit-analyzer \
@semantic-release/exec \
@semantic-release/git \
@semantic-release/release-notes-generator

Configuration#

With the tool installed, now we need to setup a configuration file. Create a .releaserc file in the root of your repository.

The following is configuration for my Terraform provider repository:

{
"branch": "master",
"plugins": [
"@semantic-release/commit-analyzer",
"@semantic-release/release-notes-generator",
[
"@semantic-release/changelog",
{
"changelogFile": "CHANGELOG.md",
"changelogTitle": "# Semantic Versioning Changelog"
}
],
[
"@semantic-release/exec",
{
"prepareCmd": "./prepare-release.sh ${nextRelease.version}"
}
],
[
"@semantic-release/git",
{
"assets": [
"CHANGELOG.md"
]
}
],
[
"@semantic-release/github",
{
"assets": [
{
"path": "dist/**"
}
]
}
]
]
}

The two parts of the configuration file we are using are:

You can read more details about the configuration file page of the semantic-release documentation.

Create and set a GitHub token#

We need to create a GitHub token to allow semantic-release to create GitHub releases.

Follow the steps here to create one.

You will need to give the following permissions:

GitHub Permissions

GitHub Permissions

Copy the token and set it as the GITHUB_TOKEN environment variable.

Terminal window
export GITHUB_TOKEN=PUT-YOUR-TOKEN-HERE

Running locally#

Ideally, you will run semantic-release inside a Continuous Integration (CI) tool. Let’s test it out locally first though.

I opened a repository of mine and ran the following command:

Terminal window
npx semantic-release
# You can also run it with the --debug flag for more details.

When running locally, it will detect that its not running in a CI tool and run in dryrun mode.

You will see all the configuration load and an output like this:

Semantic Release No Change

Semantic Release No Change

Let’s create a commit and see what happens.

Terminal window
# Add a test file and commit
touch testfile.txt
git add testfile.txt
git commit -m "feat: add a test file"
# Rerun
npx semantic-release

This time we see the changelog is generated and a new release would be created:

Semantic Release With Change

Semantic Release With Change

😍 Pretty awesome right!

Let’s say I already had a version that was released of my project using my old manual method, how would I have semantic-release continue on from there?

Terminal window
# Find the hash of the latest change
git log -2 --oneline
# Returns:
# f3cd02e (HEAD -> master) feat: add a test file
# 6548458 (origin/master, origin/HEAD, 1541972003) Update Builds
# Add a tag to the commit before our change, lets say it was version 1.5.0
git tag v1.5.0 6548458

Now we are in-line with our previous release 😎.

Semantic Release With Change

Semantic Release With Change

Building your release#

Once semantic-release has decided a new version needs to be created, and generated a version number for you, we need a way to trigger a build of our software.

This is where the exec plugin comes in.

For example, using the following option:

{
"plugins": [
[
"@semantic-release/exec",
{
"prepareCmd": "./prepare-release.sh ${nextRelease.version}"
}
]
]
}

When ever the prepare step is run by semantic-release, it will execute the prepare-release.sh script, passing the version of the new release with ${nextRelease.version}.

In this step I could, for example:

The options here are endless.

:white_check_mark: The prepare step does not run in dry run mode. You will need to run it in CI to get this step to trigger.

Get your build running in CI#

Now we have all the pieces together, we want to start the build using a build server.

This part is now pretty easy. You just need to:

Conclusion#

Following a semantic release process doesn’t have to mean manual work. With the ground work set using semantic-release, you can automate all this work for ANY type of project.

Go and get automating ⭐!