Skip to content

Commit 9db79ba

Browse files
committed
tools: add GitHub Action to ping code owners
Migrates the ping code owners functionality from the GitHub bot to a GitHub Action, as part of removing the bot infrastructure. Refs: #51236 Signed-off-by: Rahman Mahmutovic <mahmutovicrahman5@gmail.com>
1 parent 2edd842 commit 9db79ba

2 files changed

Lines changed: 105 additions & 0 deletions

File tree

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
name: Ping Code Owners
2+
3+
on:
4+
pull_request_target:
5+
types: [opened]
6+
7+
permissions:
8+
pull-requests: write
9+
contents: read
10+
11+
jobs:
12+
ping-owners:
13+
if: github.repository == 'nodejs/node'
14+
runs-on: ubuntu-slim
15+
steps:
16+
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
17+
with:
18+
persist-credentials: false
19+
20+
- name: Use Node.js lts/*
21+
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
22+
with:
23+
node-version: lts/*
24+
25+
- name: Install codeowners-utils
26+
run: npm install --no-save codeowners-utils@1.0.2
27+
28+
- name: Ping code owners
29+
env:
30+
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
31+
PR_NUMBER: ${{ github.event.pull_request.number }}
32+
REPO_OWNER: ${{ github.repository_owner }}
33+
REPO_NAME: ${{ github.event.repository.name }}
34+
run: node tools/actions/ping-owners.mjs

tools/actions/ping-owners.mjs

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
import { matchPattern, parse } from 'codeowners-utils';
2+
import { readFileSync } from 'node:fs';
3+
4+
const { GITHUB_TOKEN, PR_NUMBER, REPO_OWNER, REPO_NAME } = process.env;
5+
6+
async function githubRequest(path, options = {}) {
7+
const response = await fetch(`https://api.github.com${path}`, {
8+
...options,
9+
headers: {
10+
'Authorization': `Bearer ${GITHUB_TOKEN}`,
11+
'Accept': 'application/vnd.github.v3+json',
12+
'X-GitHub-Api-Version': '2022-11-28',
13+
...options.headers,
14+
},
15+
});
16+
if (!response.ok) {
17+
throw new Error(`GitHub API error: ${response.status} ${response.statusText}`);
18+
}
19+
return response.json();
20+
}
21+
22+
async function getChangedFiles() {
23+
const files = [];
24+
let page = 1;
25+
while (true) {
26+
const data = await githubRequest(
27+
`/repos/${REPO_OWNER}/${REPO_NAME}/pulls/${PR_NUMBER}/files?per_page=100&page=${page}`,
28+
);
29+
files.push(...data.map((f) => f.filename));
30+
if (data.length < 100) break;
31+
page++;
32+
}
33+
return files;
34+
}
35+
36+
export function getOwnersForPaths(codeownersContent, changedFiles) {
37+
const definitions = parse(codeownersContent);
38+
let ownersForPaths = [];
39+
40+
for (const { pattern, owners } of definitions) {
41+
for (const file of changedFiles) {
42+
if (matchPattern(file, pattern)) {
43+
ownersForPaths = ownersForPaths.concat(owners);
44+
}
45+
}
46+
}
47+
48+
return ownersForPaths.filter((v, i) => ownersForPaths.indexOf(v) === i).sort();
49+
}
50+
51+
export function getCommentBody(owners) {
52+
return `Review requested:\n\n${owners.map((i) => `- [ ] ${i}`).join('\n')}`;
53+
}
54+
55+
try {
56+
const changedFiles = await getChangedFiles();
57+
const codeownersContent = readFileSync('.github/CODEOWNERS', 'utf8');
58+
const owners = getOwnersForPaths(codeownersContent, changedFiles);
59+
if (owners.length !== 0) {
60+
await githubRequest(
61+
`/repos/${REPO_OWNER}/${REPO_NAME}/issues/${PR_NUMBER}/comments`,
62+
{
63+
method: 'POST',
64+
body: JSON.stringify({ body: getCommentBody(owners) }),
65+
},
66+
);
67+
}
68+
} catch (err) {
69+
console.error(err);
70+
process.exitCode = 1;
71+
}

0 commit comments

Comments
 (0)