DEV Community

Cover image for Automating testing, building and publishing of TypeScript libraries
Karolis for Webhook Relay

Posted on • Edited on

Automating testing, building and publishing of TypeScript libraries

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
  });
Enter fullscreen mode Exit fullscreen mode

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

Enter fullscreen mode Exit fullscreen mode

here, npm run build is actually just:

"scripts": {
    "build": "tsc"
  },
Enter fullscreen mode Exit fullscreen mode

And in the Makefile make test:

test:
    ./node_modules/mocha/bin/mocha --reporter spec --compilers ts:ts-node/register src/*.test.ts
Enter fullscreen mode Exit fullscreen mode

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 ]
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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!

Top comments (1)

Collapse
 
sirtimbly profile image
Tim Bendt

Have you tried semantic-release? I've used it lately and it's setup process was a breeze, it installs itself into TravisCI and does a lot of good automation, very similar to what you've outlined here