diff --git a/.github/scripts/report-inactive-collaborators.mjs b/.github/scripts/report-inactive-collaborators.mjs new file mode 100644 index 0000000000000..5f441474e2ce3 --- /dev/null +++ b/.github/scripts/report-inactive-collaborators.mjs @@ -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? + +@nodejs/nodejs-website 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); +} diff --git a/.github/workflows/find-inactive-collaborators.yml b/.github/workflows/find-inactive-collaborators.yml new file mode 100644 index 0000000000000..70d9ae441c90e --- /dev/null +++ b/.github/workflows/find-inactive-collaborators.yml @@ -0,0 +1,31 @@ +name: Find inactive collaborators + +on: + schedule: + # Run every Monday at 4:05 AM UTC. + - cron: 5 4 * * 1 + + workflow_dispatch: + +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); diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 47016c323f154..e1c5ce1bb82c0 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -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) @@ -54,6 +55,18 @@ If you're an active contributor seeking to become a member, we recommend you con +### 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 diff --git a/GOVERNANCE.md b/GOVERNANCE.md index 6f6d463030dbc..247bb3eeb7bc7 100644 --- a/GOVERNANCE.md +++ b/GOVERNANCE.md @@ -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 [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. + +#### Current Members + +- [araujogui](https://github.com/araujogui) - **Guilherme Araújo** (he/him) + +- [AugustinMauroy](https://github.com/AugustinMauroy) - **Augustin Mauroy** (he/him) + +- [avivkeller](https://github.com/avivkeller) - **Aviv Keller** (he/him) + +- [aymen94](https://github.com/aymen94) - **Aymen Naghmouchi** + +- [benhalverson](https://github.com/benhalverson) - **Ben Halverson** (he/him) + +- [bjohansebas](https://github.com/bjohansebas) - **Sebastian Beltran** + +- [bmuenzenmeyer](https://github.com/bmuenzenmeyer) - **Brian Muenzenmeyer** (he/him) + +- [bnb](https://github.com/bnb) - **Tierney Cyren** (they/them) + +- [canerakdas](https://github.com/canerakdas) - **Caner Akdas** + +- [dario-piotrowicz](https://github.com/dario-piotrowicz) - **Dario Piotrowicz** + +- [Harkunwar](https://github.com/Harkunwar) - **Harkunwar Kochar** (he/him) + +- [HinataKah0](https://github.com/HinataKah0) - **HinataKah0** (he/him) + +- [manishprivet](https://github.com/manishprivet) - **Manish Kumar** (he/him) + +- [mikeesto](https://github.com/mikeesto) - **Michael Esteban** (he/him) + +- [ovflowd](https://github.com/ovflowd) - **Claudio Wunder** (they/them) + +- [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`)