DEV Community

Cover image for Effortless Automation: Configuring CI/CD for NPM Library Publishing with GitHub Actions
Kevin Lactio Kemta
Kevin Lactio Kemta

Posted on

Effortless Automation: Configuring CI/CD for NPM Library Publishing with GitHub Actions

Day 013 - 100DaysAWSIaCDevopsChallenge

Recently, I developed an npm package for JavaScript and TypeScript projects. Initially, I manually handled the publishing process, which involved testing, compiling, and publishing the library using various npm commands. This approach was time-consuming, required significant effort, and left little room for error—I had to remember the exact sequence of commands and their specific options. Recognizing the inefficiency, I decided to automate the publishing process.

In this article, I’ll walk you through the steps I took to automate the process and streamline my workflow.

By the end, you’ll have a clear understanding of how to efficiently automate your npm package publishing with GitHub Actions. The process will involve the following steps:

  1. Create a GitHub Workflow to automate Linting, building, and Testing process for every push events - We'll start by setting up a GitHub Actions workflow that automatically runs linting, builds your project, and executes tests each time code is pushed to the repository. This ensures that any issues are caught early in the development process, maintaining the integrity of your code source.

  2. Create the second Workflow to automate the Release creation and Publishing proccess for every tag push events - Next, we’ll create a dedicated workflow to handle the release process. This workflow will trigger when a new tag is pushed, automatically compiling the code, updating version numbers, and publishing the package to npm. This step removes the manual intervention from the release cycle, making it more efficient and less prone to errors.

  3. Configure Github Actions (Setting up NPM Access Tokens) - To enable GitHub Actions to publish your package to npm, we’ll configure the necessary secrets within your repository. This involves generating an npm access token and securely storing it in your GitHub repository settings, allowing for seamless and secure automated publishing.

  4. Update README.md by adding the build badges status - Finally, we’ll update your project’s README.md file to include build status badges. These badges provide instant visual feedback on the status of your project’s workflows, showing whether your builds are passing, if tests are successful, and the overall health of your code source.

Flow diagram

Image description

Create a GitHub Workflow to automate Linting, building, and Testing

This step consists of creating a workflow that allows me to automate the linting, testing, and building processes for every push event. By doing this, we ensure that any potential errors or overlooked issues are caught early in development, ensuring that the code is clean and ready before considering deployment.

Create a new file named ci.yml in the following directory: .github/workflows, and add this content:

Github Actions trigger


name: CI-Build
on:
  push:
    branches:
      - "**"
    tags-ignore:
      - "v*"
  pull_request:
    types:
      - closed
    branches:
      - master


Enter fullscreen mode Exit fullscreen mode
  • name - The name of the pipeline
  • on - The configuration of event trigger. In my case,
    • The workflow is triggered by any push event to any branch (branch: "*"), *except for tags starting with "v" (tags-ignore: "v*").
    • It is also triggered when a pull request is closed on the master branch.
Jobs: linting code


# ....
    jobs:
    eslint:
        name: Check Syntax with ESLint
        runs-on: ubuntu-22.04
        if: github.event_name == 'push' && github.actor.name != 'github-actions[bot]'
        strategy:
          matrix:
            node-version: [ 18.x, 20.x, 22.x ]
        steps:
        - name: Checkout code style
            uses: actions/checkout@v4
        - name: Use Node.js ${{ matrix.node-version }} to check Lint
            uses: actions/setup-node@v4
            with:
            always-auth: 'false'
            cache: 'npm'
            node-version: ${{ matrix.node-version }}
        - name: Install Dependencies
            run: npm ci
        - name: Run ESLint
            run: |
            npm run lint


