-
Notifications
You must be signed in to change notification settings - Fork 152
139 lines (126 loc) · 5.36 KB
/
Copy pathpending-maintainer.yml
File metadata and controls
139 lines (126 loc) · 5.36 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
name: Label pending-maintainer PRs
on:
schedule:
- cron: '0 * * * *' # hourly safety net
issue_comment:
types: [created]
pull_request_review:
types: [submitted]
workflow_dispatch:
jobs:
check-pending:
if: github.event_name == 'workflow_dispatch' || github.event_name == 'schedule' || github.event.issue.pull_request != null || github.event.pull_request != null
runs-on: ubuntu-latest
permissions:
pull-requests: write
issues: write
checks: read
steps:
- uses: actions/github-script@v7
with:
script: |
const MAINTAINER = 'pending-maintainer';
const CONTRIBUTOR = 'pending-contributor';
let prNumbers = [];
if (context.eventName === 'workflow_dispatch' || context.eventName === 'schedule') {
const prs = await github.rest.pulls.list({
...context.repo,
state: 'open',
per_page: 100
});
prNumbers = prs.data.map(pr => pr.number);
} else if (context.eventName === 'issue_comment') {
prNumbers = [context.payload.issue.number];
} else if (context.eventName === 'pull_request_review') {
prNumbers = [context.payload.pull_request.number];
}
for (const prNumber of prNumbers) {
const { data: pr } = await github.rest.pulls.get({
...context.repo,
pull_number: prNumber
});
const labels = pr.labels.map(l => l.name);
// Skip drafts — not ready for maintainer review
if (pr.draft) {
console.log(`#${prNumber} — draft, skipping`);
continue;
}
// Skip if has merge conflicts or already labeled needs-rebase
if (pr.mergeable === false || labels.includes('needs-rebase')) {
console.log(`#${prNumber} — has conflicts or needs-rebase, skipping`);
continue;
}
// Skip if wontfix or not-planned
if (labels.includes('wontfix') || labels.includes('not-planned')) {
console.log(`#${prNumber} — wontfix/not-planned, skipping`);
continue;
}
// Skip if closing-soon — don't override stale-close workflow
if (labels.includes('closing-soon')) {
console.log(`#${prNumber} — closing-soon, skipping`);
continue;
}
// Check CI status on head commit
const { data: status } = await github.rest.repos.getCombinedStatusForRef({
...context.repo,
ref: pr.head.sha
});
const { data: checks } = await github.rest.checks.listForRef({
...context.repo,
ref: pr.head.sha,
per_page: 100
});
const ciRed = status.state === 'failure'
|| checks.check_runs.some(c => c.conclusion === 'failure');
if (ciRed) {
if (labels.includes(MAINTAINER)) {
await github.rest.issues.removeLabel({
...context.repo,
issue_number: prNumber,
name: MAINTAINER
}).catch(() => {});
}
if (!labels.includes(CONTRIBUTOR)) {
await github.rest.issues.addLabels({
...context.repo,
issue_number: prNumber,
labels: [CONTRIBUTOR]
});
}
console.log(`#${prNumber} — CI failing, flipped to ${CONTRIBUTOR}`);
continue;
}
// Check last comment from a human (skip bot comments) is from PR author
const { data: allComments } = await github.rest.issues.listComments({
...context.repo,
issue_number: prNumber,
per_page: 100
});
const EXCLUDED_BOTS = ['shaun-agent'];
const humanComments = allComments.filter(c =>
c.user.type !== 'Bot' && !EXCLUDED_BOTS.includes(c.user.login)
);
// If there are human comments, check who spoke last.
// Last comment by someone other than author = ball is with contributor, skip.
// No human comments = new PR awaiting first review, fall through.
if (humanComments.length > 0) {
const lastCommenter = humanComments[humanComments.length - 1].user.login;
if (lastCommenter !== pr.user.login) continue;
}
// All conditions met: not draft, no conflicts, CI green, awaiting maintainer
if (!labels.includes(MAINTAINER)) {
await github.rest.issues.addLabels({
...context.repo,
issue_number: prNumber,
labels: [MAINTAINER]
});
}
if (labels.includes(CONTRIBUTOR)) {
await github.rest.issues.removeLabel({
...context.repo,
issue_number: prNumber,
name: CONTRIBUTOR
}).catch(() => {});
}
console.log(`#${prNumber} — all clear, set ${MAINTAINER}`);
}