Autodeploy javascript library to npmjs via Github Actions
Oleksii Khrushch
Posted on September 12, 2021
Overview
It's time to simplify and automate the release of our js library in npmjs using Github Actions.
- Project initialization
- Automating via Github Actions
- Finalizing the result
- Bonus: Vuepress documentation
- Conclusion
- Reference
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
- 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
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"
}
}
- 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 };
- 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);
});
- And add a couple more files: .eslintrc.js, .npmignore, .gitignore
- Done, there is a small project that can be built, added tests, linter, etc.
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
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 }}
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
- Go to the page https://www.npmjs.com/settings/SET_YOUR_LOGIN/tokens
- Click on
Generate New Token
- Choosing a type
Automation
- Add it to our repository under the key
NPM_TOKEN
.
Settings > Secrets > New Secret
Finalizing the result
Ready! Now we can upload our code to the Github repository and immediately see that the first Workflow
is launched.
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.
The release
event was triggered, and now we can watch how our project is being built and uploaded to the npmjs resource.
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
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"
}
}
- Let's add a simple
docs/README.md
file that will display the contents of the file.
# VuePress
<<< @/../package.json
- 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]}/`
}
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/**'
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
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
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
Posted on September 12, 2021
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.