Automating testing, building and publishing of TypeScript libraries
Karolis
Posted on February 17, 2019
It doesn't matter whether you are working on a side project, small open source library or your full-time job project, automating builds, tests and releases can greatly improve your life. You can then concentrate on code quality, features or just have a small break when you finish a task instead of trying to remember all the required steps to make a release.
In my previous article I demonstrated how to set up a self-hosted CI/CD solution with Drone. You don't need a powerful CI server or expensive VMs to run it, you can easily get one running on your laptop to perform these tasks in the background a lot faster than the free alternatives while also getting much greater flexibility.
Now, I would like to share some practical pipelines that I have recently implemented.
A short disclaimer: I don't identify myself as an experienced TypeScript/JavaScript developer, I always lean to Go but in this case I needed to write some JavaScript so it was a great opportunity to finally try out TypeScript :) The package itself can be found here, it's a simple library that allows you to receive webhooks inside your app without exposing it to the internet.
Testing the library
My library tests were probably not what you find in a standard library. Since they rely on the SaaS service (to receive those public webhooks), it has to get some credentials from environment and perform asynchronous actions. That's where I learnt about done
callback:
it('should be able to forward the webhook', (done) => {
var payload = "payload-" + Math.floor((Math.random() * 100000) + 1);
// creating a handler
var handler = function (data: string) {
var msg = JSON.parse(data);
if (msg.type === 'status' && msg.status == 'subscribed') { // <---- once received, send a webhook
var dispatchWebhook = function() {
axios.post('https://my.webhookrelay.com/v1/webhooks/9c1f0997-1a34-4357-8a88-87f604daeca9', payload)
.then(function (response) {
expect(response.status).to.equal(200)
})
}
setTimeout(dispatchWebhook, 1000)
}
if (msg.type === 'webhook' && msg.body === payload) {
expect(msg.method).to.equal('POST');
done(); // <---- once webhook received, end the test case
client.disconnect();
}
}
var client = new WebhookRelayClient(key, secret, [testBucket], handler)
client.connect(); // <---- connecting so our handler will be called
});
While this not really related to automation, it might be useful for someone :)
Building the library
When using Drone, everything runs in a Docker container. The main benefit of this is that it becomes trivial to get a reproducible builds. In our case, the first step includes:
- Install dependencies
- Build with
tsc
(TypeScript needs to be converted back to JavaScript) - Run tests
Our Drone file looks like:
kind: pipeline
name: default
steps:
- name: build
image: node:latest
environment: # supplying environment variables for testing
RELAY_KEY:
from_secret: relay_key
RELAY_SECRET:
from_secret: relay_secret
RELAY_BUCKET: ws-client-tests
commands:
- npm install
- npm run build
- make test
here, npm run build
is actually just:
"scripts": {
"build": "tsc"
},
And in the Makefile make test
:
test:
./node_modules/mocha/bin/mocha --reporter spec --compilers ts:ts-node/register src/*.test.ts
Publishing to npm registry
It's always good to automate publishing packages as well. This way you will get a good release process for almost zero effort. When you are happy with the package functionality, you just tag a Github release and Drone will build, test and publish your package to npm registry:
- name: publish
image: node:latest
environment:
NPM_TOKEN:
from_secret: npm_token
commands:
- make drone-publish
when:
event: [ tag ]
environment variable NPM_TOKEN
is a token that you can generate for your account.
make drone-publish
command looks like:
drone-publish:
echo "//registry.npmjs.org/:_authToken=${NPM_TOKEN}" > ~/.npmrc
npm publish
It is important to set that .npmrc
file as the publishing won't work without it. Strange? yes.
Bonus: Notifications
This last step is repeated across all my Drone pipelines, it's a notification to a Slack channel:
- name: slack
image: plugins/slack
when:
status: [ success, failure ]
settings:
webhook:
from_secret: slack_url
channel: general
username: drone
For this to work, get your Slack's webhook URL and create a slack_url
secret.
Wrapping up
It takes 30-90 minutes to set up everything initially and once you have a CI system running, subsequent repositories can be added in seconds. Even if you think that running npm run build
and npm publish
takes only 1 minute of your time every time you release, automating this process will greatly improve your developer experience and life in general :) Combining automated builds and releases with tests will ensure that there is only one path to getting your package published. I have seen many cases where a TypeScript package build step was missed and the previous version was released. Or, after a 'quick fix' and push to registry the package was broken because someone forgot to run the test. Or, just consider that in the next year you might do 200 releases that would end up in many hours saved by automation!
Posted on February 17, 2019
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.