Skip to content

Commit 411449c

Browse files
authored
fix(fetch): fixup generators and add error handling (#8395)
* fix(fetch): fixup generators and add error handling * fixup! * fixup! * fixup! * fixup!
1 parent 9f38df8 commit 411449c

File tree

7 files changed

+67
-23
lines changed

7 files changed

+67
-23
lines changed

apps/site/next-data/generators/supportersData.mjs

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,14 @@
1+
import { OPENCOLLECTIVE_MEMBERS_URL } from '#site/next.constants.mjs';
2+
import { fetchWithRetry } from '#site/util/fetch';
3+
14
/**
25
* Fetches supporters data from Open Collective API, filters active backers,
36
* and maps it to the Supporters type.
47
*
5-
* @returns {Promise<Array<import('#site/types/supporters')>>} Array of supporters
8+
* @returns {Promise<Array<import('#site/types/supporters').OpenCollectiveSupporter>>} Array of supporters
69
*/
710
async function fetchOpenCollectiveData() {
8-
const endpoint = 'https://opencollective.com/nodejs/members/all.json';
9-
10-
const response = await fetch(endpoint);
11+
const response = await fetchWithRetry(OPENCOLLECTIVE_MEMBERS_URL);
1112

1213
const payload = await response.json();
1314

apps/site/next-data/generators/vulnerabilities.mjs

Lines changed: 10 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
import { VULNERABILITIES_URL } from '#site/next.constants.mjs';
2+
import { fetchWithRetry } from '#site/util/fetch';
23

34
const RANGE_REGEX = /([<>]=?)\s*(\d+)(?:\.(\d+))?/;
5+
const V0_REGEX = /^0\.\d+(\.x)?$/;
6+
const VER_REGEX = /^\d+\.x$/;
47

58
/**
69
* Fetches vulnerability data from the Node.js Security Working Group repository,
@@ -9,7 +12,7 @@ const RANGE_REGEX = /([<>]=?)\s*(\d+)(?:\.(\d+))?/;
912
* @returns {Promise<import('#site/types/vulnerabilities').GroupedVulnerabilities>} Grouped vulnerabilities
1013
*/
1114
export default async function generateVulnerabilityData() {
12-
const response = await fetch(VULNERABILITIES_URL);
15+
const response = await fetchWithRetry(VULNERABILITIES_URL);
1316

1417
/** @type {Array<import('#site/types/vulnerabilities').RawVulnerability>} */
1518
const data = Object.values(await response.json());
@@ -26,14 +29,14 @@ export default async function generateVulnerabilityData() {
2629
// Helper function to process version patterns
2730
const processVersion = (version, vulnerability) => {
2831
// Handle 0.X versions (pre-semver)
29-
if (/^0\.\d+(\.x)?$/.test(version)) {
32+
if (V0_REGEX.test(version)) {
3033
addToGroup('0', vulnerability);
3134

3235
return;
3336
}
3437

3538
// Handle simple major.x patterns (e.g., 12.x)
36-
if (/^\d+\.x$/.test(version)) {
39+
if (VER_REGEX.test(version)) {
3740
const majorVersion = version.split('.')[0];
3841

3942
addToGroup(majorVersion, vulnerability);
@@ -67,25 +70,14 @@ export default async function generateVulnerabilityData() {
6770
}
6871
};
6972

70-
for (const vulnerability of Object.values(data)) {
71-
const parsedVulnerability = {
72-
cve: vulnerability.cve,
73-
url: vulnerability.ref,
74-
vulnerable: vulnerability.vulnerable,
75-
patched: vulnerability.patched,
76-
description: vulnerability.description,
77-
overview: vulnerability.overview,
78-
affectedEnvironments: vulnerability.affectedEnvironments,
79-
severity: vulnerability.severity,
80-
};
73+
for (const { ref, ...vulnerability } of Object.values(data)) {
74+
vulnerability.url = ref;
8175

8276
// Process all potential versions from the vulnerable field
83-
const versions = parsedVulnerability.vulnerable
84-
.split(' || ')
85-
.filter(Boolean);
77+
const versions = vulnerability.vulnerable.split(' || ').filter(Boolean);
8678

8779
for (const version of versions) {
88-
processVersion(version, parsedVulnerability);
80+
processVersion(version, vulnerability);
8981
}
9082
}
9183

apps/site/next.calendar.mjs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import {
44
BASE_CALENDAR_URL,
55
SHARED_CALENDAR_KEY,
66
} from './next.calendar.constants.mjs';
7+
import { fetchWithRetry } from './util/fetch';
78

89
/**
910
*
@@ -33,7 +34,7 @@ export const getCalendarEvents = async (calendarId = '', maxResults = 20) => {
3334
calendarQueryUrl.searchParams.append(key, value)
3435
);
3536

36-
return fetch(calendarQueryUrl)
37+
return fetchWithRetry(calendarQueryUrl)
3738
.then(response => response.json())
3839
.then(calendar => calendar.items ?? []);
3940
};

apps/site/next.constants.mjs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -213,3 +213,9 @@ export const EOL_VERSION_IDENTIFIER = 'End-of-life';
213213
*/
214214
export const VULNERABILITIES_URL =
215215
'https://raw.githubusercontent.com/nodejs/security-wg/main/vuln/core/index.json';
216+
217+
/**
218+
* The location of the OpenCollective data
219+
*/
220+
export const OPENCOLLECTIVE_MEMBERS_URL =
221+
'https://opencollective.com/nodejs/members/all.json';

apps/site/types/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,3 +16,4 @@ export * from './download';
1616
export * from './userAgent';
1717
export * from './vulnerabilities';
1818
export * from './page';
19+
export * from './supporters';

apps/site/types/supporters.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
export type Supporter<T extends string> = {
2+
name: string;
3+
image: string;
4+
url: string;
5+
profile: string;
6+
source: T;
7+
};
8+
9+
export type OpenCollectiveSupporter = Supporter<'opencollective'>;

apps/site/util/fetch.ts

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
import { setTimeout } from 'node:timers/promises';
2+
3+
type RetryOptions = RequestInit & {
4+
maxRetry?: number;
5+
delay?: number;
6+
};
7+
8+
type FetchError = {
9+
cause: {
10+
code: string;
11+
};
12+
};
13+
14+
export const fetchWithRetry = async (
15+
url: string,
16+
{ maxRetry = 3, delay = 100, ...options }: RetryOptions = {}
17+
) => {
18+
for (let i = 1; i <= maxRetry; i++) {
19+
try {
20+
return fetch(url, options);
21+
} catch (e) {
22+
console.debug(
23+
`fetch of ${url} failed at ${Date.now()}, attempt ${i}/${maxRetry}`,
24+
e
25+
);
26+
27+
if (i === maxRetry || (e as FetchError).cause.code !== 'ETIMEDOUT') {
28+
throw e;
29+
}
30+
31+
await setTimeout(delay * i);
32+
}
33+
}
34+
};

0 commit comments

Comments
 (0)