If you've ever released an npm package manually, you know how repetitive it can be. You have to bump the version, update the changelog, create a Git tag, push everything to GitHub, publish to npm, and maybe even draft a release. Mess up one step, and things can get messy fast.
That's why automating this process with GitHub Actions is a game-changer. It takes care of versioning, publishing, and creating releases - all without you lifting a finger. In this post, I'll walk you through setting up a GitHub Action that does it all for you.
Let's dive in! 🚀

🔐 Setup: Creating and Adding Required Tokens

1️⃣ Create an npm token

  • We need to create and add an npm token in the GitHub repository secrets to publish packages from the workflow.

  • Follow the official docs: Creating and Viewing Access Tokens

2️⃣ Create a GitHub Repository Secret

  • In your GitHub repository, go to Settings → Secrets and variables → Actions.

  • Click on New repository secret.

  • Name the secret NPM_TOKEN.

  • Paste the npm token as the secret value.

  • Click Add secret.

3️⃣ GitHub Token (Created Automatically)

  • GitHub provides an authentication token for GitHub Actions, allowing secure API requests.

  • More info: GitHub Token Documentation

🔄 Workflow Overview
📌 Triggering the Action
The action runs when a PR is merged into main and has the release label.
It checks if the PR includes a version bump label (patch, minor, or major).
🔼 Version Bumping

  • Based on the label, the script updates the package version using pnpm version.
  • The new version is committed and pushed back to the repository. 🏗️ Building and Publishing
  • The package is built using pnpm build.
  • It is then published to npm using pnpm publish. 🏷️ Creating a GitHub Release
  • Once published, the workflow tags the new version and creates a GitHub release.
  • This makes the new version easily accessible and trackable for users.

🛠️ GitHub Actions Workflow (YAML)

name: Release & Publish

on:
  pull_request:
    types:
      - closed
    branches:
      - main

jobs:
  release:
    if: >-
      github.event.pull_request.merged == true &&
      contains(join(github.event.pull_request.labels.*.name, ','), 'release')
    runs-on: ubuntu-latest
    steps:
      - name: Checkout repository
        uses: actions/checkout@v3

      - name: Setup Node.js
        uses: actions/setup-node@v3
        with:
          node-version: "lts/*"
          registry-url: "https://registry.npmjs.org/"
        env:
          NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}

      - name: Setup pnpm
        run: |
          corepack enable
          pnpm install
          echo "pnpm setup complete."

      - name: Determine version bump type
        id: version_bump
        run: |
          labels=("${{ join(github.event.pull_request.labels.*.name, ' ') }}")

          if [[ "${labels[@]}" == *patch* ]]; then
            echo "RELEASE_TYPE=patch" >> $GITHUB_ENV
          elif [[ "${labels[@]}" == *minor* ]]; then
            echo "RELEASE_TYPE=minor" >> $GITHUB_ENV
          elif [[ "${labels[@]}" == *major* ]]; then
            echo "RELEASE_TYPE=major" >> $GITHUB_ENV
          else
            echo "No valid release label found. Exiting..."
            exit 1
          fi

      - name: Setup git user
        run: |
          git config user.name ""
          git config user.email ""
          echo "Git user setup complete."

      - name: Bump version
        run: |
          pnpm version ${{ env.RELEASE_TYPE }} --no-git-tag-version
          git add package.json
          git commit -m "ACTION: Bump version to $(node -p "require('./package.json').version")"
          git push

      - name: Build package
        run: |
          pnpm build

      - name: Publish package
        run: |
            pnpm publish --access public --no-git-checks
        env:
            NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}

      - name: Get new version
        id: package_version
        run: echo "VERSION=$(jq -r .version package.json)" >> $GITHUB_ENV

      - name: Create GitHub Release
        uses: softprops/action-gh-release@v1
        with:
          tag_name: v${{ env.VERSION }}
          name: Release v${{ env.VERSION }}
          generate_release_notes: true
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

🔧 Key GitHub Actions Used

  • actions/checkout@v3 → This action checks out your repository so that the workflow can access your code.
  • actions/setup-node@v3 → Sets up a Node.js environment, specifying the version and npm registry authentication.
  • softprops/action-gh-release@v1 → Automatically creates a GitHub Release with the new version tag and release notes.

🔀 Alternative: Raising a PR Instead of Pushing Directly to Main

If you don't want the workflow to commit version bumps and changes directly to main, you can configure it to create a pull request instead. This keeps your main branch clean and allows for review before merging.

🛠 Using peter-evans/create-pull-request

Modify your workflow to use the peter-evans/create-pull-request action:

yamlCopy- name: Create Pull Request for Version Bump
  uses: peter-evans/create-pull-request@v7
  with:
    branch: release/version-bump
    title: "Bump package version to ${{ env.VERSION }}"
    body: "This PR updates the package version to ${{ env.VERSION }}."
    commit-message: "chore: bump package version to ${{ env.VERSION }}"

🎉 Wrapping Up

Automating npm package releases with GitHub Actions can save time, reduce errors, and make your workflow seamless. By setting up this automation, you ensure that every release is consistent, well-documented, and instantly available to your users.
Whether you push changes directly to main or prefer a review process with a pull request, this setup gives you flexibility and control over your releases.
Now it's your turn! Have you automated your npm releases? Do you have any improvements or additional steps in your workflow? Drop your thoughts in the comments! 🚀