|
1 | 1 | /**
|
2 |
| - * @fileoverview Script to fetch sponsor data from Open Collective. Call using: |
| 2 | + * @fileoverview Script to fetch sponsor data from Open Collective and GitHub. |
| 3 | + * Call using: |
3 | 4 | * node _tools/fetch-sponsors.js
|
4 | 5 | * @author Nicholas C. Zakas
|
5 | 6 | */
|
|
14 | 15 |
|
15 | 16 | const fs = require("fs");
|
16 | 17 | const fetch = require("node-fetch");
|
| 18 | +const { graphql: githubGraphQL } = require("@octokit/graphql"); |
17 | 19 |
|
18 | 20 | //-----------------------------------------------------------------------------
|
19 | 21 | // Data
|
20 | 22 | //-----------------------------------------------------------------------------
|
21 | 23 |
|
22 |
| -// filename to output to |
23 |
| -const filename = "./_data/sponsors.json"; |
| 24 | +// filename to output sponsors to |
| 25 | +const sponsorsFilename = "./_data/sponsors.json"; |
24 | 26 |
|
25 | 27 | // simplified data structure
|
26 |
| -const sponsors = { |
| 28 | +const tierSponsors = { |
27 | 29 | gold: [],
|
28 | 30 | silver: [],
|
29 | 31 | bronze: [],
|
30 | 32 | backers: []
|
31 | 33 | };
|
32 | 34 |
|
33 |
| -const graphqlEndpoint = "https://api.opencollective.com/graphql/v2"; |
34 |
| - |
35 |
| -const graphqlQuery = `{ |
36 |
| - account(slug: "eslint") { |
37 |
| - orders(status: ACTIVE) { |
38 |
| - totalCount |
39 |
| - nodes { |
40 |
| - fromAccount { |
41 |
| - name |
42 |
| - website |
43 |
| - imageUrl |
44 |
| - } |
45 |
| - amount { |
46 |
| - value |
| 35 | +const { ESLINT_GITHUB_TOKEN } = process.env; |
| 36 | + |
| 37 | +if (!ESLINT_GITHUB_TOKEN) { |
| 38 | + throw new Error("Missing ESLINT_GITHUB_TOKEN."); |
| 39 | +} |
| 40 | + |
| 41 | +//----------------------------------------------------------------------------- |
| 42 | +// Helpers |
| 43 | +//----------------------------------------------------------------------------- |
| 44 | + |
| 45 | +/** |
| 46 | + * Returns the tier ID for a given donation amount. |
| 47 | + * @param {int} monthlyDonation The monthly donation in dollars. |
| 48 | + * @returns {string} The ID of the tier the donation belongs to. |
| 49 | + */ |
| 50 | +function getTierSlug(monthlyDonation) { |
| 51 | + if (monthlyDonation >= 1000) { |
| 52 | + return "gold-sponsor"; |
| 53 | + } |
| 54 | + |
| 55 | + if (monthlyDonation >= 500) { |
| 56 | + return "silver-sponsor"; |
| 57 | + } |
| 58 | + |
| 59 | + if (monthlyDonation >= 200) { |
| 60 | + return "bronze-sponsor"; |
| 61 | + } |
| 62 | + |
| 63 | + return "backer"; |
| 64 | +} |
| 65 | + |
| 66 | +/** |
| 67 | + * Fetch order data from Open Collective using the GraphQL API. |
| 68 | + * @returns {Array} An array of sponsors. |
| 69 | + */ |
| 70 | +async function fetchOpenCollectiveSponsors() { |
| 71 | + |
| 72 | + const endpoint = "https://api.opencollective.com/graphql/v2"; |
| 73 | + |
| 74 | + const query = `{ |
| 75 | + account(slug: "eslint") { |
| 76 | + orders(status: ACTIVE) { |
| 77 | + totalCount |
| 78 | + nodes { |
| 79 | + fromAccount { |
| 80 | + name |
| 81 | + website |
| 82 | + imageUrl |
| 83 | + } |
| 84 | + amount { |
| 85 | + value |
| 86 | + } |
| 87 | + tier { |
| 88 | + slug |
| 89 | + } |
| 90 | + frequency |
| 91 | + totalDonations { |
| 92 | + value |
| 93 | + } |
| 94 | + } |
| 95 | + } |
47 | 96 | }
|
48 |
| - tier { |
49 |
| - slug |
| 97 | + }`; |
| 98 | + |
| 99 | + const result = await fetch(endpoint, { |
| 100 | + method: "POST", |
| 101 | + headers: { "Content-Type": "application/json" }, |
| 102 | + body: JSON.stringify({ query }) |
| 103 | + }); |
| 104 | + |
| 105 | + const payload = await result.json(); |
| 106 | + |
| 107 | + return payload.data.account.orders.nodes.map(order => ({ |
| 108 | + name: order.fromAccount.name, |
| 109 | + url: order.fromAccount.website, |
| 110 | + image: order.fromAccount.imageUrl, |
| 111 | + monthlyDonation: order.frequency === "year" ? Math.round(order.amount.value * 100 / 12) : order.amount.value * 100, |
| 112 | + totalDonations: order.totalDonations.value * 100, |
| 113 | + source: "opencollective", |
| 114 | + tier: order.tier ? order.tier.slug : null |
| 115 | + })); |
| 116 | + |
| 117 | +} |
| 118 | + |
| 119 | +/** |
| 120 | + * Fetches GitHub Sponsors data using the GraphQL API. |
| 121 | + * @returns {Array} An array of sponsors. |
| 122 | + */ |
| 123 | +async function fetchGitHubSponsors() { |
| 124 | + |
| 125 | + const { organization } = await githubGraphQL(`query { |
| 126 | + organization(login: "eslint") { |
| 127 | + sponsorshipsAsMaintainer (first: 100) { |
| 128 | + nodes { |
| 129 | + sponsor { |
| 130 | + name, |
| 131 | + login, |
| 132 | + avatarUrl, |
| 133 | + url, |
| 134 | + websiteUrl |
| 135 | + }, |
| 136 | + tier { |
| 137 | + monthlyPriceInDollars |
| 138 | + } |
| 139 | + } |
| 140 | + } |
50 | 141 | }
|
51 |
| - frequency |
52 |
| - totalDonations { |
53 |
| - value |
| 142 | + }`, { |
| 143 | + headers: { |
| 144 | + authorization: `token ${ESLINT_GITHUB_TOKEN}` |
54 | 145 | }
|
55 |
| - } |
56 |
| - } |
57 |
| - } |
58 |
| -}`; |
| 146 | + }); |
| 147 | + |
| 148 | + // return an array in the same format as Open Collective |
| 149 | + return organization.sponsorshipsAsMaintainer.nodes.map(({ sponsor, tier }) => ({ |
| 150 | + name: sponsor.name, |
| 151 | + image: sponsor.avatarUrl, |
| 152 | + url: sponsor.websiteUrl || sponsor.url, |
| 153 | + monthlyDonation: tier.monthlyPriceInDollars * 100, |
| 154 | + source: "github", |
| 155 | + tier: getTierSlug(tier.monthlyPriceInDollars) |
| 156 | + })); |
| 157 | + |
| 158 | +} |
59 | 159 |
|
60 | 160 | //-----------------------------------------------------------------------------
|
61 | 161 | // Main
|
62 | 162 | //-----------------------------------------------------------------------------
|
63 | 163 |
|
64 | 164 | (async() => {
|
65 | 165 |
|
66 |
| - // fetch the data |
67 |
| - const result = await fetch(graphqlEndpoint, { |
68 |
| - method: "POST", |
69 |
| - headers: { "Content-Type": "application/json" }, |
70 |
| - body: JSON.stringify({ query: graphqlQuery }) |
71 |
| - }); |
72 |
| - const orders = await result.json().then(res => res.data.account.orders.nodes); |
73 |
| - |
74 |
| - // process into a useful format |
75 |
| - for (const order of orders) { |
| 166 | + const [openCollectiveSponsors, githubSponsors] = await Promise.all([ |
| 167 | + fetchOpenCollectiveSponsors(), |
| 168 | + fetchGitHubSponsors() |
| 169 | + ]); |
76 | 170 |
|
77 |
| - const sponsor = { |
78 |
| - name: order.fromAccount.name, |
79 |
| - url: order.fromAccount.website, |
80 |
| - image: order.fromAccount.imageUrl, |
81 |
| - monthlyDonation: order.frequency === "year" ? Math.round(order.amount.value * 100 / 12) : order.amount.value * 100, |
82 |
| - totalDonations: order.totalDonations.value * 100 |
83 |
| - }; |
| 171 | + const sponsors = openCollectiveSponsors.concat(githubSponsors); |
84 | 172 |
|
85 |
| - const tierSlug = order.tier ? order.tier.slug : null; |
| 173 | + // process into a useful format |
| 174 | + for (const sponsor of sponsors) { |
86 | 175 |
|
87 |
| - switch (tierSlug) { |
| 176 | + switch (sponsor.tier) { |
88 | 177 | case "gold-sponsor":
|
89 |
| - sponsors.gold.push(sponsor); |
| 178 | + tierSponsors.gold.push(sponsor); |
90 | 179 | break;
|
91 | 180 |
|
92 | 181 | case "silver-sponsor":
|
93 |
| - sponsors.silver.push(sponsor); |
| 182 | + tierSponsors.silver.push(sponsor); |
94 | 183 | break;
|
95 | 184 |
|
96 | 185 | case "bronze-sponsor":
|
97 |
| - sponsors.bronze.push(sponsor); |
| 186 | + tierSponsors.bronze.push(sponsor); |
98 | 187 | break;
|
99 | 188 |
|
100 | 189 | default:
|
101 |
| - sponsors.backers.push(sponsor); |
| 190 | + tierSponsors.backers.push(sponsor); |
102 | 191 |
|
103 | 192 | }
|
104 | 193 | }
|
105 | 194 |
|
106 | 195 | // sort order based on total donations
|
107 |
| - for (const key of Object.keys(sponsors)) { |
108 |
| - sponsors[key].sort((a, b) => b.monthlyDonation - a.monthlyDonation); |
| 196 | + for (const key of Object.keys(tierSponsors)) { |
| 197 | + tierSponsors[key].sort((a, b) => b.monthlyDonation - a.monthlyDonation); |
109 | 198 | }
|
110 | 199 |
|
111 |
| - fs.writeFileSync(filename, JSON.stringify(sponsors, null, " "), { encoding: "utf8" }); |
| 200 | + fs.writeFileSync(sponsorsFilename, JSON.stringify(tierSponsors, null, " "), { encoding: "utf8" }); |
112 | 201 | })();
|
0 commit comments