Faster GitHub Actions with Build and Tag Action
Tags: github, githubactions
Published: 2021-01-12 22:02:47

---
title: Faster GitHub Actions with Build and Tag Action
published: true
date: 2021-01-08 20:32:06 UTC
tags: github,githubactions
canonical_url: https://michaelheap.com/build-and-tag-action/
series: "Action Spotlight"
---

It’s weird saying that I have a favourite GitHub Action, but I do, and that action is JasonEtco’s [build-and-tag-action](https://github.com/JasonEtco/build-and-tag-action).

## Why is it important?

One of the biggest complaints about using the Docker runtime for Javascript based GitHub Actions is that they’re _slow_. Real slow. You can convert them to Node based actions fairly easily, but that adds another barrier - you need to start committing your `node_modules` folder as Actions doesn’t run `npm ci` automatically for you.

Trying to get the best of both worlds (no committed `node_modules` folder, but fast execution) I built a project named [auto-compile-node](https://github.com/mheap/github-action-auto-compile-node) which uses [@vercel/ncc](https://github.com/vercel/ncc) to build a single JS file containing an action and all of it’s dependencies whenever a release is tagged. This allowed me to use Docker for development, but still get the speed benefits whenever a release is tagged.

Jason took this one step further, generalising the build process to run `npm run build` and integrating automatic retagging of major versions (similar to [actions-tagger](https://github.com/Actions-R-Us/actions-tagger)).

## How does it work?

`build-and-tag-action` is designed to run when a `release` is `published` or `edited`. Your workflow should use `actions/checkout` to clone the code for that tag before `build-and-tag-action` [runs your setup script](https://github.com/JasonEtco/build-and-tag-action/blob/master/src/lib/index.ts#L21). If you don’t specify a `setup` script `build-and-tag-action` will [use a default](https://github.com/JasonEtco/build-and-tag-action/blob/master/action.yml#L11) of `npm ci && npm run build --if-present`. This installs all of your project’s dependencies (including development dependencies) and runs your `npm build` script if you have one defined.

If you want to use `build-and-tag-action` for building a JavaScript GitHub Action, your `build` script will likely contain `npx @vercel/ncc build`. This compiles your action and all it’s dependencies into a single file (the value of `main` in your `package.json`).

Once `npm run build` has finished executing, `build-and-tag-action` will [create a new commit](https://github.com/JasonEtco/build-and-tag-action/blob/master/src/lib/create-commit.ts#L12-L28) containing only `action.yml` and your `main` file and update the tag that triggered this workflow to point at that new commit. This means that the tag now points at a fully compiled JavaScript Action without any additional files (which reduces the time it takes to clone the action).

Finally, `build-and-tag-action` will [extract the major version from your tag](https://github.com/JasonEtco/build-and-tag-action/blob/master/src/lib/create-or-update-major-ref.ts#L9) - if you just tagged `v1.8.2` it will extract `v1` - and update the SHA that the `v1` tag points to. This means that your consumers can depend on `v1` and still get updates to the action.

`build-and-tag-action` expects that your `action.yml` contains `runs.using: node12` by default. This prevents you using Docker for branch based development. If you’d like to use Docker for branches and Node for tags, set your `build` script to `npx @vercel/ncc build && npx convert-action`. `convert-action` will automatically rewrite your `action.yml` file to use the Node runtime.

Here’s an example `package.json` that compiles your action and updates `action.yml` in a single command:

```
{
  "name": "your-action-name",
  "main": "dist/index.js",
  "scripts": {
    "build": "npx @vercel/ncc build && npx convert-action"
  }
}
```

All of the above assumes that you are creating a release manually or using the API with a Personal Access Token (PAT). If you’re using a workflow with actions that generate a release and want to use this action you can specify the `tag_name` input when calling `build-and-tag-action`:

```
- uses: fictional/releaser@v1 # Not a real action!
  id: releaser
- uses: JasonEtco/build-and-tag-action@v1
  with:
    tag_name: ${{ steps.releaser.outputs.tag_name }}
```

This is required as releases created using the `GITHUB_TOKEN` secret do not trigger workflow runs, so you need to use `build-and-tag-action` as an additional step and pass `tag_name` as an input. In this example the `releaser` step returns a `tag_name` output that we can use.

> `tag_name` is [automatically populated](https://github.com/JasonEtco/build-and-tag-action/blob/master/src/lib/get-tag-name.ts#L9) for the `release` event, so we do not need to pass any inputs to the workflow. If the `tag_name` input is provided, it will override the auto-detection

You can iterate on your actions and test them out using the Docker runtime on your `main` branch and benefit from the speed increase when using the Node runtime for any tagged releases using `build-and-tag-action` to run `@vercel/ncc` and `convert-action`.

In addition, `build-and-tag-action` keeps your major version branch (e.g. `v1`, `v2` etc) up to date with the latest minor and patch versions of your actions, reducing the effort it takes for your consumers to keep up to date. I use it on [all of my actions](https://github.com/marketplace?type=actions&query=mheap) and recommend that you do too!

## Useful Links

- [GitHub Marketplace](https://github.com/marketplace/actions/build-and-tag)
- [GitHub Repo](https://github.com/JasonEtco/build-and-tag-action)
- [Example Usage](https://github.com/JasonEtco/build-and-tag-action#usage)