Skip to content

Commit c8147f9

Browse files
authored
Merge pull request #33 from ewels/rfc-automate-approval
RFCs: Actions workflow to automate approval process.
2 parents d26a453 + 9278524 commit c8147f9

File tree

1 file changed

+113
-0
lines changed

1 file changed

+113
-0
lines changed

.github/workflows/rfc_approval.yml

Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
name: RFC approval automation
2+
3+
on:
4+
issues:
5+
types: [opened]
6+
issue_comment:
7+
types: [created, edited]
8+
9+
permissions:
10+
contents: read
11+
issues: write
12+
13+
jobs:
14+
rfc_approval:
15+
# Only run for RFC proposal issues that are still open
16+
if: startsWith(github.event.issue.title, 'New RFC') && github.event.issue.state == 'open'
17+
runs-on: ubuntu-latest
18+
19+
steps:
20+
- name: Handle RFC approval logic
21+
uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea #v7.0.1
22+
with:
23+
github-token: ${{ secrets.GITHUB_TOKEN }}
24+
script: |
25+
const issueNumber = context.issue.number;
26+
const org = context.repo.owner;
27+
const repo = context.repo.repo;
28+
29+
// ---------------------------------------------
30+
// Fetch members of the core team
31+
// ---------------------------------------------
32+
async function getTeamMembers() {
33+
const res = await github.request('GET /orgs/{org}/teams/{team_slug}/members', {
34+
org,
35+
team_slug: 'core',
36+
per_page: 100
37+
});
38+
return res.data.map(m => m.login);
39+
}
40+
41+
const teamMembers = await getTeamMembers();
42+
const quorum = Math.ceil(teamMembers.length / 2);
43+
44+
// -------------------------------------------------
45+
// If this workflow was triggered by issue creation
46+
// -------------------------------------------------
47+
if (context.eventName === 'issues' && context.payload.action === 'opened') {
48+
const body = `# RFC approval status: Pending\n\nCore team approvers:\nRejection from:\nAwaiting approval from:`;
49+
50+
await github.rest.issues.createComment({
51+
owner: org,
52+
repo,
53+
issue_number: issueNumber,
54+
body
55+
});
56+
return; // Nothing more to do for newly-opened issues
57+
}
58+
59+
// ---------------------------------------------------------------------
60+
// From here on we handle updates triggered by comment creation / edits
61+
// ---------------------------------------------------------------------
62+
63+
// Collect all comments on the issue (may require pagination)
64+
const comments = await github.paginate(github.rest.issues.listComments, {
65+
owner: org,
66+
repo,
67+
issue_number: issueNumber,
68+
per_page: 100
69+
});
70+
71+
const approvals = new Set();
72+
const rejections = new Set();
73+
74+
for (const comment of comments) {
75+
const commenter = comment.user.login;
76+
if (!teamMembers.includes(commenter)) continue; // Only core team members count
77+
78+
// Count approvals / rejections based on line starting with /approve or /reject
79+
const lines = comment.body.split(/\r?\n/);
80+
for (const rawLine of lines) {
81+
const line = rawLine.trim();
82+
if (/^\/approve\b/i.test(line)) {
83+
approvals.add(commenter);
84+
} else if (/^\/reject\b/i.test(line)) {
85+
rejections.add(commenter);
86+
}
87+
}
88+
}
89+
90+
const awaiting = teamMembers.filter(u => !approvals.has(u) && !rejections.has(u));
91+
const status = approvals.size >= quorum && rejections.size === 0 ? 'Approved' : 'Pending';
92+
93+
const statusBody = `# RFC approval status: ${status}\n\nCore team approvers: ${approvals.size ? [...approvals].join(', ') : 'None'}\nRejection from: ${rejections.size ? [...rejections].join(', ') : 'None'}\nAwaiting approval from: ${awaiting.length ? awaiting.join(', ') : 'None'}`;
94+
95+
// Try to locate the existing status comment (starts with our header)
96+
let statusComment = comments.find(c => c.body.startsWith('# RFC approval status:'));
97+
98+
if (statusComment) {
99+
await github.rest.issues.updateComment({
100+
owner: org,
101+
repo,
102+
comment_id: statusComment.id,
103+
body: statusBody
104+
});
105+
} else {
106+
// Fallback: create a new status comment if missing (shouldn't normally happen)
107+
await github.rest.issues.createComment({
108+
owner: org,
109+
repo,
110+
issue_number: issueNumber,
111+
body: statusBody
112+
});
113+
}

0 commit comments

Comments
 (0)