-
Notifications
You must be signed in to change notification settings - Fork 63
140 lines (123 loc) · 5.25 KB
/
Copy pathauto-close-stale-issues.yaml
File metadata and controls
140 lines (123 loc) · 5.25 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
name: Auto-close stale issues by status label
on:
schedule:
# 毎日 UTC 0:00(JST 9:00)に実行
- cron: "0 0 * * *"
workflow_dispatch: # 手動実行も可能に
permissions:
issues: write
jobs:
close-stale:
runs-on: ubuntu-latest
steps:
- name: Close stale issues based on status labels
uses: actions/github-script@v7
with:
script: |
// ============================================================
// 設定:ラベルごとのクローズ条件
// ============================================================
const rules = [
{
labels: ["Status: duplicate"],
days: 14,
comment: null, // コメントなしでクローズ
},
{
labels: ["Status: needs info from reporter", "Status: help wanted"],
days: 60,
comment: "This issue has been closed due to no progress for 60 days. If the issue persists, please post again and we will reopen it.",
},
{
labels: ["Status: fixed", "Status: resolved"],
days: 60,
comment: "This issue has been resolved by the maintainers, so we will close it.",
},
];
const { owner, repo } = context.repo;
const now = Date.now();
// 全ての対象ラベルを集める
const allTargetLabels = [...new Set(rules.flatMap(r => r.labels))];
for (const targetLabel of allTargetLabels) {
// 該当ラベルが付いた open issue を取得(PR は除外)
const issues = await github.paginate(
github.rest.issues.listForRepo,
{
owner,
repo,
state: "open",
labels: targetLabel,
per_page: 100,
}
);
for (const issue of issues) {
// Pull Request は除外
if (issue.pull_request) continue;
// このラベルに対応するルールを取得
const rule = rules.find(r => r.labels.includes(targetLabel));
if (!rule) continue;
// ラベルが「最後に」付与された時刻を取得
// (途中で外して再付与された場合に備え、最新の labeled イベントを使う)
const events = await github.paginate(
github.rest.issues.listEventsForTimeline,
{
owner,
repo,
issue_number: issue.number,
per_page: 100,
}
);
const lastLabeledAt = events
.filter(e => e.event === "labeled" && e.label?.name === targetLabel)
.map(e => new Date(e.created_at).getTime())
.sort((a, b) => b - a)[0];
if (!lastLabeledAt) continue;
// ラベル付与後の最終活動時刻を取得
// (issue 自体の updated_at にはラベル変更も含まれるため、
// ここでは「コメント追加」をユーザー活動と見なす)
const comments = await github.paginate(
github.rest.issues.listComments,
{
owner,
repo,
issue_number: issue.number,
since: new Date(lastLabeledAt).toISOString(),
per_page: 100,
}
);
const lastCommentAt = comments.length
? Math.max(...comments.map(c => new Date(c.created_at).getTime()))
: 0;
// ラベル付与 or それ以降のコメントの、より新しい方を基準にする
const referenceTime = Math.max(lastLabeledAt, lastCommentAt);
const daysInactive = (now - referenceTime) / (1000 * 60 * 60 * 24);
if (daysInactive >= rule.days) {
console.log(
`Closing #${issue.number} ` +
`(label="${targetLabel}", inactive=${daysInactive.toFixed(1)}d)`
);
// コメント投稿(設定されている場合)
if (rule.comment) {
await github.rest.issues.createComment({
owner,
repo,
issue_number: issue.number,
body: rule.comment,
});
}
// クローズ実行
await github.rest.issues.update({
owner,
repo,
issue_number: issue.number,
state: "closed",
state_reason: "not_planned", // または "completed"
});
} else {
console.log(
`Skipping #${issue.number} ` +
`(label="${targetLabel}", inactive=${daysInactive.toFixed(1)}d / ${rule.days}d)`
);
}
}
}