CREATING A NPM MODULE RELEASE-PIPELINE

Using github workflow and standard-release to manage semantic-versioning, changelog and release of a npm module

Preface

In order to follow-up with a set of new disciplines I wanted to start this year, not only to help preserve my mind sanity, but also because one day I realized the responsibility of us as a community in giving back our gathered knowledge, I decided to commit more in planning, documenting and building those ideas (no matter how stupid they may seem) that can’t leave this monkey’s mind, and share my experience at doing them.

The first resulting project of this new discipline was this small open-source library for validating JavaScript data-schemas @devtin/schema-validator.

As I started building this library, one of my goals was to come with a boilerplate that would help me automate versioning and releasing to the npm registry.

This post is meant to share how I managed to implement a continuous integration / continuous delivery (CI/CD) pipeline with automated versioning for developing a npm module.

Hands to work

Semantic Versioning

In my previous practice I tried (as much as possible) to follow a manual semantic versioning approach (semver.org), but always struggled in deciding when was the right time for a bump or release.

{
  "version": "<MAJOR>.<MINOR>.<PATCH>"
}

A tool that definitely helped with my struggle was standard-version: Automate versioning and CHANGELOG generation, with semver.org and conventionalcommits.org (from their repository).

# adding standard-version as a devDependency
$ npm i standard-version --save-dev

standard-version can now use my commit history not only to build a CHANGELOG but also to determine what kind of version bump to perform according to the notable changes of my git history.

In order to use standard-version I had to follow a practice I wanted to implement for a while…

Conventional commits

A specification for adding human and machine readable meaning to commit messages: conventionalcommits.org

Conventional commits not only helped improve my commit history, but also helped me in the practice of committing often and organizing my commits. I’m glad I finally took the time in reading through it and putting it into practice.

If you want to know more about conventional commits, I actually found pretty useful this other repository where I think they explain deeper what each commit type is for.

$ git log --oneline
b91dc4e (HEAD -> dev, origin/dev) metadata: add author's last name
3c437b2 (origin/next, origin/master, next, master) chore(release): 2.1.4
a228f2c docs: add link to coverage report
af1adf0 chore(git): remove user-specific paths from .gitignore
f5f317f chore(ci): improve pipeline cycle / messages
cb33ace fix(docs): point tests badge to /test/features directory
9314716 fix(docs): typo
b9cfb5d docs: add npm version / link
8f7274d (tag: v2.1.3) chore(release): 2.1.3

Now that my git history is conventional-commit-compliant, I’m gonna go an add a script in my package.json file in order to perform releases.

{
  "scripts": {
    "release": "standard-version -a --branch master"
  }
}

Now whenever I run $ npm run release, standard-version will automatically bump the version in package.json and alternative files. Also will update (or create) the CHANGELOG.md file with all notable changes from my commit history previous-version onwards and will automatically create a git commit / tag of the new version.

standard-version also provides the ability to hook custom code along their lifecycle. I would suggest checking their docs for more info: standard-version

Github actions

In order to create an automated pipeline I’m gonna use github actions so I can perform clean builds, tests and releases from a fresh isolated containerized environment.

Github actions leverages automation by bringing the ability of running containers to perform custom logic given a particular event. Github actions use a syntax in yaml format (similar to Docker, I would say) to define the container setup / workflow.

npm auth token

I’m gonna need a npm access token so the container can have access to publish the module in my npm account. I will create one by going to my npm account, under my profile badge » Auth tokens » create new token.

Github secrets

From my github repository settings, under ‘Secret’, I can store sensitive content encrypted that I could refer-to safely within the public repository workflow files. This seems like the right place to store my recently created npm token.

Have a look at this article from the github help for more information: Creating and storing encrypted secrets

Summary

My workflow should:

  • Make my git repository available in the container (thanks to actions/checkout)
  • Identify the git cli as myself
  • Retrieve my repository commit history for standard-release (actions/checkout only brings the most recent commit only)
  • Grab node.js
  • Install my repository dependencies
  • Use my npm token secret
  • Run: module tests » release script » npm publish » git commit version / tag version » git push version with tags

Since I’m the only collaborator, I’m setting up my workflow to perform a release every time I push changes to master. Ideally, if I had a team and a clear SDLC, I rather suggest the schedule option according to the team pace.

# .github/workflows/release.yaml

name: release

on:
  push:
    branches:
      - master # runs only on master branch

jobs:
  release:
    runs-on: ubuntu-latest
    strategy:
      matrix:
        node-version: [12.x]

    steps:
    # makes the repository available in the container
    - name: Authenticate github cli
      uses: actions/checkout@v2
    
    - name: grab node.js ${{ matrix.node-version }}
      uses: actions/setup-node@v1
      with:
        node-version: ${{ matrix.node-version }}
        registry-url: 'https://registry.npmjs.org'
    
    - name: Retrieves commit history
      run: |
        git fetch origin next
        git fetch --depth=1 origin +refs/tags/*:refs/tags/*
    
    - name: Identify github user
      run: |
        git config --global user.email "tin@devtin.io"
        git config --global user.name "Martin Rafael Gonzalez"
    
    - name: install dependencies
      run: |
        npm ci
    - name: bump and publish package to npm registry
      run: |
        npm run test --if-present && \
        npm run release && \
        npm publish --access public && \
        git push --tags
        
      # provides my created NPM_TOKEN secret as an env variable
      # under the name 'NODE_AUTH_TOKEN' used automatically by npm to authenticate
      # in the npm registry
      env:
        NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}

All done! Now, I can work on my dev branch and whenever I want to release my changes I just need to merge to master and push to my repository. After doing so, this action will:

  • Build my project from the source
  • Run the project tests and if everything goes right…
  • It will perform the appropriate version bump according to my commit history in the package.json and related files
  • It will create a new commit / tag referring to the new version and will push it to the project’s github repository.
  • It will make the package available in the npm registry by publishing this new version under my npm account.

In order to continue working on my project, from my local repository I will need to $ git pull origin master after each release.

Hope this help somebody!