@stats-organization/github-readme-stats-core 2.1.4 → 2.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/gist.d.ts +2 -7
- package/build/api/gist.d.ts.map +1 -1
- package/build/api/gist.js +28 -26
- package/build/api/index.d.ts +3 -8
- package/build/api/index.d.ts.map +1 -1
- package/build/api/index.js +32 -36
- package/build/api/pin.d.ts +2 -7
- package/build/api/pin.d.ts.map +1 -1
- package/build/api/pin.js +18 -33
- package/build/api/top-langs.d.ts +2 -7
- package/build/api/top-langs.d.ts.map +1 -1
- package/build/api/top-langs.js +32 -44
- package/build/api/wakatime.d.ts +2 -7
- package/build/api/wakatime.d.ts.map +1 -1
- package/build/api/wakatime.js +28 -26
- package/build/calculateRank.d.ts +12 -11
- package/build/calculateRank.d.ts.map +1 -1
- package/build/calculateRank.js +18 -18
- package/build/cards/common-options.d.ts +13 -0
- package/build/cards/common-options.d.ts.map +1 -0
- package/build/cards/common-options.js +1 -0
- package/build/cards/gist.d.ts +11 -16
- package/build/cards/gist.d.ts.map +1 -1
- package/build/cards/gist.js +26 -31
- package/build/cards/repo.d.ts +19 -16
- package/build/cards/repo.d.ts.map +1 -1
- package/build/cards/repo.js +79 -72
- package/build/cards/stats.d.ts +29 -44
- package/build/cards/stats.d.ts.map +1 -1
- package/build/cards/stats.js +118 -173
- package/build/cards/top-languages.d.ts +81 -77
- package/build/cards/top-languages.d.ts.map +1 -1
- package/build/cards/top-languages.js +186 -184
- package/build/cards/wakatime.d.ts +21 -11
- package/build/cards/wakatime.d.ts.map +1 -1
- package/build/cards/wakatime.js +92 -109
- package/build/common/Card.d.ts +41 -14
- package/build/common/Card.d.ts.map +1 -1
- package/build/common/Card.js +92 -31
- package/build/common/I18n.d.ts +4 -2
- package/build/common/I18n.d.ts.map +1 -1
- package/build/common/color.d.ts +91 -15
- package/build/common/color.d.ts.map +1 -1
- package/build/common/color.js +163 -17
- package/build/common/config.d.ts +2 -1
- package/build/common/config.d.ts.map +1 -1
- package/build/common/config.js +16 -26
- package/build/common/constants.d.ts +2 -1
- package/build/common/constants.d.ts.map +1 -1
- package/build/common/date.d.ts +37 -0
- package/build/common/date.d.ts.map +1 -0
- package/build/common/date.js +31 -0
- package/build/common/html.js +1 -1
- package/build/common/http.d.ts +37 -5
- package/build/common/http.d.ts.map +1 -1
- package/build/common/http.js +29 -6
- package/build/common/icons.d.ts +1 -0
- package/build/common/icons.d.ts.map +1 -1
- package/build/common/icons.js +1 -0
- package/build/common/languageColors.d.ts +11 -0
- package/build/common/languageColors.d.ts.map +1 -0
- package/build/common/languageColors.js +15 -0
- package/build/common/languageColors.json +10 -1
- package/build/common/ops.d.ts +27 -33
- package/build/common/ops.d.ts.map +1 -1
- package/build/common/ops.js +31 -48
- package/build/common/render.d.ts +154 -113
- package/build/common/render.d.ts.map +1 -1
- package/build/common/render.js +209 -92
- package/build/common/retryer.d.ts +15 -6
- package/build/common/retryer.d.ts.map +1 -1
- package/build/common/retryer.js +2 -0
- package/build/fetchers/gist.d.ts +6 -21
- package/build/fetchers/gist.d.ts.map +1 -1
- package/build/fetchers/gist.js +28 -64
- package/build/fetchers/repo.d.ts +12 -11
- package/build/fetchers/repo.d.ts.map +1 -1
- package/build/fetchers/repo.js +27 -68
- package/build/fetchers/stats.d.ts +24 -17
- package/build/fetchers/stats.d.ts.map +1 -1
- package/build/fetchers/stats.js +302 -184
- package/build/fetchers/top-languages.d.ts +10 -15
- package/build/fetchers/top-languages.d.ts.map +1 -1
- package/build/fetchers/top-languages.js +52 -103
- package/build/fetchers/types.d.ts +130 -0
- package/build/fetchers/types.d.ts.map +1 -0
- package/build/fetchers/types.js +1 -0
- package/build/fetchers/wakatime.d.ts +9 -5
- package/build/fetchers/wakatime.d.ts.map +1 -1
- package/build/fetchers/wakatime.js +8 -4
- package/build/graphql/contributionsDocument.d.ts +18 -0
- package/build/graphql/contributionsDocument.d.ts.map +1 -0
- package/build/graphql/contributionsDocument.js +30 -0
- package/build/graphql/generated/common.d.ts +42 -0
- package/build/graphql/generated/common.d.ts.map +1 -0
- package/build/graphql/generated/common.js +2 -0
- package/build/graphql/generated/gist.d.ts +44 -0
- package/build/graphql/generated/gist.d.ts.map +1 -0
- package/build/graphql/generated/gist.js +27 -0
- package/build/graphql/generated/repo.d.ts +67 -0
- package/build/graphql/generated/repo.d.ts.map +1 -0
- package/build/graphql/generated/repo.js +30 -0
- package/build/graphql/generated/stats.d.ts +150 -0
- package/build/graphql/generated/stats.d.ts.map +1 -0
- package/build/graphql/generated/stats.js +95 -0
- package/build/graphql/generated/top-languages.d.ts +54 -0
- package/build/graphql/generated/top-languages.d.ts.map +1 -0
- package/build/graphql/generated/top-languages.js +27 -0
- package/build/graphql/graphqlDocument.d.ts +22 -0
- package/build/graphql/graphqlDocument.d.ts.map +1 -0
- package/build/graphql/graphqlDocument.js +8 -0
- package/build/graphql/reposContributedToDocument.d.ts +27 -0
- package/build/graphql/reposContributedToDocument.d.ts.map +1 -0
- package/build/graphql/reposContributedToDocument.js +59 -0
- package/build/index.d.ts +3 -1
- package/build/index.d.ts.map +1 -1
- package/build/index.js +2 -1
- package/build/themes/index.d.ts +32 -0
- package/build/themes/index.d.ts.map +1 -1
- package/build/themes/index.js +28 -0
- package/build/translations.d.ts +17 -0
- package/build/translations.d.ts.map +1 -1
- package/build/translations.js +17 -0
- package/package.json +14 -8
- package/src/_emoji-name-map.d.ts +10 -0
- package/src/_github-username-regex.d.ts +4 -0
- package/src/api/gist.js +31 -31
- package/src/api/index.js +40 -42
- package/src/api/pin.js +20 -38
- package/src/api/top-langs.js +36 -49
- package/src/api/wakatime.js +31 -31
- package/src/{calculateRank.js → calculateRank.ts} +29 -19
- package/src/cards/common-options.ts +13 -0
- package/src/cards/{gist.js → gist.ts} +37 -37
- package/src/cards/{repo.js → repo.ts} +107 -79
- package/src/cards/{stats.js → stats.ts} +173 -210
- package/src/cards/{top-languages.js → top-languages.ts} +325 -243
- package/src/cards/{wakatime.js → wakatime.ts} +158 -127
- package/src/common/Card.ts +123 -43
- package/src/common/I18n.ts +6 -2
- package/src/common/color.ts +240 -28
- package/src/common/config.ts +22 -29
- package/src/common/constants.ts +3 -1
- package/src/common/date.ts +45 -0
- package/src/common/html.ts +1 -1
- package/src/common/http.ts +71 -0
- package/src/common/icons.ts +1 -0
- package/src/common/languageColors.json +10 -1
- package/src/common/languageColors.ts +19 -0
- package/src/common/ops.ts +158 -0
- package/src/common/render.ts +652 -0
- package/src/common/retryer.ts +21 -12
- package/src/fetchers/gist.ts +79 -0
- package/src/fetchers/repo.ts +114 -0
- package/src/fetchers/stats.ts +743 -0
- package/src/fetchers/top-languages.ts +135 -0
- package/src/fetchers/{types.d.ts → types.ts} +28 -20
- package/src/fetchers/{wakatime.js → wakatime.ts} +19 -5
- package/src/graphql/contributionsDocument.ts +44 -0
- package/src/graphql/generated/common.ts +26 -0
- package/src/graphql/generated/gist.ts +59 -0
- package/src/graphql/generated/repo.ts +90 -0
- package/src/graphql/generated/stats.ts +194 -0
- package/src/graphql/generated/top-languages.ts +75 -0
- package/src/graphql/graphqlDocument.ts +25 -0
- package/src/graphql/queries/gist.graphql +25 -0
- package/src/graphql/queries/repo.graphql +28 -0
- package/src/graphql/queries/stats.graphql +120 -0
- package/src/graphql/queries/top-languages.graphql +34 -0
- package/src/graphql/reposContributedToDocument.ts +86 -0
- package/src/index.ts +3 -1
- package/src/themes/index.ts +33 -0
- package/src/translations.ts +17 -0
- package/src/cards/types.d.ts +0 -78
- package/src/common/http.js +0 -19
- package/src/common/ops.js +0 -170
- package/src/common/render.js +0 -440
- package/src/fetchers/gist.js +0 -112
- package/src/fetchers/repo.js +0 -161
- package/src/fetchers/stats.js +0 -474
- package/src/fetchers/top-languages.js +0 -175
- package/src/themes/README.md +0 -229
package/src/fetchers/stats.js
DELETED
|
@@ -1,474 +0,0 @@
|
|
|
1
|
-
import axios from "axios";
|
|
2
|
-
import githubUsernameRegex from "github-username-regex";
|
|
3
|
-
|
|
4
|
-
import { calculateRank } from "../calculateRank.js";
|
|
5
|
-
import { getConfig } from "../common/config.js";
|
|
6
|
-
import { CustomError, MissingParamError } from "../common/error.js";
|
|
7
|
-
import { wrapTextMultiline } from "../common/fmt.js";
|
|
8
|
-
import { request } from "../common/http.js";
|
|
9
|
-
import { logger } from "../common/log.js";
|
|
10
|
-
import { buildSearchFilter, parseOwnerAffiliations } from "../common/ops.js";
|
|
11
|
-
import { retryer } from "../common/retryer.js";
|
|
12
|
-
|
|
13
|
-
// GraphQL queries.
|
|
14
|
-
const GRAPHQL_REPOS_FIELD = `
|
|
15
|
-
repositories(first: 100, after: $after, ownerAffiliations: $ownerAffiliations, orderBy: {direction: DESC, field: STARGAZERS}) {
|
|
16
|
-
totalCount
|
|
17
|
-
nodes {
|
|
18
|
-
name
|
|
19
|
-
stargazerCount
|
|
20
|
-
}
|
|
21
|
-
pageInfo {
|
|
22
|
-
hasNextPage
|
|
23
|
-
endCursor
|
|
24
|
-
}
|
|
25
|
-
}
|
|
26
|
-
`;
|
|
27
|
-
|
|
28
|
-
const GRAPHQL_REPOS_QUERY = `
|
|
29
|
-
query userInfo($login: String!, $after: String, $ownerAffiliations: [RepositoryAffiliation]) {
|
|
30
|
-
user(login: $login) {
|
|
31
|
-
${GRAPHQL_REPOS_FIELD}
|
|
32
|
-
}
|
|
33
|
-
}
|
|
34
|
-
`;
|
|
35
|
-
|
|
36
|
-
const GRAPHQL_STATS_QUERY = `
|
|
37
|
-
query userInfo($login: String!, $after: String, $includeMergedPullRequests: Boolean!, $includeDiscussions: Boolean!, $includeDiscussionsAnswers: Boolean!, $startTime: DateTime = null, $ownerAffiliations: [RepositoryAffiliation]) {
|
|
38
|
-
user(login: $login) {
|
|
39
|
-
name
|
|
40
|
-
login
|
|
41
|
-
commits: contributionsCollection (from: $startTime) {
|
|
42
|
-
totalCommitContributions,
|
|
43
|
-
}
|
|
44
|
-
reviews: contributionsCollection {
|
|
45
|
-
totalPullRequestReviewContributions
|
|
46
|
-
}
|
|
47
|
-
repositoriesContributedTo(first: 1, contributionTypes: [COMMIT, ISSUE, PULL_REQUEST, REPOSITORY]) {
|
|
48
|
-
totalCount
|
|
49
|
-
}
|
|
50
|
-
pullRequests(first: 1) {
|
|
51
|
-
totalCount
|
|
52
|
-
}
|
|
53
|
-
mergedPullRequests: pullRequests(states: MERGED) @include(if: $includeMergedPullRequests) {
|
|
54
|
-
totalCount
|
|
55
|
-
}
|
|
56
|
-
openIssues: issues(states: OPEN) {
|
|
57
|
-
totalCount
|
|
58
|
-
}
|
|
59
|
-
closedIssues: issues(states: CLOSED) {
|
|
60
|
-
totalCount
|
|
61
|
-
}
|
|
62
|
-
followers {
|
|
63
|
-
totalCount
|
|
64
|
-
}
|
|
65
|
-
repositoryDiscussions @include(if: $includeDiscussions) {
|
|
66
|
-
totalCount
|
|
67
|
-
}
|
|
68
|
-
repositoryDiscussionComments(onlyAnswers: true) @include(if: $includeDiscussionsAnswers) {
|
|
69
|
-
totalCount
|
|
70
|
-
}
|
|
71
|
-
${GRAPHQL_REPOS_FIELD}
|
|
72
|
-
}
|
|
73
|
-
}
|
|
74
|
-
`;
|
|
75
|
-
|
|
76
|
-
/**
|
|
77
|
-
* Stats fetcher object.
|
|
78
|
-
*
|
|
79
|
-
* @param {object & { after: string | null }} variables Fetcher variables.
|
|
80
|
-
* @param {string} token GitHub token.
|
|
81
|
-
* @returns {Promise<import('axios').AxiosResponse>} Axios response.
|
|
82
|
-
*/
|
|
83
|
-
const fetcher = (variables, token) => {
|
|
84
|
-
const query = variables.after ? GRAPHQL_REPOS_QUERY : GRAPHQL_STATS_QUERY;
|
|
85
|
-
return request(
|
|
86
|
-
{
|
|
87
|
-
query,
|
|
88
|
-
variables,
|
|
89
|
-
},
|
|
90
|
-
{
|
|
91
|
-
Authorization: `bearer ${token}`,
|
|
92
|
-
},
|
|
93
|
-
);
|
|
94
|
-
};
|
|
95
|
-
|
|
96
|
-
/**
|
|
97
|
-
* Fetch stats information for a given username.
|
|
98
|
-
*
|
|
99
|
-
* @param {object} variables Fetcher variables.
|
|
100
|
-
* @param {string} variables.username GitHub username.
|
|
101
|
-
* @param {boolean} variables.includeMergedPullRequests Include merged pull requests.
|
|
102
|
-
* @param {boolean} variables.includeDiscussions Include discussions.
|
|
103
|
-
* @param {boolean} variables.includeDiscussionsAnswers Include discussions answers.
|
|
104
|
-
* @param {string|undefined} variables.startTime Time to start the count of total commits.
|
|
105
|
-
* @param {string[]} variables.ownerAffiliations The owner affiliations to filter by. Default: OWNER.
|
|
106
|
-
* @param {string | null} variables.pat PAT override or null.
|
|
107
|
-
* @returns {Promise<import('axios').AxiosResponse>} Axios response.
|
|
108
|
-
*
|
|
109
|
-
* @description This function supports multi-page fetching if the 'FETCH_MULTI_PAGE_STARS' environment variable is set to true or a limit of fetches.
|
|
110
|
-
*/
|
|
111
|
-
const statsFetcher = async ({
|
|
112
|
-
username,
|
|
113
|
-
includeMergedPullRequests,
|
|
114
|
-
includeDiscussions,
|
|
115
|
-
includeDiscussionsAnswers,
|
|
116
|
-
startTime,
|
|
117
|
-
ownerAffiliations,
|
|
118
|
-
pat,
|
|
119
|
-
}) => {
|
|
120
|
-
let stats;
|
|
121
|
-
let hasNextPage = true;
|
|
122
|
-
let endCursor = null;
|
|
123
|
-
let fetchedPages = 0;
|
|
124
|
-
while (hasNextPage) {
|
|
125
|
-
const variables = {
|
|
126
|
-
login: username,
|
|
127
|
-
first: 100,
|
|
128
|
-
after: endCursor,
|
|
129
|
-
includeMergedPullRequests,
|
|
130
|
-
includeDiscussions,
|
|
131
|
-
includeDiscussionsAnswers,
|
|
132
|
-
startTime,
|
|
133
|
-
ownerAffiliations,
|
|
134
|
-
};
|
|
135
|
-
let res = await retryer(fetcher, variables, pat);
|
|
136
|
-
if (res.data.errors) {
|
|
137
|
-
return res;
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
// Store stats data.
|
|
141
|
-
const repoNodes = res.data.data.user.repositories.nodes;
|
|
142
|
-
if (stats) {
|
|
143
|
-
if (fetchedPages === 1) {
|
|
144
|
-
// make deep copy of relevant stats fields to avoid altering the cached response object in frontend
|
|
145
|
-
stats = structuredClone({
|
|
146
|
-
data: stats.data,
|
|
147
|
-
statusText: stats.statusText,
|
|
148
|
-
});
|
|
149
|
-
}
|
|
150
|
-
stats.data.data.user.repositories.nodes.push(...repoNodes);
|
|
151
|
-
} else {
|
|
152
|
-
stats = res;
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
// Disable multi page fetching on public Vercel instance due to rate limits.
|
|
156
|
-
fetchedPages++;
|
|
157
|
-
const repoNodesWithStars = repoNodes.filter(
|
|
158
|
-
(node) => node.stargazerCount !== 0,
|
|
159
|
-
);
|
|
160
|
-
|
|
161
|
-
hasNextPage =
|
|
162
|
-
(getConfig().fetchMultiPageStars === "true" ||
|
|
163
|
-
getConfig().fetchMultiPageStars > fetchedPages) &&
|
|
164
|
-
repoNodes.length === repoNodesWithStars.length &&
|
|
165
|
-
res.data.data.user.repositories.pageInfo.hasNextPage;
|
|
166
|
-
|
|
167
|
-
endCursor = res.data.data.user.repositories.pageInfo.endCursor;
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
return stats;
|
|
171
|
-
};
|
|
172
|
-
|
|
173
|
-
/**
|
|
174
|
-
* Fetch total commits using the REST API.
|
|
175
|
-
*
|
|
176
|
-
* @param {object} variables Fetcher variables.
|
|
177
|
-
* @param {string} token GitHub token.
|
|
178
|
-
* @returns {Promise<import('axios').AxiosResponse>} Axios response.
|
|
179
|
-
*
|
|
180
|
-
* @see https://developer.github.com/v3/search/#search-commits
|
|
181
|
-
*/
|
|
182
|
-
const fetchTotalItems = (variables, token) => {
|
|
183
|
-
return axios({
|
|
184
|
-
method: "get",
|
|
185
|
-
url:
|
|
186
|
-
`https://api.github.com/search/` +
|
|
187
|
-
variables.type +
|
|
188
|
-
`?per_page=1&q=` +
|
|
189
|
-
buildSearchFilter(variables.repo, variables.owner).replaceAll(" ", "+") +
|
|
190
|
-
variables.filter,
|
|
191
|
-
headers: {
|
|
192
|
-
"Content-Type": "application/json",
|
|
193
|
-
Accept: "application/vnd.github.cloak-preview",
|
|
194
|
-
Authorization: `token ${token}`,
|
|
195
|
-
},
|
|
196
|
-
});
|
|
197
|
-
};
|
|
198
|
-
|
|
199
|
-
/**
|
|
200
|
-
* Fetch all the commits for all the repositories of a given username.
|
|
201
|
-
*
|
|
202
|
-
* @param {string} username GitHub username.
|
|
203
|
-
* @returns {Promise<number>} Total commits.
|
|
204
|
-
*
|
|
205
|
-
* @description Done like this because the GitHub API does not provide a way to fetch all the commits. See
|
|
206
|
-
* #92#issuecomment-661026467 and #211 for more information.
|
|
207
|
-
*/
|
|
208
|
-
const totalItemsFetcher = async (username, repo, owner, type, filter, pat) => {
|
|
209
|
-
if (!githubUsernameRegex.test(username)) {
|
|
210
|
-
logger.log("Invalid username provided.");
|
|
211
|
-
throw new Error("Invalid username provided.");
|
|
212
|
-
}
|
|
213
|
-
|
|
214
|
-
let res;
|
|
215
|
-
try {
|
|
216
|
-
res = await retryer(
|
|
217
|
-
fetchTotalItems,
|
|
218
|
-
{
|
|
219
|
-
login: username,
|
|
220
|
-
repo,
|
|
221
|
-
owner,
|
|
222
|
-
type,
|
|
223
|
-
filter,
|
|
224
|
-
},
|
|
225
|
-
pat,
|
|
226
|
-
);
|
|
227
|
-
} catch (err) {
|
|
228
|
-
logger.log(err);
|
|
229
|
-
throw err;
|
|
230
|
-
}
|
|
231
|
-
|
|
232
|
-
const totalCount = res.data.total_count;
|
|
233
|
-
if (isNaN(totalCount)) {
|
|
234
|
-
logger.error("GitHub error: " + JSON.stringify(res.data));
|
|
235
|
-
throw new CustomError(
|
|
236
|
-
"Could not fetch data from GitHub REST API.",
|
|
237
|
-
CustomError.GITHUB_REST_API_ERROR,
|
|
238
|
-
);
|
|
239
|
-
}
|
|
240
|
-
return totalCount;
|
|
241
|
-
};
|
|
242
|
-
|
|
243
|
-
const fetchRepoUserStats = async (
|
|
244
|
-
username,
|
|
245
|
-
repo,
|
|
246
|
-
owner,
|
|
247
|
-
include_prs_authored,
|
|
248
|
-
include_prs_commented,
|
|
249
|
-
include_prs_reviewed,
|
|
250
|
-
include_issues_authored,
|
|
251
|
-
include_issues_commented,
|
|
252
|
-
pat,
|
|
253
|
-
) => {
|
|
254
|
-
let stats = {};
|
|
255
|
-
if (include_prs_authored) {
|
|
256
|
-
stats.totalPRsAuthored = await totalItemsFetcher(
|
|
257
|
-
username,
|
|
258
|
-
repo,
|
|
259
|
-
owner,
|
|
260
|
-
"issues",
|
|
261
|
-
`author:${username}+type:pr`,
|
|
262
|
-
pat,
|
|
263
|
-
);
|
|
264
|
-
}
|
|
265
|
-
if (include_prs_commented) {
|
|
266
|
-
stats.totalPRsCommented = await totalItemsFetcher(
|
|
267
|
-
username,
|
|
268
|
-
repo,
|
|
269
|
-
owner,
|
|
270
|
-
"issues",
|
|
271
|
-
`commenter:${username}+-author:${username}+type:pr`,
|
|
272
|
-
pat,
|
|
273
|
-
);
|
|
274
|
-
}
|
|
275
|
-
if (include_prs_reviewed) {
|
|
276
|
-
stats.totalPRsReviewed = await totalItemsFetcher(
|
|
277
|
-
username,
|
|
278
|
-
repo,
|
|
279
|
-
owner,
|
|
280
|
-
"issues",
|
|
281
|
-
`reviewed-by:${username}+-author:${username}+type:pr`,
|
|
282
|
-
pat,
|
|
283
|
-
);
|
|
284
|
-
}
|
|
285
|
-
if (include_issues_authored) {
|
|
286
|
-
stats.totalIssuesAuthored = await totalItemsFetcher(
|
|
287
|
-
username,
|
|
288
|
-
repo,
|
|
289
|
-
owner,
|
|
290
|
-
"issues",
|
|
291
|
-
`author:${username}+type:issue`,
|
|
292
|
-
pat,
|
|
293
|
-
);
|
|
294
|
-
}
|
|
295
|
-
if (include_issues_commented) {
|
|
296
|
-
stats.totalIssuesCommented = await totalItemsFetcher(
|
|
297
|
-
username,
|
|
298
|
-
repo,
|
|
299
|
-
owner,
|
|
300
|
-
"issues",
|
|
301
|
-
`commenter:${username}+-author:${username}+type:issue`,
|
|
302
|
-
pat,
|
|
303
|
-
);
|
|
304
|
-
}
|
|
305
|
-
return stats;
|
|
306
|
-
};
|
|
307
|
-
|
|
308
|
-
/**
|
|
309
|
-
* Fetch stats for a given username.
|
|
310
|
-
*
|
|
311
|
-
* @param {string} username GitHub username.
|
|
312
|
-
* @param {boolean} include_all_commits Include all commits.
|
|
313
|
-
* @param {string[]} exclude_repo Repositories to exclude.
|
|
314
|
-
* @param {boolean} include_merged_pull_requests Include merged pull requests.
|
|
315
|
-
* @param {boolean} include_discussions Include discussions.
|
|
316
|
-
* @param {boolean} include_discussions_answers Include discussions answers.
|
|
317
|
-
* @param {number|undefined} commits_year Year to count total commits
|
|
318
|
-
* @param {string[]} ownerAffiliations Owner affiliations. Default: OWNER.
|
|
319
|
-
* @returns {Promise<import("./types").StatsData>} Stats data.
|
|
320
|
-
*/
|
|
321
|
-
const fetchStats = async (
|
|
322
|
-
username,
|
|
323
|
-
include_all_commits = false,
|
|
324
|
-
exclude_repo = [],
|
|
325
|
-
include_merged_pull_requests = false,
|
|
326
|
-
include_discussions = false,
|
|
327
|
-
include_discussions_answers = false,
|
|
328
|
-
commits_year,
|
|
329
|
-
repo = [],
|
|
330
|
-
owner = [],
|
|
331
|
-
include_prs_authored = false,
|
|
332
|
-
include_prs_commented = false,
|
|
333
|
-
include_prs_reviewed = false,
|
|
334
|
-
include_issues_authored = false,
|
|
335
|
-
include_issues_commented = false,
|
|
336
|
-
ownerAffiliations = [],
|
|
337
|
-
pat = null,
|
|
338
|
-
) => {
|
|
339
|
-
if (!username) {
|
|
340
|
-
throw new MissingParamError(["username"]);
|
|
341
|
-
}
|
|
342
|
-
|
|
343
|
-
const stats = {
|
|
344
|
-
name: "",
|
|
345
|
-
totalPRs: 0,
|
|
346
|
-
totalPRsMerged: 0,
|
|
347
|
-
mergedPRsPercentage: 0,
|
|
348
|
-
totalReviews: 0,
|
|
349
|
-
totalCommits: 0,
|
|
350
|
-
totalIssues: 0,
|
|
351
|
-
totalStars: 0,
|
|
352
|
-
totalDiscussionsStarted: 0,
|
|
353
|
-
totalDiscussionsAnswered: 0,
|
|
354
|
-
contributedTo: 0,
|
|
355
|
-
totalPRsAuthored: 0,
|
|
356
|
-
totalPRsCommented: 0,
|
|
357
|
-
totalPRsReviewed: 0,
|
|
358
|
-
totalIssuesAuthored: 0,
|
|
359
|
-
totalIssuesCommented: 0,
|
|
360
|
-
rank: { level: "C", percentile: 100 },
|
|
361
|
-
};
|
|
362
|
-
ownerAffiliations = parseOwnerAffiliations(ownerAffiliations);
|
|
363
|
-
|
|
364
|
-
let res = await statsFetcher(
|
|
365
|
-
{
|
|
366
|
-
username,
|
|
367
|
-
includeMergedPullRequests: include_merged_pull_requests,
|
|
368
|
-
includeDiscussions: include_discussions,
|
|
369
|
-
includeDiscussionsAnswers: include_discussions_answers,
|
|
370
|
-
startTime: commits_year ? `${commits_year}-01-01T00:00:00Z` : undefined,
|
|
371
|
-
ownerAffiliations,
|
|
372
|
-
},
|
|
373
|
-
pat,
|
|
374
|
-
);
|
|
375
|
-
|
|
376
|
-
// Catch GraphQL errors.
|
|
377
|
-
if (res.data.errors) {
|
|
378
|
-
logger.error(res.data.errors);
|
|
379
|
-
if (res.data.errors[0].type === "NOT_FOUND") {
|
|
380
|
-
throw new CustomError(
|
|
381
|
-
res.data.errors[0].message || "Could not fetch user.",
|
|
382
|
-
CustomError.USER_NOT_FOUND,
|
|
383
|
-
);
|
|
384
|
-
}
|
|
385
|
-
if (res.data.errors[0].message) {
|
|
386
|
-
throw new CustomError(
|
|
387
|
-
wrapTextMultiline(res.data.errors[0].message, 525, 12)[0],
|
|
388
|
-
res.statusText,
|
|
389
|
-
);
|
|
390
|
-
}
|
|
391
|
-
throw new CustomError(
|
|
392
|
-
"Something went wrong while trying to retrieve the stats data using the GraphQL API.",
|
|
393
|
-
CustomError.GRAPHQL_ERROR,
|
|
394
|
-
);
|
|
395
|
-
}
|
|
396
|
-
|
|
397
|
-
const user = res.data.data.user;
|
|
398
|
-
|
|
399
|
-
stats.name = user.name || user.login;
|
|
400
|
-
|
|
401
|
-
// if include_all_commits, fetch all commits using the REST API.
|
|
402
|
-
if (include_all_commits) {
|
|
403
|
-
stats.totalCommits = await totalItemsFetcher(
|
|
404
|
-
username,
|
|
405
|
-
repo,
|
|
406
|
-
owner,
|
|
407
|
-
"commits",
|
|
408
|
-
`author:${username}`,
|
|
409
|
-
pat,
|
|
410
|
-
);
|
|
411
|
-
} else {
|
|
412
|
-
stats.totalCommits = user.commits.totalCommitContributions;
|
|
413
|
-
}
|
|
414
|
-
let repoUserStats = await fetchRepoUserStats(
|
|
415
|
-
username,
|
|
416
|
-
repo,
|
|
417
|
-
owner,
|
|
418
|
-
include_prs_authored,
|
|
419
|
-
include_prs_commented,
|
|
420
|
-
include_prs_reviewed,
|
|
421
|
-
include_issues_authored,
|
|
422
|
-
include_issues_commented,
|
|
423
|
-
pat,
|
|
424
|
-
);
|
|
425
|
-
Object.assign(stats, repoUserStats);
|
|
426
|
-
|
|
427
|
-
stats.totalPRs = user.pullRequests.totalCount;
|
|
428
|
-
if (include_merged_pull_requests) {
|
|
429
|
-
stats.totalPRsMerged = user.mergedPullRequests.totalCount;
|
|
430
|
-
stats.mergedPRsPercentage =
|
|
431
|
-
(user.mergedPullRequests.totalCount / user.pullRequests.totalCount) *
|
|
432
|
-
100 || 0;
|
|
433
|
-
}
|
|
434
|
-
stats.totalReviews = user.reviews.totalPullRequestReviewContributions;
|
|
435
|
-
stats.totalIssues = user.openIssues.totalCount + user.closedIssues.totalCount;
|
|
436
|
-
if (include_discussions) {
|
|
437
|
-
stats.totalDiscussionsStarted = user.repositoryDiscussions.totalCount;
|
|
438
|
-
}
|
|
439
|
-
if (include_discussions_answers) {
|
|
440
|
-
stats.totalDiscussionsAnswered =
|
|
441
|
-
user.repositoryDiscussionComments.totalCount;
|
|
442
|
-
}
|
|
443
|
-
stats.contributedTo = user.repositoriesContributedTo.totalCount;
|
|
444
|
-
|
|
445
|
-
// Retrieve stars while filtering out repositories to be hidden.
|
|
446
|
-
const allExcludedRepos = [
|
|
447
|
-
...exclude_repo,
|
|
448
|
-
...getConfig().excludeRepositories,
|
|
449
|
-
];
|
|
450
|
-
let repoToHide = new Set(allExcludedRepos);
|
|
451
|
-
|
|
452
|
-
stats.totalStars = user.repositories.nodes
|
|
453
|
-
.filter((data) => {
|
|
454
|
-
return !repoToHide.has(data.name);
|
|
455
|
-
})
|
|
456
|
-
.reduce((prev, curr) => {
|
|
457
|
-
return prev + curr.stargazerCount;
|
|
458
|
-
}, 0);
|
|
459
|
-
|
|
460
|
-
stats.rank = calculateRank({
|
|
461
|
-
all_commits: include_all_commits,
|
|
462
|
-
commits: stats.totalCommits,
|
|
463
|
-
prs: stats.totalPRs,
|
|
464
|
-
reviews: stats.totalReviews,
|
|
465
|
-
issues: stats.totalIssues,
|
|
466
|
-
repos: user.repositories.totalCount,
|
|
467
|
-
stars: stats.totalStars,
|
|
468
|
-
followers: user.followers.totalCount,
|
|
469
|
-
});
|
|
470
|
-
|
|
471
|
-
return stats;
|
|
472
|
-
};
|
|
473
|
-
|
|
474
|
-
export { fetchStats, fetchRepoUserStats };
|
|
@@ -1,175 +0,0 @@
|
|
|
1
|
-
import { getConfig } from "../common/config.js";
|
|
2
|
-
import { CustomError, MissingParamError } from "../common/error.js";
|
|
3
|
-
import { wrapTextMultiline } from "../common/fmt.js";
|
|
4
|
-
import { request } from "../common/http.js";
|
|
5
|
-
import { logger } from "../common/log.js";
|
|
6
|
-
import { parseOwnerAffiliations } from "../common/ops.js";
|
|
7
|
-
import { retryer } from "../common/retryer.js";
|
|
8
|
-
|
|
9
|
-
/**
|
|
10
|
-
* Top languages fetcher object.
|
|
11
|
-
*
|
|
12
|
-
* @param {any} variables Fetcher variables.
|
|
13
|
-
* @param {string} token GitHub token.
|
|
14
|
-
* @returns {Promise<import("axios").AxiosResponse>} Languages fetcher response.
|
|
15
|
-
*/
|
|
16
|
-
const fetcher = (variables, token) => {
|
|
17
|
-
return request(
|
|
18
|
-
{
|
|
19
|
-
query: `
|
|
20
|
-
query userInfo($login: String!, $ownerAffiliations: [RepositoryAffiliation]) {
|
|
21
|
-
user(login: $login) {
|
|
22
|
-
# do not fetch forks
|
|
23
|
-
repositories(ownerAffiliations: $ownerAffiliations, isFork: false, first: 100) {
|
|
24
|
-
nodes {
|
|
25
|
-
name
|
|
26
|
-
languages(first: 10, orderBy: {field: SIZE, direction: DESC}) {
|
|
27
|
-
edges {
|
|
28
|
-
size
|
|
29
|
-
node {
|
|
30
|
-
color
|
|
31
|
-
name
|
|
32
|
-
}
|
|
33
|
-
}
|
|
34
|
-
}
|
|
35
|
-
}
|
|
36
|
-
}
|
|
37
|
-
}
|
|
38
|
-
}
|
|
39
|
-
`,
|
|
40
|
-
variables,
|
|
41
|
-
},
|
|
42
|
-
{
|
|
43
|
-
Authorization: `token ${token}`,
|
|
44
|
-
},
|
|
45
|
-
);
|
|
46
|
-
};
|
|
47
|
-
|
|
48
|
-
/**
|
|
49
|
-
* @typedef {import("./types").TopLangData} TopLangData Top languages data.
|
|
50
|
-
*/
|
|
51
|
-
|
|
52
|
-
/**
|
|
53
|
-
* Fetch top languages for a given username.
|
|
54
|
-
*
|
|
55
|
-
* @param {string} username GitHub username.
|
|
56
|
-
* @param {string[]} exclude_repo List of repositories to exclude. Default: [].
|
|
57
|
-
* @param {number} size_weight Weightage to be given to size.
|
|
58
|
-
* @param {number} count_weight Weightage to be given to count.
|
|
59
|
-
* @param {string[]} ownerAffiliations The owner affiliations to filter by. Default: OWNER.
|
|
60
|
-
* @param {string|null} pat Optional PAT override.
|
|
61
|
-
* @returns {Promise<TopLangData>} Top languages data.
|
|
62
|
-
*/
|
|
63
|
-
const fetchTopLanguages = async (
|
|
64
|
-
username,
|
|
65
|
-
exclude_repo = [],
|
|
66
|
-
size_weight = 1,
|
|
67
|
-
count_weight = 0,
|
|
68
|
-
ownerAffiliations = [],
|
|
69
|
-
pat = null,
|
|
70
|
-
) => {
|
|
71
|
-
if (!username) {
|
|
72
|
-
throw new MissingParamError(["username"]);
|
|
73
|
-
}
|
|
74
|
-
ownerAffiliations = parseOwnerAffiliations(ownerAffiliations);
|
|
75
|
-
|
|
76
|
-
const res = await retryer(
|
|
77
|
-
fetcher,
|
|
78
|
-
{
|
|
79
|
-
login: username,
|
|
80
|
-
ownerAffiliations,
|
|
81
|
-
},
|
|
82
|
-
pat,
|
|
83
|
-
);
|
|
84
|
-
|
|
85
|
-
if (res.data.errors) {
|
|
86
|
-
logger.error(res.data.errors);
|
|
87
|
-
if (res.data.errors[0].type === "NOT_FOUND") {
|
|
88
|
-
throw new CustomError(
|
|
89
|
-
res.data.errors[0].message || "Could not fetch user.",
|
|
90
|
-
CustomError.USER_NOT_FOUND,
|
|
91
|
-
);
|
|
92
|
-
}
|
|
93
|
-
if (res.data.errors[0].message) {
|
|
94
|
-
throw new CustomError(
|
|
95
|
-
wrapTextMultiline(res.data.errors[0].message, 525, 12)[0],
|
|
96
|
-
res.statusText,
|
|
97
|
-
);
|
|
98
|
-
}
|
|
99
|
-
throw new CustomError(
|
|
100
|
-
"Something went wrong while trying to retrieve the language data using the GraphQL API.",
|
|
101
|
-
CustomError.GRAPHQL_ERROR,
|
|
102
|
-
);
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
let repoNodes = res.data.data.user.repositories.nodes;
|
|
106
|
-
/** @type {Record<string, boolean>} */
|
|
107
|
-
let repoToHide = {};
|
|
108
|
-
const allExcludedRepos = [
|
|
109
|
-
...exclude_repo,
|
|
110
|
-
...getConfig().excludeRepositories,
|
|
111
|
-
];
|
|
112
|
-
|
|
113
|
-
// populate repoToHide map for quick lookup
|
|
114
|
-
// while filtering out
|
|
115
|
-
if (allExcludedRepos) {
|
|
116
|
-
allExcludedRepos.forEach((repoName) => {
|
|
117
|
-
repoToHide[repoName] = true;
|
|
118
|
-
});
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
// filter out repositories to be hidden
|
|
122
|
-
repoNodes = repoNodes
|
|
123
|
-
.sort((a, b) => b.size - a.size)
|
|
124
|
-
.filter((name) => !repoToHide[name.name]);
|
|
125
|
-
|
|
126
|
-
let repoCount = 0;
|
|
127
|
-
|
|
128
|
-
repoNodes = repoNodes
|
|
129
|
-
.filter((node) => node.languages.edges.length > 0)
|
|
130
|
-
// flatten the list of language nodes
|
|
131
|
-
.reduce((acc, curr) => curr.languages.edges.concat(acc), [])
|
|
132
|
-
.reduce((acc, prev) => {
|
|
133
|
-
// get the size of the language (bytes)
|
|
134
|
-
let langSize = prev.size;
|
|
135
|
-
|
|
136
|
-
// if we already have the language in the accumulator
|
|
137
|
-
// & the current language name is same as previous name
|
|
138
|
-
// add the size to the language size and increase repoCount.
|
|
139
|
-
if (acc[prev.node.name] && prev.node.name === acc[prev.node.name].name) {
|
|
140
|
-
langSize = prev.size + acc[prev.node.name].size;
|
|
141
|
-
repoCount += 1;
|
|
142
|
-
} else {
|
|
143
|
-
// reset repoCount to 1
|
|
144
|
-
// language must exist in at least one repo to be detected
|
|
145
|
-
repoCount = 1;
|
|
146
|
-
}
|
|
147
|
-
return {
|
|
148
|
-
...acc,
|
|
149
|
-
[prev.node.name]: {
|
|
150
|
-
name: prev.node.name,
|
|
151
|
-
color: prev.node.color,
|
|
152
|
-
size: langSize,
|
|
153
|
-
count: repoCount,
|
|
154
|
-
},
|
|
155
|
-
};
|
|
156
|
-
}, {});
|
|
157
|
-
|
|
158
|
-
Object.keys(repoNodes).forEach((name) => {
|
|
159
|
-
// comparison index calculation
|
|
160
|
-
repoNodes[name].size =
|
|
161
|
-
Math.pow(repoNodes[name].size, size_weight) *
|
|
162
|
-
Math.pow(repoNodes[name].count, count_weight);
|
|
163
|
-
});
|
|
164
|
-
|
|
165
|
-
const topLangs = Object.keys(repoNodes)
|
|
166
|
-
.sort((a, b) => repoNodes[b].size - repoNodes[a].size)
|
|
167
|
-
.reduce((result, key) => {
|
|
168
|
-
result[key] = repoNodes[key];
|
|
169
|
-
return result;
|
|
170
|
-
}, {});
|
|
171
|
-
|
|
172
|
-
return topLangs;
|
|
173
|
-
};
|
|
174
|
-
|
|
175
|
-
export { fetchTopLanguages };
|