Created
March 3, 2023 22:58
-
-
Save jsumners/e32f9c3e1e199184d0813720f67596fe to your computer and use it in GitHub Desktop.
Close and lock issues and pull requests for a GitHub repo
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
{ | |
"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