Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
54 changes: 51 additions & 3 deletions docs/scripts/update-contributors.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import fs from "node:fs/promises";
import timers from "node:timers/promises";
import { URL } from "node:url";

const MAINTAINERS = {
Expand Down Expand Up @@ -220,16 +221,61 @@ class UserFetchError extends Error {
}
}

async function fetchUserInfo(username) {
const res = await fetch(`https://api.github.com/users/${username}`, {
const BACKOFF_INTERVALS_MINUTES = [1, 2, 3, 5];
const MAX_RETRIES = BACKOFF_INTERVALS_MINUTES.length - 1;

async function rateLimitDelay({ retryAfter, ratelimitReset, retryCount }) {
let timeoutInMilliseconds = BACKOFF_INTERVALS_MINUTES[retryCount] * 1000;
if (retryAfter) {
timeoutInMilliseconds = retryAfter * 1000;
} else if (ratelimitRemaining === "0" && ratelimitReset) {
timeoutInMilliseconds = ratelimitReset * 1000 - Date.now();
}

const timeoutInSeconds = (timeoutInMilliseconds / 1000).toLocaleString();
console.warn(`Waiting for ${timeoutInSeconds} seconds...`);

await timers.setTimeout(timeoutInMilliseconds);
}

async function fetchUserInfo(username, retryCount = 0) {
if (retryCount >= MAX_RETRIES) {
throw new Error(`Hit max retries (${MAX_RETRIES}) for fetching user ${username}`);
}

const request = new Request(`https://api.github.com/users/${username}`, {
headers: {
Accept: "application/vnd.github+json",
"X-GitHub-Api-Version": "2022-11-28",
},
});
if (process.env.GITHUB_TOKEN) {
request.headers.set("Authorization", `Bearer ${process.env.GITHUB_TOKEN}`);
}

const res = await fetch(request);

if (!res.ok) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Only do the rate-limit retry if we get a 429? (res.status === 429)?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Normally I'd concur, but after reading the docs on handling rate limit errors appropriately, I think retrying in this manner should cover us.

const retryAfter = res.headers.get("retry-after"); // seconds
const ratelimitRemaining = res.headers.get("x-ratelimit-remaining"); // quantity of requests remaining
const ratelimitReset = res.headers.get("x-ratelimit-reset"); // UTC epoch seconds

if (retryAfter || ratelimitRemaining === "0" || ratelimitReset) {
// See https://docs.github.com/en/rest/using-the-rest-api/best-practices-for-using-the-rest-api?apiVersion=2022-11-28#handle-rate-limit-errors-appropriately
console.warn("Rate limited by GitHub API");

await rateLimitDelay({
retryAfter,
ratelimitReset,
retryCount,
});

return await fetchUserInfo(username, retryCount + 1);
}

throw new UserFetchError(`${res.url} responded with ${res.status}`, res);
}

return await res.json();
}

Expand Down Expand Up @@ -282,4 +328,6 @@ async function main() {
}
}

main();
if (import.meta.main) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Heh :) First time I see this in JS/TS.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

😄 it's the ESM equivalent of require.main === module

main();
}