Skip to content

Commit a4cf4d3

Browse files
authored
Add script for validating PR title (#4050)
1 parent e0dbd33 commit a4cf4d3

14 files changed

Lines changed: 172 additions & 79 deletions

.github/labeler.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ iOS:
66
- any-glob-to-any-file:
77
- 'platform/ios/**/*'
88
- 'platform/darwin/**/*'
9-
cpp-core:
9+
core:
1010
- changed-files:
1111
- any-glob-to-any-file:
1212
- 'src/**/*'
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
import test from "node:test";
2+
import { validateTitle } from "./validate-pr-title.ts";
3+
4+
test("validateTitle - missing Android label", (t) => {
5+
t.assert.deepEqual(validateTitle({
6+
labels: new Set(["android"]),
7+
title: "make a change"
8+
}), {
9+
mismatches: ["android"],
10+
ok: false
11+
});
12+
});
13+
14+
test("validateTitle - android required and title contains Android", (t) => {
15+
t.assert.deepEqual(validateTitle({
16+
labels: new Set(["android"]),
17+
title: "make change to Android"
18+
}), {
19+
ok: true
20+
});
21+
});
22+
23+
test("validateTitle - only core required when label contains core", (t) => {
24+
t.assert.deepEqual(validateTitle({
25+
labels: new Set(["core", "android"]),
26+
title: "core: make some change"
27+
}), {
28+
ok: true
29+
});
30+
});
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
#!/usr/bin/env node
2+
3+
// This script validates that a PR title also contains its labels
4+
// in the title. This is a requirement, because this makes it easier
5+
// to parse commit messages. For example, for generating a changelog
6+
// for a particular platform.
7+
// Usage:
8+
// PR_TITLE="Make some changes to Android" PR_LABELS="android" node validate-pr-title.ts
9+
10+
export function validateTitle({
11+
title,
12+
labels
13+
}: {
14+
title: string,
15+
labels: Set<string>
16+
}): { ok: true } | { ok: false, mismatches: string[] } {
17+
// these labels are the ones we care about
18+
const keywords = new Set(['node', 'core', 'ios', 'android', 'qt']);
19+
20+
// when labels includes core, only 'core' is required
21+
const requiredKeyWords = labels.has('core')
22+
? new Set(['core'])
23+
: labels.intersection(keywords);
24+
25+
title = title.toLocaleLowerCase();
26+
27+
const actualKeywords = new Set();
28+
for (const keyword of requiredKeyWords) {
29+
if (title.includes(keyword)) actualKeywords.add(keyword);
30+
}
31+
32+
const mismatches = requiredKeyWords.difference(actualKeywords);
33+
34+
if (mismatches.size === 0) return { ok: true };
35+
return { ok: false, mismatches: [...mismatches] };
36+
}
37+
38+
if (import.meta.main) {
39+
if (!process.env.PR_TITLE) {
40+
console.log('::error::PR_TITLE environment variable is not set');
41+
process.exit(1);
42+
}
43+
44+
if (!process.env.PR_LABELS) {
45+
console.log('::error::PR_LABELS environment variable is not set');
46+
process.exit(1);
47+
}
48+
49+
const title = process.env.PR_TITLE;
50+
const labelsString = process.env.PR_LABELS.toLowerCase();
51+
const labels = new Set(labelsString.split(',').map(l => l.trim()).filter(l => l));
52+
53+
const result = validateTitle({
54+
title,
55+
labels
56+
});
57+
if (result.ok) {
58+
console.log('✅ PR labels match the title');
59+
process.exit(0);
60+
}
61+
62+
if (!result.ok) {
63+
if (result.mismatches.length > 0) {
64+
console.log(`::error::PR title does not match PR labels. Mismatched: ${result.mismatches.join(', ')}. Title: "${process.env.PR_TITLE}". Labels: "${process.env.PR_LABELS}"`);
65+
process.exit(1);
66+
}
67+
}
68+
}

.github/workflows/android-ci.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,7 @@ jobs:
6969
node-version-file: ".nvmrc"
7070

7171
- name: npm install
72-
run: npm install --ignore-scripts
72+
run: npm ci --ignore-scripts
7373
working-directory: .
7474
shell: bash
7575

.github/workflows/typecheck-scripts.yml

Lines changed: 0 additions & 18 deletions
This file was deleted.
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
name: Validate PR title
2+
3+
on:
4+
pull_request:
5+
types: [labeled, unlabeled, opened, edited]
6+
7+
jobs:
8+
validate-tags-match-title:
9+
runs-on: ubuntu-latest
10+
steps:
11+
- name: Checkout
12+
uses: actions/checkout@v4
13+
14+
- uses: actions/setup-node@395ad3262231945c25e8478fd5baf05154b1d79f # v4
15+
with:
16+
node-version-file: ".nvmrc"
17+
18+
- name: Check if labels match PR title
19+
env:
20+
PR_TITLE: ${{ github.event.pull_request.title }}
21+
PR_LABELS: ${{ join(github.event.pull_request.labels.*.name, ',') }}
22+
run: node .github/scripts/validate-pr-title.ts
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
name: validate-scripts
2+
3+
on:
4+
workflow_dispatch:
5+
push:
6+
pull_request:
7+
8+
jobs:
9+
validate-scripts:
10+
runs-on: ubuntu-latest
11+
steps:
12+
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v4
13+
with:
14+
persist-credentials: false
15+
16+
- uses: actions/setup-node@395ad3262231945c25e8478fd5baf05154b1d79f # v4
17+
with:
18+
node-version-file: ".nvmrc"
19+
20+
- name: Typecheck
21+
run: |
22+
npm install
23+
npx tsc -p tsconfig.json
24+
25+
- name: Run tests
26+
run: node --test .github/scripts/**.test.ts

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ local.properties
2020
.DS_Store
2121
.cache
2222
compile_commands.json
23+
.build
2324
/build
2425
/build-*
2526
**/.idea

.nvmrc

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
23
1+
24

package-lock.json

Lines changed: 13 additions & 12 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)