1515 *
1616 * Usage:
1717 * LIBRARIES_IO_API_KEY=xxx GITHUB_TOKEN=yyy \
18- * tsx src/scripts/fetchMavenMaintainers.ts /path/to/packages_top500.csv [output.csv]
18+ * tsx src/maven/ scripts/fetchMavenMaintainers.ts /path/to/packages_top500.csv [output.csv]
1919 */
2020import axios , { AxiosInstance } from 'axios'
2121import * as fs from 'fs'
@@ -94,7 +94,7 @@ async function safeGet<T>(client: AxiosInstance, url: string): Promise<T | null>
9494 await sleep ( wait )
9595 return safeGet < T > ( client , url )
9696 }
97- console . warn ( ` HTTP ${ status } for ${ url } ` )
97+ console . warn ( ` HTTP ${ status } for ${ url . replace ( / ( [ ? & ] a p i _ k e y = ) [ ^ & ] + / , '$1[redacted]' ) } ` )
9898 }
9999 return null
100100 }
@@ -358,10 +358,11 @@ function parseCODEOWNERS(content: string): string[] {
358358 for ( const line of content . split ( '\n' ) ) {
359359 const trimmed = line . trim ( )
360360 if ( ! trimmed || trimmed . startsWith ( '#' ) ) continue
361- // Match @login — skip @org/team (contain a slash after @ )
361+ // Match @login — skip @org/team entries ( slash immediately after the handle )
362362 let match : RegExpExecArray | null
363363 const re = / @ ( [ a - z A - Z 0 - 9 ] (?: [ a - z A - Z 0 - 9 - ] * [ a - z A - Z 0 - 9 ] ) ? ) / g
364364 while ( ( match = re . exec ( trimmed ) ) !== null ) {
365+ if ( trimmed [ match . index + match [ 0 ] . length ] === '/' ) continue
365366 const login = match [ 1 ] . toLowerCase ( )
366367 if ( ! seen . has ( login ) ) {
367368 seen . add ( login )
@@ -447,8 +448,9 @@ async function fetchTopContributors(
447448) : Promise < GithubContributor [ ] > {
448449 const results : GithubContributor [ ] = [ ]
449450 let page = 1
451+ let hasMore = true
450452
451- while ( true ) {
453+ while ( hasMore ) {
452454 const data = await safeGet < GithubContributor [ ] > (
453455 github ,
454456 `/repos/${ owner } /${ repo } /contributors?per_page=100&page=${ page } &anon=false` ,
@@ -458,13 +460,11 @@ async function fetchTopContributors(
458460 const users = data . filter ( ( c ) => c . type === 'User' )
459461 results . push ( ...users )
460462
461- // Stop if we've hit the requested limit (0 = unlimited)
462- if ( limit > 0 && results . length >= limit ) break
463- // Stop if GitHub returned fewer than a full page — no more pages
464- if ( data . length < 100 ) break
465-
466- page ++
467- await sleep ( GITHUB_DELAY_MS )
463+ hasMore = data . length === 100 && ( limit === 0 || results . length < limit )
464+ if ( hasMore ) {
465+ page ++
466+ await sleep ( GITHUB_DELAY_MS )
467+ }
468468 }
469469
470470 return limit > 0 ? results . slice ( 0 , limit ) : results
@@ -658,7 +658,7 @@ async function main() {
658658 const inputPath = process . argv [ 2 ]
659659 if ( ! inputPath ) {
660660 console . error (
661- 'Usage: LIBRARIES_IO_API_KEY=xxx GITHUB_TOKEN=yyy tsx src/scripts/fetchMavenMaintainers.ts <input.csv> [output.csv]' ,
661+ 'Usage: LIBRARIES_IO_API_KEY=xxx GITHUB_TOKEN=yyy tsx src/maven/ scripts/fetchMavenMaintainers.ts <input.csv> [output.csv]' ,
662662 )
663663 process . exit ( 1 )
664664 }
@@ -676,16 +676,17 @@ async function main() {
676676 const CSV_HEADER =
677677 'package_purl,package_namespace,package_name,github_repo,repo_source,maintainer_github_login,maintainer_display_name,maintainer_email,maintainer_url,role,source,contributions,notes'
678678
679- const lines : string [ ] = [ CSV_HEADER ]
679+ const resultsByIndex : MaintainerRow [ ] [ ] = new Array ( rows . length )
680680 const stats = { codeowners : 0 , maintainers_file : 0 , contributors : 0 , none : 0 }
681+ let completed = 0
681682
682683 // Concurrency 3 — Libraries.io rate limit is the bottleneck
683684 await withConcurrency ( rows , 3 , async ( row , i ) => {
684685 const pkg = `${ row . namespace } /${ row . name } `
685686 process . stdout . write ( `[${ i + 1 } /${ rows . length } ] ${ pkg } ... ` )
686687
687688 const results = await enrichPackage ( row )
688- for ( const r of results ) lines . push ( toCsvLine ( r ) )
689+ resultsByIndex [ i ] = results
689690
690691 const source = results [ 0 ] ?. source ?? ''
691692 if ( source === 'codeowners' ) stats . codeowners ++
@@ -695,15 +696,19 @@ async function main() {
695696
696697 console . log ( `${ results . length } maintainer(s) [${ source || 'none' } ]` )
697698
698- // Checkpoint every 50 packages — safe to re-run
699- if ( ( i + 1 ) % 50 === 0 ) {
700- fs . writeFileSync ( outputPath , lines . join ( '\n' ) )
701- console . log ( ` ✓ checkpoint saved (${ i + 1 } done)` )
699+ // Checkpoint every 50 packages — write rows in original input order
700+ completed ++
701+ if ( completed % 50 === 0 ) {
702+ const checkpointLines = [ CSV_HEADER , ...resultsByIndex . flat ( ) . map ( toCsvLine ) ]
703+ fs . writeFileSync ( outputPath , checkpointLines . join ( '\n' ) )
704+ console . log ( ` ✓ checkpoint saved (${ completed } done)` )
702705 }
703706 } )
704707
708+ const lines = [ CSV_HEADER , ...resultsByIndex . flat ( ) . map ( toCsvLine ) ]
705709 fs . writeFileSync ( outputPath , lines . join ( '\n' ) )
706710 console . log ( `\nDone. ${ lines . length - 1 } rows written to: ${ outputPath } ` )
711+
707712 console . log (
708713 ` CODEOWNERS: ${ stats . codeowners } | MAINTAINERS file: ${ stats . maintainers_file } | contributors fallback: ${ stats . contributors } | not found: ${ stats . none } ` ,
709714 )
0 commit comments