DEV Community

Josh Aguilar
Josh Aguilar

Posted on • Edited on

Fully automating npm package releases

Automation

An "automated" npm release

Contents

Introduction

One of the challenges when trying to automate npm package releases is semantic versioning. It is quite common for developers to "manually" decide what the next version bump should be, which makes the decision biased and sentimental.

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

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 website.

Enforcing conventional commits

There are many tools available to help with enforcing conventional commits in your codebase. In this example, we'll be using husky and commitlint.

Install husky and commitlint by running the following command.

npm install --save-dev husky @commitlint/config-conventional @commitlint/cli

Then add the following configuration to your project's package.json file.

{
  "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.

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

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 and GitHub 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.

install --save-dev semantic-release

Then, add the following to the scripts section of your package.json file.

{
  "scripts": {
    "semantic-release": "semantic-release"
  }
}

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

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.

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

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 for commit hooks
  • commitlint for enforcing conventional commit messages
  • 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.

Top comments (0)