Want to learn how to build your own GitHub Actions? Click here
< Back to more bits

NPM

Action Router

#github-actions
router({
pull_request: [require("./allPr")],
"pull_request.opened": [handleOpenedPr],
"pull_request.labeled": [
(tools) => {
tools.github.removeLabel({ owner, repo, name });
},
],
});

Context

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);
}

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]
);

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 });
},
],
});

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);

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