@@ -2,40 +2,12 @@ import { setTimeout as sleep } from "node:timers/promises"
22import { readFile , writeFile } from "node:fs/promises"
33import { fileURLToPath , pathToFileURL } from "node:url"
44import { dirname , resolve } from "node:path"
5-
6- import type { ExecutionResult } from "graphql"
7-
8- import { graphql } from "./generated/index.ts"
9- import type { TypedDocumentString } from "./generated/graphql.ts"
5+ import { fetchRepoContributors } from "./fetch-repo-contributors.ts"
106
117type RepoRef = `${string } /${string } `
128
13- const QUERY = graphql ( `
14- query RepoContributors($owner: String!, $name: String!, $after: String) {
15- repository(owner: $owner, name: $name) {
16- defaultBranchRef {
17- target {
18- ... on Commit {
19- history(first: 100, after: $after) {
20- pageInfo {
21- hasNextPage
22- endCursor
23- }
24- nodes {
25- author {
26- user {
27- login
28- websiteUrl
29- }
30- }
31- }
32- }
33- }
34- }
35- }
36- }
37- }
38- ` )
9+ const __dirname = dirname ( fileURLToPath ( import . meta. url ) )
10+ const outPath = resolve ( __dirname , "data.json" )
3911
4012export const REPO_TO_PROJECT : Record < RepoRef , string > = {
4113 "graphql/graphql-spec" : "GraphQL" ,
@@ -134,100 +106,8 @@ export async function getContributors(
134106 return result
135107}
136108
137- /**
138- * Fetch contributors (by commit authors tied to GitHub users) for a single repo.
139- * Traverses the full commit history of the default branch using pagination.
140- *
141- * Returns a Map: login -> { contributions, website }
142- */
143- async function fetchRepoContributors (
144- owner : string ,
145- repo : string ,
146- accessToken : string ,
147- ) {
148- const contributors = new Map <
149- string /* handle */ ,
150- { contributions : number ; website ?: string }
151- > ( )
152- let after : string | null = null
153- let page = 0
154- let hasMore = true
155-
156- const fetchMore = ( ) =>
157- execute (
158- QUERY ,
159- {
160- owner,
161- name : repo ,
162- after,
163- } ,
164- {
165- Authorization : `Bearer ${ accessToken } ` ,
166- "User-Agent" : "graphql.org contributors sync client" ,
167- } ,
168- )
169-
170- while ( hasMore ) {
171- const response = await fetchMore ( )
172-
173- if ( response . errors ?. length ) {
174- throw new Error (
175- `GitHub GraphQL errors for ${ owner } /${ repo } : ${ response . errors
176- . map ( ( e : { message : string } ) => e . message )
177- . join ( "; " ) } `,
178- )
179- }
180-
181- const repoData = response . data ?. repository
182- if ( ! repoData ) {
183- console . warn ( `Repository not found: ${ owner } /${ repo } ` )
184- break
185- }
186-
187- const defaultBranchRef = repoData . defaultBranchRef
188- if ( ! defaultBranchRef ?. target ) {
189- console . warn ( `Default branch not found for ${ owner } /${ repo } ` )
190- break
191- }
192-
193- if ( ! ( "history" in defaultBranchRef . target ) ) {
194- console . warn ( `History not found for ${ owner } /${ repo } ` )
195- break
196- }
197-
198- const history = defaultBranchRef . target . history
199-
200- for ( const node of history . nodes || [ ] ) {
201- const user = node ?. author ?. user
202- if ( ! user ?. login ) continue
203- const prev = contributors . get ( user . login )
204- if ( prev ) {
205- prev . contributions += 1
206- // keep existing website unless we don't have one and GitHub provides it
207- prev . website ||= user . websiteUrl
208- } else {
209- contributors . set ( user . login , {
210- contributions : 1 ,
211- website : user . websiteUrl ?? undefined ,
212- } )
213- }
214- }
215-
216- const hasNext = history . pageInfo ?. hasNextPage
217- after = history . pageInfo ?. endCursor || null
218- hasMore = ! ! hasNext
219- page += 1
220-
221- if ( page % 5 === 0 ) await sleep ( 200 )
222- }
223-
224- return contributors
225- }
226-
227109// CLI entrypoint: when executed directly, write contributors to data.json next to this file
228110if ( import . meta. url === pathToFileURL ( process . argv [ 1 ] ?? "" ) . href ) {
229- const __dirname = dirname ( fileURLToPath ( import . meta. url ) )
230- const outPath = resolve ( __dirname , "data.json" )
231111 getContributors ( )
232112 . then ( async data => {
233113 await writeFile ( outPath , JSON . stringify ( data , null , 2 ) + "\n" , "utf8" )
@@ -240,25 +120,3 @@ if (import.meta.url === pathToFileURL(process.argv[1] ?? "").href) {
240120 process . exitCode = 1
241121 } )
242122}
243-
244- async function execute < TResult , TVariables > (
245- query : TypedDocumentString < TResult , TVariables > ,
246- variables ?: TVariables ,
247- headers ?: Record < string , string > ,
248- ) : Promise < ExecutionResult < TResult > > {
249- const response = await fetch ( "https://api.github.com/graphql" , {
250- method : "POST" ,
251- headers : {
252- "Content-Type" : "application/json" ,
253- ...headers ,
254- } ,
255- body : JSON . stringify ( { query, variables } ) ,
256- } )
257-
258- if ( ! response . ok ) {
259- console . error ( "Network response was not ok:" , response )
260- throw new Error ( "Network response was not ok" )
261- }
262-
263- return ( await response . json ( ) ) as ExecutionResult < TResult >
264- }
0 commit comments