Add close-issues script and GitHub Action
- Create script/github/close-issues.ts to close stale issues after 60 days - Add GitHub Action workflow to run daily at 2 AM - Remove old stale-issues workflow to avoid conflictspull/19058/head
parent
958a80cc05
commit
79e9d19019
|
|
@ -0,0 +1,23 @@
|
|||
name: close-issues
|
||||
|
||||
on:
|
||||
schedule:
|
||||
- cron: "0 2 * * *" # Daily at 2:00 AM
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
close:
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
issues: write
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- uses: oven-sh/setup-bun@v2
|
||||
with:
|
||||
bun-version: latest
|
||||
|
||||
- name: Close stale issues
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
run: bun script/github/close-issues.ts
|
||||
|
|
@ -1,34 +0,0 @@
|
|||
name: stale-issues
|
||||
|
||||
on:
|
||||
schedule:
|
||||
- cron: "30 1 * * *" # Daily at 1:30 AM
|
||||
workflow_dispatch:
|
||||
|
||||
env:
|
||||
DAYS_BEFORE_STALE: 90
|
||||
DAYS_BEFORE_CLOSE: 7
|
||||
|
||||
jobs:
|
||||
stale:
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
issues: write
|
||||
steps:
|
||||
- uses: actions/stale@v10.2.0
|
||||
with:
|
||||
operations-per-run: 1000
|
||||
days-before-stale: ${{ env.DAYS_BEFORE_STALE }}
|
||||
days-before-close: ${{ env.DAYS_BEFORE_CLOSE }}
|
||||
stale-issue-label: "stale"
|
||||
close-issue-message: |
|
||||
[automated] Closing due to ${{ env.DAYS_BEFORE_STALE }}+ days of inactivity.
|
||||
|
||||
Feel free to reopen if you still need this!
|
||||
stale-issue-message: |
|
||||
[automated] This issue has had no activity for ${{ env.DAYS_BEFORE_STALE }} days.
|
||||
|
||||
It will be closed in ${{ env.DAYS_BEFORE_CLOSE }} days if there's no new activity.
|
||||
remove-stale-when-updated: true
|
||||
exempt-issue-labels: "pinned,security,feature-request,on-hold"
|
||||
start-date: "2025-12-27"
|
||||
|
|
@ -0,0 +1,92 @@
|
|||
#!/usr/bin/env bun
|
||||
|
||||
const repo = "anomalyco/opencode"
|
||||
const days = 60
|
||||
const msg =
|
||||
"To stay organized issues are automatically closed after 90 days of no activity. If the issue is still relevant please open a new one."
|
||||
|
||||
const token = process.env.GITHUB_TOKEN
|
||||
if (!token) {
|
||||
console.error("GITHUB_TOKEN environment variable is required")
|
||||
process.exit(1)
|
||||
}
|
||||
|
||||
const cutoff = new Date(Date.now() - days * 24 * 60 * 60 * 1000)
|
||||
|
||||
type Issue = {
|
||||
number: number
|
||||
updated_at: string
|
||||
}
|
||||
|
||||
const headers = {
|
||||
Authorization: `Bearer ${token}`,
|
||||
"Content-Type": "application/json",
|
||||
Accept: "application/vnd.github+json",
|
||||
"X-GitHub-Api-Version": "2022-11-28",
|
||||
}
|
||||
|
||||
async function close(num: number) {
|
||||
const base = `https://api.github.com/repos/${repo}/issues/${num}`
|
||||
|
||||
const comment = await fetch(`${base}/comments`, {
|
||||
method: "POST",
|
||||
headers,
|
||||
body: JSON.stringify({ body: msg }),
|
||||
})
|
||||
if (!comment.ok) throw new Error(`Failed to comment #${num}: ${comment.status} ${comment.statusText}`)
|
||||
|
||||
const patch = await fetch(base, {
|
||||
method: "PATCH",
|
||||
headers,
|
||||
body: JSON.stringify({ state: "closed", state_reason: "not_planned" }),
|
||||
})
|
||||
if (!patch.ok) throw new Error(`Failed to close #${num}: ${patch.status} ${patch.statusText}`)
|
||||
|
||||
console.log(`Closed https://github.com/${repo}/issues/${num}`)
|
||||
}
|
||||
|
||||
async function main() {
|
||||
let page = 1
|
||||
let closed = 0
|
||||
|
||||
while (true) {
|
||||
const res = await fetch(
|
||||
`https://api.github.com/repos/${repo}/issues?state=open&sort=updated&direction=asc&per_page=100&page=${page}`,
|
||||
{ headers },
|
||||
)
|
||||
if (!res.ok) throw new Error(res.statusText)
|
||||
|
||||
const all = (await res.json()) as Issue[]
|
||||
if (all.length === 0) break
|
||||
|
||||
const stale: number[] = []
|
||||
for (const i of all) {
|
||||
const updated = new Date(i.updated_at)
|
||||
if (updated < cutoff) {
|
||||
stale.push(i.number)
|
||||
} else {
|
||||
console.log(`\nFound fresh issue #${i.number}, stopping`)
|
||||
if (stale.length > 0) {
|
||||
await Promise.all(stale.map(close))
|
||||
closed += stale.length
|
||||
}
|
||||
console.log(`Closed ${closed} issues total`)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
if (stale.length > 0) {
|
||||
await Promise.all(stale.map(close))
|
||||
closed += stale.length
|
||||
}
|
||||
|
||||
page++
|
||||
}
|
||||
|
||||
console.log(`Closed ${closed} issues total`)
|
||||
}
|
||||
|
||||
main().catch((err) => {
|
||||
console.error("Error:", err)
|
||||
process.exit(1)
|
||||
})
|
||||
Loading…
Reference in New Issue