In this article, we’ll create a custom reusable GitHub Action using TypeScript. As covered in our article on reusing GitHub workflows and steps, GitHub Actions allow you to automate your software development workflows. By creating a custom GitHub Action, you can extend the functionality of GitHub Actions to suit your specific needs.
We will create a simple GitHub Action to replace GitHub’s broken Pull Request review process. This custom GitHub Action will automatically approve Pull Requests that meet specific criteria.
Start with GitHub’s Action template
GitHub provides a template for creating a new GitHub Action using TypeScript. You can use this template to get started quickly. The template includes the necessary files and structure to create a new GitHub Action. Anything unnecessary can be removed or modified to suit your requirements.
Follow the instructions in the template’s README to set up your action.
- Click the Use this template button at the top of the repository
- Select Create a new repository
- Select an owner and name for your new repository
- Click Create repository
- Clone your new repository
Check out your new repository, go to the cloned repo directory, and make sure your node version matches the one in the
.node-version
file. If you’re using nvm (Node Version Manager),
you can run:
cp .node-version .nvmrc
nvm install
node --version
Note: Our example is based on this commit of the template repository.
Implement your custom GitHub Action
Install the template’s dependencies with npm install
. In addition, install the @actions/github
package with
npm install @actions/github
.
The template provides a basic structure for your GitHub Action. Update the action.yml
file with your action’s name,
description, author, and updated inputs/outputs.
name: 'Code review'
description: 'Improved code review process'
author: 'Victor on Software'
# Add your action's branding here. This will appear on the GitHub Marketplace.
branding:
icon: 'heart'
color: 'red'
# Define your inputs here.
inputs:
github-token:
description: 'GitHub secret token'
required: true
runs:
using: node20
main: dist/index.js
In this example, our new action will have a single input, github-token
, the GitHub secret token to use the GitHub API.
Next, we can modify the code in the src/main.ts
file to implement our custom logic.
import * as core from '@actions/core'
import * as github from '@actions/github'
import { readFileSync } from 'fs'
/**
* The main function of the action.
* @returns {Promise<void>} Resolves when the action is complete.
*/
export async function run(): Promise<void> {
try {
// Get the PR number from the payload. This action is only intended for PRs.
const prNumber = github.context.payload.pull_request?.number
if (!prNumber) {
return
}
// Get the required reviewer from the REVIEWERS file.
// This simplified example assumes that the REVIEWERS file contains a single reviewer.
// In a real-world scenario, we would need to parse the REVIEWERS file at the top directory
// of our changed files to get the reviewers.
const reviewer = readFileSync('REVIEWERS', 'utf8').trim()
if (!reviewer) {
core.setFailed('No reviewer found in REVIEWERS file')
return
}
// Get all the reviews for this PR.
const githubToken = core.getInput('github-token')
const octokit = github.getOctokit(githubToken)
const reviews = await octokit.rest.pulls.listReviews({
owner: github.context.repo.owner,
repo: github.context.repo.repo,
pull_number: prNumber
})
// Check if the required reviewer has approved the PR.
// This action does not require the reviewer to re-approve the PR if new changes are pushed.
let approved = false
reviews.data.forEach(review => {
if (review.user?.login === reviewer && review.state === 'APPROVED') {
approved = true
}
})
// Fail the workflow run if the required reviewer has not approved the PR.
if (!approved) {
core.setFailed(`Reviewer ${reviewer} needs to approve the PR`)
return
}
} catch (error) {
// Fail the workflow run if an error occurs
if (error instanceof Error) core.setFailed(error.message)
}
}
We read the REVIEWERS
file to get the required reviewer for the PR. We then retrieve all the reviews for the pull
request and check if the needed reviewer has approved the PR. If the reviewer has not approved the PR, we fail the run.
Build your custom GitHub Action
To build your action, run npm run bundle
. This will compile the TypeScript code in the src/
directory and output the
JavaScript code in the dist/
directory. The exact command for bundle
is defined in the package.json
file.
The bundle
step is required before you can test your action locally or use it in a workflow because the action.yml
file points at the dist/index.js
bundled version of your code.
This step can easily be forgotten, and you may wonder why your changes are not reflected in the action. You can
configure our IDE to do npm run bundle
automatically on save, create a check as a
git pre-commit hook to ensure the dist/
directory is
up-to-date, or rely on the Check Transpiled Javascript
workflow in the .github/workflows/
directory.
Now, commit your changes and push them to your repository.
Note: We will not cover testing in this article, but you should add tests to the __tests__/
directory for your
source code. For this example, we must refactor the code to make it more testable.
Test your custom GitHub Action in another repository
Add workflows and REVIEWERS file
You can use the uses
keyword in a workflow file to try your action in another repository. Create a new workflow file
in the repository’s .github/workflows/
directory where you want to test your action. For example:
name: Code review
on:
workflow_dispatch: # Manual (for debug)
pull_request:
jobs:
code-review:
name: Code review
runs-on: ubuntu-latest
steps:
- name: Checkout
id: checkout
uses: actions/checkout@v4
- name: Code review action
id: test-action
uses: getvictor/code-review-demo@main # Your action goes here
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
We are using @main
as the version of the action to test the latest code on the main branch. You can replace this with
a specific version tag or branch name.
In addition, create a REVIEWERS
file at the repository’s root with the required reviewer’s GitHub username.
Notice that the above workflow runs on pull_request
events but not on pull_request_review
events. This is because
GitHub Actions treats the pull_request
workflow runs and the pull_request_review
workflow runs as distinct. Instead,
we want the same workflow run to be triggered by both events. This is a common pitfall when working with GitHub Actions.
We need to add another workflow file that listens for pull_request_review
events and triggers the pull_request
workflow run to fix this.
name: Rerun checks after review
on:
pull_request_review:
types:
- submitted
- dismissed
jobs:
rerun_checks:
name: Rerun specified checks
permissions:
actions: write
runs-on: ubuntu-latest
steps:
- name: Rerun Checks
uses: shqear93/rerun-checks@v3
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
check-names: 'Code review'
Configure a GitHub rule to require code review
Create a branch protection rule in the repository settings that requires the above Code review
workflow to pass in a
PR before merging to your default branch.
Create a pull request
Commit your changes to a new branch and create a pull request. The Code review
workflow should run automatically and
show the Failed status.
Once the required reviewer approves the PR, the Rerun checks after review
workflow will run and trigger the
Code review
workflow. The rerun should pass, and you should be able to merge the PR.
Clean up your custom GitHub Action repo
As an optional step, you can clean up your custom GitHub Action repository:
- Update README.md with instructions on how to use your new action
- Remove the
src/wait.ts
file and associated tests - Update workflows in the
.github/workflows/
directory
Further reading
In a previous article, we described
what happens in a GitHub pull request after a git merge
.
In another article, we covered how to use GitHub Actions for general-purpose tasks.
Example code on GitHub
The code for our simple GitHub Action is available on GitHub: https://github.com/getvictor/code-review-demo
Watch how to create a custom GitHub Action using TypeScript
Note: If you want to comment on this article, please do so on the YouTube video.