devops
Use GitHub Actions to Automatically Publish a Repo Subfolder as an npm Library
Publish a project subfolder to npm with just a package.json and a GitHub Actions workflow.
Introduction
I've said it before, and I'll say it again, working as a software engineer for a startup is fun and challenging because of the sheer number of different things I get to do on a regular basis.
I work for an Internet of Things startup called Blues which specializes in getting IoT data from devices in the real world into the cloud via cellular. To help show our customers all the possibilities of what they can do with our hardware (Notecards) and our cloud (Notehub), a group of my coworkers and I have been building lots of JavaScript-based web apps to monitor and display IoT data for a variety of different use cases.
A little more about Notecards and Notehub.io
Notecards are low-power, 30mm x 35mm, prepaid cellular-connected devices designed to integrate with any IoT device, and Notecards know, out of the box, how to connect with the Notehub cloud to send as well as receive data from it.
Notehub.io is a cloud-based service for securely connecting to Notecard devices and storing data from them before sending it on to your cloud application of choice.
It quickly became apparent that rewriting the same HTTP calls in multiple projects to fetch data from the native Notehub API endpoints wasn’t easy to reuse or keep up to date as the API continues to grow and evolve.
So we created our own open source, JavaScript-based library for the Notehub API and published it on npm for anyone to use for free. Notehub JS is designed to get you connected to the Notehub API quickly, and allow you to access all of the API routes relevant to interacting with Notehub in a JavaScript-friendly way.
Figuring out how to build an open source library like this and publish it to npm was a first for me, and I learned a lot of interesting things along the way that I'd like to share with you all over the course of a few blog posts in the hope that it might help you if you decide to undertake a similar effort.
In this blog, I'll show you how to set up a GitHub Actions workflow to automatically publish a new release of a GitHub repo to npm - no muss, no fuss, no manual input required.
Notehub JS
Ok, before we get to the GitHub Actions workflow, I want to give you a little background on the Notehub JS project because it's a bit unusual.
As I mentioned above, Notehub JS is a JavaScript-based library for interacting with the native Notehub API, and it's actually generated from the Notehub project's own openapi.yaml
file, which follows the OpenAPI specification standards.
OpenAPI Specification
The OpenAPI Specification (OAS) defines a standard, language-agnostic interface to HTTP APIs which allows both humans and computers to discover and understand the capabilities of the service without access to source code, documentation, or through network traffic inspection. When properly defined, a consumer can understand and interact with the remote service with a minimal amount of implementation logic.
An OpenAPI definition can then be used by documentation generation tools to display the API, code generation tools to generate servers and clients in various programming languages, testing tools, and many other use cases.
OpenAPI Generator CLI
Because the Notehub API has an openapi.yaml
file that is updated regularly, I was able to use the OpenAPI Generator CLI to automatically generate a JavaScript-based library to interact with the Notehub API in just a few commands from a terminal.
The catch is, when the new folder is generated which has all the API endpoints and models, the documents, and the dist/
folder that packages everything up for consumption as an npm module, it's stored as a subfolder inside of the main Notehub JS repo.
The openapi.yaml
file lives at the root level of the project along with its config.json
file, a package.json
file, and a few other other bits and bobs (license, contribution guidelines, code of conduct, etc.), but the real meat of the library lives inside of the src/
subfolder.
Here's a simplified view of the Notehub JS repo's folder structure:
root/
├── .github/
| ├── workflows/
| | ├── publish-npm.yml
├── src/ <- this is the folder generated by the OpenAPI Generator CLI
| ├── dist/
| ├── docs/
| ├── src/
| | ├── api/
| | ├── model/
| | ├── index.js
| openapi.yaml
| config.json
| package.json
From the diagram above you can see the openapi.yaml
file that this library is generated from lives at the root of the repo, and the src/
folder is what actually holds all the Notehub API JavaScript code that powers the Notehub JS library.
This src/
folder is what needs to be published to npm - not the root of the repo as is the case with most projects in GitHub. But not to worry, publishing just this subfolder can be done, and better yet, it can be automated.
Configure the root package.json
to point to the subfolder where the Notehub JS library code lives
So now that I've covered why I only need to publish the subfolder of the Notehub JS repo to npm, let's get on to the how.
First thing: add a few extra configuration details to the package.json
at the root of the project.
Update the main
field to point to the subfolder's index.js
file
According to the npm docs, the main
field points to the primary entry point of an app, and when it's not specifically defined it defaults to index.js
at the root of the project.
Since the src/dist/
folder is where the index.js
file lives for Notehub JS library code that should be uploaded to npm, main
needs to be updated to point to it instead.
Now the package.json
's main
field should read:
"main": "./src/dist/index.js"
Update repository
to include a directory
field
Next, the repository
field in a package.json
specifies where the code lives, and it's generally helpful for people who want to contribute to the project.
It has another benefit though: if the package.json
for the npm package is not in the root directory (like for us, where the auto-generated src/
folder has its own package.json
), you can specify the directory in which that file lives.
Here's what the repository
field should look like:
"repository": {
"type": "git",
"url": "https://github.com/blues/notehub-js.git",
"directory": "src"
}
What this translates to on the npm package page is a handy link that points back to root of the GitHub folder (see image below).
Add the publishConfig
field to specify directory
Typically, the publishConfig
field is used to set config values that will be used at publish time like tags, registries, or access.
Another field that can be added here is directory
, which customizes the published subdirectory relative to the current package.json
. This ensures npm knows the package.json
it needs to read from (and display) in the npm package page is inside of the auto-generated src/
folder, not at the root of the project.
Add the following publishConfig
code to the package.json
like so:
"publishConfig": {
"registry": "https://registry.npmjs.org",
"directory": "./src"
}
In the end, the whole package.json
at the root of the Notehub JS repo looks like the code snippet below.
NOTE: I've omitted parts of the
package.json
for clarity, but if you'd like to see the whole file, you can click the file name below. It's linked to the actual file in the repo in GitHub.
{
"name": "notehub-js",
"description": "JavaScript library for accessing the Blues Wireless Notehub API endpoints",
"main": "./src/dist/index.js",
"repository": {
"type": "git",
"url": "https://github.com/blues/notehub-js.git",
"directory": "src"
},
"publishConfig": {
"registry": "https://registry.npmjs.org",
"directory": "./src"
}
}
Great, the changes to the package.json
to publish just the src/
folder to npm are complete, let's move on to the GitHub Actions workflow to make publishing a new version of the Notehub JS library to npm as automated as possible.
Create a GitHub Actions workflow to publish to npm
Need a refresher on GitHub Actions?
If you want a quick primer on what GitHub Actions are, I recommend you check out a previous article I wrote about them here.
I decided that for my use case, creating a new release made the most sense to trigger a GitHub Actions workflow to publish the Notehub JS src/
subfolder to npm.
GitHub specifically defines a release as a deployable software iteration that you can package and make available for a wider audience to download and use, which is exactly what I want.
Once I had settled on this as the trigger for my GitHub Actions workflow, it was a pretty straightforward set of steps to publish to npm.
Here's what the finished publish-npm.yml
file will look like inside of the ./github/workflows/
folder - I'll break it all down afterwards.
name: Publish notehub-js to npm
on:
release:
types: [created]
jobs:
npm:
runs-on: ubuntu-latest
defaults:
run:
working-directory: ./src
steps:
- name: Check out code
uses: actions/checkout@v3
- name: Setup .npmrc file to publish to npm
uses: actions/setup-node@v3
with:
node-version: "16.x"
registry-url: "https://registry.npmjs.org"
- name: Install dependencies
run: npm ci
- name: Publish to npm
run: npm publish
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
Each workflow file needs a name
, so I chose a descriptive one: Publish notehub-js to npm
.
Next, as I already explained above, this workflow is triggered whenever a new release is created in GitHub, which is where
on:
release:
types: [created]
comes into play.
on
is how a workflow is triggered.release
is the event that triggers the workflow.types: [created]
is the activity type for arelease
event that triggers the workflow. This gives us more fine-grained control of when the workflow should run.
Then the jobs
section runs inside of the workflow. This particular script only has one job, npm
, but if there's multiple jobs, they'll run sequentially unless otherwise specified.
The npm
job defines that it runs on the latest version of Ubuntu in runs-on
.
defaults.run
is an important part of the script because it's where I specify that the working directory for all the following steps is the src/
folder (because I don't need any of the files from the root of the repo, just what's inside the src/
folder, I can define this at the job level).
Finally, I get to the steps
.
The steps are as follows:
- Check out the code so the workflow can access it using the GitHub Action
actions/checkout@v3
. - Download a Node.js version to build the package before publishing it to npm using the GitHub Action
actions/setup-node@v3
(specify thenode-version
andregistry-url
for npm here). - Install the project's dependencies stored in the
src/
folder'spackage.json
file usingnpm ci
. - Publish to npm by running the command
npm publish
and supplying it with anNPM_TOKEN
(generated in npm) to verify this workflow has permissions to publish to the specified npm package registry.
And there you have it: each time a new release is created in the Notehub JS repo, this GitHub Actions workflow will run and deploy the updated code to npm.
Conclusion
Working as an engineer for a startup company is cool because no two days are alike for me. One day I'm building web apps and updating documentation on our developer experience site, the next I'm reading up on best practices for npm packages and how to automate tasks with GitHub Actions.
Venturing into the realm of open source software and publishing my first JavaScript package to npm was quite a learning experience for me, but I'm so grateful I had the chance to push myself into this space and create something useful for my team (and hopefully our users as well!).
One thing I knew I wanted, right from the start, was to automate the tasks I knew I'd be repeating fairly regularly over time: namely, regenerating the JavaScript library as the Notehub API continued to evolve, and redeploying a new version of the library to npm with the latest updates.
With some extra configurations in the Notehub JS repo's package.json
, I was able to focus in on the auto-generated subfolder that contains the useful JavaScript-based code for easily interacting with the Notehub API, and with a few GitHub Actions inside of a workflow, I was able to build the code for the package and publish it to npm with the click of a button. Not too bad for an end result.
Check back in a few weeks — I’ll be writing more about the useful things I learned while building this project in addition to other topics on JavaScript, React, IoT, or something else related to web development.
Thanks for reading. I hope learning how to target a specific folder inside of a project and automating deployments to npm through GitHub Actions workflows come in handy for you in the future. Enjoy!
References & Further Resources
- Notehub JS GitHub repo
- notehub-js npm library
- OpenAPI Generator CLI
Want to be notified first when I publish new content? Subscribe to my newsletter.