Jenkins + npm package is [not] a good idea
Tags: javascript, npm, jenkins
Published: 2019-09-30 18:04:00

## Prologue

Automating **npm package** deployment (**C**ontinues **D**eployment) is a good practice but not an easy one especially the combination of **jenkins** and **npm package**. 
During my latest project I was developing an **npm package** that was going to be used by multiple teams and projects across the entire company. After a week or two of development we decided that we are ready to publish our first version and that we are going to need a CI/CD pipeline to push the package into our local **registery**. Our pipeline was very simple and had only 4 stages:
- **Initialization** - In this stage we simply build our application: `npm ci`
- **Unit Tests** - In this is stage we run the tests and check the coverage
- **Build** - This builds the project and prepares it for a publish(`npm run build`).
- **Publish** - This stage simply runs the following command: `npm publish`

## Problem

If you are anything like me you are going to forget to update the version of your package and push your changes and since our **Jenkins** doesn't check the version it will give a green light to merge your branch and when you do it is going to fail. Why? because we forgot to update the version and when `npm` wants to perform **publish** it fails. 
  I personally encountered this many times and it was getting very frustrating and expensive because I broke 2 keyboards, 3 mugs and someone's skull (apparently mine). Anyway there are many solutions to this problem here is one that I really liked and implemented personally.

## Solution

Idea is to check the version before **Jenkins** reaches that last **Publish** stage.
**Jenkinsfile**
```groovy
   stage('Version Check') {
      steps {
        script {
          sh "chmod 777 config/version_check.sh"
          sh "config/version_check.sh"
        }
      }
    }
```

**config/versoin_check.sh**
```bash
#!/bin/bash
set -exuo pipefail

LOCAL_VERSION=$(node -p -e "require('./package.json').version");
REMOTE_VERSION=$(npm view . version);

if [ "${LOCAL_VERSION}" == "${REMOTE_VERSION}" ]
then
    echo "Package with v$LOCAL_VERSION already exists"
    exit 1;
else
    exit 0;
fi
```

This new stage is coming right after the very first **Initialization** stage so now our `Jenkins pipeline` has this **5** stages:
- **Initialization** - In this stage we simply build our application: `npm ci`
- **Version Check** - *In this stage we are verifying our version*
- **Unit Tests** - In this is stage we run the tests and check the coverage
- **Build** - This builds the project and prepares it for a publish(`npm run build`).
- **Publish** - This stage simply runs the following command: `npm publish`

The script that performs the version check is quite simple just make sure that **Jenkins** is using a container that has **node** installed or use `node` agent for this stage and don't forget to make the script **executable** (`sh "chmod 777 config/version_check.sh"`)

## Limitations & Further Improvements 

This solution is not perfect just like any other "solution". First things first it doesn't check all remote versions only the latest one which is not ideal we want to be sure our local **version** is unique across all published versions. Fortunately it is possible to get all version numbers of published package and check against the list. 

Yet another issue is that our code doesn't perform any kind of **validation** to make sure that our version number is compliant to [Semantic v2.0.0](https://semver.org/) standards. Fortunately there are some good **npm packages** to perform those validations.

## Thank you
Thank you for reading this article. Hope it can help someone and if you have any questions or suggestions let's talk about it in the comments below.