Skip to content

feat(meta): require collaborators to be active #7775

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 8 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
121 changes: 121 additions & 0 deletions .github/scripts/report-inactive-collaborators.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
import { readFile } from 'node:fs/promises';

const CONFIG = {
GOVERNANCE_FILE: 'GOVERNANCE.md',
CURRENT_MEMBERS_HEADER: '#### Current Members',
INACTIVE_MONTHS: 12,
ISSUE_TITLE: 'Inactive Collaborator Report',
ISSUE_LABELS: ['meta', 'inactive-collaborator-report'],
};

// Get date N months ago in YYYY-MM-DD format
const getDateMonthsAgo = (months = CONFIG.INACTIVE_MONTHS) => {
const date = new Date();
date.setMonth(date.getMonth() - months);
return date.toISOString().split('T')[0];
};

// Parse collaborator usernames from governance file
async function parseCollaborators() {
const content = await readFile(CONFIG.GOVERNANCE_FILE, 'utf8');
const lines = content.split('\n');
const collaborators = [];

const startIndex =
lines.findIndex(l => l.startsWith(CONFIG.CURRENT_MEMBERS_HEADER)) + 1;
if (startIndex <= 0) return collaborators;

for (let i = startIndex; i < lines.length; i++) {
const line = lines[i];
if (line.startsWith('#')) break;

const match = line.match(/^\s*-\s*\[([^\]]+)\]/);
if (match) collaborators.push(match[1]);
}

return collaborators;
}

// Check if users have been active since cutoff date
async function getInactiveUsers(github, usernames, repo, cutoffDate) {
const inactiveUsers = [];

for (const username of usernames) {
const { data } = await github.rest.search.commits({
q: `author:${username} repo:${repo} committer-date:>=${cutoffDate}`,
per_page: 1,
});

if (data.total_count === 0) {
inactiveUsers.push(username);
}
}

return inactiveUsers;
}

// Generate report for inactive members
function formatReport(inactiveMembers, cutoffDate) {
if (!inactiveMembers.length) return null;

const today = getDateMonthsAgo(0);
return `# Inactive Collaborators Report

Last updated: ${today}
Checking for inactivity since: ${cutoffDate}

## Inactive Collaborators (${inactiveMembers.length})

| Login |
| ----- |
${inactiveMembers.map(m => `| @${m} |`).join('\n')}

## What happens next?

Team maintainers should review this list and contact inactive collaborators to confirm their continued interest in participating in the project.`;
}

async function createOrUpdateIssue(github, context, report) {
if (!report) return;

const { owner, repo } = context.repo;
const { data: issues } = await github.rest.issues.listForRepo({
owner,
repo,
state: 'open',
labels: CONFIG.ISSUE_LABELS[1],
per_page: 1,
});

if (issues.total_count > 0) {
await github.rest.issues.update({
owner,
repo,
issue_number: issues.items[0].number,
body: report,
});
} else {
await github.rest.issues.create({
owner,
repo,
title: CONFIG.ISSUE_TITLE,
body: report,
labels: CONFIG.ISSUE_LABELS,
});
}
}

export default async function (github, context) {
const cutoffDate = getDateMonthsAgo();
const collaborators = await parseCollaborators();

const inactiveMembers = await getInactiveUsers(
github,
collaborators,
`${context.repo.owner}/${context.repo.repo}`,
cutoffDate
);
const report = formatReport(inactiveMembers, cutoffDate);

await createOrUpdateIssue(github, context, report);
}
34 changes: 34 additions & 0 deletions .github/workflows/find-inactive-collaborators.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
name: Find inactive collaborators

on:
schedule:
# Run every Monday at 4:05 AM UTC.
- cron: 5 4 * * 1

workflow_dispatch:

env:
NODE_VERSION: lts/*

permissions: {}

jobs:
find:
if: github.repository == 'nodejs/nodejs.org'
runs-on: ubuntu-latest

steps:
- name: Harden Runner
uses: step-security/harden-runner@0634a2670c59f64b4a01f0f96f84700a4088b9f0 # v2.12.0
with:
egress-policy: audit

- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2

- name: Report inactive collaborators
id: inactive
uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7.0.1
with:
script: |
const report = await import("${{github.workspace}}/.github/scripts/report-inactive-collaborators.mjs");
report(github, exec)
15 changes: 14 additions & 1 deletion CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@ Thank you for your interest in contributing to the Node.js Website. Before you p

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

</details>

### Maintaining Collaborator Status

Once you become a collaborator, you are expected to uphold certain responsibilities and standards to maintain your status:

- **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.

- **Remain Active**: Collaborators are expected to author commits at least once in the past twelve months.

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.

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.

## Getting started

The steps below will give you a general idea of how to prepare your local environment for the Node.js Website and general steps
Expand Down
40 changes: 39 additions & 1 deletion GOVERNANCE.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,45 @@ The Node.js Website Team is responsible for the day-to-day technical development

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.

Members of this team are nominated through the guidelines provided in the Collaborator Guidelines within this repository.
Members of this team are nominated through the guidelines provided in the Collaborator Guidelines within this repository. After a passed nominations, members should submit a PR to add themselves to the list of current members, shown below.

#### Current Members

- [araujogui](https://github.com/araujogui) - **Guilherme Araújo**

- [AugustinMauroy](https://github.com/AugustinMauroy) - **Augustin Mauroy**

- [avivkeller](https://github.com/avivkeller) - **Aviv Keller**

- [aymen94](https://github.com/aymen94) - **Aymen Naghmouchi**

- [benhalverson](https://github.com/benhalverson) - **Ben Halverson**

- [bjohansebas](https://github.com/bjohansebas) - **Sebastian Beltran**

- [bmuenzenmeyer](https://github.com/bmuenzenmeyer) - **Brian Muenzenmeyer**

- [bnb](https://github.com/bnb) - **Tierney Cyren**

- [canerakdas](https://github.com/canerakdas) - **Caner Akdas**

- [dario-piotrowicz](https://github.com/dario-piotrowicz) - **Dario Piotrowicz**

- [Harkunwar](https://github.com/Harkunwar) - **Harkunwar Kochar**

- [HinataKah0](https://github.com/HinataKah0) - **HinataKah0**

- [manishprivet](https://github.com/manishprivet) - **Manish Kumar**

- [mikeesto](https://github.com/mikeesto) - **Michael Esteban**

- [ovflowd](https://github.com/ovflowd) - **Claudio Wunder**

- [rodion-arr](https://github.com/rodion-arr) - **Rodion Abdurakhimov**

- [SEWeiTung](https://github.com/SEWeiTung) - **Wei Tung**

- [shanpriyan](https://github.com/shanpriyan) - **Shanmughapriyan S**

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

Expand Down
Loading