-
-
Notifications
You must be signed in to change notification settings - Fork 6.5k
chore: add github sponsors on supporters #8531
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
46d49e7
00e81c5
309a8bb
70c5076
21655fa
448e37e
0ad4e41
6da91ff
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -1,4 +1,9 @@ | ||||||||||||||||||||||||||||||||
| import { OPENCOLLECTIVE_MEMBERS_URL } from '#site/next.constants.mjs'; | ||||||||||||||||||||||||||||||||
| import { | ||||||||||||||||||||||||||||||||
| OPENCOLLECTIVE_MEMBERS_URL, | ||||||||||||||||||||||||||||||||
| GITHUB_GRAPHQL_URL, | ||||||||||||||||||||||||||||||||
| GITHUB_API_KEY, | ||||||||||||||||||||||||||||||||
| } from '#site/next.constants.mjs'; | ||||||||||||||||||||||||||||||||
| import { shuffle } from '#site/util/array'; | ||||||||||||||||||||||||||||||||
| import { fetchWithRetry } from '#site/util/fetch'; | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| /** | ||||||||||||||||||||||||||||||||
|
|
@@ -15,15 +20,201 @@ async function fetchOpenCollectiveData() { | |||||||||||||||||||||||||||||||
| const members = payload | ||||||||||||||||||||||||||||||||
| .filter(({ role, isActive }) => role === 'BACKER' && isActive) | ||||||||||||||||||||||||||||||||
| .sort((a, b) => b.totalAmountDonated - a.totalAmountDonated) | ||||||||||||||||||||||||||||||||
| .map(({ name, website, image, profile }) => ({ | ||||||||||||||||||||||||||||||||
| .map(({ name, image, profile }) => ({ | ||||||||||||||||||||||||||||||||
| name, | ||||||||||||||||||||||||||||||||
| image, | ||||||||||||||||||||||||||||||||
| url: website, | ||||||||||||||||||||||||||||||||
| profile, | ||||||||||||||||||||||||||||||||
| url: profile, | ||||||||||||||||||||||||||||||||
bjohansebas marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||||||||||||||||||||||||||||
| source: 'opencollective', | ||||||||||||||||||||||||||||||||
| })); | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| return members; | ||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| export default fetchOpenCollectiveData; | ||||||||||||||||||||||||||||||||
| /** | ||||||||||||||||||||||||||||||||
| * Fetches supporters data from Github API, filters active backers, | ||||||||||||||||||||||||||||||||
| * and maps it to the Supporters type. | ||||||||||||||||||||||||||||||||
| * | ||||||||||||||||||||||||||||||||
| * @returns {Promise<Array<import('#site/types/supporters').GithubSponsorSupporter>>} Array of supporters | ||||||||||||||||||||||||||||||||
| */ | ||||||||||||||||||||||||||||||||
| async function fetchGithubSponsorsData() { | ||||||||||||||||||||||||||||||||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think we can break this function in two:
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. sgtm |
||||||||||||||||||||||||||||||||
| if (!GITHUB_API_KEY) { | ||||||||||||||||||||||||||||||||
| return []; | ||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| const sponsors = []; | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| // Fetch sponsorship pages | ||||||||||||||||||||||||||||||||
| let cursor = null; | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| while (true) { | ||||||||||||||||||||||||||||||||
| const query = sponsorshipsQuery(cursor); | ||||||||||||||||||||||||||||||||
| const data = await graphql(query); | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| if (data.errors) { | ||||||||||||||||||||||||||||||||
| throw new Error(JSON.stringify(data.errors)); | ||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| const nodeRes = data.data.organization?.sponsorshipsAsMaintainer; | ||||||||||||||||||||||||||||||||
| if (!nodeRes) { | ||||||||||||||||||||||||||||||||
| break; | ||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| const { nodes, pageInfo } = nodeRes; | ||||||||||||||||||||||||||||||||
| const mapped = nodes.map(n => { | ||||||||||||||||||||||||||||||||
| const s = n.sponsor || n.sponsorEntity || n.sponsorEntity; // support different field names | ||||||||||||||||||||||||||||||||
| return { | ||||||||||||||||||||||||||||||||
| name: s?.name || s?.login || null, | ||||||||||||||||||||||||||||||||
| image: s?.avatarUrl || null, | ||||||||||||||||||||||||||||||||
| url: s?.url || null, | ||||||||||||||||||||||||||||||||
| source: 'github', | ||||||||||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||||||||||
bjohansebas marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| sponsors.push(...mapped); | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| if (!pageInfo.hasNextPage) { | ||||||||||||||||||||||||||||||||
| break; | ||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| cursor = pageInfo.endCursor; | ||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| const query = donationsQuery(); | ||||||||||||||||||||||||||||||||
| const data = await graphql(query); | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| if (data.errors) { | ||||||||||||||||||||||||||||||||
| throw new Error(JSON.stringify(data.errors)); | ||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| const nodeRes = data.data.organization?.sponsorsActivities; | ||||||||||||||||||||||||||||||||
bjohansebas marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||||||||||||||||||||||||||||
| if (!nodeRes) { | ||||||||||||||||||||||||||||||||
| return sponsors; | ||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| const { nodes } = nodeRes; | ||||||||||||||||||||||||||||||||
|
Comment on lines
89
to
94
This comment was marked as outdated.
Sorry, something went wrong. |
||||||||||||||||||||||||||||||||
| const mapped = nodes.map(n => { | ||||||||||||||||||||||||||||||||
| const s = n.sponsor || n.sponsorEntity || n.sponsorEntity; // support different field names | ||||||||||||||||||||||||||||||||
| return { | ||||||||||||||||||||||||||||||||
| name: s?.name || s?.login || null, | ||||||||||||||||||||||||||||||||
| image: s?.avatarUrl || null, | ||||||||||||||||||||||||||||||||
| url: s?.url || null, | ||||||||||||||||||||||||||||||||
| source: 'github', | ||||||||||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| sponsors.push(...mapped); | ||||||||||||||||||||||||||||||||
|
Comment on lines
+95
to
+105
This comment was marked as outdated.
Sorry, something went wrong. |
||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| return sponsors; | ||||||||||||||||||||||||||||||||
bjohansebas marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||||||||||||||||||||||||||||
| return sponsors; | |
| // De-duplicate sponsors from both queries using a stable key | |
| const seen = new Set(); | |
| const uniqueSponsors = []; | |
| for (const sponsor of sponsors) { | |
| const key = sponsor.url || `${sponsor.name || ''}::${sponsor.image || ''}`; | |
| if (seen.has(key)) { | |
| continue; | |
| } | |
| seen.add(key); | |
| uniqueSponsors.push(sponsor); | |
| } | |
| return uniqueSponsors; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
hmm, even though it’s a good idea to try to deduplicate it, I don’t think this is the right approach. I’ll have to look into the logic of https://github.com/antfu-collective/sponsorkit33
bjohansebas marked this conversation as resolved.
Show resolved
Hide resolved
bjohansebas marked this conversation as resolved.
Show resolved
Hide resolved
bjohansebas marked this conversation as resolved.
Show resolved
Hide resolved
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
definitely, I’d prefer that we only show sponsors that are active, but why do we want to show everyone? see #8268.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
wdyt @nodejs/nodejs-website @nodejs/marketing ?
Copilot
AI
Feb 8, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The GraphQL query strings contain trailing commas after fields (e.g., id: databaseId,, name,, and a comma after the sponsorEntity { ... } block). GitHub’s GraphQL API does not accept comma-separated field selections, so this query will fail to parse. Remove the commas (and any trailing commas) from the query documents.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
These can be constants, cursor can be a GraphQL variable
ovflowd marked this conversation as resolved.
Show resolved
Hide resolved
Copilot
AI
Feb 8, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For consistency/reliability, consider using fetchWithRetry for the GitHub GraphQL POST as well (this file already uses it for OpenCollective). That would reduce transient build/runtime failures due to network timeouts when generating supporters data.
Copilot
AI
Feb 8, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This introduces substantial new external-fetching logic (GitHub GraphQL pagination, error handling, and merging with OpenCollective) but there are still no generator tests for supportersData.mjs (other generators like releaseData/vulnerabilities have tests). Adding tests that mock fetch to cover pagination, missing token behavior, and de-duping/shape mapping would help prevent regressions.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -23,7 +23,7 @@ without we can't test and release new versions of Node.js. | |
| ## Supporters | ||
|
|
||
| Supporters are individuals and organizations that provide financial support through | ||
| [OpenCollective](https://opencollective.com/nodejs) of the Node.js project. | ||
| [OpenCollective](https://opencollective.com/nodejs) and [GitHub Sponsors](https://github.com/sponsors/nodejs) of the Node.js project. | ||
|
||
|
|
||
| <WithSupporters /> | ||
|
|
||
|
|
||
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
|
|
@@ -7,3 +7,4 @@ export type Supporter<T extends string> = { | |||||
| }; | ||||||
|
|
||||||
| export type OpenCollectiveSupporter = Supporter<'opencollective'>; | ||||||
| export type GithubSponsorSupporter = Supporter<'github'>; | ||||||
|
||||||
| export type GithubSponsorSupporter = Supporter<'github'>; | |
| export type GitHubSponsorSupporter = Supporter<'github'>; |
Uh oh!
There was an error while loading. Please reload this page.