Enter fullscreen mode Exit fullscreen mode
  • This job defines an automated process to check the code's syntax using ESLint↗ whenever certain conditions are met (like a push event). It runs on multiple Node.js versions (strategy.matrix.node-version: [ 18.x, 20.x, 22.x ]) to ensure compatibility and helps maintain code quality by catching issues early.
  • if: github.event_name == 'push' && github.actor.name != 'github-actions[bot]' - This condition checks if the event that triggered the workflow is a push, and if the person who triggered the event is not the github-actionsbot.
Jobs: Testing


  tests:
    if: github.event_name == 'push' && github.actor.name != 'github-actions[bot]'
    name: Run Tests
    needs: eslint
    runs-on: ubuntu-22.04
    strategy:
      matrix:
        node-version: [ 18.x, 20.x, 22.x ]
    steps:
      - name: Checkout code
        uses: actions/checkout@v4
      - name: Use Node.js ${{ matrix.node-version }} to run Tests Covering
        uses: actions/setup-node@v4
        with:
          always-auth: 'false'
          node-version: ${{ matrix.node-version }}
          cache: "npm"
      - name: Install Dependencies
        run: npm ci
      - name: Run Tests Command (Jest-CI)
        run: npm test


Enter fullscreen mode Exit fullscreen mode
  • This job, tests, is designed to run the project's tests across multiple Node.js versions (18.x, 20.x, and 22.x). The job is executed after the eslint job completes successfully, ensuring that only clean, lint-free code is tested. The job checks out the code, sets up the appropriate Node.js environment, installs dependencies, and then runs the test suite.
Jobs: Building


jobs:
  # {...} <-- Linting job
  buildDist:
    if: github.event_name == 'push' && github.actor.name != 'github-actions[bot]'
    name: Build-to-JS
    needs: [ eslint ]
    runs-on: ubuntu-22.04
    strategy:
      matrix:
        node-version: [ 18.x, 20.x, 22.x ]
        arch:
          - x64
          # - x86
    steps:
      - name: Checkout code
        uses: actions/checkout@v4
      - name: Use Node.js ${{matrix.node-version}} to run build dist on ${{ matrix.arch }}
        uses: actions/setup-node@v4
        with:
          cache: "npm"
          node-version: ${{ matrix.node-version}}
      - name: Install dependencies
        run: npm ci
      - name: Run Build Dist
        run: npm run build


Enter fullscreen mode Exit fullscreen mode
  • This job, buildDist, is designed to compile the project's distribution files (dist) after linting and testing have successfully completed. It does so across different Node.js versions (18.x, 20.x, and 22.x). The job uses caching for npm dependencies to improve speed and reliability.

  • The job runs conditionally, only on push events and only if the workflow wasn't triggered by GitHub's automated bot account, ensuring that it only executes in relevant contexts.

Full code



name: CI-Build
on:
  push:
    branches:
      - "**"
    tags-ignore:
      - "v*"
  pull_request:
    types:
      - closed
    branches:
      - master
jobs:
  eslint:
    name: Check Syntax with ESLint
    runs-on: ubuntu-22.04
    if: github.event_name == 'push' && github.actor.name != 'github-actions[bot]'
    strategy:
      matrix:
        node-version: [ 18.x, 20.x, 22.x ]
    steps:
      - name: Checkout code style
        uses: actions/checkout@v4
      - name: Use Node.js ${{ matrix.node-version }} to check Lint
        uses: actions/setup-node@v4
        with:
          always-auth: 'false'
          cache: 'npm'
          node-version: ${{ matrix.node-version }}
      - name: Install Dependencies
        run: npm ci
      - name: Run ESLint
        run: |
          npm run lint
  buildDist:
    if: github.event_name == 'push' && github.actor.name != 'github-actions[bot]'
    name: Build-to-JS
    needs: [ eslint, tests ]
    runs-on: ubuntu-22.04
    strategy:
      matrix:
        node-version: [ 18.x, 20.x, 22.x ]
        arch:
          - x64
    steps:
      - name: Checkout code
        uses: actions/checkout@v4
      - name: Use Node.js ${{matrix.node-version}} to run build dist on ${{ matrix.arch }}
        uses: actions/setup-node@v4
        with:
          cache: "npm"
          node-version: ${{ matrix.node-version}}
      - name: Install dependencies
        run: npm ci
      - name: Run Build Dist
        run: npm run build
  tests:
    if: github.event_name == 'push' && github.actor.name != 'github-actions[bot]'
    name: Run Tests
    needs: eslint
    runs-on: ubuntu-22.04
    strategy:
      matrix:
        node-version: [ 18.x, 20.x, 22.x ]
    steps:
      - name: Checkout code
        uses: actions/checkout@v4
      - name: Use Node.js ${{ matrix.node-version }} to run Tests Covering
        uses: actions/setup-node@v4
        with:
          always-auth: 'false'
          node-version: ${{ matrix.node-version }}
          cache: "npm"
      - name: Install Dependencies
        run: npm ci
      - name: Run Tests Command (Jest-CI)
        run: npm test


