Autodeploy javascript library to npmjs via Github Actions

oleksiikhr

Oleksii Khrushch

Posted on September 12, 2021

Autodeploy javascript library to npmjs via Github Actions

Overview

It's time to simplify and automate the release of our js library in npmjs using Github Actions.

Node.js 14 version with yarn was used

Project initialization

  • Let's create a project called javascript-library-autodeploy.
mkdir javascript-library-autodeloy && cd javascript-library-autodeploy
Enter fullscreen mode Exit fullscreen mode
  • Initialize package.json and add a couple of libraries for our library to work.
yarn init
yarn add -D webpack@5.51.1 webpack-cli@4.8.0 eslint@7.32.0 jest@27.1.0
Enter fullscreen mode Exit fullscreen mode

Yes, this example will use Javascript Linter - ESLint and for running tests - Jest. We all write tests, right? :)

  • Final version of package.json.
{
  "name": "@alexeykhr/javascript-library-autodeploy",
  "version": "1.0.0",
  "license": "MIT",
  "scripts": {
    "dev": "webpack --mode=development",
    "build": "webpack --mode=production",
    "test": "jest",
    "lint": "eslint src"
  },
  "devDependencies": {
    "eslint": "^7.32.0",
    "jest": "^27.1.0",
    "webpack": "^5.51.1",
    "webpack-cli": "^4.8.0"
  }
}
Enter fullscreen mode Exit fullscreen mode
  • Done, now let's add some logic to our library. In this example, it will be a simple function to add two numbers.
// src/index.js

function sum(a, b) {
    return a + b;
}

module.exports = { sum };
Enter fullscreen mode Exit fullscreen mode
  • And we will immediately cover this complex logic with tests.
// tests/sum.test.js

const { sum } = require('../src/index');

test('1 + 2 = 3', () => {
    expect(sum(1, 2)).toBe(3);
});
Enter fullscreen mode Exit fullscreen mode

Automating via Github Actions

In this step, we will create 2 Github Workflows.

The first one will be executed after each code change in the Github repository, in any branch, and the second one will push our build into npmjs and Github Packages after the release.

  • Let's create the first Workflow to constantly check our code for a working build, Code Style and tests.
# .github/workflows/library.yml

name: Library

on: push

jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v2

      - uses: actions/setup-node@v2
        with:
          node-version: '14.x'

      - name: Get yarn cache directory path
        id: yarn-cache-dir-path
        run: echo "::set-output name=dir::$(yarn cache dir)"

      - uses: actions/cache@v2
        id: yarn-cache
        with:
          path: ${{ steps.yarn-cache-dir-path.outputs.dir }}
          key: ${{ runner.os }}-yarn-${{ hashFiles('yarn.lock') }}
          restore-keys: |
            ${{ runner.os }}-yarn-

      - run: yarn
      - run: yarn lint
      - run: yarn test
      - run: yarn build
Enter fullscreen mode Exit fullscreen mode

The workflow is as follows:

  • Calling trigger on push event
  • Installing the latest Ubuntu
  • Receive the code from Github
  • Installing Node.js version 14
  • Install js libraries and cache them until the yarn.lock file changes
  • Run a static code analyzer, tests and build

At this stage, we have already automated most of the work for checking Pull Requests.

  • Now let's create a second Workflow, which will release the build.
# .github/workflows/release.yml

name: Release

on:
  release:
    types: [published]

jobs:
  library:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v2

      - uses: actions/setup-node@v2
        with:
          node-version: '14.x'
          registry-url: 'https://registry.npmjs.org'

      - name: Get yarn cache directory path
        id: yarn-cache-dir-path
        run: echo "::set-output name=dir::$(yarn cache dir)"

      - uses: actions/cache@v2
        id: yarn-cache
        with:
          path: ${{ steps.yarn-cache-dir-path.outputs.dir }}
          key: ${{ runner.os }}-yarn-${{ hashFiles('yarn.lock') }}
          restore-keys: |
            ${{ runner.os }}-yarn-

      - run: yarn

      - run: npm publish --access public
        env:
          NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}

      - uses: actions/setup-node@v2
        with:
          registry-url: 'https://npm.pkg.github.com'

      - run: npm publish
        env:
          NODE_AUTH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
Enter fullscreen mode Exit fullscreen mode

The logic of this workflow is very similar to the previous workflow. The only difference is that it fires on the release event, and instead of executing linters and tests,npm publish is called for npmjs and Github Packages.

But to work correctly, we need to add Github Secrets to this repository called NPM_TOKEN.

