@@ -423,58 +423,74 @@ export const generateContributorReport = (username, { includeAllFiles = false }
423423 * @returns {string }
424424 */
425425export const generateMarkdownReport = ( reports , missingCount = 0 ) => {
426- // The report is intentionally minimal: a single list of affected PRs and
427- // a single copy/paste command maintainers can run locally.
428- // No timestamps, per-file breakdowns, or duplicated metadata.
429-
430426 if ( ! missingCount ) {
431427 return 'No missing contributors detected.\n' ;
432428 }
433429
434- // 1) Single list of affected PRs (deduped).
435- const prEntries = new Map ( ) ; // key=prNumber or url, value={number,url,mergedAt}
436- for ( const report of reports ) {
437- for ( const pr of report . prs ) {
438- const key = pr . prUrl || String ( pr . prNumber ) ;
439- if ( ! prEntries . has ( key ) ) {
440- prEntries . set ( key , {
441- number : pr . prNumber ,
442- url : pr . prUrl ,
443- mergedAt : pr . mergedAt
444- } ) ;
445- }
446- }
447- }
430+ const nowIso = new Date ( ) . toISOString ( ) ;
448431
449- const prList = Array . from ( prEntries . values ( ) ) . sort ( ( a , b ) => {
450- // Prefer chronological sort for stable “what happened” review.
451- const aTime = a . mergedAt ? Date . parse ( a . mergedAt ) : 0 ;
452- const bTime = b . mergedAt ? Date . parse ( b . mergedAt ) : 0 ;
453- if ( aTime !== bTime ) return aTime - bTime ;
454- return ( a . number ?? 0 ) - ( b . number ?? 0 ) ;
455- } ) ;
456432
457- // 2) One command (one line). If multiple users are missing, chain them.
458- const commandParts = [ ] ;
459- for ( const report of reports ) {
433+ const computeTypesArg = ( report ) => {
460434 const typeSet = new Set ( ) ;
461- for ( const pr of report . prs ) {
435+ for ( const pr of report . prs || [ ] ) {
462436 for ( const type of pr . contributionTypes || [ ] ) {
463- typeSet . add ( type ) ;
437+ if ( type ) {
438+ typeSet . add ( type ) ;
439+ }
464440 }
465441 }
466442
467- const types = Array . from ( typeSet ) . filter ( Boolean ) . sort ( ( a , b ) => a . localeCompare ( b ) ) ;
468- const typesArg = types . length > 0 ? types . join ( ',' ) : 'code' ;
469- commandParts . push ( `npx all-contributors add ${ report . username } ${ typesArg } ` ) ;
443+ const types = Array . from ( typeSet ) . sort ( ( a , b ) => a . localeCompare ( b ) ) ;
444+ return types . length > 0 ? types . join ( ',' ) : 'code' ;
445+ } ;
446+
447+ const lines = [ ] ;
448+
449+ lines . push ( '# Missing Contributors Report' ) ;
450+ lines . push ( '' ) ;
451+ lines . push ( `Generated (ISO): ${ nowIso } ` ) ;
452+ lines . push ( '' ) ;
453+ lines . push ( `Missing contributors: ${ missingCount } ` ) ;
454+ lines . push ( '' ) ;
455+
456+ for ( const report of reports ) {
457+ lines . push ( `## @${ report . username } ` ) ;
458+ lines . push ( '' ) ;
459+
460+ const prs = Array . from ( report . prs || [ ] ) . sort ( ( a , b ) => {
461+ // Prefer most recent PRs first.
462+ const aTime = a . mergedAt ? Date . parse ( a . mergedAt ) : 0 ;
463+ const bTime = b . mergedAt ? Date . parse ( b . mergedAt ) : 0 ;
464+ if ( aTime !== bTime ) return bTime - aTime ;
465+ return ( b . prNumber ?? 0 ) - ( a . prNumber ?? 0 ) ;
466+ } ) ;
467+
468+ for ( const pr of prs ) {
469+ lines . push ( `### PR #${ pr . prNumber } : ${ pr . prTitle } ` ) ;
470+ lines . push ( '' ) ;
471+ lines . push ( `Add this comment on PR: ${ pr . prUrl } ` ) ;
472+ lines . push ( '' ) ;
473+
474+ const prTypes = ( pr . contributionTypes || [ ] ) . filter ( Boolean ) ;
475+ const prTypesArg = prTypes . length > 0 ? prTypes . join ( ', ' ) : 'code' ;
476+
477+ lines . push ( '```text' ) ;
478+ lines . push ( `@all-contributors please add @${ report . username } for ${ prTypesArg } ` ) ;
479+ lines . push ( '```' ) ;
480+ lines . push ( '' ) ;
481+ }
482+
483+ lines . push ( '### CLI command (all-contributors-cli)' ) ;
484+ lines . push ( '' ) ;
485+ lines . push ( '```bash' ) ;
486+ lines . push ( `npx all-contributors add ${ report . username } ${ computeTypesArg ( report ) } ` ) ;
487+ lines . push ( '```' ) ;
488+ lines . push ( '' ) ;
489+ lines . push ( '---' ) ;
490+ lines . push ( '' ) ;
470491 }
471492
472- let markdown = '' ;
473- markdown += prList . map ( ( pr ) => `- #${ pr . number } ${ pr . url } ` ) . join ( '\n' ) ;
474- markdown += '\n\n' ;
475- markdown += commandParts . join ( ' && ' ) ;
476- markdown += '\n' ;
477- return markdown ;
493+ return `${ lines . join ( '\n' ) } \n` ;
478494} ;
479495
480496/**
@@ -552,14 +568,9 @@ export const autoAddCommentsToReports = (reports) => {
552568
553569const main = ( ) => {
554570 try {
555- const ghToken = process . env . GITHUB_TOKEN || process . env . PRIVATE_TOKEN ;
556- if ( ! ghToken ) {
557- console . error ( '❌ GITHUB_TOKEN or PRIVATE_TOKEN environment variable is required for GitHub CLI operations' ) ;
558- process . exit ( 1 ) ;
559- }
560-
561- // gh CLI only reads GITHUB_TOKEN or GH_TOKEN, so ensure it's set
562- if ( process . env . PRIVATE_TOKEN && ! process . env . GITHUB_TOKEN ) {
571+ // gh CLI can use either its own authenticated session or token env vars.
572+ // In CI, we commonly receive a token via PRIVATE_TOKEN.
573+ if ( process . env . PRIVATE_TOKEN && ! process . env . GITHUB_TOKEN && ! process . env . GH_TOKEN ) {
563574 process . env . GITHUB_TOKEN = process . env . PRIVATE_TOKEN ;
564575 }
565576
0 commit comments