Fully automating npm package releases
Tags: npm, javascript, node, semver
Published: 2020-03-12 05:34:27

![Automation](https://media.giphy.com/media/CmFMWpEa4IFtS/giphy.gif)
<figcaption>An "automated" npm release</figcaption>

# Contents
- [Introduction](#introduction)
- [Conventional commits](#conventional-commits)
- [Enforcing conventional commits](#enforcing-conventional-commits)
- [Automating with semantic-release](#semantic-release)
- [Conclusion](#conclusion)

# Introduction <a name="introduction"></a>

One of the challenges when trying to automate npm package releases is [semantic versioning](https://semver.org/). It is quite common for developers to "manually" decide what the next version bump should be, which makes the decision biased and [sentimental](http://sentimentalversioning.org/).

The obvious way to remove that subjective bias is to automate the version bump and, to do this, we will need machine readable information in the codebase to help determine the next version. Enter conventional commits!

# Conventional commits <a name="conventional-commits"></a>

As described in the conventional commits website, it is:
> A specification for adding human and machine readable meaning to commit messages.

It provides a set of rules for creating a commit history that can be easily used by automated tools such as a CI/CD pipeline.

Conventional commits should use the following structure:
```
<type>(optional scope): <description>

[optional body]

[optional footer(s)]
```

Here's a more concrete example:
```
feat(server): upgrade json parsing library

BREAKING CHANGE: replacing the library introduces a breaking change
```

For now, all you need to know to get started are these conventions:

- `fix` commit type - indicates a bugfix and corresponds to a PATCH update
- `feat` commit type - indicates an introduced feature and corresponds to a MINOR update
- `BREAKING CHANGE:` footer comment - indicates a breaking change  and corresponds to a MAJOR update

You can learn more about the full specification for conventional commits on the [Conventional Commits](https://www.conventionalcommits.org/) website.

# Enforcing conventional commits <a name="enforcing-conventional-commits"></a>

There are many tools available to help with enforcing conventional commits in your codebase. In this example, we'll be using [husky](https://github.com/typicode/husky) and [commitlint](https://github.com/conventional-changelog/commitlint).

Install husky and commitlint by running the following command.
```bash
npm install --save-dev husky @commitlint/config-conventional @commitlint/cli
```

Then add the following configuration to your project's `package.json` file.
```json
{
  "husky": {
    "hooks": {
      "commit-msg": "commitlint -E HUSKY_GIT_PARAMS"
    }
  }
}
```

Now if you try adding a commit message that doesn't conform to the conventional commits spec, you will get an error message, and the commit will fail.
```bash
git commit -m 'some random commit'
husky > commit-msg (node v12.16.1)
⧗ input: some random commit
✖ subject may not be empty [subject-empty]
✖ type may not be empty [type-empty]
 
✖ found 2 problems, 0 warnings
ⓘ Get help: https://github.com/conventional-changelog/commitlint/#what-is-commitlint
```

# Automating with semantic-release <a name="semantic-release"></a>

Now that your codebase is using conventional commits, you have a commit history that is machine readable, which means you can start automating your version updates and release.

Again, there are numerous tools out there that help facilitate this, but we've chosen [semantic-release](https://github.com/semantic-release/semantic-release) and [GitHub Actions](https://help.github.com/en/actions) as an example.

> **semantic-release** automates the whole package release workflow including: determining the next version number, generating the release notes and publishing the package.

Feel free to read more about these tools in the official documentation as the following examples are very basic.



To get started, install semantic-release first.
```bash
install --save-dev semantic-release
```

Then, add the following to the scripts section of your `package.json` file.
```json
{
  "scripts": {
    "semantic-release": "semantic-release"
  }
}
```

You can test if versioning works by running semantic-release in dry-run mode.
```bash
npm run semantic-release -- --dry-run
```

Next, let's set up our Github Action workflow. Add a new directory in your project's codebase called `.github/workflows`. In this directory, add a new YAML file, `build.yml` with the following contents.
```yaml
name: Build
on: [push]

jobs:
  audit:
    runs-on: ubuntu-latest
    steps:

      - name: Git checkout
        uses: actions/checkout@v2

      - name: Install node
        uses: actions/setup-node@v1
        with:
          node-version: 12.x

      - name: Install npm dependencies
        run: npm install

      - name: Run tests
        run: npm test

      - name: Package
        if: github.ref == 'refs/heads/master'
        run: npm run semantic-release
```

This will create a GitHub Action workflow called `Build` which will be triggered every time you push a commit to GitHub. The workflow will go through the following steps.

- Spin up a docker container with Ubuntu.
- Install Node.js
- Check out the git repo.
- Install the npm dependencies for the project.
- Run tests.
- Execute semantic-release if the current branch is `master`. This step will essentially do the following tasks.
  - Automatically determine the next version using information gathered from commit messages
  - Bump the version up in package.json
  - Publish the package to the registry
  - Create a new tag/release on GitHub

There you have it! Your npm release is now fully automated!

# Conclusion <a name="conclusion"></a>

As we mentioned before, one of the biggest challenges with automating semantic versioning is finding a way to have machine readable information that automation tools can use to determine version updates.

We established that Conventional Commits would be able to address this by providing a structure that would allow both humans and machines to objectively determine the "extent" of the changes included in the new version.

You can use whatever tools best suit your project but here are some that have proven to be quite useful.

- [husky](https://github.com/typicode/husky) for commit hooks
- [commitlint](https://github.com/conventional-changelog/commitlint) for enforcing conventional commit messages
- [semantic-release](https://github.com/semantic-release/semantic-release) for full automation of versioning and releasing of npm packages

Hope this post was helpful! I'm also keen to hear your ideas on the topic so feel free to comment.