Skip to content

Instantly share code, notes, and snippets.

@jsumners
Created March 3, 2023 22:58
Show Gist options
  • Save jsumners/e32f9c3e1e199184d0813720f67596fe to your computer and use it in GitHub Desktop.
Save jsumners/e32f9c3e1e199184d0813720f67596fe to your computer and use it in GitHub Desktop.
Close and lock issues and pull requests for a GitHub repo
import dotenv from "dotenv";
dotenv.config();
// Create a .env file in the same directory
// as this script. Add the following constants
// to that file:
const ORG = process.env.ORG;
const REPO = process.env.REPO;
const GITHUB_TOKEN = process.env.GITHUB_TOKEN;
import { Client } from "undici";
const client = new Client("https://api.github.com");
const headers = {
"user-agent": "local",
accept: "application/vnd.github.v3+json",
authorization: `token ${GITHUB_TOKEN}`,
"content-type": "application/json",
};
const prsToSkip = []
const issuesToSkip = [544, 839]
const issueComment = `
👋
This is a message that will be added as a new comment on each
issue that is closed.
`.trim()
const issues = getPagesGen();
for await (const issue of issues) {
const issueNumber = issue.number;
if (issuesToSkip.includes(issueNumber)) {
console.log('skipped issue:', issueNumber)
continue
}
console.log('adding comment on issue:', issueNumber)
let result = await addComment(issueNumber)
if (result === false) continue
console.log('locking issue:', issueNumber)
result = await lockIssue(issueNumber)
if (result === false) continue
console.log('closing issue:', issueNumber)
await closeIssue(issueNumber)
}
const prs = getPagesGen('pulls');
for await (const pr of prs) {
const prNumber = pr.number
if (prsToSkip.includes(prNumber)) {
console.log('skipped pr:', prNumber)
continue
}
console.log('adding comment on pr:', prNumber)
let result = await addComment(prNumber)
if (result === false) continue
console.log('locking pr:', prNumber)
result = await lockIssue(prNumber)
if (result === false) continue
console.log('closing pr:', prNumber)
await closeIssue(prNumber)
}
async function addComment(issueNumber) {
const response = await client.request({
method: 'POST',
path: `/repos/${ORG}/${REPO}/issues/${issueNumber}/comments`,
headers,
body: JSON.stringify({body: issueComment})
});
if (response.statusCode != 201) {
console.error('failed to comment on issue:', issueNumber);
return false;
}
return true;
}
async function lockIssue(issueNumber) {
const response = await client.request({
method: 'PUT',
path: `/repos/${ORG}/${REPO}/issues/${issueNumber}/lock`,
headers,
body: JSON.stringify({lock_reason: 'resolved'})
});
if (response.statusCode != 204) {
console.error('failed to lock issue:', issueNumber);
return false;
}
return true;
}
async function closeIssue(issueNumber) {
const response = await client.request({
method: 'PATCH',
path: `/repos/${ORG}/${REPO}/issues/${issueNumber}`,
headers,
body: JSON.stringify({state: 'closed'})
});
if (response.statusCode != 200) {
console.error('failed to close issue:', issueNumber);
return false;
}
return true;
}
async function unsubscribeFromNotifications(repoName) {
const response = await client.request({
method: "DELETE",
path: `/repos/${ORG}/${repoName}/subscription`,
headers,
});
if (response.statusCode !== 204) {
throw Error(
`Failed to unsubscribe from ${repoName}. Got code: ${response.statusCode}`
);
}
}
async function clearNotifications(repoName) {
const response = await client.request({
method: "PUT",
path: `/repos/${ORG}/${repoName}/notifications`,
headers,
});
if (response.statusCode > 299) {
throw Error(
`Failed to mark notificatons done. Got code: ${response.statusCode}`
);
}
}
async function* getPagesGen(type = 'issues') {
const params = new URLSearchParams();
// params.set("type", "public");
params.set("sort", "full_name");
params.set("per_page", "100");
let response = await client.request({
method: "GET",
path: `/repos/${ORG}/${REPO}/${type}?${params.toString()}`,
headers,
});
const firstPage = await response.body.json();
for (const repo of firstPage) {
yield repo;
}
let finished = false;
do {
if (!response.headers.link) {
break;
}
// On the first page there will be `rel=next` and `rel=last`.
// On middle pages there will be `rel=prev`, `rel=next`, and `rel=first`.
// On the last page there will be `rel=prev` and `rel=first`.
const links = response.headers.link.split(",");
const nextLink = links.find((l) => l.includes(`rel="next"`));
if (!nextLink) {
finished = true;
break;
}
const parts = nextLink.split(";");
const url = new URL(parts[0].replace(/[<>]/g, ""));
// const rel = parts[1].slice(6, -1);
response = await client.request({
method: "GET",
path: url.pathname + url.search,
headers,
});
const repos = await response.body.json();
for (const repo of repos) {
yield repo;
}
} while (finished === false);
}
{
"dependencies": {
"dotenv": "^16.0.0",
"undici": "^5.20.0"
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment