Skip to content

Commit 1f9c6e4

Browse files
avivkellerCopilot
andauthored
feat(meta): require collaborators to be active (#7775)
* feat(meta): require collaborators to me active * fixup! * Update .github/workflows/find-inactive-collaborators.yml Co-authored-by: Copilot <[email protected]> Signed-off-by: Aviv Keller <[email protected]> * store workflow in .github * use local list * remove unused tests * use more lenient matching Signed-off-by: Aviv Keller <[email protected]> * fixup! * code review --------- Signed-off-by: Aviv Keller <[email protected]> Co-authored-by: Copilot <[email protected]>
1 parent dffae08 commit 1f9c6e4

File tree

4 files changed

+205
-2
lines changed

4 files changed

+205
-2
lines changed
Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
1+
import { readFile } from 'node:fs/promises';
2+
3+
const CONFIG = {
4+
GOVERNANCE_FILE: 'GOVERNANCE.md',
5+
CURRENT_MEMBERS_HEADER: '#### Current Members',
6+
INACTIVE_MONTHS: 12,
7+
ISSUE_TITLE: 'Inactive Collaborator Report',
8+
ISSUE_LABELS: ['meta', 'inactive-collaborator-report'],
9+
};
10+
11+
// Get date N months ago in YYYY-MM-DD format
12+
const getDateMonthsAgo = (months = CONFIG.INACTIVE_MONTHS) => {
13+
const date = new Date();
14+
date.setMonth(date.getMonth() - months);
15+
return date.toISOString().split('T')[0];
16+
};
17+
18+
// Parse collaborator usernames from governance file
19+
async function parseCollaborators() {
20+
const content = await readFile(CONFIG.GOVERNANCE_FILE, 'utf8');
21+
const lines = content.split('\n');
22+
const collaborators = [];
23+
24+
const startIndex =
25+
lines.findIndex(l => l.startsWith(CONFIG.CURRENT_MEMBERS_HEADER)) + 1;
26+
if (startIndex <= 0) return collaborators;
27+
28+
for (let i = startIndex; i < lines.length; i++) {
29+
const line = lines[i];
30+
if (line.startsWith('#')) break;
31+
32+
const match = line.match(/^\s*-\s*\[([^\]]+)\]/);
33+
if (match) collaborators.push(match[1]);
34+
}
35+
36+
return collaborators;
37+
}
38+
39+
// Check if users have been active since cutoff date
40+
async function getInactiveUsers(github, usernames, repo, cutoffDate) {
41+
const inactiveUsers = [];
42+
43+
for (const username of usernames) {
44+
const { data } = await github.rest.search.commits({
45+
q: `author:${username} repo:${repo} committer-date:>=${cutoffDate}`,
46+
per_page: 1,
47+
});
48+
49+
if (data.total_count === 0) {
50+
inactiveUsers.push(username);
51+
}
52+
}
53+
54+
return inactiveUsers;
55+
}
56+
57+
// Generate report for inactive members
58+
function formatReport(inactiveMembers, cutoffDate) {
59+
if (!inactiveMembers.length) return null;
60+
61+
const today = getDateMonthsAgo(0);
62+
return `# Inactive Collaborators Report
63+
64+
Last updated: ${today}
65+
Checking for inactivity since: ${cutoffDate}
66+
67+
## Inactive Collaborators (${inactiveMembers.length})
68+
69+
| Login |
70+
| ----- |
71+
${inactiveMembers.map(m => `| @${m} |`).join('\n')}
72+
73+
## What happens next?
74+
75+
@nodejs/nodejs-website should review this list and contact inactive collaborators to confirm their continued interest in participating in the project.`;
76+
}
77+
78+
async function createOrUpdateIssue(github, context, report) {
79+
if (!report) return;
80+
81+
const { owner, repo } = context.repo;
82+
const { data: issues } = await github.rest.issues.listForRepo({
83+
owner,
84+
repo,
85+
state: 'open',
86+
labels: CONFIG.ISSUE_LABELS[1],
87+
per_page: 1,
88+
});
89+
90+
if (issues.total_count > 0) {
91+
await github.rest.issues.update({
92+
owner,
93+
repo,
94+
issue_number: issues.items[0].number,
95+
body: report,
96+
});
97+
} else {
98+
await github.rest.issues.create({
99+
owner,
100+
repo,
101+
title: CONFIG.ISSUE_TITLE,
102+
body: report,
103+
labels: CONFIG.ISSUE_LABELS,
104+
});
105+
}
106+
}
107+
108+
export default async function (github, context) {
109+
const cutoffDate = getDateMonthsAgo();
110+
const collaborators = await parseCollaborators();
111+
112+
const inactiveMembers = await getInactiveUsers(
113+
github,
114+
collaborators,
115+
`${context.repo.owner}/${context.repo.repo}`,
116+
cutoffDate
117+
);
118+
const report = formatReport(inactiveMembers, cutoffDate);
119+
120+
await createOrUpdateIssue(github, context, report);
121+
}
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
name: Find inactive collaborators
2+
3+
on:
4+
schedule:
5+
# Run every Monday at 4:05 AM UTC.
6+
- cron: 5 4 * * 1
7+
8+
workflow_dispatch:
9+
10+
permissions: {}
11+
12+
jobs:
13+
find:
14+
if: github.repository == 'nodejs/nodejs.org'
15+
runs-on: ubuntu-latest
16+
17+
steps:
18+
- name: Harden Runner
19+
uses: step-security/harden-runner@0634a2670c59f64b4a01f0f96f84700a4088b9f0 # v2.12.0
20+
with:
21+
egress-policy: audit
22+
23+
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
24+
25+
- name: Report inactive collaborators
26+
id: inactive
27+
uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7.0.1
28+
with:
29+
script: |
30+
const report = await import("${{github.workspace}}/.github/scripts/report-inactive-collaborators.mjs");
31+
report(github, exec);

