8000 chore: clean up generate-sponsors (#9757) · jakebailey/typescript-eslint@9dbb0a4 · GitHub
[go: up one dir, main page]

Skip to content

Commit 9dbb0a4

Browse files
authored
chore: clean up generate-sponsors (typescript-eslint#9757)
* cleanup * add clarifying comment
1 parent da02f61 commit 9dbb0a4

File tree

2 files changed

+83
-183
lines changed

2 files changed

+83
-183
lines changed

tools/scripts/generate-sponsors.mts

Lines changed: 81 additions & 182 deletions
< 8000 colgroup>
Original file line numberDiff line numberDiff line change
@@ -2,202 +2,101 @@ import * as fs from 'node:fs';
22
import * as path from 'node:path';
33

44
import fetch from 'cross-fetch';
5-
import prettier from 'prettier';
65

76
import { PACKAGES_WEBSITE } from './paths.mts';
87

9-
const graphqlEndpoint = 'https://api.opencollective.com/graphql/v2';
10-
11-
const queries = {
12-
account: `{
13-
account(slug: "typescript-eslint") {
14-
orders(status: ACTIVE, limit: 1000) {
15-
totalCount
16-
nodes {
17-
tier {
18-
slug
19-
}
20-
fromAccount {
21-
id
22-
imageUrl
23-
8000 name
24-
website
25-
}
26-
}
27-
}
28-
}
29-
}`,
30-
collective: `{
31-
collective(slug: "typescript-eslint") {
32-
members(limit: 1000, role: BACKER) {
33-
nodes {
34-
account {
35-
id
36-
imageUrl
37-
name
38-
website
39-
}
40-
tier {
41-
amount {
42-
valueInCents
43-
}
44-
orders(limit: 100) {
45-
nodes {
46-
amount {
47-
valueInCents
48-
}
49-
}
50-
}
51-
}
52-
totalDonations {
53-
valueInCents
54-
}
55-
updatedAt
56-
}
57-
}
58-
}
59-
}`,
60-
};
61-
62-
interface AccountData {
63-
orders: {
64-
nodes: OrderNode[];
65-
};
66-
}
67-
68-
interface OrderNode {
69-
fromAccount: MemberAccount;
70-
tier?: Tier;
71-
}
72-
73-
interface Tier {
74-
slug: string;
75-
}
76-
77-
interface CollectiveData {
78-
members: {
79-
nodes: MemberNode[];
8+
type MemberNodes = {
9+
account: {
10+
id: string;
11+
imageUrl: string;
12+
name: string;
13+
website: string;
8014
};
81-
}
82-
83-
interface MemberNode {
84-
account: MemberAccount;
85-
totalDonations: {
86-
valueInCents: number;
87-
};
88-
}
89-
90-
interface MemberAccount {
91-
id: string;
92-
imageUrl: string;
93-
name: string;
94-
website: string;
95-
}
15+
totalDonations: { valueInCents: number };
16+
}[];
9617

9718
const excludedNames = new Set([
98-
'Guest', // Apparent anonymous donor equivalent without an avatar
9919
'Josh Goldberg', // Team member 💖
10020
]);
10121

10222
const filteredTerms = ['casino', 'deepnude', 'tiktok'];
10323

104-
async function requestGraphql<Data>(key: keyof typeof queries): Promise<Data> {
105-
const response = await fetch(graphqlEndpoint, {
106-
method: 'POST',
107-
headers: { 'Content-Type': 'application/json' },
108-
body: JSON.stringify({ query: queries[key] }),
109-
});
110-
111-
const { data } = (await response.json()) as {
112-
data: Record<typeof key, unknown>;
113-
};
114-
return data[key] as Data;
115-
}
116-
117-
async function main(): Promise<void> {
118-
const [account, collective] = await Promise.all([
119-
requestGraphql<AccountData>('account'),
120-
requestGraphql<CollectiveData>('collective'),
121-
]);
122-
123-
const accountsById = account.orders.nodes.reduce<
124-
Record<string, MemberAccount>
125-
>((accumulator, account) => {
126-
const name = account.fromAccount.name || account.fromAccount.id;
127-
accumulator[name] = {
128-
...accumulator[name],
129-
...account.fromAccount,
130-
};
131-
return accumulator;
132-
}, {});
133-
134-
const totalDonationsById = collective.members.nodes.reduce<
135-
Record<string, number>
136-
>((accumulator, member) => {
137-
const name = member.account.name || member.account.id;
138-
accumulator[name] ||= 0;
139-
accumulator[name] += member.totalDonations.valueInCents;
140-
return accumulator;
141-
}, {});
142-
143-
const uniqueNames = new Set<string>(excludedNames);
144-
const allSponsorsConfig = collective.members.nodes
145-
.map(member => {
146-
const name = member.account.name || member.account.id;
147-
const fromAccount = {
148-
...member.account,
149-
...accountsById[name],
150-
};
151-
const totalDonations = totalDonationsById[name];
152-
const website = fromAccount.website;
153-
154-
return {
155-
id: name,
156-
image: fromAccount.imageUrl,
157-
name: fromAccount.name,
158-
totalDonations,
159-
website,
160-
};
24+
const { members } = (
25+
(await (
26+
await fetch('https://api.opencollective.com/graphql/v2', {
27+
method: 'POST',
28+
headers: { 'Content-Type': 'application/json' },
29+
body: JSON.stringify({
30+
query: `
31+
{
32+
collective(slug: "typescript-eslint") {
33+
members(limit: 1000, role: BACKER) {
34+
nodes {
35+
account {
36+
id
37+
imageUrl
38+
name
39+
website
40+
}
41+
tier {
42+
amount {
43+
valueInCents
44+
}
45+
orders(limit: 100) {
46+
nodes {
47+
amount {
48+
valueInCents
49+
}
50+
}
51+
}
52+
}
53+
totalDonations {
54+
valueInCents
55+
}
56+
updatedAt
57+
}
58+
}
59+
}
60+
}
61+
`,
62+
}),
16163
})
162-
.filter(({ id, name, totalDonations, website }) => {
163-
if (
64+
).json()) as { data: { collective: { members: { nodes: MemberNodes } } } }
65+
).data.collective;
66+
67+
const sponsors = (
68+
Object.entries(
69+
Object.groupBy(members.nodes, ({ account }) => account.name || account.id),
70+
// When using `Object.entries` to iterate the result of `Object.groupBy`, we do not get any `undefined`s
71+
) as [string, MemberNodes][]
72+
)
73+
.map(([id, members]) => {
74+
const [{ account }] = members;
75+
return {
76+
id,
77+
image: account.imageUrl,
78+
name: account.name,
79+
totalDonations: members.reduce(
80+
(sum, { totalDonations }) => sum + totalDonations.valueInCents,
81+
0,
82+
),
83+
website: account.website,
84+
};
85+
})
86+
.filter(
87+
({ id, name, totalDonations, website }) =>
88+
!(
16489
filteredTerms.some(filteredTerm =>
16590
name.toLowerCase().includes(filteredTerm),
16691
) ||
167-
uniqueNames.has(id) ||
92+
excludedNames.has(id) ||
16893
totalDonations < 10000 ||
16994
!website
170-
) {
171-
return false;
172-
}
173-
174-
uniqueNames.add(id);
175-
return true;
176-
})
177-
.sort((a, b) => b.totalDonations - a.totalDonations);
178-
179-
const rcPath = path.join(PACKAGES_WEBSITE, 'data', 'sponsors.json');
180-
fs.writeFileSync(rcPath, await stringifyObject(rcPath, allSponsorsConfig));
181-
}
182-
183-
async function stringifyObject(
184-
filePath: string,
185-
data: unknown,
186-
): Promise<string> {
187-
const config = await prettier.resolveConfig(filePath);
188-
const text = JSON.stringify(
189-
data,
190-
(_, value: unknown) => value ?? undefined,
191-
2,
192-
);
193-
194-
return await prettier.format(text, {
195-
...config,
196-
parser: 'json',
197-
});
198-
}
199-
200-
main().catch((error: unknown) => {
201-
console.error(error);
202-
process.exitCode = 1;
203-
});
95+
),
96+
)
97+
.sort((a, b) => b.totalDonations - a.totalDonations);
98+
99+
fs.writeFileSync(
100+
path.join(PACKAGES_WEBSITE, 'data', 'sponsors.json'),
101+
`${JSON.stringify(sponsors, null, 2)}\n`,
102+
);

tsconfig.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,8 @@
33
"types": ["@types/node"],
44
"noEmit": true,
55
"allowJs": true,
6-
"allowImportingTsExtensions": true
6+
"allowImportingTsExtensions": true,
7+
"lib": ["ESNext"]
78
},
89
"extends": "./tsconfig.base.json",
910
"include": [

0 commit comments

Comments
 (0)
0