A webhook to approve dependency PRs created by bots

The webhook can be configured on the repo or on the org level

  • it needs to have the Payload URL set to the "web endpoint" of the val ( ... -> Endpoints -> Copy web endpoint)
  • it needs to POST a json payload
  • it needs to receive the workflow_runs events
  • it needs to have the webhookSecret configured to the same value as in val town secrets (line 7)

(else response is 401: Not verified)

It will only approve if all of the following conditions are met:

  • the event action is completed, the workflow_run.conclusion has to be success, and the event is related to exactly one pull request
    (else response is 202: Ignored (event))
  • the PR is authored authored by one of the users listed in allowedAuthors (line 5)
    (else response is 202: Ignored (pr author))
  • the githubApiToken configured in line 9 needs to have repo access to the related repository
    (else response is 50x: some error message)
  • a branch protection has to be configured that requires at least one review approval and at least one status check
    (else response is 202: Ignored (branch protection))
  • the PR has auto-merge enabled
    (else response is 202: Ignored (pr status))
  • the PR has any failing status checks (even if not required)
    (else response is 202: Ignored (pr checks))
  • the current value for dryRun is false (line 3)
    (else response is 200: Would have been approved (dryRun))

If it approves the PR, it leaves a comment pointing to the website of this val.

Readme
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
import { SignatureCheck } from "https://esm.town/v/karfau/SignatureCheck";
import { response } from "https://esm.town/v/karfau/response";
import { isRequest } from "https://esm.town/v/karfau/isRequest";
import { thisValUrl } from "https://esm.town/v/neverstew/thisValUrl?v=1";
import process from "node:process";
export const githubWebhookApproveDependencyPRs = async (req?: Request | unknown) => {
// 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 = ["renovate[bot]"];
// the secret shared with the webhook
const webhookSecret = process.env.githubWebhookApproveDependencyPRs;
// the token to make github requests (needs `repo` permissions)
const githubApiToken = process.env.githubApproveDependencyPRsToken;
const valHttpUrl = thisValUrl();
const approvalMessage = `Automatically approved by ${valHttpUrl}`;
if (!isRequest(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 body = await req.text();
const signature = req.headers.get("X-Hub-Signature-256");
const { verify } = SignatureCheck();
const verified = await verify({ payload: body, signature }, webhookSecret);
if (!verified) {
return response(401, `Not verified, see ${valHttpUrl}`);
}
const payload: Payload = JSON.parse(body);
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,
👆 This is a val. Vals are TypeScript snippets of code, written in the browser and run on our servers. Create scheduled functions, email yourself, and persist small pieces of data — all from the browser.