1
+ import { readFile } from 'node:fs/promises' ;
2
+
3
+ const CONFIG = {
4
+ FILE : 'MEMBERS.md' ,
5
+ HEADER : '## Node.js Website Team (`@nodejs/nodejs-website`)' ,
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
+ // Check if there's already an open issue
19
+ async function hasOpenIssue ( github , context ) {
20
+ const { owner, repo } = context . repo ;
21
+ const { data : issues } = await github . rest . issues . listForRepo ( {
22
+ owner,
23
+ repo,
24
+ state : 'open' ,
25
+ labels : CONFIG . ISSUE_LABELS [ 1 ] ,
26
+ per_page : 1 ,
27
+ } ) ;
28
+
29
+ return issues . length > 0 ;
30
+ }
31
+
32
+ // Parse collaborator usernames from governance file
33
+ async function parseCollaborators ( ) {
34
+ const content = await readFile ( CONFIG . FILE , 'utf8' ) ;
35
+ const lines = content . split ( '\n' ) ;
36
+ const collaborators = [ ] ;
37
+
38
+ const startIndex =
39
+ lines . findIndex ( l => l . startsWith ( CONFIG . HEADER ) ) + 1 ;
40
+ if ( startIndex <= 0 ) return collaborators ;
41
+
42
+ for ( let i = startIndex ; i < lines . length ; i ++ ) {
43
+ const line = lines [ i ] ;
44
+ if ( line . startsWith ( '#' ) ) break ;
45
+
46
+ const match = line . match ( / ^ \s * - \s * \[ ( [ ^ \] ] + ) \] / ) ;
47
+ if ( match ) collaborators . push ( match [ 1 ] ) ;
48
+ }
49
+
50
+ return collaborators ;
51
+ }
52
+
53
+ // Check if users have been active since cutoff date
54
+ async function getInactiveUsers ( github , usernames , repo , cutoffDate ) {
55
+ const inactiveUsers = [ ] ;
56
+
57
+ for ( const username of usernames ) {
58
+ // Check commits
59
+ const { data : commits } = await github . rest . search . commits ( {
60
+ q : `author:${ username } repo:${ repo } committer-date:>=${ cutoffDate } ` ,
61
+ per_page : 1 ,
62
+ } ) ;
63
+
64
+ // Check issues and PRs
65
+ const { data : issues } = await github . rest . search . issuesAndPullRequests ( {
66
+ q : `involves:${ username } repo:${ repo } updated:>=${ cutoffDate } ` ,
67
+ per_page : 1 ,
68
+ } ) ;
69
+
70
+ // User is inactive if they have no commits AND no issues/PRs
71
+ if ( commits . total_count === 0 && issues . total_count === 0 ) {
72
+ inactiveUsers . push ( username ) ;
73
+ }
74
+ }
75
+
76
+ return inactiveUsers ;
77
+ }
78
+
79
+ // Generate report for inactive members
80
+ function formatReport ( inactiveMembers , cutoffDate ) {
81
+ if ( ! inactiveMembers . length ) return null ;
82
+
83
+ const today = getDateMonthsAgo ( 0 ) ;
84
+ return `# Inactive Collaborators Report
85
+
86
+ Last updated: ${ today }
87
+ Checking for inactivity since: ${ cutoffDate }
88
+
89
+ ## Inactive Collaborators (${ inactiveMembers . length } )
90
+
91
+ | Login |
92
+ | ----- |
93
+ ${ inactiveMembers . map ( m => `| @${ m } |` ) . join ( '\n' ) }
94
+
95
+ ## What happens next?
96
+
97
+ @nodejs/nodejs-website should review this list and contact inactive collaborators to confirm their continued interest in participating in the project.` ;
98
+ }
99
+
100
+ async function createIssue ( github , context , report ) {
101
+ if ( ! report ) return ;
102
+
103
+ const { owner, repo } = context . repo ;
104
+ await github . rest . issues . create ( {
105
+ owner,
106
+ repo,
107
+ title : CONFIG . ISSUE_TITLE ,
108
+ body : report ,
109
+ labels : CONFIG . ISSUE_LABELS ,
110
+ } ) ;
111
+ }
112
+
113
+ export default async function ( github , context ) {
114
+ // Check for existing open issue first - exit early if one exists
115
+ if ( await hasOpenIssue ( github , context ) ) {
116
+ return ;
117
+ }
118
+
119
+ const cutoffDate = getDateMonthsAgo ( ) ;
120
+ const collaborators = await parseCollaborators ( ) ;
121
+
122
+ const inactiveMembers = await getInactiveUsers (
123
+ github ,
124
+ collaborators ,
125
+ `${ context . repo . owner } /${ context . repo . repo } ` ,
126
+ cutoffDate
127
+ ) ;
128
+ const report = formatReport ( inactiveMembers , cutoffDate ) ;
129
+
130
+ await createIssue ( github , context , report ) ;
131
+ }
0 commit comments