Enter fullscreen mode Exit fullscreen mode

Create the second Workflow to automate the Release creation and Publishing proccess

This step consists of creating a workflow that automates the release and publishing processes for every tag push event. Let's dive into the code and explore how I automated these processes.

First, create a new file named release.yaml in the .github/workflows directory.

Github Action trigger


name: Release
on:
  create:

permissions:
  contents: write


Enter fullscreen mode Exit fullscreen mode
  • on.create: - The workflow is triggered by the creation of a tag, branch, or release.
  • permissions.contents: write - The workflow is allowed to write to the repository's contents, which is essential for automating tasks like tagging, updating files, or committing changes as part of the release process. In this case we need the content write permission to create a new Github Release.
Jobs: Github releasing


jobs:
  release:
    if: ${{ startsWith(github.ref, 'refs/tags/v') }}
    name: Release co2mjs
    runs-on: ubuntu-latest
    steps:
      - name: Create release
        env:
          TAG_NAME: ${{ github.ref_name }}
          GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
        run: |
          gh release create "$TAG_NAME" \
             --repo="$GITHUB_REPOSITORY" \
             --title="${TAG_NAME#v}" \
             --generate-notes


Enter fullscreen mode Exit fullscreen mode

This job automates the creation of a release in a GitHub repository whenever a new tag starting with "v" is pushed.

  • if: ${{ startsWith(github.ref, 'refs/tags/v') }} - This line ensures that the job only runs if the event is triggered by pushing a tag that starts with "v" (e.g., v1.0.0). This line very important, because remember that the workflow is triggered by the creation of a tag, branch, or release.

  • GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} - This sets the GitHub token required to authenticate the gh command. GitHub automatically provides this token, and it is securely stored in secrets.GITHUB_TOKEN

  • The job uses the gh (GitHub CLI) command to create the release, setting the title and generating release notes based on the tag.

Jobs: Github publishing


jobs:
  publish-npm:
    if: ${{ startsWith(github.ref, 'refs/tags/v') }}
    name: "Publish the new version to the npmjs"
    runs-on: ubuntu-22.04
    needs: release
    steps:
      - name: Checkout Code
        uses: actions/checkout@v4
      - name: Use Node.js
        uses: actions/setup-node@v4
        with:
          cache: "npm"
          node-version: 18.x
          architecture: x64
          registry-url: https://registry.npmjs.org/
      - name: Run NPM Install
        run: npm ci && npm run build
      - name: Publish
        run: |
          cd dist
          npm publish --access public
        env:
          NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}


Enter fullscreen mode Exit fullscreen mode
  • The publish-npm job automates the process of publishing a new version of the package to npm whenever a new tag starting with "v" is pushed. It relies on the successful completion of the release job and performs the following actions: checks out the code, installs dependencies and builds the project, and finally publishes the built package to npm.