You don't need to add GITHUB_TOKEN, it is already enabled by default - About the GITHUB_TOKEN secret

generate-npm-token

  • Add it to our repository under the key NPM_TOKEN.

Settings > Secrets > New Secret

npm-token

Finalizing the result

Ready! Now we can upload our code to the Github repository and immediately see that the first Workflow is launched.

git-push

Now let's try to create a new release. To do this, on the right sidebar, click on Releases, and then Create a new release.

first-release

The release event was triggered, and now we can watch how our project is being built and uploaded to the npmjs resource.

github-workflow-release

Done, here is our library: @alexeykhr/javascript-library-autodeploy

You can also notice that the library has appeared in the Packages sidebar block.

Bonus: Vuepress documentation

And of course, how can it be without documentation, I took the first thing that came to hand - this is Vuepress.

The documentation will live in the current repository, and of course, the build process will be done for us by Github.

  • Let's start by adding a docs folder and initializingpackage.json.
mkdir docs && cd docs && yarn init
yarn add -D vuepress@1.8.2
Enter fullscreen mode Exit fullscreen mode

Yes, we make a separate package.json so that the library versions do not conflict with each other (as, for example, webpack with a different major version). This way the library will not affect our core libraries in any way.

{
  "license": "MIT",
  "scripts": {
    "dev": "vuepress dev .",
    "build": "vuepress build ."
  },
  "devDependencies": {
    "vuepress": "^1.8.2"
  }
}
Enter fullscreen mode Exit fullscreen mode
  • Let's add a simple docs/README.md file that will display the contents of the file.
# VuePress

<<< @/../package.json
Enter fullscreen mode Exit fullscreen mode
  • And some settings for Vuepress.
// docs/.vuepress/config.js

const { version, name } = require('../../package');

const parts = name.split('/');

module.exports = {
    title: `Version ${version}`,
    base: `/${parts[parts.length - 1]}/`
}
Enter fullscreen mode Exit fullscreen mode

My library name is the same as the name of the Github repository, so the base url is taken from package.json

This is enough to be able to build something, and for an explicit display of the build, we are publishing the version of the library in the documentation.

  • Now let's update our Github Workflows.

In .github/workflows/library.yml let's change the trigger so that it doesn't fire when we just edit the documentation.

on:
  push:
    paths:
      - '*/**'
      - '!docs/**'
Enter fullscreen mode Exit fullscreen mode

And in .github/workflows/release.yml add another job.

docs:
  runs-on: ubuntu-latest
  defaults:
    run:
      working-directory: ./docs
  steps:
    - uses: actions/checkout@v2

    - uses: actions/setup-node@v2
      with:
        node-version: '14.x'

    - name: Get yarn cache directory path
      id: yarn-cache-dir-path
      run: echo "::set-output name=dir::$(yarn cache dir)"

    - uses: actions/cache@v2
      id: yarn-cache
      with:
        path: ${{ steps.yarn-cache-dir-path.outputs.dir }}
        key: ${{ runner.os }}-yarn-docs-${{ hashFiles('yarn.lock') }}
        restore-keys: |
          ${{ runner.os }}-yarn-docs-

    - run: yarn
    - run: yarn build

    - name: Commit changes
      working-directory: ./docs/.vuepress/dist
      run: |
        git config --global user.name "github-actions"
        git config --global user.email "github-actions@github.com"
        git init
        git add -A
        git commit -m "deploy"

    - name: Push changes
      uses: ad-m/github-push-action@master
      with:
        github_token: ${{ secrets.GITHUB_TOKEN }}
        branch: gh-pages
        force: true
        directory: ./docs/.vuepress/dist
Enter fullscreen mode Exit fullscreen mode

The logic is as follows:

  • Runs yarn install inside thedocs folder
  • Then the documentation is built
  • The entire build is uploaded using force push under the gh-pages branch

And these 2 jobs will run in parallel.

  • Add new code, update the library version and push to Github.
  • Add one more release to execute a new job
  • It remains to add the gh-pages branch to Github Pages to display the documentation

github-pages

Conclusion

Congratulations, we have automated a lot of our work, now there is no need to write huge scripts, what and why to perform at release. Sometimes forget to deploy code in npmjs :)

But you can also automate even more, if, for example, you write according to Conventional Commits, you can also automate the creation of tags using the standard-version library.

And use all sorts of tools, like actions/labeler.

Reference

💖 💪 🙅 🚩
oleksiikhr
Oleksii Khrushch

Posted on September 12, 2021

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related