Lately I’ve been involved with an Open Source project called AskQL. I really like the project, try to contribute as much as I can and learn a lot in the process.
One of the issues there was to enforce conventional commit names.
This looked pretty easy – there must be tons of github actions in the marketplace for it, right?
If you don’t know what github actions are here’s a one liner that should explain it:
Github actions are a group of instructions set in
It might come out as a two liner – depending on your screen resolution 😉yaml
files that are being ran on certain conditions like commit, push, pull request etc.
I googled and found several such actions. They all looked promising and I immediately implemented the first one I found.
It seemed relatively easy: just add a yaml file with the given example and it should work out of the box!
name: "Lint PR"
on:
pull_request:
types:
- opened
- edited
- synchronize
jobs:
main:
runs-on: ubuntu-latest
steps:
- uses: amannn/[email protected]
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
The yaml
file above is pretty simple. The procedure name is “Lint PR”. It fires on pull request open, edit and synchronize.
It eventually just needs to run on ubuntu and uses the github action from the marketplace with GITHUB_TOKEN as an environment variable.
Easy, right?
Table of Contents
Aw, Snap!
Well… seems like it works – if you are not working Open Source style. That is – if you are not working with forks.
Let me explain – the GITHUB_TOKEN is available only if you create a Pull Request directly from the main repository. If you are creating a Pull Request from a fork (as in most Open Source projects), GITHUB_TOKEN is not available.
It’s a well known issue, summarized nicely in this post: https://github.community/t/github-actions-are-severely-limited-on-prs/18179/8
So how does one lint the Pull Requests?
DIY script
It’s time to do the wrong thing – write my own script.
Because we wanted to use conventional commits, I turned to npm
.
A quick search found this really nice library called commitlint.
This library can lint a PR according to conventional commit standards. I created a simple script that gets a title and makes sure it is a valid conventional commit:
const load = require('@commitlint/load').default; | |
const lint = require('@commitlint/lint').default; | |
const CONFIG = { | |
extends: ['@commitlint/config-conventional'], | |
}; | |
function buildLintError(lintErrors) { | |
return lintErrors.map((error) => error.message); | |
} | |
export async function testTitle(title) { | |
const lintOptions = await load(CONFIG); | |
const lintResult = await lint( | |
title, | |
lintOptions.rules, | |
lintOptions.parserPreset ? lintOptions.parserPreset : {} | |
); | |
if (!lintResult.valid) { | |
throw new Error(buildLintError(lintResult.errors)); | |
} | |
} |
This script works pretty well locally. To make it work in a github action we’re going to need 3 more things.
#1 Get the PR Title
Github actions has a really nice SDK one can use to get all the data and processes one needs in order to build a fully-fledged CI/CD.
Using @actions/github
gives us access to the data we need. Let’s use it in our code:
const { testTitle } = require('./lintPR'); | |
const github = require('@actions/github'); | |
function getTitle() { | |
return github.context.payload.pull_request.title; | |
} | |
async function run() { | |
await testTitle(getTitle()); | |
} | |
run(); |
Our code gets the title of the PR and verifies it is a valid conventional commit. Hooray!
Now we are missing 2 more things.
#2 Throwing an error during the CI
Github actions has us covered here as well. Using the @actions/core
library we can easily set the CI status to failed:
const { testTitle } = require('./lintPR'); | |
const github = require('@actions/github'); | |
const core = require('@actions/core'); | |
function getTitle() { | |
return github.context.payload.pull_request.title; | |
} | |
async function run() { | |
await testTitle(getTitle()); | |
} | |
run().catch(e => core.setFailed(e)); |
Adding core.setFailed on line 13 to notify the CI process it should fail
This small addition will set this job’s status to failed so our CI will fail if our title is invalid.
And now for the final piece of the puzzle.
#3 Setting up the yaml file
This part can be a bit tricky. Eventually, we need to clone our repository (because we put our script in it) and install the needed dependnecies (@actions/core, @actions/github, @commitlint/config-conventional, @commitlint/load and @commitlint/lint).
After these two steps are done, we can move on to actually running our script.
Here’s the yaml
file for this task:
name: "Lint PR" | |
on: | |
pull_request: | |
types: | |
- opened | |
- edited | |
- synchronize | |
jobs: | |
main: | |
runs-on: ubuntu-latest | |
steps: | |
- uses: actions/checkout@v2 | |
- name: Install dependencies | |
run: npm install @actions/core @actions/github @commitlint/config-conventional @commitlint/lint @commitlint/load | |
- name: Checks the PR title | |
run: node ./scripts/useTestTitle |
Again – very simple. Checkout, install npm dependencies and run our script.
And it appears to be working! Figure 2 shows what happens with an invalid PR title, while Figure 3 shows the results of a valid one.
I guess that means mission accomplished right? Hooray!
You can view the PR here: https://github.com/xFAANG/askql/pull/230/files
Summary and Future plans
This task was really nice. I learned something new about github actions in addition to using new libraries I also didn’t know about before. All in all – a great day!
The issue that got me a-searching was that the marketplace actions did not work out of the box. The cause was GITHUB_TOKEN not being available for PR’s that are coming from forks.
I eventually wrote a script and ran it inside my own action – and just created the solution we needed for the project.
A future improvement would be to create the useTestTitle
as a package. This way, the github action using it would not need to install the dependencies (which take most of its run time). From there – why not just turn this code to an official action in the marketplace?
Hope you enjoyed the article 🙂
Thanks a lot for this article reviewer MichalKutz.Â