11export type Role = 'steward' | 'maintainer' | 'contributor'
22
3+ export interface SocialAccount {
4+ provider : string
5+ url : string
6+ }
7+
38export interface GitHubContributor {
49 login : string
510 id : number
@@ -8,9 +13,12 @@ export interface GitHubContributor {
813 contributions : number
914 role : Role
1015 sponsors_url : string | null
16+ bio : string | null
17+ twitterUsername : string | null
18+ socialAccounts : SocialAccount [ ]
1119}
1220
13- type GitHubAPIContributor = Omit < GitHubContributor , 'role' | 'sponsors_url' >
21+ type GitHubAPIContributor = Omit < GitHubContributor , 'role' | 'sponsors_url' | 'bio' | 'twitterUsername' | 'socialAccounts' >
1422
1523// Fallback when no GitHub token is available (e.g. preview environments).
1624// Only stewards are shown as maintainers; everyone else is a contributor.
@@ -60,16 +68,31 @@ async function fetchTeamMembers(token: string): Promise<TeamMembers | null> {
6068 }
6169}
6270
71+ interface GovernanceProfile {
72+ hasSponsorsListing : boolean
73+ bio : string | null
74+ twitterUsername : string | null
75+ socialAccounts : SocialAccount [ ]
76+ }
77+
6378/**
64- * Batch-query GitHub GraphQL API to check which users have sponsors enabled .
65- * Returns a Set of logins that have a sponsors listing.
79+ * Batch-query GitHub GraphQL API to fetch profile data for governance members .
80+ * Returns bio, social accounts, and sponsors listing status .
6681 */
67- async function fetchSponsorable ( token : string , logins : string [ ] ) : Promise < Set < string > > {
68- if ( logins . length === 0 ) return new Set ( )
82+ async function fetchGovernanceProfiles (
83+ token : string ,
84+ logins : string [ ] ,
85+ ) : Promise < Map < string , GovernanceProfile > > {
86+ if ( logins . length === 0 ) return new Map ( )
6987
70- // Build aliased GraphQL query: user0: user(login: "x") { hasSponsorsListing login }
7188 const fragments = logins . map (
72- ( login , i ) => `user${ i } : user(login: "${ login } ") { hasSponsorsListing login }` ,
89+ ( login , i ) => `user${ i } : user(login: "${ login } ") {
90+ login
91+ hasSponsorsListing
92+ bio
93+ twitterUsername
94+ socialAccounts(first: 10) { nodes { provider url } }
95+ }` ,
7396 )
7497 const query = `{ ${ fragments . join ( '\n' ) } }`
7598
@@ -85,26 +108,40 @@ async function fetchSponsorable(token: string, logins: string[]): Promise<Set<st
85108 } )
86109
87110 if ( ! response . ok ) {
88- console . warn ( `Failed to fetch sponsors info : ${ response . status } ` )
89- return new Set ( )
111+ console . warn ( `Failed to fetch governance profiles : ${ response . status } ` )
112+ return new Map ( )
90113 }
91114
92115 const json = ( await response . json ( ) ) as {
93- data ?: Record < string , { login : string ; hasSponsorsListing : boolean } | null >
116+ data ?: Record < string , {
117+ login : string
118+ hasSponsorsListing : boolean
119+ bio : string | null
120+ twitterUsername : string | null
121+ socialAccounts : { nodes : { provider : string ; url : string } [ ] }
122+ } | null >
94123 }
95124
96- const sponsorable = new Set < string > ( )
125+ const profiles = new Map < string , GovernanceProfile > ( )
97126 if ( json . data ) {
98127 for ( const user of Object . values ( json . data ) ) {
99- if ( user ?. hasSponsorsListing ) {
100- sponsorable . add ( user . login )
128+ if ( user ) {
129+ profiles . set ( user . login , {
130+ hasSponsorsListing : user . hasSponsorsListing ,
131+ bio : user . bio ,
132+ twitterUsername : user . twitterUsername ,
133+ socialAccounts : user . socialAccounts . nodes . map ( n => ( {
134+ provider : n . provider ,
135+ url : n . url ,
136+ } ) ) ,
137+ } )
101138 }
102139 }
103140 }
104- return sponsorable
141+ return profiles
105142 } catch ( error ) {
106- console . warn ( 'Failed to fetch sponsors info :' , error )
107- return new Set ( )
143+ console . warn ( 'Failed to fetch governance profiles :' , error )
144+ return new Map ( )
108145 }
109146}
110147
@@ -172,18 +209,22 @@ export default defineCachedEventHandler(
172209 . filter ( c => teams . steward . has ( c . login ) || teams . maintainer . has ( c . login ) )
173210 . map ( c => c . login )
174211
175- const sponsorable = githubToken
176- ? await fetchSponsorable ( githubToken , maintainerLogins )
177- : new Set < string > ( )
212+ const governanceProfiles = githubToken
213+ ? await fetchGovernanceProfiles ( githubToken , maintainerLogins )
214+ : new Map < string , GovernanceProfile > ( )
178215
179216 return filtered
180217 . map ( c => {
181218 const { role, order } = getRoleInfo ( c . login , teams )
182- const sponsors_url = sponsorable . has ( c . login )
219+ const profile = governanceProfiles . get ( c . login )
220+ const sponsors_url = profile ?. hasSponsorsListing
183221 ? `https://github.com/sponsors/${ c . login } `
184222 : null
185- Object . assign ( c , { role, order, sponsors_url } )
186- return c as GitHubContributor & { order : number ; sponsors_url : string | null ; role : Role }
223+ const bio = profile ?. bio ?? null
224+ const twitterUsername = profile ?. twitterUsername ?? null
225+ const socialAccounts = profile ?. socialAccounts ?? [ ]
226+ Object . assign ( c , { role, order, sponsors_url, bio, twitterUsername, socialAccounts } )
227+ return c as GitHubContributor & { order : number }
187228 } )
188229 . sort ( ( a , b ) => a . order - b . order || b . contributions - a . contributions )
189230 . map ( ( { order : _ , ...rest } ) => rest )
0 commit comments