CONTRIBUTING.md

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,8 @@ Thank you for your interest in contributing to the Node.js Website. Before you p
44

55
- [Code of Conduct](https://github.com/nodejs/node/blob/HEAD/CODE_OF_CONDUCT.md)
66
- [Contributing](#contributing)
7-
- [Becoming a collaborator](#becoming-a-collaborator)
7+
- [Becoming a Collaborator](#becoming-a-collaborator)
8+
- [Maintaining Collaborator Status](#maintaining-collaborator-status)
89
- [Getting started](#getting-started)
910
- [CLI Commands](#cli-commands)
1011
- [Cloudflare Deployment](#cloudflare-deployment)
@@ -54,6 +55,18 @@ If you're an active contributor seeking to become a member, we recommend you con
5455

5556
</details>
5657

58+
### Maintaining Collaborator Status
59+
60+
Once you become a collaborator, you are expected to uphold certain responsibilities and standards to maintain your status:
61+
62+
- **Adhere to Policies**: Collaborators must abide by the [Node.js Moderation Policy](https://github.com/nodejs/admin/blob/HEAD/Moderation-Policy.md) and [Code of Conduct](https://github.com/nodejs/node/blob/HEAD/CODE_OF_CONDUCT.md) at all times.
63+
64+
- **Remain Active**: Collaborators are expected to author commits at least once in the past twelve months.
65+
66+
If a collaborator becomes inactive for more than twelve months, they may be removed from the active collaborators list. They can be reinstated upon returning to active participation by going through the full nomination process again.
67+
68+
Violations of the Code of Conduct or Moderation Policy may result in immediate removal of collaborator status, depending on the severity of the violation and the decision of the Technical Steering Committee and/or the OpenJS Foundation.
69+
5770
## Getting started
5871

5972
The steps below will give you a general idea of how to prepare your local environment for the Node.js Website and general steps

GOVERNANCE.md

Lines changed: 39 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,45 @@ The Node.js Website Team is responsible for the day-to-day technical development
1515

1616
The maintainers on the Node.js Website Team are responsible for steering the technical direction of the Node.js Website, and reserve the right to make final decisions on any issues or pull requests, in line with the Contribution Guidelines, Collaborator Guidelines, the Code of Conduct and the overall Governance premises of the Node.js project.
1717

18-
Members of this team are nominated through the guidelines provided in the Collaborator Guidelines within this repository.
18+
Members of this team are nominated through the guidelines provided in the [Contributing Guidelines](https://github.com/nodejs/nodejs.org/blob/main/CONTRIBUTING.md#becoming-a-collaborator) within this repository. After a passed nomination, members should submit a PR to add themselves to the list of current members, shown below.
19+
20+
#### Current Members
21+
22+
- [araujogui](https://github.com/araujogui) - **Guilherme Araújo** (he/him)
23+
24+
- [AugustinMauroy](https://github.com/AugustinMauroy) - **Augustin Mauroy** (he/him)
25+
26+
- [avivkeller](https://github.com/avivkeller) - **Aviv Keller** (he/him)
27+
28+
- [aymen94](https://github.com/aymen94) - **Aymen Naghmouchi**
29+
30+
- [benhalverson](https://github.com/benhalverson) - **Ben Halverson** (he/him)
31+
32+
- [bjohansebas](https://github.com/bjohansebas) - **Sebastian Beltran**
33+
34+
- [bmuenzenmeyer](https://github.com/bmuenzenmeyer) - **Brian Muenzenmeyer** (he/him)
35+
36+
- [bnb](https://github.com/bnb) - **Tierney Cyren** (they/them)
37+
38+
- [canerakdas](https://github.com/canerakdas) - **Caner Akdas**
39+
40+
- [dario-piotrowicz](https://github.com/dario-piotrowicz) - **Dario Piotrowicz**
41+
42+
- [Harkunwar](https://github.com/Harkunwar) - **Harkunwar Kochar** (he/him)
43+
44+
- [HinataKah0](https://github.com/HinataKah0) - **HinataKah0** (he/him)
45+
46+
- [manishprivet](https://github.com/manishprivet) - **Manish Kumar** (he/him)
47+
48+
- [mikeesto](https://github.com/mikeesto) - **Michael Esteban** (he/him)
49+
50+
- [ovflowd](https://github.com/ovflowd) - **Claudio Wunder** (they/them)
51+
52+
- [rodion-arr](https://github.com/rodion-arr) - **Rodion Abdurakhimov**
53+
54+
- [SEWeiTung](https://github.com/SEWeiTung) - **Wei Tung**
55+
56+
- [shanpriyan](https://github.com/shanpriyan) - **Shanmughapriyan S**
1957

2058
### Node.js Web Infra Team (`@nodejs/web-infra`)
2159

0 commit comments

Comments
 (0)