@@ -143,87 +143,93 @@ async function fetchGitHubUserData(
143143) : Promise < Set < string > > {
144144 if ( logins . length === 0 ) return new Set ( )
145145
146- // Build aliased GraphQL query: user0: user(login: "x") { hasSponsorsListing login }
147- const fragments = logins . map (
148- ( login , i ) =>
149- `user${ i } : user(login: "${ login } ") { hasSponsorsListing login name bio company companyHTML location websiteUrl twitterUsername socialAccounts(first: 10) { nodes { provider url } } }` ,
150- )
151- const query = `{ ${ fragments . join ( '\n' ) } }`
152-
153- try {
154- const response = await fetch ( 'https://api.github.com/graphql' , {
155- method : 'POST' ,
156- headers : {
157- 'Authorization' : `Bearer ${ token } ` ,
158- 'Content-Type' : 'application/json' ,
159- 'User-Agent' : 'npmx' ,
160- } ,
161- body : JSON . stringify ( { query } ) ,
162- } )
146+ const sponsorable = new Set < string > ( )
147+ const chunkSize = 100
148+
149+ for ( let i = 0 ; i < logins . length ; i += chunkSize ) {
150+ const chunk = logins . slice ( i , i + chunkSize )
151+
152+ // Build aliased GraphQL query: user0: user(login: "x") { hasSponsorsListing login }
153+ const fragments = chunk . map (
154+ ( login , idx ) =>
155+ `user${ idx } : user(login: "${ login } ") { hasSponsorsListing login name bio company companyHTML location websiteUrl twitterUsername socialAccounts(first: 10) { nodes { provider url } } }` ,
156+ )
157+ const query = `{ ${ fragments . join ( '\n' ) } }`
158+
159+ try {
160+ const response = await fetch ( 'https://api.github.com/graphql' , {
161+ method : 'POST' ,
162+ headers : {
163+ 'Authorization' : `Bearer ${ token } ` ,
164+ 'Content-Type' : 'application/json' ,
165+ 'User-Agent' : 'npmx' ,
166+ } ,
167+ body : JSON . stringify ( { query } ) ,
168+ } )
163169
164- if ( ! response . ok ) {
165- console . warn ( `Failed to fetch sponsors info: ${ response . status } ` )
166- return new Set ( )
167- }
170+ if ( ! response . ok ) {
171+ console . warn ( `Failed to fetch sponsors info (chunk ${ i } ) : ${ response . status } ` )
172+ continue
173+ }
168174
169- const json = ( await response . json ( ) ) as {
170- data ?: Record <
171- string ,
172- | ( GitHubUserData & {
173- login : string
174- hasSponsorsListing : boolean
175- socialAccounts : { nodes : { provider : string ; url : string } [ ] }
176- } )
177- | null
178- >
179- }
175+ const json = ( await response . json ( ) ) as {
176+ data ?: Record <
177+ string ,
178+ | ( GitHubUserData & {
179+ login : string
180+ hasSponsorsListing : boolean
181+ socialAccounts : { nodes : { provider : string ; url : string } [ ] }
182+ } )
183+ | null
184+ >
185+ }
180186
181- const sponsorable = new Set < string > ( )
182- if ( json . data ) {
183- for ( const user of Object . values ( json . data ) ) {
184- if ( ! user ) continue
185- if ( user . hasSponsorsListing ) {
186- sponsorable . add ( user . login )
187- }
187+ if ( json . data ) {
188+ for ( const user of Object . values ( json . data ) ) {
189+ if ( ! user ) continue
190+ if ( user . hasSponsorsListing ) {
191+ sponsorable . add ( user . login )
192+ }
188193
189- // Extract Bluesky and Mastodon from socialAccounts
190- let blueskyHandle : string | null = null
191- let mastodonUrl : string | null = null
192-
193- if ( user . socialAccounts ?. nodes ) {
194- for ( const account of user . socialAccounts . nodes ) {
195- if ( account . url . includes ( 'bsky.app' ) ) {
196- // Extract handle from URL: https://bsky.app/profile/handle.bsky.social
197- const match = account . url . match ( / b s k y \. a p p \/ p r o f i l e \/ ( [ ^ / ? ] + ) / )
198- if ( match ) {
199- blueskyHandle = match [ 1 ] as string
194+ // Extract Bluesky and Mastodon from socialAccounts
195+ let blueskyHandle : string | null = null
196+ let mastodonUrl : string | null = null
197+
198+ if ( user . socialAccounts ?. nodes ) {
199+ for ( const account of user . socialAccounts . nodes ) {
200+ if ( account . url . includes ( 'bsky.app' ) ) {
201+ // Extract handle from URL: https://bsky.app/profile/handle.bsky.social
202+ const match = account . url . match ( / b s k y \. a p p \/ p r o f i l e \/ ( [ ^ / ? ] + ) / )
203+ if ( match ) {
204+ blueskyHandle = match [ 1 ] as string
205+ }
206+ } else if ( account . provider === 'MASTODON' ) {
207+ mastodonUrl = cleanString ( account . url , true )
200208 }
201- } else if ( account . provider === 'MASTODON' ) {
202- mastodonUrl = cleanString ( account . url , true )
203209 }
204210 }
205- }
206211
207- // --- SERVER-SIDE SANITIZATION AND BATCHING ---
208- usersData . set ( user . login , {
209- name : cleanString ( user . name ) ,
210- bio : cleanString ( user . bio ) ,
211- company : cleanString ( user . company ) ,
212- // Rich HTML sanitization for company mentions/orgs
213- companyHTML : sanitizeGitHubHTML ( user . companyHTML ) ,
214- location : cleanString ( user . location ) ,
215- websiteUrl : cleanString ( user . websiteUrl , true ) ,
216- twitterUsername : cleanString ( user . twitterUsername ) ,
217- blueskyHandle,
218- mastodonUrl,
219- } )
212+ // --- SERVER-SIDE SANITIZATION AND BATCHING ---
213+ usersData . set ( user . login , {
214+ name : cleanString ( user . name ) ,
215+ bio : cleanString ( user . bio ) ,
216+ company : cleanString ( user . company ) ,
217+ // Rich HTML sanitization for company mentions/orgs
218+ companyHTML : sanitizeGitHubHTML ( user . companyHTML ) ,
219+ location : cleanString ( user . location ) ,
220+ websiteUrl : cleanString ( user . websiteUrl , true ) ,
221+ twitterUsername : cleanString ( user . twitterUsername ) ,
222+ blueskyHandle,
223+ mastodonUrl,
224+ } )
225+ }
220226 }
227+ } catch ( error ) {
228+ console . warn ( `Failed to fetch sponsors info (chunk ${ i } ):` , error )
221229 }
222- return sponsorable
223- } catch ( error ) {
224- console . warn ( 'Failed to fetch sponsors info:' , error )
225- return new Set ( )
226230 }
231+
232+ return sponsorable
227233}
228234
229235function getRoleInfo ( login : string , teams : TeamMembers ) : { role : Role ; order : number } {
0 commit comments