@stats-forge/github-stats-forge-core 0.1.0 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/build/api/contributed-to.d.ts +23 -0
- package/build/api/contributed-to.d.ts.map +1 -0
- package/build/api/contributed-to.js +43 -0
- package/build/api/gist.d.ts +8 -15
- package/build/api/gist.d.ts.map +1 -1
- package/build/api/gist.js +14 -29
- package/build/api/handler.d.ts +16 -0
- package/build/api/handler.d.ts.map +1 -0
- package/build/api/handler.js +29 -0
- package/build/api/index.d.ts +1 -0
- package/build/api/index.d.ts.map +1 -1
- package/build/api/index.js +1 -0
- package/build/api/params.d.ts +11 -5
- package/build/api/params.d.ts.map +1 -1
- package/build/api/params.js +26 -6
- package/build/api/pin.d.ts +11 -15
- package/build/api/pin.d.ts.map +1 -1
- package/build/api/pin.js +34 -46
- package/build/api/stats.d.ts +15 -22
- package/build/api/stats.d.ts.map +1 -1
- package/build/api/stats.js +59 -72
- package/build/api/top-langs.d.ts +11 -22
- package/build/api/top-langs.d.ts.map +1 -1
- package/build/api/top-langs.js +34 -51
- package/build/api/wakatime.d.ts +10 -24
- package/build/api/wakatime.d.ts.map +1 -1
- package/build/api/wakatime.js +27 -47
- package/build/cards/contributed-to.d.ts +19 -0
- package/build/cards/contributed-to.d.ts.map +1 -0
- package/build/cards/contributed-to.js +242 -0
- package/build/cards/gist.d.ts +1 -0
- package/build/cards/gist.d.ts.map +1 -1
- package/build/cards/gist.js +13 -9
- package/build/cards/repo.d.ts +12 -1
- package/build/cards/repo.d.ts.map +1 -1
- package/build/cards/repo.js +40 -18
- package/build/cards/stats.d.ts +15 -3
- package/build/cards/stats.d.ts.map +1 -1
- package/build/cards/stats.js +91 -76
- package/build/cards/top-languages.d.ts +13 -2
- package/build/cards/top-languages.d.ts.map +1 -1
- package/build/cards/top-languages.js +26 -9
- package/build/cards/wakatime.d.ts +13 -2
- package/build/cards/wakatime.d.ts.map +1 -1
- package/build/cards/wakatime.js +22 -9
- package/build/common/Card.d.ts +9 -0
- package/build/common/Card.d.ts.map +1 -1
- package/build/common/Card.js +44 -9
- package/build/common/I18n.d.ts +1 -1
- package/build/common/I18n.d.ts.map +1 -1
- package/build/common/I18n.js +4 -2
- package/build/common/brand.d.ts +76 -0
- package/build/common/brand.d.ts.map +1 -0
- package/build/common/brand.js +73 -0
- package/build/common/constants.d.ts +3 -1
- package/build/common/constants.d.ts.map +1 -1
- package/build/common/constants.js +3 -1
- package/build/common/date.d.ts +50 -3
- package/build/common/date.d.ts.map +1 -1
- package/build/common/date.js +104 -2
- package/build/common/icons.d.ts +4 -2
- package/build/common/icons.d.ts.map +1 -1
- package/build/common/icons.js +10 -5
- package/build/common/ops.d.ts +8 -3
- package/build/common/ops.d.ts.map +1 -1
- package/build/common/ops.js +8 -14
- package/build/common/render.d.ts +7 -1
- package/build/common/render.d.ts.map +1 -1
- package/build/common/render.js +60 -58
- package/build/fetchers/contributed-to.d.ts +62 -0
- package/build/fetchers/contributed-to.d.ts.map +1 -0
- package/build/fetchers/contributed-to.js +184 -0
- package/build/fetchers/graphql-error.d.ts +13 -0
- package/build/fetchers/graphql-error.d.ts.map +1 -0
- package/build/fetchers/graphql-error.js +29 -0
- package/build/fetchers/index.d.ts +2 -1
- package/build/fetchers/index.d.ts.map +1 -1
- package/build/fetchers/index.js +1 -0
- package/build/fetchers/stats.d.ts +14 -8
- package/build/fetchers/stats.d.ts.map +1 -1
- package/build/fetchers/stats.js +72 -195
- package/build/fetchers/top-languages.d.ts.map +1 -1
- package/build/fetchers/top-languages.js +3 -18
- package/build/fetchers/types.d.ts +24 -0
- package/build/fetchers/types.d.ts.map +1 -1
- package/build/graphql/contributionsCollection.d.ts +9 -0
- package/build/graphql/contributionsCollection.d.ts.map +1 -0
- package/build/graphql/contributionsCollection.js +12 -0
- package/build/graphql/contributionsDocument.d.ts +5 -5
- package/build/graphql/contributionsDocument.d.ts.map +1 -1
- package/build/graphql/contributionsDocument.js +8 -15
- package/build/graphql/generated/contributed-to.d.ts +22 -0
- package/build/graphql/generated/contributed-to.d.ts.map +1 -0
- package/build/graphql/generated/contributed-to.js +11 -0
- package/build/graphql/generated/stats.d.ts +13 -1
- package/build/graphql/generated/stats.d.ts.map +1 -1
- package/build/graphql/generated/stats.js +2 -2
- package/build/graphql/graphqlDocument.d.ts +1 -1
- package/build/graphql/reposContributedToDocument.d.ts.map +1 -1
- package/build/graphql/reposContributedToDocument.js +11 -5
- package/build/translations.d.ts +64 -1
- package/build/translations.d.ts.map +1 -1
- package/build/translations.js +65 -1
- package/package.json +1 -1
- package/src/api/contributed-to.ts +84 -0
- package/src/api/gist.ts +20 -37
- package/src/api/handler.ts +42 -0
- package/src/api/index.ts +1 -0
- package/src/api/params.ts +48 -9
- package/src/api/pin.ts +33 -45
- package/src/api/stats.ts +78 -89
- package/src/api/top-langs.ts +34 -56
- package/src/api/wakatime.ts +33 -54
- package/src/cards/contributed-to.ts +373 -0
- package/src/cards/gist.ts +17 -8
- package/src/cards/repo.ts +44 -20
- package/src/cards/stats.ts +119 -90
- package/src/cards/top-languages.ts +27 -18
- package/src/cards/wakatime.ts +23 -13
- package/src/common/Card.ts +56 -11
- package/src/common/I18n.ts +4 -2
- package/src/common/brand.ts +109 -0
- package/src/common/constants.ts +4 -1
- package/src/common/date.ts +135 -3
- package/src/common/icons.ts +10 -5
- package/src/common/ops.ts +10 -19
- package/src/common/render.ts +65 -62
- package/src/fetchers/contributed-to.ts +271 -0
- package/src/fetchers/graphql-error.ts +36 -0
- package/src/fetchers/index.ts +3 -0
- package/src/fetchers/stats.ts +123 -274
- package/src/fetchers/top-languages.ts +5 -19
- package/src/fetchers/types.ts +26 -0
- package/src/graphql/contributionsCollection.ts +18 -0
- package/src/graphql/contributionsDocument.ts +11 -18
- package/src/graphql/generated/contributed-to.ts +25 -0
- package/src/graphql/generated/stats.ts +19 -6
- package/src/graphql/graphqlDocument.ts +1 -1
- package/src/graphql/queries/contributed-to.graphql +8 -0
- package/src/graphql/queries/stats.graphql +14 -2
- package/src/graphql/reposContributedToDocument.ts +11 -9
- package/src/translations.ts +77 -0
package/build/fetchers/stats.js
CHANGED
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
import { calculateRank } from '../calculateRank.js';
|
|
2
2
|
import { GITHUB_USERNAME_PATTERN } from '../common/constants.js';
|
|
3
|
-
import {
|
|
3
|
+
import { getWidestRange, toContributionRanges, toGitHubDateTime, toRange, toYearRanges, } from '../common/date.js';
|
|
4
4
|
import { CardError, USER_NOT_FOUND } from '../common/error.js';
|
|
5
5
|
import { createGraphQLFetcher, httpRequest } from '../common/http.js';
|
|
6
6
|
import { logger } from '../common/log.js';
|
|
7
|
-
import { buildSearchFilter,
|
|
8
|
-
import { wrapTextMultiline } from '../common/render.js';
|
|
7
|
+
import { buildSearchFilter, parseOwnerAffiliations } from '../common/ops.js';
|
|
9
8
|
import { retryer } from '../common/retryer.js';
|
|
10
9
|
import { buildContributionsDocument } from '../graphql/contributionsDocument.js';
|
|
11
10
|
import { UserInfoDocument, UserReposDocument } from '../graphql/generated/stats.js';
|
|
12
|
-
import {
|
|
11
|
+
import { fetchReposContributedTo } from './contributed-to.js';
|
|
12
|
+
import { graphqlError } from './graphql-error.js';
|
|
13
13
|
const fetcher = createGraphQLFetcher(UserInfoDocument, 'bearer');
|
|
14
14
|
/** Fetcher for the pages after the first, which only need `repositories`. */
|
|
15
15
|
const reposFetcher = createGraphQLFetcher(UserReposDocument, 'bearer');
|
|
@@ -21,7 +21,7 @@ const reposFetcher = createGraphQLFetcher(UserReposDocument, 'bearer');
|
|
|
21
21
|
*
|
|
22
22
|
* @returns The stats response, with every fetched page's repos merged in.
|
|
23
23
|
*/
|
|
24
|
-
const statsFetcher = async ({ username, includeMergedPullRequests, includeDiscussions, includeDiscussionsAnswers,
|
|
24
|
+
const statsFetcher = async ({ username, includeMergedPullRequests, includeDiscussions, includeDiscussionsAnswers, commitsRange, ownerAffiliations, includeUserRepositories, }, config) => {
|
|
25
25
|
// only the first request carries the stats themselves
|
|
26
26
|
let stats = await retryer(fetcher, {
|
|
27
27
|
login: username,
|
|
@@ -29,7 +29,9 @@ const statsFetcher = async ({ username, includeMergedPullRequests, includeDiscus
|
|
|
29
29
|
includeMergedPullRequests,
|
|
30
30
|
includeDiscussions,
|
|
31
31
|
includeDiscussionsAnswers,
|
|
32
|
-
|
|
32
|
+
// both ends together: an open `to` would run the range into the next 1st of January
|
|
33
|
+
startTime: commitsRange && toGitHubDateTime(commitsRange.from),
|
|
34
|
+
endTime: commitsRange && toGitHubDateTime(commitsRange.to),
|
|
33
35
|
ownerAffiliations,
|
|
34
36
|
includeUserRepositories,
|
|
35
37
|
}, config);
|
|
@@ -79,20 +81,14 @@ const statsFetcher = async ({ username, includeMergedPullRequests, includeDiscus
|
|
|
79
81
|
*
|
|
80
82
|
* @returns The search response, carrying `total_count`.
|
|
81
83
|
*/
|
|
82
|
-
const fetchTotalItems = (
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
'Content-Type': 'application/json',
|
|
91
|
-
Accept: 'application/vnd.github.cloak-preview',
|
|
92
|
-
Authorization: `token ${token}`,
|
|
93
|
-
},
|
|
94
|
-
});
|
|
95
|
-
};
|
|
84
|
+
const fetchTotalItems = ({ type, filter, repo, owner }, token, { fetch }) => httpRequest(fetch, `https://api.github.com/search/${type}?per_page=1&q=${buildSearchFilter(repo, owner).replaceAll(' ', '+')}${filter}`, {
|
|
85
|
+
method: 'GET',
|
|
86
|
+
headers: {
|
|
87
|
+
'Content-Type': 'application/json',
|
|
88
|
+
Accept: 'application/vnd.github.cloak-preview',
|
|
89
|
+
Authorization: `token ${token}`,
|
|
90
|
+
},
|
|
91
|
+
});
|
|
96
92
|
/**
|
|
97
93
|
* Fetch a total count for a given username via the REST search API.
|
|
98
94
|
*
|
|
@@ -102,7 +98,7 @@ const fetchTotalItems = (variables, token, { fetch }) => {
|
|
|
102
98
|
*
|
|
103
99
|
* @returns Total count.
|
|
104
100
|
*/
|
|
105
|
-
const totalItemsFetcher = async ({ username,
|
|
101
|
+
const totalItemsFetcher = async ({ username, ...search }, config) => {
|
|
106
102
|
if (!GITHUB_USERNAME_PATTERN.test(username)) {
|
|
107
103
|
logger.log('Invalid username provided.');
|
|
108
104
|
throw new CardError('Invalid username provided.', {
|
|
@@ -112,7 +108,7 @@ const totalItemsFetcher = async ({ username, repo, owner, type, filter, }, confi
|
|
|
112
108
|
}
|
|
113
109
|
let res;
|
|
114
110
|
try {
|
|
115
|
-
res = await retryer(fetchTotalItems,
|
|
111
|
+
res = await retryer(fetchTotalItems, search, config);
|
|
116
112
|
}
|
|
117
113
|
catch (error) {
|
|
118
114
|
logger.log(error);
|
|
@@ -127,85 +123,55 @@ const totalItemsFetcher = async ({ username, repo, owner, type, filter, }, confi
|
|
|
127
123
|
}
|
|
128
124
|
return totalCount;
|
|
129
125
|
};
|
|
126
|
+
/** Each count the REST search API answers: the flag asking for it, and the qualifiers that find it. */
|
|
127
|
+
const REPO_USER_SEARCHES = [
|
|
128
|
+
['include_prs_authored', 'totalPRsAuthored', (username) => `author:${username}+type:pr`],
|
|
129
|
+
[
|
|
130
|
+
'include_prs_commented',
|
|
131
|
+
'totalPRsCommented',
|
|
132
|
+
(username) => `commenter:${username}+-author:${username}+type:pr`,
|
|
133
|
+
],
|
|
134
|
+
[
|
|
135
|
+
'include_prs_reviewed',
|
|
136
|
+
'totalPRsReviewed',
|
|
137
|
+
(username) => `reviewed-by:${username}+-author:${username}+type:pr`,
|
|
138
|
+
],
|
|
139
|
+
['include_issues_authored', 'totalIssuesAuthored', (username) => `author:${username}+type:issue`],
|
|
140
|
+
[
|
|
141
|
+
'include_issues_commented',
|
|
142
|
+
'totalIssuesCommented',
|
|
143
|
+
(username) => `commenter:${username}+-author:${username}+type:issue`,
|
|
144
|
+
],
|
|
145
|
+
];
|
|
130
146
|
/**
|
|
131
147
|
* Fetch the per-repository counts the REST search API answers, one request each.
|
|
132
148
|
*
|
|
133
149
|
* @returns Only the counts that were asked for.
|
|
134
150
|
*/
|
|
135
|
-
const fetchRepoUserStats = async ({ username, repo = [], owner = [],
|
|
151
|
+
const fetchRepoUserStats = async ({ username, repo = [], owner = [], ...flags }, config) => {
|
|
136
152
|
const stats = {};
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
username,
|
|
140
|
-
|
|
141
|
-
owner,
|
|
142
|
-
type: 'issues',
|
|
143
|
-
filter: `author:${username}+type:pr`,
|
|
144
|
-
}, config);
|
|
145
|
-
}
|
|
146
|
-
if (include_prs_commented) {
|
|
147
|
-
stats.totalPRsCommented = await totalItemsFetcher({
|
|
148
|
-
username,
|
|
149
|
-
repo,
|
|
150
|
-
owner,
|
|
151
|
-
type: 'issues',
|
|
152
|
-
filter: `commenter:${username}+-author:${username}+type:pr`,
|
|
153
|
-
}, config);
|
|
154
|
-
}
|
|
155
|
-
if (include_prs_reviewed) {
|
|
156
|
-
stats.totalPRsReviewed = await totalItemsFetcher({
|
|
157
|
-
username,
|
|
158
|
-
repo,
|
|
159
|
-
owner,
|
|
160
|
-
type: 'issues',
|
|
161
|
-
filter: `reviewed-by:${username}+-author:${username}+type:pr`,
|
|
162
|
-
}, config);
|
|
163
|
-
}
|
|
164
|
-
if (include_issues_authored) {
|
|
165
|
-
stats.totalIssuesAuthored = await totalItemsFetcher({
|
|
166
|
-
username,
|
|
167
|
-
repo,
|
|
168
|
-
owner,
|
|
169
|
-
type: 'issues',
|
|
170
|
-
filter: `author:${username}+type:issue`,
|
|
171
|
-
}, config);
|
|
172
|
-
}
|
|
173
|
-
if (include_issues_commented) {
|
|
174
|
-
stats.totalIssuesCommented = await totalItemsFetcher({
|
|
175
|
-
username,
|
|
176
|
-
repo,
|
|
177
|
-
owner,
|
|
178
|
-
type: 'issues',
|
|
179
|
-
filter: `commenter:${username}+-author:${username}+type:issue`,
|
|
180
|
-
}, config);
|
|
153
|
+
for (const [flag, stat, filter] of REPO_USER_SEARCHES) {
|
|
154
|
+
if (flags[flag]) {
|
|
155
|
+
stats[stat] = await totalItemsFetcher({ username, repo, owner, type: 'issues', filter: filter(username) }, config);
|
|
156
|
+
}
|
|
181
157
|
}
|
|
182
158
|
return stats;
|
|
183
159
|
};
|
|
184
160
|
/**
|
|
185
|
-
*
|
|
186
|
-
*/
|
|
187
|
-
const graphqlError = (errors, statusText, fallback) => {
|
|
188
|
-
logger.error(errors);
|
|
189
|
-
const message = errors[0]?.message;
|
|
190
|
-
return message
|
|
191
|
-
? new CardError(wrapTextMultiline(message, 525, 12)[0] ?? '', {
|
|
192
|
-
code: 'upstream',
|
|
193
|
-
secondaryMessage: statusText,
|
|
194
|
-
})
|
|
195
|
-
: new CardError(fallback, { code: 'upstream' });
|
|
196
|
-
};
|
|
197
|
-
/**
|
|
198
|
-
* Fetch all-time contributions by building a single GraphQL query
|
|
199
|
-
* for all the given years.
|
|
161
|
+
* Sum one figure over a set of ranges, fetched as aliased fields of a single query.
|
|
200
162
|
*
|
|
201
163
|
* Whether private contributions are included depends on the user's profile settings:
|
|
202
164
|
* https://docs.github.com/en/account-and-profile/how-tos/contribution-settings/manage-visibility-settings-for-private-contributions-and-achievements#changing-the-visibility-of-your-private-contributions
|
|
165
|
+
*
|
|
166
|
+
* @returns The total over every range, and 0 when there are none.
|
|
203
167
|
*/
|
|
204
|
-
const
|
|
205
|
-
|
|
168
|
+
const sumOverRanges = async (username, ranges,
|
|
169
|
+
/** What one range contributes to the total. */
|
|
170
|
+
pick, config) => {
|
|
171
|
+
if (ranges.length === 0) {
|
|
206
172
|
return 0;
|
|
207
173
|
}
|
|
208
|
-
const contributionsFetcher = createGraphQLFetcher(buildContributionsDocument(
|
|
174
|
+
const contributionsFetcher = createGraphQLFetcher(buildContributionsDocument(ranges), 'bearer');
|
|
209
175
|
const contribRes = await retryer(contributionsFetcher, { login: username }, config);
|
|
210
176
|
if (contribRes.data.errors) {
|
|
211
177
|
throw graphqlError(contribRes.data.errors, contribRes.statusText, 'Something went wrong while trying to retrieve the contributions data using the GraphQL API.');
|
|
@@ -215,102 +181,17 @@ const fetchTotalContributions = async (username, years, config) => {
|
|
|
215
181
|
return 0;
|
|
216
182
|
}
|
|
217
183
|
let total = 0;
|
|
218
|
-
for (const
|
|
219
|
-
|
|
220
|
-
if (yearBlock?.contributionCalendar.totalContributions) {
|
|
221
|
-
total += yearBlock.contributionCalendar.totalContributions;
|
|
222
|
-
}
|
|
184
|
+
for (const block of Object.values(user)) {
|
|
185
|
+
total += pick(block);
|
|
223
186
|
}
|
|
224
187
|
return total;
|
|
225
188
|
};
|
|
226
|
-
const MS_PER_DAY = 24 * 60 * 60 * 1000;
|
|
227
|
-
/**
|
|
228
|
-
* Ranges per request.
|
|
229
|
-
* Each costs roughly `4 * MAX_REPOSITORIES_LIMIT` nodes,
|
|
230
|
-
* so an unchunked round of a heavily split account would breach GitHub's 500k node ceiling
|
|
231
|
-
* and lose the ranges that had already resolved along with it.
|
|
232
|
-
*/
|
|
233
|
-
const MAX_RANGES_PER_REQUEST = 100;
|
|
234
|
-
const REPOS_CONTRIBUTED_TO_ERROR = 'Something went wrong while trying to retrieve the repository contributions data using the GraphQL API.';
|
|
235
|
-
/**
|
|
236
|
-
* Count the repositories a user contributed to across every contribution year.
|
|
237
|
-
*
|
|
238
|
-
* `repositoriesContributedTo` spans at most one year,
|
|
239
|
-
* so every year is fetched as an aliased `contributionsCollection(from, to)` in one request and the repos de-duplicated.
|
|
240
|
-
* A range returning `MAX_REPOSITORIES_LIMIT` results may have more,
|
|
241
|
-
* so it is halved and requeried in the next round.
|
|
242
|
-
*
|
|
243
|
-
* Whether private contributions are included depends on the used PAT.
|
|
244
|
-
*
|
|
245
|
-
* @returns Count of repositories.
|
|
246
|
-
*/
|
|
247
|
-
const fetchAllTimeReposContributedTo = async (
|
|
248
|
-
/** `nameWithOwner` uses GitHub's casing, which the query-string username need not match */
|
|
249
|
-
canonicalUsername, years, includeOwnRepos, config) => {
|
|
250
|
-
const repos = new Set();
|
|
251
|
-
let pending = years.map((year) => getGitHubYearRange(year));
|
|
252
|
-
while (pending.length > 0) {
|
|
253
|
-
const nextPending = [];
|
|
254
|
-
for (const chunk of chunkArray(pending, MAX_RANGES_PER_REQUEST)) {
|
|
255
|
-
const chunkFetcher = createGraphQLFetcher(buildReposContributedToDocument(chunk, includeOwnRepos), 'bearer');
|
|
256
|
-
const res = await retryer(chunkFetcher, { login: canonicalUsername, maxRepositories: MAX_REPOSITORIES_LIMIT }, config);
|
|
257
|
-
if (res.data.errors) {
|
|
258
|
-
throw graphqlError(res.data.errors, res.statusText, REPOS_CONTRIBUTED_TO_ERROR);
|
|
259
|
-
}
|
|
260
|
-
const { user } = res.data.data;
|
|
261
|
-
if (!user) {
|
|
262
|
-
throw new CardError(REPOS_CONTRIBUTED_TO_ERROR, { code: 'upstream' });
|
|
263
|
-
}
|
|
264
|
-
for (const [index, range] of chunk.entries()) {
|
|
265
|
-
const rangeResponse = user[`range_${index}`];
|
|
266
|
-
if (!rangeResponse) {
|
|
267
|
-
throw new CardError(REPOS_CONTRIBUTED_TO_ERROR, {
|
|
268
|
-
code: 'upstream',
|
|
269
|
-
});
|
|
270
|
-
}
|
|
271
|
-
const lists = [
|
|
272
|
-
rangeResponse.commitContributionsByRepository,
|
|
273
|
-
rangeResponse.issueContributionsByRepository,
|
|
274
|
-
rangeResponse.pullRequestContributionsByRepository,
|
|
275
|
-
(rangeResponse.repositoryContributions?.nodes ?? []).filter((node) => node !== null),
|
|
276
|
-
];
|
|
277
|
-
const isSaturated = lists.some((list) => list.length >= MAX_REPOSITORIES_LIMIT);
|
|
278
|
-
const rangeDays = Math.round((range.to.getTime() - range.from.getTime()) / MS_PER_DAY);
|
|
279
|
-
// a range of 1 day or less can't be split any further
|
|
280
|
-
if (isSaturated && rangeDays >= 2) {
|
|
281
|
-
// every `from` sits on UTC midnight, so the split lands on a day boundary too
|
|
282
|
-
const mid = new Date(range.from.getTime() + Math.floor(rangeDays / 2) * MS_PER_DAY);
|
|
283
|
-
// GitHub only reads the date portion,
|
|
284
|
-
// so the first half ends 1 second before `mid` to keep the halves from sharing a day
|
|
285
|
-
nextPending.push({ from: range.from, to: new Date(mid.getTime() - 1000) }, { from: mid, to: range.to });
|
|
286
|
-
continue;
|
|
287
|
-
}
|
|
288
|
-
if (isSaturated) {
|
|
289
|
-
logger.log(`Range ${range.from.toISOString()} - ${range.to.toISOString()} is saturated but cannot be split further.`);
|
|
290
|
-
}
|
|
291
|
-
for (const { repository } of lists.flat()) {
|
|
292
|
-
const name = repository.nameWithOwner;
|
|
293
|
-
if (includeOwnRepos || !name.startsWith(`${canonicalUsername}/`)) {
|
|
294
|
-
repos.add(name);
|
|
295
|
-
}
|
|
296
|
-
}
|
|
297
|
-
}
|
|
298
|
-
}
|
|
299
|
-
// each saturated range pushes both of its halves
|
|
300
|
-
const saturatedCount = nextPending.length / 2;
|
|
301
|
-
if (saturatedCount > 0) {
|
|
302
|
-
logger.log(`found ${saturatedCount} saturated ranges, splitting and retrying...`);
|
|
303
|
-
}
|
|
304
|
-
pending = nextPending;
|
|
305
|
-
}
|
|
306
|
-
return repos.size;
|
|
307
|
-
};
|
|
308
189
|
/**
|
|
309
190
|
* Fetch stats for a given username.
|
|
310
191
|
*
|
|
311
192
|
* @returns Stats data.
|
|
312
193
|
*/
|
|
313
|
-
const fetchStats = async ({ username, include_all_commits = false, exclude_repo = [], include_merged_pull_requests = false, include_discussions = false, include_discussions_answers = false,
|
|
194
|
+
const fetchStats = async ({ username, include_all_commits = false, exclude_repo = [], include_merged_pull_requests = false, include_discussions = false, include_discussions_answers = false, from, to, repo = [], owner = [], include_prs_authored = false, include_prs_commented = false, include_prs_reviewed = false, include_issues_authored = false, include_issues_commented = false, ownerAffiliations = [], include_contributions = false, include_all_time_contribs = false, contribs_include_own_repos = false, }, config) => {
|
|
314
195
|
if (!username) {
|
|
315
196
|
throw CardError.missingParam(['username']);
|
|
316
197
|
}
|
|
@@ -321,6 +202,7 @@ const fetchStats = async ({ username, include_all_commits = false, exclude_repo
|
|
|
321
202
|
mergedPRsPercentage: 0,
|
|
322
203
|
totalReviews: 0,
|
|
323
204
|
totalCommits: 0,
|
|
205
|
+
commitsRange: undefined,
|
|
324
206
|
totalIssues: 0,
|
|
325
207
|
totalStars: 0,
|
|
326
208
|
totalDiscussionsStarted: 0,
|
|
@@ -336,34 +218,22 @@ const fetchStats = async ({ username, include_all_commits = false, exclude_repo
|
|
|
336
218
|
rank: { level: 'C', percentile: 100 },
|
|
337
219
|
};
|
|
338
220
|
const affiliations = parseOwnerAffiliations(ownerAffiliations);
|
|
221
|
+
// neither end given leaves GitHub's trailing year in place, which `userInfo` already carries
|
|
222
|
+
const commitsRange = from === undefined && to === undefined ? undefined : toRange(from, to);
|
|
223
|
+
const commitsYearRanges = commitsRange ? toYearRanges(commitsRange) : [];
|
|
224
|
+
// one calendar year is a range `userInfo` can ask for itself; anything else is summed separately
|
|
225
|
+
const inlineRange = commitsYearRanges.length === 1 ? commitsYearRanges[0] : undefined;
|
|
339
226
|
const res = await statsFetcher({
|
|
340
227
|
username,
|
|
341
228
|
includeMergedPullRequests: include_merged_pull_requests,
|
|
342
229
|
includeDiscussions: include_discussions,
|
|
343
230
|
includeDiscussionsAnswers: include_discussions_answers,
|
|
344
|
-
|
|
345
|
-
? undefined
|
|
346
|
-
: toGitHubDateTime(getGitHubYearRange(commits_year).from),
|
|
231
|
+
commitsRange: inlineRange,
|
|
347
232
|
ownerAffiliations: affiliations,
|
|
348
233
|
includeUserRepositories: contribs_include_own_repos,
|
|
349
234
|
}, config);
|
|
350
|
-
// Catch GraphQL errors.
|
|
351
235
|
if (res.data.errors) {
|
|
352
|
-
|
|
353
|
-
const [firstError] = res.data.errors;
|
|
354
|
-
if (firstError?.type === 'NOT_FOUND') {
|
|
355
|
-
throw new CardError(firstError.message || 'Could not fetch user.', {
|
|
356
|
-
code: 'not_found',
|
|
357
|
-
secondaryMessage: USER_NOT_FOUND,
|
|
358
|
-
});
|
|
359
|
-
}
|
|
360
|
-
if (firstError?.message) {
|
|
361
|
-
throw new CardError(wrapTextMultiline(firstError.message, 525, 12)[0] ?? '', {
|
|
362
|
-
code: 'upstream',
|
|
363
|
-
secondaryMessage: res.statusText,
|
|
364
|
-
});
|
|
365
|
-
}
|
|
366
|
-
throw new CardError('Something went wrong while trying to retrieve the stats data using the GraphQL API.', { code: 'upstream' });
|
|
236
|
+
throw graphqlError(res.data.errors, res.statusText, 'Something went wrong while trying to retrieve the stats data using the GraphQL API.');
|
|
367
237
|
}
|
|
368
238
|
const { user } = res.data.data;
|
|
369
239
|
if (!user) {
|
|
@@ -384,7 +254,12 @@ const fetchStats = async ({ username, include_all_commits = false, exclude_repo
|
|
|
384
254
|
}, config);
|
|
385
255
|
}
|
|
386
256
|
else {
|
|
387
|
-
stats.
|
|
257
|
+
stats.commitsRange = commitsRange;
|
|
258
|
+
// an inverted range sums no years, which is no commits rather than GitHub's default year
|
|
259
|
+
stats.totalCommits =
|
|
260
|
+
commitsRange && !inlineRange
|
|
261
|
+
? await sumOverRanges(username, commitsYearRanges, (block) => block.totalCommitContributions, config)
|
|
262
|
+
: user.commits.totalCommitContributions;
|
|
388
263
|
}
|
|
389
264
|
const repoUserStats = await fetchRepoUserStats({
|
|
390
265
|
username,
|
|
@@ -412,11 +287,13 @@ const fetchStats = async ({ username, include_all_commits = false, exclude_repo
|
|
|
412
287
|
stats.totalDiscussionsAnswered = user.repositoryDiscussionComments?.totalCount ?? 0;
|
|
413
288
|
}
|
|
414
289
|
stats.contributedTo = user.repositoriesContributedTo.totalCount;
|
|
290
|
+
const contributionRanges = toContributionRanges(user.contributionsCollection.contributionYears, getWidestRange());
|
|
415
291
|
if (include_contributions) {
|
|
416
|
-
stats.totalContributions = await
|
|
292
|
+
stats.totalContributions = await sumOverRanges(username, contributionRanges, (block) => block.contributionCalendar.totalContributions, config);
|
|
417
293
|
}
|
|
418
294
|
if (include_all_time_contribs) {
|
|
419
|
-
|
|
295
|
+
const reposContributedTo = await fetchReposContributedTo(user.login, contributionRanges, contribs_include_own_repos, config);
|
|
296
|
+
stats.allTimeContributedTo = reposContributedTo.size;
|
|
420
297
|
}
|
|
421
298
|
// Retrieve stars while filtering out repositories to be hidden.
|
|
422
299
|
const allExcludedRepos = [...exclude_repo, ...config.excludeRepositories];
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"top-languages.d.ts","sourceRoot":"","sources":["../../src/fetchers/top-languages.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,qBAAqB,CAAC;
|
|
1
|
+
{"version":3,"file":"top-languages.d.ts","sourceRoot":"","sources":["../../src/fetchers/top-languages.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,qBAAqB,CAAC;AAYtD,OAAO,KAAK,EAAQ,WAAW,EAAE,MAAM,YAAY,CAAC;AAIpD;;;;GAIG;AACH,QAAA,MAAM,iBAAiB,8EAOlB;IACD,QAAQ,EAAE,MAAM,GAAG,SAAS,CAAC;IAC7B,YAAY,CAAC,EAAE,KAAK,CAAC,MAAM,CAAC,CAAC;IAC7B,WAAW,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IACjC,YAAY,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAClC,iBAAiB,CAAC,EAAE,KAAK,CAAC,MAAM,CAAC,CAAC;CACnC,UACO,UAAU,KACjB,OAAO,CAAC,WAAW,CAmErB,CAAC;AAEF,OAAO,EAAE,iBAAiB,EAAE,CAAC"}
|
|
@@ -1,10 +1,9 @@
|
|
|
1
|
-
import { CardError
|
|
1
|
+
import { CardError } from '../common/error.js';
|
|
2
2
|
import { createGraphQLFetcher } from '../common/http.js';
|
|
3
|
-
import { logger } from '../common/log.js';
|
|
4
3
|
import { parseOwnerAffiliations } from '../common/ops.js';
|
|
5
|
-
import { wrapTextMultiline } from '../common/render.js';
|
|
6
4
|
import { retryer } from '../common/retryer.js';
|
|
7
5
|
import { TopLanguagesDocument } from '../graphql/generated/top-languages.js';
|
|
6
|
+
import { graphqlError } from './graphql-error.js';
|
|
8
7
|
const fetcher = createGraphQLFetcher(TopLanguagesDocument, 'token');
|
|
9
8
|
/**
|
|
10
9
|
* Fetch top languages for a given username.
|
|
@@ -21,21 +20,7 @@ const fetchTopLanguages = async ({ username, exclude_repo = [], size_weight = 1,
|
|
|
21
20
|
ownerAffiliations: affiliations,
|
|
22
21
|
}, config);
|
|
23
22
|
if (res.data.errors) {
|
|
24
|
-
|
|
25
|
-
const [firstError] = res.data.errors;
|
|
26
|
-
if (firstError?.type === 'NOT_FOUND') {
|
|
27
|
-
throw new CardError(firstError.message || 'Could not fetch user.', {
|
|
28
|
-
code: 'not_found',
|
|
29
|
-
secondaryMessage: USER_NOT_FOUND,
|
|
30
|
-
});
|
|
31
|
-
}
|
|
32
|
-
if (firstError?.message) {
|
|
33
|
-
throw new CardError(wrapTextMultiline(firstError.message, 525, 12)[0] ?? '', {
|
|
34
|
-
code: 'upstream',
|
|
35
|
-
secondaryMessage: res.statusText,
|
|
36
|
-
});
|
|
37
|
-
}
|
|
38
|
-
throw new CardError('Something went wrong while trying to retrieve the language data using the GraphQL API.', { code: 'upstream' });
|
|
23
|
+
throw graphqlError(res.data.errors, res.statusText, 'Something went wrong while trying to retrieve the language data using the GraphQL API.');
|
|
39
24
|
}
|
|
40
25
|
const repoToHide = {};
|
|
41
26
|
const allExcludedRepos = [...exclude_repo, ...config.excludeRepositories];
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { GitHubDateRange } from '../common/date.ts';
|
|
1
2
|
import type { RepoInfoFragment } from '../graphql/generated/repo.ts';
|
|
2
3
|
export interface GistData {
|
|
3
4
|
name: string;
|
|
@@ -33,6 +34,8 @@ export interface StatsData {
|
|
|
33
34
|
mergedPRsPercentage: number;
|
|
34
35
|
totalReviews: number;
|
|
35
36
|
totalCommits: number;
|
|
37
|
+
/** The range `totalCommits` covers; `undefined` when it is GitHub's own last year, or all time. */
|
|
38
|
+
commitsRange: GitHubDateRange | undefined;
|
|
36
39
|
totalIssues: number;
|
|
37
40
|
totalStars: number;
|
|
38
41
|
totalDiscussionsStarted: number;
|
|
@@ -50,6 +53,27 @@ export interface StatsData {
|
|
|
50
53
|
percentile: number;
|
|
51
54
|
};
|
|
52
55
|
}
|
|
56
|
+
/** One repository a user contributed to, as the card ranks it. */
|
|
57
|
+
export interface ContributedRepo {
|
|
58
|
+
nameWithOwner: string;
|
|
59
|
+
/**
|
|
60
|
+
* Commit-days plus issues and pull requests opened, summed over every year.
|
|
61
|
+
* Not a commit count: GitHub groups commit contributions by day, not by commit.
|
|
62
|
+
*/
|
|
63
|
+
contributions: number;
|
|
64
|
+
/** Every year the user contributed to it, ascending. */
|
|
65
|
+
years: Array<number>;
|
|
66
|
+
}
|
|
67
|
+
export interface ContributedToData {
|
|
68
|
+
/** GitHub's own casing of the login, which the query param need not match. */
|
|
69
|
+
login: string;
|
|
70
|
+
/** The ranked slice, most contributions first. */
|
|
71
|
+
repos: Array<ContributedRepo>;
|
|
72
|
+
/** Every repository the walk found, which `repos` is a slice of. */
|
|
73
|
+
totalRepos: number;
|
|
74
|
+
/** The account's contribution years, ascending — the range the year marks cover. */
|
|
75
|
+
years: Array<number>;
|
|
76
|
+
}
|
|
53
77
|
export interface Lang {
|
|
54
78
|
name: string;
|
|
55
79
|
color: string | null;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/fetchers/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,8BAA8B,CAAC;AAErE,MAAM,WAAW,QAAQ;IACvB,IAAI,EAAE,MAAM,CAAC;IACb,aAAa,EAAE,MAAM,CAAC;IACtB,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,UAAU,EAAE,MAAM,CAAC;IACnB,UAAU,EAAE,MAAM,CAAC;CACpB;AAED;;;GAGG;AACH,KAAK,QAAQ,GAAG,IAAI,CAAC,gBAAgB,EAAE,iBAAiB,CAAC,GAAG;IAC1D,eAAe,EAAE;QACf,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;QACrB,EAAE,CAAC,EAAE,MAAM,CAAC;QACZ,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;KACrB,GAAG,IAAI,CAAC;CACV,CAAC;AAEF,MAAM,WAAW,aAAa;IAE5B,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,mBAAmB,CAAC,EAAE,MAAM,CAAC;IAC7B,oBAAoB,CAAC,EAAE,MAAM,CAAC;CAC/B;AAED,MAAM,MAAM,cAAc,GAAG,QAAQ,GAAG,aAAa,CAAC;AAEtD,MAAM,WAAW,SAAS;IACxB,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,MAAM,CAAC;IACjB,cAAc,EAAE,MAAM,CAAC;IACvB,mBAAmB,EAAE,MAAM,CAAC;IAC5B,YAAY,EAAE,MAAM,CAAC;IACrB,YAAY,EAAE,MAAM,CAAC;IACrB,WAAW,EAAE,MAAM,CAAC;IACpB,UAAU,EAAE,MAAM,CAAC;IACnB,uBAAuB,EAAE,MAAM,CAAC;IAChC,wBAAwB,EAAE,MAAM,CAAC;IACjC,aAAa,EAAE,MAAM,CAAC;IACtB,oBAAoB,EAAE,MAAM,CAAC;IAC7B,gBAAgB,EAAE,MAAM,CAAC;IACzB,iBAAiB,EAAE,MAAM,CAAC;IAC1B,gBAAgB,EAAE,MAAM,CAAC;IACzB,mBAAmB,EAAE,MAAM,CAAC;IAC5B,oBAAoB,EAAE,MAAM,CAAC;IAC7B,kBAAkB,EAAE,MAAM,CAAC;IAC3B,IAAI,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,UAAU,EAAE,MAAM,CAAA;KAAE,CAAC;CAC7C;AAED,MAAM,WAAW,IAAI;IACnB,IAAI,EAAE,MAAM,CAAC;IAEb,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,IAAI,EAAE,MAAM,CAAC;IACb,sDAAsD;IACtD,KAAK,EAAE,MAAM,CAAC;CACf;AAED,MAAM,MAAM,WAAW,GAAG,MAAM,CAAC,MAAM,EAAE,IAAI,CAAC,CAAC;AAE/C,MAAM,WAAW,YAAY;IAC3B,UAAU,EAAE,KAAK,CAAC;QAChB,OAAO,EAAE,MAAM,CAAC;QAChB,KAAK,EAAE,MAAM,CAAC;QACd,OAAO,EAAE,MAAM,CAAC;QAChB,IAAI,EAAE,MAAM,CAAC;QACb,OAAO,EAAE,MAAM,CAAC;QAChB,IAAI,EAAE,MAAM,CAAC;QACb,aAAa,EAAE,MAAM,CAAC;KACvB,CAAC,CAAC;IACH,aAAa,EAAE,MAAM,CAAC;IACtB,sCAAsC,EAAE,MAAM,CAAC;IAC/C,uBAAuB,EAAE,MAAM,CAAC;IAChC,mBAAmB,EAAE,MAAM,CAAC;IAC5B,OAAO,EAAE,KAAK,CAAC;QACb,OAAO,EAAE,MAAM,CAAC;QAChB,KAAK,EAAE,MAAM,CAAC;QACd,OAAO,EAAE,MAAM,CAAC;QAChB,IAAI,EAAE,MAAM,CAAC;QACb,OAAO,EAAE,MAAM,CAAC;QAChB,IAAI,EAAE,MAAM,CAAC;QACb,aAAa,EAAE,MAAM,CAAC;KACvB,CAAC,CAAC;IACH,QAAQ,EAAE,MAAM,CAAC;IACjB,4BAA4B,EAAE,MAAM,CAAC;IACrC,qDAAqD,EAAE,MAAM,CAAC;IAC9D,oBAAoB,EAAE,MAAM,CAAC;IAC7B,6CAA6C,EAAE,MAAM,CAAC;IACtD,EAAE,EAAE,MAAM,CAAC;IACX,mBAAmB,EAAE,OAAO,CAAC;IAC7B,0BAA0B,EAAE,OAAO,CAAC;IACpC,kBAAkB,EAAE,OAAO,CAAC;IAC5B,sBAAsB,EAAE,OAAO,CAAC;IAChC,QAAQ,EAAE,OAAO,CAAC;IAClB,aAAa,EAAE,OAAO,CAAC;IACvB,SAAS,EAAE,KAAK,CAAC;QACf,OAAO,EAAE,MAAM,CAAC;QAChB,KAAK,EAAE,MAAM,CAAC;QACd,OAAO,EAAE,MAAM,CAAC;QAChB,IAAI,EAAE,MAAM,CAAC;QACb,OAAO,EAAE,MAAM,CAAC;QAChB,IAAI,EAAE,MAAM,CAAC;QACb,aAAa,EAAE,MAAM,CAAC;KACvB,CAAC,CAAC;IACH,iBAAiB,EAAE,KAAK,CAAC;QACvB,OAAO,EAAE,MAAM,CAAC;QAChB,KAAK,EAAE,MAAM,CAAC;QACd,OAAO,EAAE,MAAM,CAAC;QAChB,IAAI,EAAE,MAAM,CAAC;QACb,OAAO,EAAE,MAAM,CAAC;QAChB,IAAI,EAAE,MAAM,CAAC;QACb,aAAa,EAAE,MAAM,CAAC;KACvB,CAAC,CAAC;IACH,kBAAkB,EAAE,MAAM,CAAC;IAC3B,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,MAAM,CAAC;IAChB,aAAa,EAAE,MAAM,CAAC;IACtB,sCAAsC,EAAE,MAAM,CAAC;IAC/C,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,MAAM,CAAC;IACjB,WAAW,EAAE,OAAO,CAAC;CACtB;AAED,MAAM,WAAW,YAAY;IAC3B,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;CACjB"}
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/fetchers/types.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { GitHubDateRange } from '../common/date.ts';
|
|
2
|
+
/**
|
|
3
|
+
* One `contributionsCollection` field per range, aliased by position.
|
|
4
|
+
*
|
|
5
|
+
* @returns The fields, for a query that selects `fragment`.
|
|
6
|
+
*/
|
|
7
|
+
declare const aliasedRanges: (ranges: Array<GitHubDateRange>, fragment: string) => string;
|
|
8
|
+
export { aliasedRanges };
|
|
9
|
+
//# sourceMappingURL=contributionsCollection.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"contributionsCollection.d.ts","sourceRoot":"","sources":["../../src/graphql/contributionsCollection.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAC;AAOzD;;;;GAIG;AACH,QAAA,MAAM,aAAa,WAAY,KAAK,CAAC,eAAe,CAAC,YAAY,MAAM,KAAG,MAG3D,CAAC;AAEhB,OAAO,EAAE,aAAa,EAAE,CAAC"}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { toGitHubDateTime } from '../common/date.js';
|
|
2
|
+
/** Naming `to` is not optional: GitHub defaults an omitted one to a year after `from`. */
|
|
3
|
+
const contributionsCollectionOf = ({ from, to }) => `contributionsCollection(from: "${toGitHubDateTime(from)}", to: "${toGitHubDateTime(to)}")`;
|
|
4
|
+
/**
|
|
5
|
+
* One `contributionsCollection` field per range, aliased by position.
|
|
6
|
+
*
|
|
7
|
+
* @returns The fields, for a query that selects `fragment`.
|
|
8
|
+
*/
|
|
9
|
+
const aliasedRanges = (ranges, fragment) => ranges
|
|
10
|
+
.map((range, index) => `range_${index}: ${contributionsCollectionOf(range)} { ...${fragment} }`)
|
|
11
|
+
.join('\n');
|
|
12
|
+
export { aliasedRanges };
|
|
@@ -1,18 +1,18 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { GitHubDateRange } from '../common/date.ts';
|
|
2
|
+
import type { RangeContributionsFragment } from './generated/stats.ts';
|
|
2
3
|
import type { GraphQLDocument } from './graphqlDocument.ts';
|
|
3
4
|
interface ContributionsQueryVariables {
|
|
4
5
|
login: string;
|
|
5
6
|
}
|
|
6
7
|
interface ContributionsQuery {
|
|
7
|
-
user: Record<`
|
|
8
|
+
user: Record<`range_${number}`, RangeContributionsFragment> | null;
|
|
8
9
|
}
|
|
9
10
|
/**
|
|
10
|
-
* Build the
|
|
11
|
-
* The shape is only known at runtime.
|
|
11
|
+
* Build the contributions query for a set of ranges. The shape is only known at runtime.
|
|
12
12
|
*
|
|
13
13
|
* @returns Document for `createGraphQLFetcher`.
|
|
14
14
|
*/
|
|
15
|
-
declare const buildContributionsDocument: (
|
|
15
|
+
declare const buildContributionsDocument: (ranges: Array<GitHubDateRange>) => GraphQLDocument<ContributionsQuery, ContributionsQueryVariables>;
|
|
16
16
|
export { buildContributionsDocument };
|
|
17
17
|
export type { ContributionsQuery };
|
|
18
18
|
//# sourceMappingURL=contributionsDocument.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"contributionsDocument.d.ts","sourceRoot":"","sources":["../../src/graphql/contributionsDocument.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"contributionsDocument.d.ts","sourceRoot":"","sources":["../../src/graphql/contributionsDocument.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAC;AAGzD,OAAO,KAAK,EAAE,0BAA0B,EAAE,MAAM,sBAAsB,CAAC;AACvE,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAC;AAG5D,UAAU,2BAA2B;IACnC,KAAK,EAAE,MAAM,CAAC;CACf;AAED,UAAU,kBAAkB;IAC1B,IAAI,EAAE,MAAM,CAAC,SAAS,MAAM,EAAE,EAAE,0BAA0B,CAAC,GAAG,IAAI,CAAC;CACpE;AAED;;;;GAIG;AACH,QAAA,MAAM,0BAA0B,WACtB,KAAK,CAAC,eAAe,CAAC,KAC7B,eAAe,CAAC,kBAAkB,EAAE,2BAA2B,CAa/D,CAAC;AAEJ,OAAO,EAAE,0BAA0B,EAAE,CAAC;AACtC,YAAY,EAAE,kBAAkB,EAAE,CAAC"}
|
|
@@ -1,29 +1,22 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { aliasedRanges } from './contributionsCollection.js';
|
|
2
2
|
import { graphqlDocument } from './graphqlDocument.js';
|
|
3
3
|
/**
|
|
4
|
-
* Build the
|
|
5
|
-
* The shape is only known at runtime.
|
|
4
|
+
* Build the contributions query for a set of ranges. The shape is only known at runtime.
|
|
6
5
|
*
|
|
7
6
|
* @returns Document for `createGraphQLFetcher`.
|
|
8
7
|
*/
|
|
9
|
-
const buildContributionsDocument = (
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
const { from, to } = getGitHubYearRange(year);
|
|
13
|
-
return `year_${year}: contributionsCollection(from: "${toGitHubDateTime(from)}", to: "${toGitHubDateTime(to)}") { ...YearContributions }`;
|
|
14
|
-
})
|
|
15
|
-
.join('\n');
|
|
16
|
-
// fragment must match queries/stats.graphql, which generates its type
|
|
17
|
-
return graphqlDocument(`
|
|
8
|
+
const buildContributionsDocument = (ranges) =>
|
|
9
|
+
// fragment must match queries/stats.graphql, which generates its type
|
|
10
|
+
graphqlDocument(`
|
|
18
11
|
query userContributions($login: String!) {
|
|
19
12
|
user(login: $login) {
|
|
20
|
-
${
|
|
13
|
+
${aliasedRanges(ranges, 'RangeContributions')}
|
|
21
14
|
}
|
|
22
15
|
}
|
|
23
|
-
fragment
|
|
16
|
+
fragment RangeContributions on ContributionsCollection {
|
|
17
|
+
totalCommitContributions
|
|
24
18
|
contributionCalendar {
|
|
25
19
|
totalContributions
|
|
26
20
|
}
|
|
27
21
|
}`);
|
|
28
|
-
};
|
|
29
22
|
export { buildContributionsDocument };
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/** Internal type. DO NOT USE DIRECTLY. */
|
|
2
|
+
type Exact<T extends {
|
|
3
|
+
[key: string]: unknown;
|
|
4
|
+
}> = {
|
|
5
|
+
[K in keyof T]: T[K];
|
|
6
|
+
};
|
|
7
|
+
export type UserContributionYearsQueryVariables = Exact<{
|
|
8
|
+
login: string;
|
|
9
|
+
}>;
|
|
10
|
+
export type UserContributionYearsQuery = {
|
|
11
|
+
user: {
|
|
12
|
+
login: string;
|
|
13
|
+
contributionsCollection: {
|
|
14
|
+
contributionYears: Array<number>;
|
|
15
|
+
};
|
|
16
|
+
} | null;
|
|
17
|
+
};
|
|
18
|
+
export declare const UserContributionYearsDocument: import("../graphqlDocument.ts").GraphQLDocument<UserContributionYearsQuery, Exact<{
|
|
19
|
+
login: string;
|
|
20
|
+
}>>;
|
|
21
|
+
export {};
|
|
22
|
+
//# sourceMappingURL=contributed-to.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"contributed-to.d.ts","sourceRoot":"","sources":["../../../src/graphql/generated/contributed-to.ts"],"names":[],"mappings":"AAEA,0CAA0C;AAC1C,KAAK,KAAK,CAAC,CAAC,SAAS;IAAE,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAA;CAAE,IAAI;KAAG,CAAC,IAAI,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC;CAAE,CAAC;AAE5E,MAAM,MAAM,mCAAmC,GAAG,KAAK,CAAC;IACtD,KAAK,EAAE,MAAM,CAAC;CACf,CAAC,CAAC;AAEH,MAAM,MAAM,0BAA0B,GAAG;IACvC,IAAI,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,uBAAuB,EAAE;YAAE,iBAAiB,EAAE,KAAK,CAAC,MAAM,CAAC,CAAA;SAAE,CAAA;KAAE,GAAG,IAAI,CAAC;CAC/F,CAAC;AAEF,eAAO,MAAM,6BAA6B;WAPjC,MAAM;GAkBZ,CAAC"}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
// Generated file — see .github/CONTRIBUTING.md
|
|
2
|
+
import { graphqlDocument } from '../graphqlDocument.js';
|
|
3
|
+
export const UserContributionYearsDocument = graphqlDocument(`
|
|
4
|
+
query userContributionYears($login: String!) {
|
|
5
|
+
user(login: $login) {
|
|
6
|
+
login
|
|
7
|
+
contributionsCollection {
|
|
8
|
+
contributionYears
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
}`);
|