Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commit b760d45

Browse files
author
Samuel Fialka
committed
feat(ci): split script and action to separate files
1 parent 50db85a commit b760d45

File tree

3 files changed

+174
-97
lines changed

3 files changed

+174
-97
lines changed

‎.github/scripts/backlog-cleanup.js

Lines changed: 149 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,149 @@
1+
/**
2+
* GitHub Action script for managing issue backlog.
3+
*
4+
* Behavior:
5+
* - Pull Requests are skipped (only opened issues are processed)
6+
* - Skips issues with 'to-be-discussed' label.
7+
* - Closes issues with label 'awaiting-response' or without assignees,
8+
* with a standard closure comment.
9+
* - Sends a Friendly Reminder comment to assigned issues without
10+
* exempt labels that have been inactive for 90+ days.
11+
* - Avoids sending duplicate Friendly Reminder comments if one was
12+
* posted within the last 7 days.
13+
*/
14+
15+
const dedent = (strings, ...values) => {
16+
const raw = typeof strings === 'string' ? [strings] : strings.raw;
17+
let result = '';
18+
raw.forEach((str, i) => {
19+
result += str + (values[i] || '');
20+
});
21+
const lines = result.split('\n');
22+
const minIndent = Math.min(...lines.filter(l => l.trim()).map(l => l.match(/^\s*/)[0].length));
23+
return lines.map(l => l.slice(minIndent)).join('\n').trim();
24+
};
25+
26+
async function fetchAllOpenIssues(github, owner, repo) {
27+
const issues = [];
28+
let page = 1;
29+
30+
while (true) {
31+
const response = await github.rest.issues.listForRepo({
32+
owner,
33+
repo,
34+
state: 'open',
35+
per_page: 100,
36+
page,
37+
});
38+
39+
const data = response.data || [];
40+
if (data.length === 0) break;
41+
const onlyIssues = data.filter(issue => !issue.pull_request);
42+
issues.push(...onlyIssues);
43+
44+
if (data.length < 100) break;
45+
page++;
46+
}
47+
return issues;
48+
}
49+
50+
const shouldSendReminder = (issue, exemptLabels, closeLabels) => {
51+
const hasExempt = issue.labels.some(l => exemptLabels.includes(l.name));
52+
const hasClose = issue.labels.some(l => closeLabels.includes(l.name));
53+
return issue.assignees.length > 0 && !hasExempt && !hasClose;
54+
};
55+
56+
57+
module.exports = async ({ github, context }) => {
58+
const { owner, repo } = context.repo;
59+
const issues = await fetchAllOpenIssues(github, owner, repo);
60+
const now = new Date();
61+
const thresholdDays = 90;
62+
const exemptLabels = ['to-be-discussed'];
63+
const closeLabels = ['awaiting-response'];
64+
const sevenDays = 7 * 24 * 60 * 60 * 1000;
65+
66+
let totalClosed = 0;
67+
let totalReminders = 0;
68+
let totalSkipped = 0;
69+
70+
for (const issue of issues) {
71+
const isAssigned = issue.assignees && issue.assignees.length > 0;
72+
const lastUpdate = new Date(issue.updated_at);
73+
const daysSinceUpdate = Math.floor((now - lastUpdate) / (1000 * 60 * 60 * 24));
74+
75+
if (daysSinceUpdate < thresholdDays) {
76+
totalSkipped++;
77+
continue;
78+
}
79+
80+
const { data: comments } = await github.rest.issues.listComments({
81+
owner,
82+
repo,
83+
issue_number: issue.number,
84+
per_page: 10,
85+
});
86+
87+
const recentFriendlyReminder = comments.find(comment =>
88+
comment.user.login === 'github-actions[bot]' &&
89+
comment.body.includes('⏰ Friendly Reminder') &&
90+
(now - new Date(comment.created_at)) < sevenDays
91+
);
92+
if (recentFriendlyReminder) {
93+
totalSkipped++;
94+
continue;
95+
}
96+
97+
if (issue.labels.some(label => exemptLabels.includes(label.name))) {
98+
totalSkipped++;
99+
continue;
100+
}
101+
102+
if (issue.labels.some(label => closeLabels.includes(label.name)) || !isAssigned) {
103+
await github.rest.issues.createComment({
104+
owner,
105+
repo,
106+
issue_number: issue.number,
107+
body: '⚠️ This issue was closed automatically due to inactivity. Please reopen or open a new one if still relevant.',
108+
});
109+
await github.rest.issues.update({
110+
owner,
111+
repo,
112+
issue_number: issue.number,
113+
state: 'closed',
114+
});
115+
totalClosed++;
116+
continue;
117+
}
118+
119+
if (shouldSendReminder(issue, exemptLabels, closeLabels)) {
120+
const assignees = issue.assignees.map(u => `@${u.login}`).join(', ');
121+
const comment = dedent`
122+
⏰ Friendly Reminder
123+
124+
Hi ${assignees}!
125+
126+
This issue has had no activity for ${daysSinceUpdate} days. If it's still relevant:
127+
- Please provide a status update
128+
- Add any blocking details
129+
- Or label it 'awaiting-response' if you're waiting on something
130+
131+
This is just a reminder; the issue remains open for now.`;
132+
133+
await github.rest.issues.createComment({
134+
owner,
135+
repo,
136+
issue_number: issue.number,
137+
body: comment,
138+
});
139+
totalReminders++;
140+
}
141+
}
142+
143+
console.log(dedent`
144+
=== Backlog cleanup summary ===
145+
Total issues processed: ${issues.length}
146+
Total issues closed: ${totalClosed}
147+
Total reminders sent: ${totalReminders}
148+
Total skipped: ${totalSkipped}`);
149+
};

‎.github/workflows/backlog-bot.yml

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
name: "Backlog Management Bot"
2+
3+
on:
4+
schedule:
5+
- cron: '0 2 * * *' # Run daily at 2 AM UTC
6+
7+
permissions:
8+
issues: write
9+
contents: read
10+
11+
jobs:
12+
backlog-bot:
13+
name: "Check for stale issues"
14+
runs-on: ubuntu-latest
15+
steps:
16+
- name: Checkout repository
17+
uses: actions/checkout@v4
18+
19+
- name: Run backlog cleanup script
20+
uses: actions/github-script@v7
21+
with:
22+
github-token: ${{ secrets.GITHUB_TOKEN }}
23+
script: |
24+
const script = require('./.github/scripts/backlog-cleanup.js');
25+
await script({ github, context });

‎.github/workflows/backlog-management.yml

Lines changed: 0 additions & 97 deletions
This file was deleted.

0 commit comments

Comments
(0)

AltStyle によって変換されたページ (->オリジナル) /