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) => {
const dryRun = false;
const allowedAuthors = ["ferrucc-io", "vinayak-mehta"];
const webhookSecret = process.env.githubWebhookApproveSmallPRs;
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");
};