Skip to content

Commit b76a483

Browse files
committed
wip
1 parent 0df703e commit b76a483

File tree

6 files changed

+98
-38
lines changed

6 files changed

+98
-38
lines changed
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,2 @@
11
generated
2+
node_modules

scripts/sync-landing-schema/get-contributors.ts

Lines changed: 53 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
1-
import { setTimeout as sleep } from "timers/promises"
2-
import { readFileSync, writeFileSync } from "node:fs"
1+
import { setTimeout as sleep } from "node:timers/promises"
2+
import { readFile, writeFile } from "node:fs/promises"
33
import { fileURLToPath, pathToFileURL } from "node:url"
44
import { dirname, resolve } from "node:path"
5-
import { graphql } from "./generated"
5+
66
import { ExecutionResult } from "graphql"
7-
import { TypedDocumentString } from "./generated/graphql"
7+
8+
import { graphql } from "./generated/index.ts"
9+
import { TypedDocumentString } from "./generated/graphql.ts"
810

911
type RepoRef = `${string}/${string}`
1012

@@ -52,7 +54,9 @@ export const REPO_TO_PROJECT: Record<RepoRef, string> = {
5254
*/
5355
export async function getContributors(
5456
repoToProject: Record<RepoRef, string> = REPO_TO_PROJECT,
55-
): Promise<ExecutionResult<typeof QUERY>> {
57+
): Promise<
58+
Record<string, Array<{ id: string; website?: string; contributions: number }>>
59+
> {
5660
const accessToken = process.env.GITHUB_ACCESS_TOKEN
5761
if (!accessToken) {
5862
console.warn(
@@ -117,7 +121,10 @@ export async function getContributors(
117121
)
118122

119123
// Convert to the requested output shape and sort by contributions
120-
const result: ExecutionResult<typeof QUERY> = {}
124+
const result: Record<
125+
string,
126+
Array<{ id: string; website?: string; contributions: number }>
127+
> = {}
121128
for (const [project, map] of perProject) {
122129
const arr = Array.from(map.values()).sort(
123130
(a, b) => b.contributions - a.contributions,
@@ -138,23 +145,30 @@ async function fetchRepoContributors(
138145
repo: string,
139146
accessToken: string,
140147
) {
141-
// login -> { contributions, website }
142-
const counts = new Map<string, { contributions: number; website?: string }>()
148+
const contributors = new Map<
149+
string /* handle */,
150+
{ contributions: number; website?: string }
151+
>()
143152
let after: string | null = null
144153
let page = 0
145154
let hasMore = true
146155

147-
while (hasMore) {
148-
const response = await execute(QUERY, {
149-
owner,
150-
name: repo,
151-
after,
152-
}, {
153-
headers: {
156+
const fetchMore = () =>
157+
execute(
158+
QUERY,
159+
{
160+
owner,
161+
name: repo,
162+
after,
163+
},
164+
{
154165
Authorization: `Bearer ${accessToken}`,
155-
'User-Agent': 'graphql.org contributors sync client',
166+
"User-Agent": "graphql.org contributors sync client",
156167
},
157-
})
168+
)
169+
170+
while (hasMore) {
171+
const response = await fetchMore()
158172

159173
if (response.errors?.length) {
160174
throw new Error(
@@ -164,55 +178,58 @@ async function fetchRepoContributors(
164178
)
165179
}
166180

167-
const repoDataRaw = response.data?[0].
168-
if (!repoDataRaw) {
181+
const repoData = response.data?.repository
182+
if (!repoData) {
169183
console.warn(`Repository not found: ${owner}/${repo}`)
170184
break
171185
}
172-
const repoData = repoDataRaw as NonNullable<RepoHistoryPage["repository"]>
173186

174187
const defaultBranchRef = repoData.defaultBranchRef
175-
if (!defaultBranchRef || !defaultBranchRef.target) {
188+
if (!defaultBranchRef?.target) {
176189
console.warn(`Default branch not found for ${owner}/${repo}`)
177190
break
178191
}
192+
193+
if (defaultBranchRef.target.__typename !== "Commit") {
194+
throw new Error(`Invalid typename for ${owner}/${repo}`)
195+
}
196+
179197
const history = defaultBranchRef.target.history
180198

181-
for (const node of history.nodes) {
182-
const user = node.author?.user
199+
for (const node of history.nodes || []) {
200+
const user = node?.author?.user
183201
if (!user?.login) continue
184-
const prev = counts.get(user.login)
202+
const prev = contributors.get(user.login)
185203
if (prev) {
186204
prev.contributions += 1
187205
// keep existing website unless we don't have one and GitHub provides it
188-
if (!prev.website && user.websiteUrl) prev.website = user.websiteUrl
206+
prev.website ||= user.websiteUrl
189207
} else {
190-
counts.set(user.login, {
208+
contributors.set(user.login, {
191209
contributions: 1,
192210
website: user.websiteUrl ?? undefined,
193211
})
194212
}
195213
}
196214

197-
const hasNext = history.pageInfo.hasNextPage
198-
after = history.pageInfo.endCursor
199-
hasMore = hasNext
215+
const hasNext = history.pageInfo?.hasNextPage
216+
after = history.pageInfo?.endCursor || null
217+
hasMore = !!hasNext
200218
page += 1
201219

202-
// Brief backoff every few pages to be nicer to the API
203220
if (page % 5 === 0) await sleep(200)
204221
}
205222

206-
return counts
223+
return contributors
207224
}
208225

209226
// CLI entrypoint: when executed directly, write contributors to data.json next to this file
210227
if (import.meta.url === pathToFileURL(process.argv[1] ?? "").href) {
211228
const __dirname = dirname(fileURLToPath(import.meta.url))
212229
const outPath = resolve(__dirname, "data.json")
213230
getContributors()
214-
.then(data => {
215-
writeFileSync(outPath, JSON.stringify(data, null, 2) + "\n", "utf8")
231+
.then(async data => {
232+
await writeFile(outPath, JSON.stringify(data, null, 2) + "\n", "utf8")
216233
console.log(
217234
`Wrote ${Object.values(data).reduce((n, arr) => n + arr.length, 0)} contributors across ${Object.keys(data).length} projects to ${outPath}`,
218235
)
@@ -225,9 +242,9 @@ if (import.meta.url === pathToFileURL(process.argv[1] ?? "").href) {
225242

226243
async function execute<TResult, TVariables>(
227244
query: TypedDocumentString<TResult, TVariables>,
228-
headers?: Record<string, string>,
229245
variables?: TVariables,
230-
) {
246+
headers?: Record<string, string>,
247+
): Promise<ExecutionResult<TResult>> {
231248
const response = await fetch("https://graphql.org/graphql/", {
232249
method: "POST",
233250
headers: {
@@ -245,5 +262,5 @@ async function execute<TResult, TVariables>(
245262
throw new Error("Network response was not ok")
246263
}
247264

248-
return response.json() as ExecutionResult<TResult>
265+
return (await response.json()) as ExecutionResult<TResult>
249266
}

scripts/sync-landing-schema/graphql-codegen.ts

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { CodegenConfig } from "@graphql-codegen/cli"
2+
import { readdir, readFile, writeFile } from "node:fs/promises"
23

34
if (!process.env.GITHUB_ACCESS_TOKEN) {
45
throw new Error("GITHUB_ACCESS_TOKEN environment variable is not set")
@@ -20,9 +21,36 @@ const config: CodegenConfig = {
2021
preset: "client",
2122
config: {
2223
documentMode: "string",
24+
enumsAsTypes: true,
2325
},
2426
},
2527
},
28+
hooks: {
29+
afterAllFileWrite: async () => {
30+
const dir = await readdir("./generated")
31+
await Promise.all(
32+
dir.map(async file => {
33+
let content = await readFile(`./generated/${file}`, "utf8")
34+
35+
// add .ts extension to imports
36+
content = content
37+
.replace(/from\s+['"](\.[^'"]*?)(?<!\.ts)['"];?/g, 'from "$1.ts";')
38+
.replace(
39+
/import\(\s*['"](\.[^'"]*?)(?<!\.ts)['"]\s*\)/g,
40+
'import("$1.ts")',
41+
)
42+
43+
// switch type-only modules to type import
44+
content = content.replace(
45+
/import\s+({[^}]+})\s+from\s+['"]@graphql-typed-document-node\/core['"];?/g,
46+
'import type $1 from "@graphql-typed-document-node/core";',
47+
)
48+
49+
await writeFile(`./generated/${file}`, content)
50+
}),
51+
)
52+
},
53+
},
2654
}
2755

2856
export default config
Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,15 @@
11
{
22
"name": "@graphql-website/sync-landing-schema",
33
"private": true,
4+
"type": "module",
5+
"main": "./get-contributors.ts",
46
"scripts": {
57
"codegen": "gql-gen --config graphql-codegen.ts",
6-
"download": "node get-contributors.ts"
8+
"start": "node get-contributors.ts"
79
},
810
"dependencies": {
911
"@graphql-codegen/cli": "^6.0.0",
10-
"@graphql-codegen/graphql-modules-preset": "^5.0.0"
12+
"@graphql-codegen/graphql-modules-preset": "^5.0.0",
13+
"@graphql-typed-document-node/core": "^3.2.0"
1114
}
1215
}

scripts/sync-landing-schema/pnpm-lock.yaml

Lines changed: 3 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
{
2+
"compilerOptions": {
3+
"module": "NodeNext",
4+
"noEmit": true,
5+
"allowImportingTsExtensions": true
6+
},
7+
"include": ["*.ts"]
8+
}

0 commit comments

Comments
 (0)