@@ -18,6 +18,13 @@ const START_DATE = '2025-01-01';
1818const END_DATE = '2025-12-31' ;
1919const MIN_CONTRIBUTIONS = 1 ; // Include anyone with at least 1 commit or PR
2020
21+ // Merge alternate accounts into primary accounts
22+ // Key = alias, Value = primary account
23+ const ACCOUNT_ALIASES = {
24+ 'eccuraa' : 'elenacura' ,
25+ 'nwoodruff-co' : 'nikhilwoodruff'
26+ } ;
27+
2128const TOKEN = process . env . GITHUB_TOKEN ;
2229const headers = {
2330 'Accept' : 'application/vnd.github.v3+json' ,
@@ -84,10 +91,15 @@ async function discoverContributors() {
8491 for ( const commit of commitsData . items ) {
8592 const author = commit . author ;
8693 if ( author && author . login && ! author . login . includes ( '[bot]' ) ) {
87- const existing = contributors . get ( author . login ) || { commits : 0 , prs : 0 } ;
94+ // Resolve alias to primary account
95+ const login = ACCOUNT_ALIASES [ author . login ] || author . login ;
96+ const existing = contributors . get ( login ) || { commits : 0 , prs : 0 , aliases : [ ] } ;
8897 existing . commits ++ ;
8998 existing . avatar = author . avatar_url ;
90- contributors . set ( author . login , existing ) ;
99+ if ( author . login !== login && ! existing . aliases . includes ( author . login ) ) {
100+ existing . aliases . push ( author . login ) ;
101+ }
102+ contributors . set ( login , existing ) ;
91103 }
92104 }
93105
@@ -101,10 +113,15 @@ async function discoverContributors() {
101113 for ( const pr of prsData . items ) {
102114 const author = pr . user ;
103115 if ( author && author . login && ! author . login . includes ( '[bot]' ) ) {
104- const existing = contributors . get ( author . login ) || { commits : 0 , prs : 0 } ;
116+ // Resolve alias to primary account
117+ const login = ACCOUNT_ALIASES [ author . login ] || author . login ;
118+ const existing = contributors . get ( login ) || { commits : 0 , prs : 0 , aliases : [ ] } ;
105119 existing . prs ++ ;
106120 existing . avatar = author . avatar_url ;
107- contributors . set ( author . login , existing ) ;
121+ if ( author . login !== login && ! existing . aliases . includes ( author . login ) ) {
122+ existing . aliases . push ( author . login ) ;
123+ }
124+ contributors . set ( login , existing ) ;
108125 }
109126 }
110127
@@ -117,7 +134,8 @@ async function discoverContributors() {
117134 github : login ,
118135 name : login , // Will be updated with real name if available
119136 avatar : data . avatar ,
120- estimatedActivity : total
137+ estimatedActivity : total ,
138+ aliases : data . aliases || [ ]
121139 } ) ;
122140 }
123141 }
@@ -167,6 +185,8 @@ async function fetchPRDetails(owner, repo, prNumber) {
167185
168186async function fetchMemberData ( member ) {
169187 console . log ( `\nFetching data for @${ member . github } ...` ) ;
188+ const aliases = member . aliases || [ ] ;
189+ const allAccounts = [ member . github , ...aliases ] ;
170190
171191 // Get user profile
172192 const profile = await fetchUserProfile ( member . github ) ;
@@ -176,36 +196,64 @@ async function fetchMemberData(member) {
176196
177197 await sleep ( 500 ) ;
178198
179- // Fetch commits
199+ // Fetch commits for all accounts (primary + aliases)
180200 console . log ( ' Commits...' ) ;
181- const commitsUrl = `https://api.github.com/search/commits?q=author:${ member . github } +org:${ GITHUB_ORG } +committer-date:${ START_DATE } ..${ END_DATE } ` ;
182- const commitsData = await fetchAllPages ( commitsUrl , 10 ) ;
201+ const allCommits = [ ] ;
202+ let totalCommits = 0 ;
203+ for ( const account of allAccounts ) {
204+ const commitsUrl = `https://api.github.com/search/commits?q=author:${ account } +org:${ GITHUB_ORG } +committer-date:${ START_DATE } ..${ END_DATE } ` ;
205+ const commitsData = await fetchAllPages ( commitsUrl , 10 ) ;
206+ allCommits . push ( ...commitsData . items ) ;
207+ totalCommits += commitsData . total_count ;
208+ await sleep ( 500 ) ;
209+ }
183210
184- await sleep ( 1000 ) ;
211+ await sleep ( 500 ) ;
185212
186- // Fetch PRs authored
213+ // Fetch PRs authored for all accounts
187214 console . log ( ' PRs authored...' ) ;
188- const prsUrl = `https://api.github.com/search/issues?q=author:${ member . github } +org:${ GITHUB_ORG } +type:pr+created:${ START_DATE } ..${ END_DATE } ` ;
189- const prsData = await fetchAllPages ( prsUrl , 10 ) ;
215+ const allPRs = [ ] ;
216+ let totalPRs = 0 ;
217+ for ( const account of allAccounts ) {
218+ const prsUrl = `https://api.github.com/search/issues?q=author:${ account } +org:${ GITHUB_ORG } +type:pr+created:${ START_DATE } ..${ END_DATE } ` ;
219+ const prsData = await fetchAllPages ( prsUrl , 10 ) ;
220+ allPRs . push ( ...prsData . items ) ;
221+ totalPRs += prsData . total_count ;
222+ await sleep ( 500 ) ;
223+ }
190224
191- await sleep ( 1000 ) ;
225+ await sleep ( 500 ) ;
192226
193- // Fetch PRs reviewed
227+ // Fetch PRs reviewed for all accounts
194228 console . log ( ' PRs reviewed...' ) ;
195- const reviewsUrl = `https://api.github.com/search/issues?q=reviewed-by:${ member . github } +org:${ GITHUB_ORG } +type:pr+created:${ START_DATE } ..${ END_DATE } ` ;
196- const reviewsData = await fetchAllPages ( reviewsUrl , 5 ) ;
229+ let totalReviews = 0 ;
230+ const allReviews = [ ] ;
231+ for ( const account of allAccounts ) {
232+ const reviewsUrl = `https://api.github.com/search/issues?q=reviewed-by:${ account } +org:${ GITHUB_ORG } +type:pr+created:${ START_DATE } ..${ END_DATE } ` ;
233+ const reviewsData = await fetchAllPages ( reviewsUrl , 5 ) ;
234+ allReviews . push ( ...reviewsData . items ) ;
235+ totalReviews += reviewsData . total_count ;
236+ await sleep ( 500 ) ;
237+ }
197238
198- await sleep ( 1000 ) ;
239+ await sleep ( 500 ) ;
199240
200- // Fetch issues created
241+ // Fetch issues created for all accounts
201242 console . log ( ' Issues created...' ) ;
202- const issuesUrl = `https://api.github.com/search/issues?q=author:${ member . github } +org:${ GITHUB_ORG } +type:issue+created:${ START_DATE } ..${ END_DATE } ` ;
203- const issuesData = await fetchAllPages ( issuesUrl , 5 ) ;
243+ let totalIssues = 0 ;
244+ const allIssues = [ ] ;
245+ for ( const account of allAccounts ) {
246+ const issuesUrl = `https://api.github.com/search/issues?q=author:${ account } +org:${ GITHUB_ORG } +type:issue+created:${ START_DATE } ..${ END_DATE } ` ;
247+ const issuesData = await fetchAllPages ( issuesUrl , 5 ) ;
248+ allIssues . push ( ...issuesData . items ) ;
249+ totalIssues += issuesData . total_count ;
250+ await sleep ( 500 ) ;
251+ }
204252
205253 // Fetch PR details for top 30 PRs
206254 console . log ( ' Fetching PR details...' ) ;
207255 const prsWithFiles = [ ] ;
208- for ( const pr of prsData . items . slice ( 0 , 30 ) ) {
256+ for ( const pr of allPRs . slice ( 0 , 30 ) ) {
209257 const repoName = pr . repository_url . split ( '/' ) . pop ( ) ;
210258 await sleep ( 300 ) ;
211259 const [ files , details ] = await Promise . all ( [
@@ -228,21 +276,30 @@ async function fetchMemberData(member) {
228276
229277 // Process commits by repo
230278 const repoCommits = { } ;
231- for ( const commit of commitsData . items ) {
279+ for ( const commit of allCommits ) {
232280 const repo = commit . repository . name ;
233281 repoCommits [ repo ] = ( repoCommits [ repo ] || 0 ) + 1 ;
234282 }
235283
284+ // Count unique repos contributed to
285+ const reposContributed = new Set ( ) ;
286+ for ( const commit of allCommits ) {
287+ reposContributed . add ( commit . repository . name ) ;
288+ }
289+ for ( const pr of allPRs ) {
290+ reposContributed . add ( pr . repository_url . split ( '/' ) . pop ( ) ) ;
291+ }
292+
236293 // Process monthly activity
237294 const monthlyPRs = new Array ( 12 ) . fill ( 0 ) ;
238295 const monthlyCommits = new Array ( 12 ) . fill ( 0 ) ;
239296
240- for ( const pr of prsData . items ) {
297+ for ( const pr of allPRs ) {
241298 const month = new Date ( pr . created_at ) . getMonth ( ) ;
242299 monthlyPRs [ month ] ++ ;
243300 }
244301
245- for ( const commit of commitsData . items ) {
302+ for ( const commit of allCommits ) {
246303 const date = commit . commit ?. author ?. date || commit . commit ?. committer ?. date ;
247304 if ( date ) {
248305 const month = new Date ( date ) . getMonth ( ) ;
@@ -253,18 +310,19 @@ async function fetchMemberData(member) {
253310 return {
254311 member,
255312 stats : {
256- commits : commitsData . total_count ,
257- prs : prsData . total_count ,
258- reviews : reviewsData . total_count ,
259- issues : issuesData . total_count ,
313+ commits : totalCommits ,
314+ prs : totalPRs ,
315+ reviews : totalReviews ,
316+ issues : totalIssues ,
317+ repos : reposContributed . size ,
260318 } ,
261319 repoCommits,
262320 monthlyPRs,
263321 monthlyCommits,
264322 prs : prsWithFiles ,
265- reviews : reviewsData . items . slice ( 0 , 30 ) ,
266- issues : issuesData . items . slice ( 0 , 30 ) ,
267- commits : commitsData . items . slice ( 0 , 100 ) . map ( c => ( {
323+ reviews : allReviews . slice ( 0 , 30 ) ,
324+ issues : allIssues . slice ( 0 , 30 ) ,
325+ commits : allCommits . slice ( 0 , 100 ) . map ( c => ( {
268326 sha : c . sha ,
269327 message : c . commit . message . split ( '\n' ) [ 0 ] ,
270328 date : c . commit . author ?. date ,
0 commit comments