Schedule a GitHub pull request merge using Deno KV queues
source link: https://pawelgrzybek.com/schedule-a-github-pull-request-merge-using-deno-kv-queues/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
Schedule a GitHub pull request merge using Deno KV queues
Published: 2023.12.21 · 5 minutes read
Scheduling a pull request merge at a specific date and time would be helpful in some situations. Unfortunately, this is not a feature that GitHub currently offers. I know third-party services and GitHub actions that help with that, but they are overkill for my use case. Of course, I built a thing!
In “Deno, a breath of fresh air for the server-side JavaScript”, I explained why I like this runtime so much, but after the series of recent announcements, I like it even more. Deno KV opens a whole new world of possibilities, and this is the core feature I used to build this project around. Combined with a simple HTTP router, I created a little service I always wanted.
Router and Deno KV queue#
The source code for deno-gh-merge is open-sourced on GitHub, so feel free to fork it and deploy your own instance on Deno Deploy (a free account is more than enough for a little project like this). In this article, I will go through each key component of the codebase. For clarity, I am skipping noisy parts of code like type definitions, imports, exports, error handling 🙈, etc.
Router and opening a Deno KV connection#
We need to retrieve all scheduled entries from the database via GET
request,
create a new one using POST
and delete one by sending a DELETE
request. I am
also opening a Deno KV connection (kv
) at the top of the file and passing it
down to every route handler alongside the request
object.
// index.ts
const kv = await Deno.openKv();
const HANDLER_MAPPER = {
GET: handlerGet,
POST: handlerPost,
DELETE: handlerDelete,
};
Deno.serve(async (request) =>
await HANDLER_MAPPER[request.method as keyof typeof HANDLER_MAPPER](
request,
kv,
);
The example below presents a data structure of entries in the KV database. Combining the prefix, repository owner and name followed by the ID of a pull request creates a unique key. The value is the scheduled time of a merge action.
key | value
----------------------------------------------------------------
['gh-merge', 'pawelgrzybek', 'div', 1] | "2023-12-25T09:30:00"
['gh-merge', 'pawelgrzybek', 'div', 2] | "2023-12-28T17:00:00"
GET handler#
The handler for the GET
request returns all the items with a PREFIX
. The
prefix can be anything (gh-merge
in my project). The kv.list()
method
returns an async iterator, and the easiest way to convert it to an array is
Array.fromAsync().
// handlerGet.ts
const handlerGet = async (_request: Request, kv: Deno.Kv) => {
const entries = kv.list<string>({
prefix: [PREFIX],
});
const response = await Array.fromAsync(entries);
return Response.json(response);
};
DELETE handler#
If we change our mind and don’t want to merge previously scheduled PR, we can
call API with the DELETE
request.
// handlerDelete.ts
const handlerDelete = async (request: Request, kv: Deno.Kv) => {
const body: Entry = await request.json();
await kv.delete(
[PREFIX, body.owner, body.repo, body.pull_number],
);
return Response.json(body);
};
POST handler#
The POST
request handler receives a body that contains information about the
pull request ID pull_number
, repository repo
, owner owner
, and the desired
scheduled merge time in a string format (ISO 8601) schedule
. It ignores
requests with incorrect schedule timestamps. Valid requests are saved to the
database, and events are enqueued to be processed in the future using
kv.enqueue()
.
// handlerPost.ts
const handlerPost = async (request: Request, kv: Deno.Kv) => {
const body: Entry = await request.json();
const now = new Date().getTime();
const schedule = new Date(body.schedule).getTime();
const delay = schedule - now;
if (delay < 0) {
console.error("Schedule date should be in the future");
return Response.json({ error: "Schedule date should be in the future" }, {
status: 400,
});
}
await kv.set(
[PREFIX, body.owner, body.repo, body.pull_number],
body.schedule,
);
await kv.enqueue(body, { delay });
return Response.json(body);
};
Process enqueued messages#
As part of the POST
request handler, I enqueued a message to be processed in
the future, determined by the delay
property. The listenQueue()
method from
the Deno.Kv
instance lets us define a handler for messages in the queue that
are ready for processing. Let’s get these PRs merged, then!
This handler checks if the scheduled event matches the entry in the database and
swallows the event in case of a mismatch. Later, it clears the record from the
database and calls GitHub API to merge a scheduled pull request. This action
requires
an authentication to REST API,
so you must generate a token with sufficient permissions. The Deno.env.get()
method allows me to retrieve secrets added to the Deno Deploy dashboard.
// index.ts
kv.listenQueue(async (event) => {
const entry = await kv.get<string>([
PREFIX,
event.owner,
event.repo,
event.pull_number,
]);
if (entry.value !== event.schedule) {
console.log("Entry has been updated or deleted.");
return;
}
const headers = new Headers({
"Accept": "application/vnd.github+json",
"Authorization": `Bearer ${Deno.env.get("GH_MERGE_GITHUB_TOKEN")}`,
"X-GitHub-Api-Version": "2022-11-28",
});
const body = JSON.stringify({
commit_title: "✨ Automated merge title by the Deno gh-merge",
commit_message: "✨ Automated merge message by the Deno gh-merge",
});
await kv.delete(
[PREFIX, event.owner, event.repo, event.pull_number],
);
await fetch(
`https://api.github.com/repos/${event.owner}/${event.repo}/pulls/${event.pull_number}/merge`,
{
method: "PUT",
headers,
body,
},
);
});
Let’s merge it#
That’s it. A lot can be improved, like checking if the PR exists before scheduling its merge, checking if the merge is allowed before triggering the operation, making dynamic commit messages, and more. This should work as a base. I hope that by the simplicity of this project, I inspired you to build something new.
Let’s give it a go and schedule this article to go live on Thursday, 2023.12.21, at 14:00, and merge this pull request.
curl --request POST \
--url https://gh-merge.deno.dev/ \
--header 'Content-Type: application/json' \
--data '{
"owner": "pawelgrzybek",
"repo": "pawelgrzybek.com",
"pull_number": 152,
"schedule": "2023-12-21T14:00:00"
}'
Did you like it? Please share it with your friends or get me a
beer coffee. Thanks!
Leave a comment#
👆 you can use Markdown here
Recommend
About Joyk
Aggregate valuable and interesting links.
Joyk means Joy of geeK