Val Town is a social website to write and deploy JavaScript.
Build APIs and schedule functions from your browser.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
import { response } from "https://esm.town/v/ferruccio/response";
import { thisValUrl } from "https://esm.town/v/neverstew/thisValUrl?v=1";
import { verifyGithubWebhookSignature } from "https://esm.town/v/vtdocs/verifyGithubWebhookSignature?v=2";
import process from "node:process";
export const githubWebhookApproveSmallPRs = async (req: Request) => {
// if true: do all the checks but don't approve PRs (status code 200 instead of 201)
const dryRun = false;
// only PRs created by these authors will be considered
const allowedAuthors = ["ferrucc-io", "vinayak-mehta"];
// the secret shared with the webhook
const webhookSecret = process.env.githubWebhookApproveSmallPRs;
// the token to make github requests (needs `repo` permissions)
const githubApiToken = process.env.githubApproveSmallPRsToken;
const valHttpUrl = thisValUrl();
const approvalMessage = `Nice one! Keep going 💪 Ping me if you want a more in-depth review`;
if (!req) {
return `Use the web endpoint for this val, see ${valHttpUrl}`;
}
else if (req.method === "GET") {
return new Response(
`Redirecting to <a href="${valHttpUrl}">${valHttpUrl}</a>`,
{ status: 303, headers: { Location: valHttpUrl } },
);
}
else if (req.method !== "POST") {
return response(
405,
`Method ${req.method} not allowed, see ${valHttpUrl}`,
);
}
const payload: Payload = await req.json();
const verified = await verifyGithubWebhookSignature(
webhookSecret,
JSON.stringify(payload),
req.headers.get("X-Hub-Signature-256"),
);
if (!verified) {
return response(
401,
`Not verified, see ${valHttpUrl}`,
);
}
const { action, workflow_run, sender } = payload;
if (
action !== "completed" || workflow_run.conclusion !== "success"
|| workflow_run.pull_requests.length !== 1
) {
return response(202, "Ignored (event)");
}
const { Octokit } = await import("npm:@octokit/core");
const octokit = new Octokit({
auth: githubApiToken,
});
const { repository, organization } = payload;
const [pull_request] = workflow_run.pull_requests;
const pr = (await octokit.request(`GET /repos/{owner}/{repo}/pulls/{pull_number}`, {
owner: organization.login,
repo: repository.name,
pull_number: pull_request.number,
})).data;
if (!allowedAuthors.includes(pr.user.login)) {
return response(202, "Ignored (pr author)");
}
if (pr.additions >= 60 || pr.deletions >= 100) {
return response(202, "Ignored (too many changes)");
}
const reviews = (await octokit.request(
`GET /repos/{owner}/{repo}/pulls/{pull_number}/reviews`,
{
owner: organization.login,
repo: repository.name,
pull_number: pull_request.number,
},
)).data;
const alreadyApproved = reviews.some((review) => review.state === "APPROVED");
if (alreadyApproved) {
return response(202, "Already approved by bot");
}
if (dryRun) {
return response(
200,
"Would have been approved (dryRun)",
);
}
await octokit.request(
"POST /repos/{owner}/{repo}/pulls/{pull_number}/reviews",
{
owner: organization.login,
repo: repository.name,
pull_number: pull_request.number,
body: approvalMessage,
event: "APPROVE",
headers: {
"X-GitHub-Api-Version": "2022-11-28",
},
},
);
return response(201, "Approved");
};
adisbanda-githubwebhookapprovesmallprs.web.val.run
January 31, 2024