DEV Community

Michael Heap
Michael Heap

Posted on • Originally published at michaelheap.com

Introducing action-router

If you’re building a GitHub Action that contains multiple triggers that have slightly different actions, you may find yourself writing code that looks like the following:

// We're working with PRs
if (tools.context.event == "pull_request") {
  if (tools.context.payload.action == "opened") {
    // Some logic for opened PRs
    handleOpenedPr(tools)
  }
  if (tools.context.payload.action == "labeled") {
    // Some logic for labelled PRs
    handleLabels(tools)
  }

  handleAnyPrEvent(tools)
}

// But we also want the label functionality to work for issues
if (
  tools.context.event == "issue" &&
  tools.context.payload.action == "labeled"
) {
  handleLabels(tools)
}
Enter fullscreen mode Exit fullscreen mode

After I found myself writing code like the above repeatedly, I realised that what my more complex actions were missing was a router. Something to work out what the event type and subtype are and delegate to another method. I ended up building action-router which allows you to do the following:

router(
  {
    "issue.labeled": [handleLabels],
    "pull_request.opened": [handleOpenedPr],
    "pull_request.labeled": [handleLabels],
    pull_request: [handleAnyPrEvent],
  },
  [tools]
)
Enter fullscreen mode Exit fullscreen mode

The router expects anything that’s callable, which means that so long as it can be called as a function you can require the code, define functions in the same file or even pass anonymous functions directly.

router({
  pull_request: [require("./allPr")],
  "pull_request.opened": [handleOpenedPr],
  "pull_request.labeled": [
    tools => {
      tools.github.removeLabel({ owner, repo, name })
    },
  ],
})
Enter fullscreen mode Exit fullscreen mode

All of the methods that match the event type and subtype are run concurrently. This means that in the first router example both handleOpenedPr and handleAnyPrEvent would run together whenever a pull_request is opened. The results of these methods are returned as an array of promises, which means you can run the following:

const results = await router({
  "issue.labeled": [handleLabels],
})

// Results is an array of results. results[0] will be the return value of `handleLabels`
console.log(results)
Enter fullscreen mode Exit fullscreen mode

I’ve used the router in anger on a few actions now and it’s definitely reducing the amount of boilerplate code I’m writing. If you’re interested in giving it a go, installation and usage instructions are available on GitHub

Top comments (0)