How to use Node.js release-it plugin with Travis CI

Release-it is an awesome Node plugin which takes care of releasing and versioning of projects. It also supports releasing on GitHub and GitLab and certainly can tag and push to git. In this article, I briefly touch down on how to use Node.js release-it plugin with Travis CI.

The end goal

Our objective is to set a CI pipeline with Travis CI for any Node.js project. This means we want to automate the process of tagging, versioning and performing releases on GitHub whenever we push anything to the master branch. To understand the process better, have a look at the below diagram,

Release workflow
Release workflow

The process is very straightforward but getting it to work is not as easy as it sounds. That’s mainly because of some weird configuration which I discuss in the following sections.

Now let’s get started.

Installing release-it

The easiest way to install release-it plugin is to use npm as follows,

$ npm install release-it

If you don’t have npm, and you are looking for an alternative approach, have a look at the release-it documentation, here.

Configuring the release-it plugin

The most important part is to configure the plugin. The release-it plugin is very flexible and we literally can do anything with it. For our use case, all we want is to release the app and create a tag on GitHub.

For that, we need to create a file in the root directory of the project called .release-it.json. Then we need to add some content inside of it.

For our purpose, we need to configure four sections, hook, git, github, and npm.

The hook section is to instruct release-it which commands to run. It supports multiple hooks, the ones that are important for us are: before:init, after-bump, and after-release.

  • before-init activates before to any release-it process. It’s the best place to run linting.
  • after-bump is triggered once the release-it increases the version of the project in XXXX file. We use this step to build the project.
  • after-release is when everything includes tagging and commit is done. We use this for prompting a message that release was successful.
{
  "hooks": {
    "before:init": ["npm run lint"], // run linting
    "after:bump": ["npm run build", "npm run build-zip"], // build the project and generate the zip file
    "after:release": "echo Successfully released ${name} v${version} to ${repo.repository}." // prompt on build success
  }
}

Then we have to configure the git section and set some settings such as changelog message, requireCleanWorkingDir and so on. All arguments are pretty self-explanatory.

{
  "git": {
    "changelog": "git log --pretty=format:\"* %s (%h)\" ${latestTag}...HEAD", // changelog message on Git
    "requireCleanWorkingDir": false, // perform release even if the directory has uncommitted files
    "requireUpstream": false, // run release without Git upstream configured
    "requireCommits": true, // ?
    "addUntrackedFiles": false, // don't add untracked files
    "commit": true, // commit the code
    "commitMessage": "Release v${version}", // message of the commit
    "commitArgs": "", // additional commit argument
    "tag": true, // tag the release
    "tagName": "${version}", // tag name
    "tagAnnotation": "Release v${version}", // tag annotation
    "tagArgs": "", // additional tag argument
    "push": true, // push the commit to upsteream
    "pushArgs": "--follow-tags", // commit plus tag to upstream
    "pushRepo": "origin" // the upstream name
  }
}

We also need to change npm section since we don’t want to upload any artifacts to npm. The main argument here is set publish flag to false.

{
  "npm": {
    "publish": false, // don't push to npm artifactory
    "publishPath": ".", // url of the npm artifactory
    "access": null, // ?
    "otp": null // ?
  }
}

And lastly, we need to apply some GitHub specific configuration. These are mainly related to the release and tag process.

{
  "github": {
    "release": true, // perform the release
    "releaseName": "Release v${version}", // release name
    "releaseNotes": null, // release not message
    "preRelease": false, // it is not a prelease release
    "draft": false, // it is not a draft release 
    "tokenRef": "GITHUB_TOKEN", // GitHub token
    "assets": ["dist-zip/*.zip"], // assest, attach all zip files in `dist-zip` directory
    "host": null, // host other than git
    "timeout": 0, // set time out
    "proxy": null // proxy settings
  }
}

So the final workable version of .release-it.json file should look like this:

{
  "hooks": {
    "before:init": ["npm run lint"],
    "after:bump": ["npm run build", "npm run build-zip"],
    "after:release": "echo Successfully released ${name} v${version} to ${repo.repository}."
  },
  "git": {
    "changelog": "git log --pretty=format:\"* %s (%h)\" ${latestTag}...HEAD",
    "requireCleanWorkingDir": false,
    "requireUpstream": false,
    "requireCommits": true,
    "addUntrackedFiles": false,
    "commit": true,
    "commitMessage": "Release v${version}",
    "commitArgs": "",
    "tag": true,
    "tagName": "${version}",
    "tagAnnotation": "Release v${version}",
    "tagArgs": "",
    "push": true,
    "pushArgs": "--follow-tags",
    "pushRepo": "origin"
  },
  "npm": {
    "publish": false,
    "publishPath": ".",
    "access": null,
    "otp": null
  },
  "github": {
    "release": true,
    "releaseName": "Release v${version}",
    "releaseNotes": null,
    "preRelease": false,
    "draft": false,
    "tokenRef": "GITHUB_TOKEN",
    "assets": ["dist-zip/*.zip"],
    "host": null,
    "timeout": 0,
    "proxy": null
  }
}

Travis CI configuration

Now we need to move to the next main thing which is configuring our CI pipeline using Travis. That’s pretty straightforward, all we need to do is to create .travis.yml file and do some configuration to install node.js, configure Git and run the release-it plugin as follows:

dist: bionic
language: node_js
node_js:
  - 12
sudo: true
install: true
addons:
  apt:
    update: true
cache: # cache npm dependencies to avoid wasting time
  directories:
    - $HOME/node_modules

install:
  - export TRAVIS_COMMIT_DESCRIPTION=`git log -n 1`

script:
  - git config --global user.email $GITHUB_EMAIL
  - git config --global user.name $GITHUB_USERNAME
  - git remote rm origin 
  - git remote add origin https://[email protected]/$TRAVIS_REPO_SLUG.git 
  - git symbolic-ref HEAD refs/heads/master
  - npm install
  - if [[ "$TRAVIS_COMMIT_DESCRIPTION" != *"Release v"* ]];then npm run release --minor --ci; fi 
  # don't release if the last message matches `Release v*` pattern

Keep in mind that Git configuration is pretty tricky due to the way that Travis clones the repository. Essentially, Travis does a shallow clone without any upstream. So we have to add the upstream manually. That’s why in the configuration of release-it we had to set requireUpstream to false.

Another important part is to add a condition to avoid Travis CI running all the time. That happens because release-it plugin pushes a commit to Git which bumps up the project version. This can be easily achieved by comparing the commit message and if it matches a pattern Travis should not run the release process.

And lastly, we need to generate a Git token, see here on how to generate it and set it as an environment variable, GITHUB_TOKEN, in Travis setting. So that release-it plugin can use it to push a tag and commit to GitHub.

Once everything is done, just we need to trigger the pipeline and see the result.

I have configured the release-it plugin in my Metis Google Chrome weather extension. If you are curious to know about it check the following GitHub link:

https://github.com/kasramp/weather-extension

Inline/featured images credits