Automate npm publishing with GitHub Actions, proper changelog, and release notes
Jan Halama
Posted on June 22, 2022
If you maintain at least one Node.js package, you probably know how painful the releases can be. Maybe running npm publish
works for you just fine, but there are so many little details one can forget: running tests with clean dependencies, keeping the changelog up to date, creating release notes… Maintaining high quality of releases is difficult, especially in a team. Usually, it makes sense to automate the release process.
At Superface, we use GitHub Actions to automate the release workflow for npm packages. When we designed the process, we had the following requirements:
- Start the release manually with an appropriate version bump (
patch
,minor
,major
,prepatch
,preminor
,premajor
, orprerelease
) - Support for manually maintained changelog file (using Keep a Changelog conventions)
- Automatically tag the package version in the repository and publish release notes with GitHub Release
- Publish the package to npm registry with appropriate distribution tags (
latest
for stable versions,next
orbeta
for pre-release versions with) - Automatically publish prerelease versions on certain branches for beta testing
Since new releases cause churn for package users, we want to be certain our release notes are usable. That's why we prefer a handcrafted changelog following the Keep a Changelog convention over automatic release notes generated from commit messages.
Unlike Conventional Commits and semantic-release, we let the developer choose the release type (major
, minor
, patch
) and the time at which the package is released.
The following steps will guide you through the setup of automated npm package releasing:
- Generate a new npm access token
- Save npm access token as a GitHub secret
- Add GitHub Workflow to your repository
- Release the npm package
Generate a new npm access token
First we need an access token for npm registry in order to publish package with GitHub Actions. Create a new access token at npmjs.com for Automation
and copy it for the next step.
Save npm access token as a GitHub secret
We will store the generated token for GitHub Actions as a repository secret. In your GitHub repository settings, visit "Secrets" → "Actions", click "New repository secret", and add the npm access token created in the previous step. We will name the secret NPMJS_ACCESS_TOKEN
.
Add GitHub workflow to your repository
In your project, create a new GitHub workflow file called release_package.yml
, in the .github/workflows
directory. Paste the following code. Optionally, you can change user.email
and user.name
under "Git configuration" step and uncomment the "Run tests" step along with the "Install dependencies" step.
Finally, commit the file and push the changes to your main branch.
name: Release package
on:
workflow_dispatch:
inputs:
release-type:
description: 'Release type (one of): patch, minor, major, prepatch, preminor, premajor, prerelease'
required: true
jobs:
release:
runs-on: ubuntu-latest
steps:
# Checkout project repository
- name: Checkout
uses: actions/checkout@v2.3.4
# Setup Node.js environment
- name: Setup Node.js
uses: actions/setup-node@v2
with:
registry-url: https://registry.npmjs.org/
node-version: '14'
# Install dependencies (required by Run tests step)
#- name: Install dependencies
# run: yarn install
# Tests
#- name: Run tests
# run: yarn test
# Configure Git
- name: Git configuration
run: |
git config --global user.email "41898282+github-actions[bot]@users.noreply.github.com"
git config --global user.name "GitHub Actions"
# Bump package version
# Use tag latest
- name: Bump release version
if: startsWith(github.event.inputs.release-type, 'pre') != true
run: |
echo "NEW_VERSION=$(npm --no-git-tag-version version $RELEASE_TYPE)" >> $GITHUB_ENV
echo "RELEASE_TAG=latest" >> $GITHUB_ENV
env:
RELEASE_TYPE: ${{ github.event.inputs.release-type }}
# Bump package pre-release version
# Use tag beta for pre-release versions
- name: Bump pre-release version
if: startsWith(github.event.inputs.release-type, 'pre')
run: |
echo "NEW_VERSION=$(npm --no-git-tag-version --preid=beta version $RELEASE_TYPE
echo "RELEASE_TAG=beta" >> $GITHUB_ENV
env:
RELEASE_TYPE: ${{ github.event.inputs.release-type }}
# Update changelog unreleased section with new version
- name: Update changelog
uses: superfaceai/release-changelog-action@v1
with:
path-to-changelog: CHANGELOG.md
version: ${{ env.NEW_VERSION }}
operation: release
# Commit changes
- name: Commit CHANGELOG.md and package.json changes and create tag
run: |
git add "package.json"
git add "CHANGELOG.md"
git commit -m "chore: release ${{ env.NEW_VERSION }}"
git tag ${{ env.NEW_VERSION }}
# Publish version to public repository
- name: Publish
run: yarn publish --verbose --access public --tag ${{ env.RELEASE_TAG }}
env:
NODE_AUTH_TOKEN: ${{ secrets.NPMJS_ACCESS_TOKEN }}
# Push repository changes
- name: Push changes to repository
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
git push origin && git push --tags
# Read version changelog
- id: get-changelog
name: Get version changelog
uses: superfaceai/release-changelog-action@v1
with:
path-to-changelog: CHANGELOG.md
version: ${{ env.NEW_VERSION }}
operation: read
# Update GitHub release with changelog
- name: Update GitHub release documentation
uses: softprops/action-gh-release@v1
with:
tag_name: ${{ env.NEW_VERSION }}
body: ${{ steps.get-changelog.outputs.changelog }}
prerelease: ${{ startsWith(github.event.inputs.release-type, 'pre') }}
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
Release the npm package
Update the changelog
Before you proceed to trigger a new release, add notable changes to the changelog file. Our workflow requires that you stick with the Keep a Changelog convention.
You can start with this empty CHANGELOG.md
file:
# Changelog
All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [Unreleased]
### Added
- Changelog
As you commit changes to your project, write them down into the [Unreleased]
section. Once you trigger a new release, unreleased changes are automatically moved under the new version heading.
Trigger new release
You can find the “Run workflow” button in your GitHub repository under “Actions” → “Release package”.
Our worfklow_dispatch event takes a single parameter named release type
(major
, minor
, patch
, …). The parameter is passed to the npm version command in order to bump version of the package.
Once you press the “Run workflow” button, all the magic will happen, and your npm package will be released.
Conclusion
Our workflow automates the boring parts, while keeping us in control regarding when and how to release new packages' versions. Support for the Keep a Changelog convention and GitHub Releases help us communicate changes to developers.
You can see the workflow in action in most of our package repositories, for example OneSDK for Node.js and Superface CLI.
We are curious about how you manage your releases.
Is your process fully automated, or do you just run npm publish
locally? Do you maintain a changelog manually or generate it from commit messages? Share your approach in the comments! 👇
Posted on June 22, 2022
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.