Skip to content

Commit 0eee37d

Browse files
committed
add retry logic
1 parent 3ee8db5 commit 0eee37d

File tree

1 file changed

+44
-22
lines changed

1 file changed

+44
-22
lines changed

packages/code-infra/src/utils/changelog.mjs

Lines changed: 44 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { graphql } from '@octokit/graphql';
1+
import { graphql, GraphqlResponseError } from '@octokit/graphql';
22
import { Octokit } from '@octokit/rest';
33
import { $ } from 'execa';
44

@@ -35,31 +35,54 @@ export async function findLatestTaggedVersion(opts) {
3535
* @property {string} lastRelease
3636
* @property {string} release
3737
* @property {string} [org="mui"]
38-
* @property {'rest' | 'graphql'} [method="rest"]
3938
*/
4039

4140
/**
41+
* Fetches commits between two refs (lastRelease..release) including PR details.
42+
* It first tries to use the GraphQL API (more efficient) and falls back to the
43+
* REST api if it fails with server error.
44+
*
4245
* @param {FetchCommitsOptions} param0
4346
* @returns {Promise<FetchedCommitDetails[]>}
4447
*/
45-
export async function fetchCommitsBetweenRefs({ method = 'rest', org = 'mui', ...options }) {
48+
export async function fetchCommitsBetweenRefs({ org = 'mui', ...options }) {
4649
if (!options.token) {
4750
throw new Error('Missing "token" option. The token needs `public_repo` permissions.');
4851
}
4952
const opts = { ...options, org };
5053

51-
return method === 'rest' ? fetchCommitsRest(opts) : fetchCommitsGraphql(opts);
54+
/**
55+
* @type {FetchedCommitDetails[]}
56+
*/
57+
try {
58+
return fetchCommitsGraphql(opts);
59+
} catch (error) {
60+
let status = 0;
61+
if (error instanceof GraphqlResponseError) {
62+
if (error.headers.status) {
63+
status = parseInt(error.headers.status, 10);
64+
// only re-throw for client errors (4xx), for server errors (5xx) we want to fall back to the REST API
65+
if (status >= 400 && status < 500) {
66+
throw error;
67+
}
68+
}
69+
}
70+
console.warn(
71+
`Failed to fetch commits using the GraphQL API, falling back to the REST API. Status Code: ${status}`,
72+
);
73+
return await fetchCommitsRest(opts);
74+
}
5275
}
5376

5477
/**
5578
* Fetches commits between two refs using GitHub's GraphQL API over a single network call.
5679
* Its efficient network-wise but is not as reliable as the REST API (in my findings).
5780
* So keeping both implementations for the time being.
5881
*
59-
* @param {Omit<FetchCommitsOptions, 'method'> & {org: string}} param0
82+
* @param {FetchCommitsOptions & {org: string}} param0
6083
* @returns {Promise<FetchedCommitDetails[]>}
6184
*/
62-
async function fetchCommitsGraphql({ org, token, repo, lastRelease, release }) {
85+
export async function fetchCommitsGraphql({ org, token, repo, lastRelease, release }) {
6386
const gql = graphql.defaults({
6487
headers: {
6588
authorization: `token ${token}`,
@@ -129,11 +152,9 @@ async function fetchCommitsGraphql({ org, token, repo, lastRelease, release }) {
129152
let allCommits = [];
130153
// fetch all commits (with pagination)
131154
do {
132-
/**
133-
* @type {CommitConnection}
134-
*/
135155
// eslint-disable-next-line no-await-in-loop
136-
const commits = (await fetchCommitsPaginated(commitAfter)).repository.ref.compare.commits;
156+
const data = await fetchCommitsPaginated(commitAfter);
157+
const commits = data.repository.ref.compare.commits;
137158
hasNextPage = !!commits.pageInfo.hasNextPage;
138159
commitAfter = hasNextPage ? commits.pageInfo.endCursor : null;
139160
allCommits.push(...commits.nodes);
@@ -142,23 +163,24 @@ async function fetchCommitsGraphql({ org, token, repo, lastRelease, release }) {
142163
allCommits = allCommits.filter((commit) => commit.associatedPullRequests.nodes.length > 0);
143164

144165
return allCommits.map((commit) => {
145-
const labels = commit.associatedPullRequests.nodes.flatMap((pr) =>
146-
pr.labels.nodes.map((label) => label.name),
147-
);
148-
const firstPr = commit.associatedPullRequests.nodes[0];
166+
const pr = commit.associatedPullRequests.nodes[0];
167+
const labels = pr.labels.nodes.map((label) => label.name);
149168

150-
return /** @type {FetchedCommitDetails} */ ({
169+
/**
170+
* @type {FetchedCommitDetails}
171+
*/
172+
return {
151173
sha: commit.oid,
152174
message: commit.message,
153175
labels,
154-
prNumber: firstPr.number,
155-
author: firstPr.author.user?.login
176+
prNumber: pr.number,
177+
author: pr.author.user?.login
156178
? {
157-
login: firstPr.author.user.login,
158-
association: getAuthorAssociation(firstPr.authorAssociation),
179+
login: pr.author.user.login,
180+
association: getAuthorAssociation(pr.authorAssociation),
159181
}
160182
: null,
161-
});
183+
};
162184
});
163185
}
164186

@@ -167,11 +189,11 @@ async function fetchCommitsGraphql({ org, token, repo, lastRelease, release }) {
167189
* It is more reliable than the GraphQL API but requires multiple network calls (1 + n).
168190
* One to list all commits between the two refs and then one for each commit to get the PR details.
169191
*
170-
* @param {Omit<FetchCommitsOptions, 'method'> & { org: string }} param0
192+
* @param {FetchCommitsOptions & { org: string }} param0
171193
*
172194
* @returns {Promise<FetchedCommitDetails[]>}
173195
*/
174-
async function fetchCommitsRest({ token, repo, lastRelease, release, org }) {
196+
export async function fetchCommitsRest({ token, repo, lastRelease, release, org }) {
175197
const octokit = new Octokit({
176198
auth: token,
177199
});

0 commit comments

Comments
 (0)