Publishing a JavaScript Package to NPM automatically with Github Actions

Let the robots do the work

ยท

4 min read


Maintaining an open-source package can be a time-consuming task. Issues to be triaged, pull requests to be reviewed and changelogs to write. Publishing new versions of the code is usually done manually and making it automated is often on the back-burner of the maintainers' to-do list. There are a couple of key features of a rock-solid release process, the changelog, Git tags, NPM versions, and enforcing Semantic Versioning. Keeping all these in sync makes it so users understand changes in a release and understand how to keep up-to-date. Maintainers who fail to perform all of these steps will have a hard time triaging issues, which leads to more time debugging and less time spent coding. I recently came across a combo of tools, semantic-release and Github Actions, which made the entire release process automated, transparent, and simple to understand.

How It Works

Before we talk about implementation, it's important to understand what work our tools will perform. That way, if there are problems or modifications, we can fix them. semantic-release is going to do the majority of the work here, they say it best on their README.

It automates the whole package release workflow including determining the next version number, generating the release notes and publishing the package.

The Next Version Number

During a release, to determine the next version number, the tool reads commits since the last release. It knows your last release by looking at your Git tags. Based on the type of commit, it can determine how to bump up the version of the package. For this to work, commits need to be written in a certain way. By default, semantic-release uses the Angular Commit Message Conventions. This is critical because consumers of the package need to know if a new version releases a new feature, introduces breaking changes or both. For example, if someone commits fix(pencil): stop graphite breaking when too much pressure applied, semantic-release knows this contains a fix and to create a patch release. This will increase the version in the minor version range (0.0.x).

Never seen this type of versioning before? Check out Semantic Versioning.

After analyzing all the commits, it takes the highest priority type of change and makes sure that is the one that is applied. For example, if two commits were introduced since the last release, one breaking (x.0.0) and one minor (0.0.x), it would know to just up the version by breaking range.

Generating Release Notes

Once it has done finding out what type of release the next version is, changelog notes are generated based on the commits. semantic-release doesn't use conventional CHANGELOG.md file to notify users of what has changed, it does so with a Github Release which is attached to a Git tag.

An example of a Github Release that semantic-release generates and pushes on builds.

Automating With Github Actions

So semantic-release will be the tool to perform most of the work, but we still need a service to run the tool on. That is where Github Actions comes into play. When pull-requests are merged into master (or any base branch you configure), Github Actions will run a job that simply runs semantic-release with your configuration. All of the work we described previously will be performed.

An example of a Github Actions run using semantic-release to publish a new release.

Steps to Take

We will be using as many defaults as possible to make configuration dead simple. semantic-release uses a plugins system to enhance functionality. Here are the default plugins semantic-release uses.

Let's go over the steps which will make this all run smoothly.

  1. Add a dummy version property to the package.json of package. Released code will have the proper version written to this file by semantic-release.
        "version": "0.0.0-development",
  1. Add a new property to the package.json, publishConfig. This will be the home of our semantic-release configuration.
        "publishConfig": { "access": "public", "branches": ['master'] }
  1. The last step is to create a Github Action YAML file. This will tell Github Actions what to do when a commit is made to the repository.
        # .github/workflows/test-and-release.yaml

        name: Test and Release
        on: [push]

        jobs:
        test-and-release:
            name: Run tests and release
            runs-on: ubuntu-18.04
            steps:
            - name: Checkout
                uses: actions/checkout@v1
            - name: Setup Node.js
                uses: actions/setup-node@v1
                with:
                node-version: 12
            - name: Install dependencies
                run: npm ci
            - name: Run tests
                run: npm test
            - name: Release
                env:
                GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
                NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
                run: npm run semantic-release
  1. Add NPM_TOKEN to the secrets in the Github repos settings page. You can generate one of these from your NPM account at npmjs.com/settings//tokens

    screenshot of github repo settings screen

And that's it! You have a fully automated package release process ๐ŸŽ‰

Bonus

I implemented this on a repo we recently open-sourced at Yolk AI. It's named next-utils and everything described here can be found there.

{% github Yolk-HQ/next-utils %}

Another great thing about using semantic-release with Github Actions is that it has out-of-the-box support for bot comments. It will go into every issue and pull-request closed since the last release and comment to make sure everyone is aware. Here is an example:

{% github github.com/Yolk-HQ/next-utils/issues/12#iss.. %}

If you liked this post, check out more at https://blog.alec.coffee and signup for my newsletter

Did you find this article valuable?

Support Alec Brunelle by becoming a sponsor. Any amount is appreciated!

ย