DEV Community

Cover image for 🤖 Automatically publish your Node package to NPM (with PNPM and GitHub actions)
Andreas Riedmüller
Andreas Riedmüller

Posted on

🤖 Automatically publish your Node package to NPM (with PNPM and GitHub actions)

I'm excited to share my experience in setting up GitHub Actions for automatically publishing a package. While I use pnpm as an alternative to npm, this guide should also work for you if you if you use npm instead.

The Setup

The core of this setup lies in a specific YAML file located in the .github/workflows directory of the repository.

Here is a detailed description of each section in the file. I will add the contents of the full file at the end of this article.

1. Name

name: Release components package
Enter fullscreen mode Exit fullscreen mode

Specifies the name of the GitHub Action workflow, this is what you will see in the GitHub user interface:

The GitHub user interface

2. Trigger

on:
  release:
    types: [published]
Enter fullscreen mode Exit fullscreen mode

This defines the event that triggers the workflow. This workflow is triggered when a release is published.

Initially, I used types: [created] but I encountered issues with triggering the action. It turns out that creating a draft release before the actual release will not trigger created
GitHub Docs: Events that trigger workflows

3. Jobs

jobs:
  build:
    runs-on: ubuntu-latest
Enter fullscreen mode Exit fullscreen mode

This workflow defines one single job named "build" that runs on the latest Ubuntu virtual environment.

4. Steps

Step 1: Checkout Repository

    steps:
      - name: Checkout
        uses: actions/checkout@v4
Enter fullscreen mode Exit fullscreen mode

The checkout@v4 action clones the repository into the runner.

Step 2: Install pnpm

      - uses: pnpm/action-setup@v2
        name: Install pnpm
        with:
          version: 8
          run_install: false
Enter fullscreen mode Exit fullscreen mode

Sets up pnpm in the GitHub runner using pnpm/action-setup@v2 to install pnpm version 8. The run_install: false parameter prevents automatic installation of dependencies at this stage.

🚀 The pnpm setup must precede the Node.js setup to take advantage of the cache: pnpm property in the setup-node action. This will significantly speed up build times by reusing the stored dependencies across workflow executions.

Step 3: Install Node.js

      - name: Install Node.js
        uses: actions/setup-node@v3
        with:
          node-version-file: ".nvmrc"
          cache: pnpm
          registry-url: https://registry.npmjs.org
Enter fullscreen mode Exit fullscreen mode

Run actions/setup-node@v3 to install Node.js. While it is not mandatory in this case I like to explicitly define to read the node version from the ".nvmrc" file. It also sets up caching for pnpm.

💡 A crucial discovery was the need to explicitly set the registry URL in the Node.js setup for npm. Otherwise you will run into an authentication issue.

Step 4: Install Dependencies

      - name: Install dependencies
        run: pnpm install --frozen-lockfile
Enter fullscreen mode Exit fullscreen mode

Executes pnpm install --frozen-lockfile to install dependencies based on the lock file, ensuring consistent versions across builds. This is the equivalent to npm ci which is like npm install but based on the lock file.

Step 5: Publish Package

      - name: Publish 🚀
        shell: bash
        run: pnpm publish packages/components --access public --no-git-checks
        env:
          NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
Enter fullscreen mode Exit fullscreen mode

Runs the command to publish the package. In my case the package is inside a monorepo and located in packages/components.

The --access public flag has to be set explicitly in the command line the first time you publish a public package to npm. This is to prevent private packages from being published accidentally.

The --no-git-checks flag is needed to bypass an issue with pnpm related to GitHub's detached HEAD state during releases.

Authentication is provided via NODE_AUTH_TOKEN, set to a value from a GitHub secret called NPM_TOKEN.

💡 An interesting hiccup occurred with the naming of the authentication environment variable. While using NPM_TOKEN seemed correct (it kind of worked), it had to be NODE_AUTH_TOKEN for the action to work correctly. https://github.com/npm/cli/issues/1637#issuecomment-1888709776

Authentication and Security

The GitHub repository's Secrets feature is used to store the npm token. To get this token you need to log in to your npm account and generate a token that with permission to publish a new version of your package.

I chose a "Granular Access Token" that is valid for a limited time and has access to all packages of my organization.

The npm user interface

Conclusion

Automating package publishing with GitHub Actions was generally straightforward, yet it required some fine-tuning to address certain issues. These included configuring workflow triggers, optimizing caching mechanisms, managing authentication tokens, and navigating the nuances of npm and GitHub environments for seamless operation.

Hope you found this guide helpful. Happy coding, and have a great day! 🚀👨‍💻👩‍💻


And here is full content of my YAML file (tested and working):

name: Release components package

on:
  release:
    types: [published]

jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - name: Checkout
        uses: actions/checkout@v4

      - uses: pnpm/action-setup@v2
        name: Install pnpm
        with:
          version: 8
          run_install: false

      - name: Install Node.js
        uses: actions/setup-node@v3
        with:
          node-version-file: ".nvmrc"
          cache: pnpm
          registry-url: https://registry.npmjs.org

      - name: Install dependencies
        run: pnpm install --frozen-lockfile

      - name: Publish 🚀
        shell: bash
        run: pnpm publish packages/components --access public --no-git-checks
        env:
          NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
Enter fullscreen mode Exit fullscreen mode

Top comments (0)