⚠️⚠️

  • NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} - Provides the authentication token for npmjs.com. This token is stored in GitHub secrets (⚠️repository⚠️ section) to securely authenticate the npm publish command.
  • registry-url: https://registry.npmjs.org/ - Sets the npm registry URL to the default public npm registry.

Full code



name: Release
on:
  create:
permissions:
  contents: write
jobs:
  release:
    if: ${{ startsWith(github.ref, 'refs/tags/v') }}
    name: Release co2mjs
    runs-on: ubuntu-latest
    steps:
      - name: Create release
        env:
          TAG_NAME: ${{ github.ref_name }}
          GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
        run: |
          gh release create "$TAG_NAME" \
             --repo="$GITHUB_REPOSITORY" \
             --title="${TAG_NAME#v}" \
             --generate-notes
  publish-npm:
    if: ${{ startsWith(github.ref, 'refs/tags/v') }}
    name: "Publish the new version to the npmjs"
    runs-on: ubuntu-22.04
    needs: release
    steps:
      - name: Checkout Code
        uses: actions/checkout@v4
      - name: Use Node.js
        uses: actions/setup-node@v4
        with:
          cache: "npm"
          node-version: 18.x
          architecture: x64
          registry-url: https://registry.npmjs.org/
      - name: Run NPM Install
        run: npm ci && npm run build
      - name: Publish
        run: |
          cd dist
          npm publish --access public
        env:
          NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}


Enter fullscreen mode Exit fullscreen mode

Configure Github Actions

To enable GitHub Actions to publish your package to npm, you'll need to configure the necessary access tokens.

1. Create an NPM Access Token
  1. Log in to your NPM account
  2. Navigate to your account settings
    1. Access Tokens
    2. click Create New Token
    3. choose Automation as the token type

Image description

  1. copy the generated token

Image description

2. Add the Token to Github Secrets
  1. Click on the Settings tab in your repository.
  2. In the left sidebar, select Secrets and variables and then Actions.
  3. Click New repository secret
  4. Name the secret NPM_TOKEN and paste the npm access token you copied earlier into the Value field.

Image description

Update README.md by adding the build badges status

To provide visibility into the status of your GitHub Actions jobs, you can add build status badges to your README.md file. Insert the following block into your README to display the status of the build and release workflows:



##### 🚦 Build Status
![CI workflow](https://github.com/<OWNER>/<REPO>/actions/workflows/ci-pipeline.yml/badge.svg?branch=master)
![Release workflow](https://github.com/<OWNER>/<REPO>/actions/workflows/release.yml/badge.svg)
___


Enter fullscreen mode Exit fullscreen mode

Now that everything is set up, you can test the workflow by following these steps:

  1. Create a New Branch and Push Changes


git checkout -b feature/your-branch-name # Create and switch to a new branch
touch ./nothing.txt && echo "some text here" >> ./nothing.txt # Create a new file and add some content
git add ./nothing.txt # Stage the new file
git commit -m "my changes" # Commit the changes
git push origin feature/your-branch-name # Push the branch to the remote repository


Enter fullscreen mode Exit fullscreen mode
  1. Monitor the Actions Tab:
    • Go to the Actions tab of your repository to observe the CI and release workflows in action.
    • Result

Image description

  1. Create and Merge a Pull Request

  2. Create and Push a New Tag



git checkout master # Switch to your default branch
npm version patch # this command update package.json and package-lock.json, commit the changes and then, create a new tag locally.
# output : v1.0.1 - or a different version number based on your package.json
git push origin v1.0.1 # Push the new tag to the remote repository


Enter fullscreen mode Exit fullscreen mode

This will create new release and publish a new version to npmjs.com.

Image description

Github release ==> https://github.com/nivekalara237/co2mjs/releases
NPM Package ==> https://www.npmjs.com/package/co2m.js

__

🥳✨
We have reached the end of the article.
Thank you so much 🙂


Your can find the full source code on GitHub Repo↗

Top comments (0)