@stats-forge/github-stats-forge-core 0.0.2 → 0.1.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/README.md +0 -10
- package/build/api/api-result.d.ts +6 -7
- package/build/api/api-result.d.ts.map +1 -1
- package/build/api/api-result.js +4 -5
- package/build/api/gist.d.ts +3 -10
- package/build/api/gist.d.ts.map +1 -1
- package/build/api/gist.js +4 -11
- package/build/api/index.d.ts +12 -12
- package/build/api/params.d.ts +11 -8
- package/build/api/params.d.ts.map +1 -1
- package/build/api/params.js +16 -14
- package/build/api/pin.d.ts +3 -19
- package/build/api/pin.d.ts.map +1 -1
- package/build/api/pin.js +6 -22
- package/build/api/stats.d.ts +10 -4
- package/build/api/stats.d.ts.map +1 -1
- package/build/api/stats.js +6 -32
- package/build/api/top-langs.d.ts +12 -6
- package/build/api/top-langs.d.ts.map +1 -1
- package/build/api/top-langs.js +6 -26
- package/build/api/wakatime.d.ts +13 -5
- package/build/api/wakatime.d.ts.map +1 -1
- package/build/api/wakatime.js +4 -21
- package/build/calculateRank.d.ts +0 -9
- package/build/calculateRank.d.ts.map +1 -1
- package/build/calculateRank.js +12 -17
- package/build/cards/gist.d.ts +2 -4
- package/build/cards/gist.d.ts.map +1 -1
- package/build/cards/gist.js +27 -36
- package/build/cards/options.d.ts +1 -1
- package/build/cards/repo.d.ts +2 -4
- package/build/cards/repo.d.ts.map +1 -1
- package/build/cards/repo.js +61 -93
- package/build/cards/stats.d.ts +3 -8
- package/build/cards/stats.d.ts.map +1 -1
- package/build/cards/stats.js +119 -165
- package/build/cards/top-languages.d.ts +5 -31
- package/build/cards/top-languages.d.ts.map +1 -1
- package/build/cards/top-languages.js +176 -384
- package/build/cards/wakatime.d.ts +4 -6
- package/build/cards/wakatime.d.ts.map +1 -1
- package/build/cards/wakatime.js +118 -185
- package/build/common/Card.d.ts +14 -62
- package/build/common/Card.d.ts.map +1 -1
- package/build/common/Card.js +97 -199
- package/build/common/I18n.d.ts +0 -6
- package/build/common/I18n.d.ts.map +1 -1
- package/build/common/I18n.js +3 -6
- package/build/common/color.d.ts +4 -17
- package/build/common/color.d.ts.map +1 -1
- package/build/common/color.js +67 -49
- package/build/common/config.d.ts +4 -14
- package/build/common/config.d.ts.map +1 -1
- package/build/common/config.js +3 -16
- package/build/common/constants.d.ts +4 -2
- package/build/common/constants.d.ts.map +1 -1
- package/build/common/constants.js +3 -1
- package/build/common/date.d.ts +0 -2
- package/build/common/date.d.ts.map +1 -1
- package/build/common/date.js +0 -2
- package/build/common/emojiMap.d.ts +8 -0
- package/build/common/emojiMap.d.ts.map +1 -0
- package/build/common/emojiMap.js +10 -0
- package/build/common/emojiMap.json +1915 -0
- package/build/common/error.d.ts +1 -10
- package/build/common/error.d.ts.map +1 -1
- package/build/common/error.js +1 -10
- package/build/common/fmt.d.ts +3 -15
- package/build/common/fmt.d.ts.map +1 -1
- package/build/common/fmt.js +7 -35
- package/build/common/http.d.ts +2 -7
- package/build/common/http.d.ts.map +1 -1
- package/build/common/http.js +8 -17
- package/build/common/icons.d.ts +18 -20
- package/build/common/icons.d.ts.map +1 -1
- package/build/common/icons.js +103 -67
- package/build/common/languageColors.d.ts +0 -1
- package/build/common/languageColors.d.ts.map +1 -1
- package/build/common/languageColors.js +1 -4
- package/build/common/languageColors.json +18 -2
- package/build/common/ops.d.ts +3 -14
- package/build/common/ops.d.ts.map +1 -1
- package/build/common/ops.js +19 -30
- package/build/common/render.d.ts +28 -82
- package/build/common/render.d.ts.map +1 -1
- package/build/common/render.js +175 -211
- package/build/common/retryer.d.ts +2 -7
- package/build/common/retryer.d.ts.map +1 -1
- package/build/common/retryer.js +4 -11
- package/build/fetchers/gist.d.ts +2 -5
- package/build/fetchers/gist.d.ts.map +1 -1
- package/build/fetchers/gist.js +1 -5
- package/build/fetchers/index.d.ts +12 -12
- package/build/fetchers/repo.d.ts +2 -11
- package/build/fetchers/repo.d.ts.map +1 -1
- package/build/fetchers/repo.js +20 -33
- package/build/fetchers/stats.d.ts +2 -32
- package/build/fetchers/stats.d.ts.map +1 -1
- package/build/fetchers/stats.js +27 -86
- package/build/fetchers/top-languages.d.ts +2 -9
- package/build/fetchers/top-languages.d.ts.map +1 -1
- package/build/fetchers/top-languages.js +15 -31
- package/build/fetchers/types.d.ts +1 -1
- package/build/fetchers/wakatime.d.ts +2 -6
- package/build/fetchers/wakatime.d.ts.map +1 -1
- package/build/fetchers/wakatime.js +1 -5
- package/build/graphql/contributionsDocument.d.ts +3 -3
- package/build/graphql/contributionsDocument.d.ts.map +1 -1
- package/build/graphql/contributionsDocument.js +0 -1
- package/build/graphql/generated/gist.d.ts +1 -1
- package/build/graphql/generated/repo.d.ts +1 -1
- package/build/graphql/generated/stats.d.ts +3 -3
- package/build/graphql/generated/top-languages.d.ts +2 -2
- package/build/graphql/graphqlDocument.d.ts +0 -1
- package/build/graphql/graphqlDocument.d.ts.map +1 -1
- package/build/graphql/graphqlDocument.js +0 -1
- package/build/graphql/reposContributedToDocument.d.ts +4 -5
- package/build/graphql/reposContributedToDocument.d.ts.map +1 -1
- package/build/graphql/reposContributedToDocument.js +0 -2
- package/build/markup/css.d.ts +35 -0
- package/build/markup/css.d.ts.map +1 -0
- package/build/markup/css.js +64 -0
- package/build/markup/element.d.ts +41 -0
- package/build/markup/element.d.ts.map +1 -0
- package/build/markup/element.js +127 -0
- package/build/markup/html.d.ts +13 -0
- package/build/markup/html.d.ts.map +1 -0
- package/build/markup/html.js +27 -0
- package/build/markup/index.d.ts +9 -0
- package/build/markup/index.d.ts.map +1 -0
- package/build/markup/index.js +6 -0
- package/build/markup/serialize.d.ts +16 -0
- package/build/markup/serialize.d.ts.map +1 -0
- package/build/markup/serialize.js +17 -0
- package/build/themes/index.d.ts +332 -333
- package/build/themes/index.d.ts.map +1 -1
- package/build/themes/index.js +1 -1
- package/build/translations.d.ts +2 -6
- package/build/translations.d.ts.map +1 -1
- package/build/translations.js +621 -628
- package/package.json +17 -26
- package/src/api/api-result.ts +10 -9
- package/src/api/gist.ts +11 -18
- package/src/api/index.ts +12 -12
- package/src/api/params.ts +27 -21
- package/src/api/pin.ts +13 -28
- package/src/api/stats.ts +13 -39
- package/src/api/top-langs.ts +13 -33
- package/src/api/wakatime.ts +11 -28
- package/src/calculateRank.ts +12 -23
- package/src/cards/gist.ts +40 -43
- package/src/cards/options.ts +1 -1
- package/src/cards/repo.ts +97 -107
- package/src/cards/stats.ts +165 -184
- package/src/cards/top-languages.ts +297 -413
- package/src/cards/wakatime.ts +193 -221
- package/src/common/Card.ts +165 -217
- package/src/common/I18n.ts +3 -6
- package/src/common/color.ts +80 -57
- package/src/common/config.ts +5 -18
- package/src/common/constants.ts +5 -2
- package/src/common/date.ts +0 -2
- package/src/common/emojiMap.json +1915 -0
- package/src/common/emojiMap.ts +13 -0
- package/src/common/error.ts +2 -11
- package/src/common/fmt.ts +7 -44
- package/src/common/http.ts +9 -15
- package/src/common/icons.ts +117 -42
- package/src/common/languageColors.json +18 -2
- package/src/common/languageColors.ts +1 -4
- package/src/common/ops.ts +26 -34
- package/src/common/render.ts +279 -232
- package/src/common/retryer.ts +8 -15
- package/src/fetchers/gist.ts +8 -12
- package/src/fetchers/index.ts +12 -12
- package/src/fetchers/repo.ts +27 -40
- package/src/fetchers/stats.ts +44 -102
- package/src/fetchers/top-languages.ts +26 -40
- package/src/fetchers/types.ts +1 -1
- package/src/fetchers/wakatime.ts +5 -9
- package/src/graphql/contributionsDocument.ts +7 -5
- package/src/graphql/generated/gist.ts +1 -1
- package/src/graphql/generated/repo.ts +1 -1
- package/src/graphql/generated/stats.ts +2 -2
- package/src/graphql/generated/top-languages.ts +2 -2
- package/src/graphql/graphqlDocument.ts +0 -1
- package/src/graphql/reposContributedToDocument.ts +6 -7
- package/src/markup/css.ts +118 -0
- package/src/markup/element.ts +246 -0
- package/src/markup/html.ts +34 -0
- package/src/markup/index.ts +9 -0
- package/src/markup/serialize.ts +27 -0
- package/src/themes/index.ts +1 -1
- package/src/translations.ts +622 -629
- package/build/cards/index.d.ts +0 -11
- package/build/cards/index.d.ts.map +0 -1
- package/build/cards/index.js +0 -7
- package/build/common/html.d.ts +0 -8
- package/build/common/html.d.ts.map +0 -1
- package/build/common/html.js +0 -14
- package/build/index.d.ts +0 -15
- package/build/index.d.ts.map +0 -1
- package/build/index.js +0 -10
- package/src/_emoji-name-map.d.ts +0 -10
- package/src/_github-username-regex.d.ts +0 -4
- package/src/cards/index.ts +0 -21
- package/src/common/html.ts +0 -17
- package/src/index.ts +0 -18
package/src/common/retryer.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import type { CardConfig } from './config.
|
|
2
|
-
import { CardError } from './error.
|
|
3
|
-
import type { FetcherContext, HttpResponse } from './http.
|
|
4
|
-
import { logger } from './log.
|
|
1
|
+
import type { CardConfig } from './config.ts';
|
|
2
|
+
import { CardError } from './error.ts';
|
|
3
|
+
import type { FetcherContext, HttpResponse } from './http.ts';
|
|
4
|
+
import { logger } from './log.ts';
|
|
5
5
|
|
|
6
6
|
/**
|
|
7
7
|
* Error-detection fields the retryer inspects to detect rate-limiting and credential failures.
|
|
@@ -19,8 +19,6 @@ interface ResponseErrors {
|
|
|
19
19
|
* The value is generated using `Math.random()` and uniformly distributed
|
|
20
20
|
* across the range.
|
|
21
21
|
*
|
|
22
|
-
* @param max The upper bound (exclusive). Must be a positive number.
|
|
23
|
-
*
|
|
24
22
|
* @returns A random integer `n` such that `0 <= n < max`.
|
|
25
23
|
*/
|
|
26
24
|
function getRandomInt(max: number): number {
|
|
@@ -44,11 +42,6 @@ type FetcherFunction<TData = unknown, TVariables = Record<string, unknown>> = (
|
|
|
44
42
|
/**
|
|
45
43
|
* Try to execute the fetcher function until it succeeds or the max number of retries is reached.
|
|
46
44
|
*
|
|
47
|
-
* @template TData Shape of `response.data` returned by the fetcher.
|
|
48
|
-
* @template TVariables Variables the fetcher accepts.
|
|
49
|
-
* @param fetcher The fetcher function.
|
|
50
|
-
* @param variables Object with arguments to pass to the fetcher function.
|
|
51
|
-
* @param config Deployment config supplying the PAT pool and the transport.
|
|
52
45
|
* @returns The response from the fetcher function.
|
|
53
46
|
*/
|
|
54
47
|
const retryer = async <TData = unknown, TVariables = Record<string, unknown>>(
|
|
@@ -58,12 +51,12 @@ const retryer = async <TData = unknown, TVariables = Record<string, unknown>>(
|
|
|
58
51
|
): Promise<FetcherResponse<TData>> => {
|
|
59
52
|
const PATs = config.pats;
|
|
60
53
|
|
|
61
|
-
if (
|
|
54
|
+
if (PATs.length === 0) {
|
|
62
55
|
throw new CardError('No GitHub API tokens found', { code: 'no_tokens' });
|
|
63
56
|
}
|
|
64
57
|
const startPAT = getRandomInt(PATs.length);
|
|
65
58
|
|
|
66
|
-
for (let retries = 0; retries < PATs.length; retries
|
|
59
|
+
for (let retries = 0; retries < PATs.length; retries += 1) {
|
|
67
60
|
const currentPAT = PATs[(startPAT + retries) % PATs.length];
|
|
68
61
|
if (!currentPAT) {
|
|
69
62
|
continue;
|
|
@@ -77,7 +70,7 @@ const retryer = async <TData = unknown, TVariables = Record<string, unknown>>(
|
|
|
77
70
|
|
|
78
71
|
// react on both type and message-based rate-limit signals.
|
|
79
72
|
// https://github.com/anuraghazra/github-readme-stats/issues/4425
|
|
80
|
-
const errors = response.data
|
|
73
|
+
const { errors } = response.data;
|
|
81
74
|
const errorType = errors?.[0]?.type;
|
|
82
75
|
const errorMsg = errors?.[0]?.message ?? '';
|
|
83
76
|
const isRateLimited =
|
|
@@ -89,7 +82,7 @@ const retryer = async <TData = unknown, TVariables = Record<string, unknown>>(
|
|
|
89
82
|
}
|
|
90
83
|
|
|
91
84
|
// also checking for bad credentials if any tokens gets invalidated
|
|
92
|
-
const message = response.data
|
|
85
|
+
const { message } = response.data;
|
|
93
86
|
const isBadCredential = message === 'Bad credentials';
|
|
94
87
|
const isAccountSuspended = message === 'Sorry. Your account was suspended.';
|
|
95
88
|
|
package/src/fetchers/gist.ts
CHANGED
|
@@ -1,18 +1,17 @@
|
|
|
1
|
-
import type { CardConfig } from '../common/config.
|
|
2
|
-
import { CardError, GIST_NOT_FOUND } from '../common/error.
|
|
3
|
-
import { createGraphQLFetcher } from '../common/http.
|
|
4
|
-
import { retryer } from '../common/retryer.
|
|
5
|
-
import { GistInfoDocument } from '../graphql/generated/gist.
|
|
6
|
-
import type { GistFileInfoFragment } from '../graphql/generated/gist.
|
|
1
|
+
import type { CardConfig } from '../common/config.ts';
|
|
2
|
+
import { CardError, GIST_NOT_FOUND } from '../common/error.ts';
|
|
3
|
+
import { createGraphQLFetcher } from '../common/http.ts';
|
|
4
|
+
import { retryer } from '../common/retryer.ts';
|
|
5
|
+
import { GistInfoDocument } from '../graphql/generated/gist.ts';
|
|
6
|
+
import type { GistFileInfoFragment } from '../graphql/generated/gist.ts';
|
|
7
7
|
|
|
8
|
-
import type { GistData } from './types.
|
|
8
|
+
import type { GistData } from './types.ts';
|
|
9
9
|
|
|
10
10
|
const fetcher = createGraphQLFetcher(GistInfoDocument, 'token');
|
|
11
11
|
|
|
12
12
|
/**
|
|
13
13
|
* This function calculates the primary language of a gist by files size.
|
|
14
14
|
*
|
|
15
|
-
* @param files Files.
|
|
16
15
|
* @returns Primary language, or `null` when no file has a language.
|
|
17
16
|
*/
|
|
18
17
|
const calculatePrimaryLanguage = (files: Array<GistFileInfoFragment>): string | null => {
|
|
@@ -39,9 +38,6 @@ const calculatePrimaryLanguage = (files: Array<GistFileInfoFragment>): string |
|
|
|
39
38
|
/**
|
|
40
39
|
* Fetch GitHub gist information by given username and ID.
|
|
41
40
|
*
|
|
42
|
-
* @param props Fetcher props.
|
|
43
|
-
* @param props.id GitHub gist ID.
|
|
44
|
-
* @param config Deployment config supplying the PAT pool.
|
|
45
41
|
* @returns Gist data.
|
|
46
42
|
*/
|
|
47
43
|
const fetchGist = async (
|
|
@@ -57,7 +53,7 @@ const fetchGist = async (
|
|
|
57
53
|
code: 'upstream',
|
|
58
54
|
});
|
|
59
55
|
}
|
|
60
|
-
const gist = res.data.data.viewer
|
|
56
|
+
const { gist } = res.data.data.viewer;
|
|
61
57
|
if (!gist) {
|
|
62
58
|
throw new CardError('Gist not found', {
|
|
63
59
|
code: 'not_found',
|
package/src/fetchers/index.ts
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
export { fetchGist } from './gist.
|
|
2
|
-
export { fetchRepo } from './repo.
|
|
3
|
-
export { fetchRepoUserStats, fetchStats } from './stats.
|
|
4
|
-
export { fetchTopLanguages } from './top-languages.
|
|
5
|
-
export { fetchWakatimeStats } from './wakatime.
|
|
1
|
+
export { fetchGist } from './gist.ts';
|
|
2
|
+
export { fetchRepo } from './repo.ts';
|
|
3
|
+
export { fetchRepoUserStats, fetchStats } from './stats.ts';
|
|
4
|
+
export { fetchTopLanguages } from './top-languages.ts';
|
|
5
|
+
export { fetchWakatimeStats } from './wakatime.ts';
|
|
6
6
|
|
|
7
7
|
export type {
|
|
8
8
|
GistData,
|
|
@@ -13,13 +13,13 @@ export type {
|
|
|
13
13
|
TopLangData,
|
|
14
14
|
WakaTimeData,
|
|
15
15
|
WakaTimeLang,
|
|
16
|
-
} from './types.
|
|
16
|
+
} from './types.ts';
|
|
17
17
|
|
|
18
18
|
// every fetcher takes a config, so this entry carries it rather than sending a
|
|
19
19
|
// consumer back to the package root for it.
|
|
20
|
-
export { CardConfig } from '../common/config.
|
|
21
|
-
export type { CardConfigInit, PersonalAccessToken } from '../common/config.
|
|
22
|
-
export type { FetchLike } from '../common/http.
|
|
23
|
-
export { retryer } from '../common/retryer.
|
|
24
|
-
export { CardError } from '../common/error.
|
|
25
|
-
export type { ErrorCode } from '../common/error.
|
|
20
|
+
export { CardConfig } from '../common/config.ts';
|
|
21
|
+
export type { CardConfigInit, PersonalAccessToken } from '../common/config.ts';
|
|
22
|
+
export type { FetchLike } from '../common/http.ts';
|
|
23
|
+
export { retryer } from '../common/retryer.ts';
|
|
24
|
+
export { CardError } from '../common/error.ts';
|
|
25
|
+
export type { ErrorCode } from '../common/error.ts';
|
package/src/fetchers/repo.ts
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
import type { CardConfig } from '../common/config.
|
|
2
|
-
import { CardError, REPO_NOT_FOUND } from '../common/error.
|
|
3
|
-
import { createGraphQLFetcher } from '../common/http.
|
|
4
|
-
import { retryer } from '../common/retryer.
|
|
5
|
-
import { GetRepoDocument } from '../graphql/generated/repo.
|
|
1
|
+
import type { CardConfig } from '../common/config.ts';
|
|
2
|
+
import { CardError, REPO_NOT_FOUND } from '../common/error.ts';
|
|
3
|
+
import { createGraphQLFetcher } from '../common/http.ts';
|
|
4
|
+
import { retryer } from '../common/retryer.ts';
|
|
5
|
+
import { GetRepoDocument } from '../graphql/generated/repo.ts';
|
|
6
6
|
|
|
7
|
-
import { fetchRepoUserStats } from './stats.
|
|
8
|
-
import type { RepositoryData } from './types.
|
|
7
|
+
import { fetchRepoUserStats } from './stats.ts';
|
|
8
|
+
import type { RepositoryData } from './types.ts';
|
|
9
9
|
|
|
10
10
|
const fetcher = createGraphQLFetcher(GetRepoDocument, 'token');
|
|
11
11
|
|
|
@@ -14,15 +14,6 @@ const urlExample = '/api/pin?username=USERNAME&repo=REPO_NAME';
|
|
|
14
14
|
/**
|
|
15
15
|
* Fetch repository data.
|
|
16
16
|
*
|
|
17
|
-
* @param props Fetcher props.
|
|
18
|
-
* @param props.username GitHub username.
|
|
19
|
-
* @param props.reponame GitHub repository name.
|
|
20
|
-
* @param props.include_prs_authored Include count of PRs authored.
|
|
21
|
-
* @param props.include_prs_commented Include count of PRs commented.
|
|
22
|
-
* @param props.include_prs_reviewed Include count of PRs reviewed.
|
|
23
|
-
* @param props.include_issues_authored Include count of issues authored.
|
|
24
|
-
* @param props.include_issues_commented Include count of issues commented.
|
|
25
|
-
* @param config Deployment config supplying the PAT pool.
|
|
26
17
|
* @returns Repository data.
|
|
27
18
|
*/
|
|
28
19
|
const fetchRepo = async (
|
|
@@ -45,35 +36,31 @@ const fetchRepo = async (
|
|
|
45
36
|
},
|
|
46
37
|
config: CardConfig,
|
|
47
38
|
): Promise<RepositoryData> => {
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
39
|
+
// `?repo=owner/name` carries the owner itself, and then it wins over `?username=`.
|
|
40
|
+
let parsedOwner = username;
|
|
41
|
+
let repo = reponame;
|
|
42
|
+
if (repo?.includes('/')) {
|
|
43
|
+
const [ownerFromRepo, nameFromRepo] = repo.split('/');
|
|
44
|
+
parsedOwner = ownerFromRepo ?? '';
|
|
45
|
+
repo = nameFromRepo ?? '';
|
|
53
46
|
}
|
|
54
47
|
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
}
|
|
58
|
-
if (username && !owner) {
|
|
59
|
-
owner = username;
|
|
60
|
-
}
|
|
61
|
-
if (!username && !reponame) {
|
|
48
|
+
const login = username || parsedOwner;
|
|
49
|
+
if (!login && !repo) {
|
|
62
50
|
throw CardError.missingParam(['username', 'repo'], urlExample);
|
|
63
51
|
}
|
|
64
|
-
if (!
|
|
52
|
+
if (!login) {
|
|
65
53
|
throw CardError.missingParam(['username'], urlExample);
|
|
66
54
|
}
|
|
67
|
-
if (!
|
|
55
|
+
if (!repo) {
|
|
68
56
|
throw CardError.missingParam(['repo'], urlExample);
|
|
69
57
|
}
|
|
70
58
|
|
|
71
|
-
|
|
72
|
-
const repoOwner = owner ?? username;
|
|
59
|
+
const repoOwner = parsedOwner || login;
|
|
73
60
|
|
|
74
|
-
const res = await retryer(fetcher, { login: repoOwner, repo
|
|
61
|
+
const res = await retryer(fetcher, { login: repoOwner, repo }, config);
|
|
75
62
|
|
|
76
|
-
const data = res.data
|
|
63
|
+
const { data } = res.data;
|
|
77
64
|
|
|
78
65
|
if (!data.user && !data.organization) {
|
|
79
66
|
throw new CardError('Not found', {
|
|
@@ -83,7 +70,7 @@ const fetchRepo = async (
|
|
|
83
70
|
}
|
|
84
71
|
|
|
85
72
|
if (data.organization === null && data.user) {
|
|
86
|
-
const repository = data.user
|
|
73
|
+
const { repository } = data.user;
|
|
87
74
|
if (!repository || repository.isPrivate) {
|
|
88
75
|
throw new CardError('User Repository Not found', {
|
|
89
76
|
code: 'not_found',
|
|
@@ -92,8 +79,8 @@ const fetchRepo = async (
|
|
|
92
79
|
}
|
|
93
80
|
const repoUserStats = await fetchRepoUserStats(
|
|
94
81
|
{
|
|
95
|
-
username,
|
|
96
|
-
repo: [`${repoOwner}/${
|
|
82
|
+
username: login,
|
|
83
|
+
repo: [`${repoOwner}/${repo}`],
|
|
97
84
|
include_prs_authored,
|
|
98
85
|
include_prs_commented,
|
|
99
86
|
include_prs_reviewed,
|
|
@@ -109,7 +96,7 @@ const fetchRepo = async (
|
|
|
109
96
|
}
|
|
110
97
|
|
|
111
98
|
if (data.user === null && data.organization) {
|
|
112
|
-
const repository = data.organization
|
|
99
|
+
const { repository } = data.organization;
|
|
113
100
|
if (!repository || repository.isPrivate) {
|
|
114
101
|
throw new CardError('Organization Repository Not found', {
|
|
115
102
|
code: 'not_found',
|
|
@@ -118,8 +105,8 @@ const fetchRepo = async (
|
|
|
118
105
|
}
|
|
119
106
|
const repoUserStats = await fetchRepoUserStats(
|
|
120
107
|
{
|
|
121
|
-
username,
|
|
122
|
-
repo: [`${repoOwner}/${
|
|
108
|
+
username: login,
|
|
109
|
+
repo: [`${repoOwner}/${repo}`],
|
|
123
110
|
include_prs_authored,
|
|
124
111
|
include_prs_commented,
|
|
125
112
|
include_prs_reviewed,
|
package/src/fetchers/stats.ts
CHANGED
|
@@ -1,30 +1,29 @@
|
|
|
1
|
-
import
|
|
2
|
-
|
|
3
|
-
import {
|
|
4
|
-
import type {
|
|
5
|
-
import
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
9
|
-
import {
|
|
10
|
-
import
|
|
11
|
-
import {
|
|
12
|
-
import {
|
|
13
|
-
import {
|
|
14
|
-
import
|
|
15
|
-
import {
|
|
16
|
-
import { UserInfoDocument, UserReposDocument } from '../graphql/generated/stats.js';
|
|
1
|
+
import { calculateRank } from '../calculateRank.ts';
|
|
2
|
+
import type { CardConfig } from '../common/config.ts';
|
|
3
|
+
import { GITHUB_USERNAME_PATTERN } from '../common/constants.ts';
|
|
4
|
+
import type { GitHubDateRange } from '../common/date.ts';
|
|
5
|
+
import { getGitHubYearRange, toGitHubDateTime } from '../common/date.ts';
|
|
6
|
+
import { CardError, USER_NOT_FOUND } from '../common/error.ts';
|
|
7
|
+
import { createGraphQLFetcher, httpRequest } from '../common/http.ts';
|
|
8
|
+
import type { FetcherContext, GraphQLResponse } from '../common/http.ts';
|
|
9
|
+
import { logger } from '../common/log.ts';
|
|
10
|
+
import { buildSearchFilter, chunkArray, parseOwnerAffiliations } from '../common/ops.ts';
|
|
11
|
+
import { wrapTextMultiline } from '../common/render.ts';
|
|
12
|
+
import { retryer } from '../common/retryer.ts';
|
|
13
|
+
import type { FetcherResponse } from '../common/retryer.ts';
|
|
14
|
+
import { buildContributionsDocument } from '../graphql/contributionsDocument.ts';
|
|
15
|
+
import { UserInfoDocument, UserReposDocument } from '../graphql/generated/stats.ts';
|
|
17
16
|
import type {
|
|
18
17
|
RepoNodeFragment,
|
|
19
18
|
UserInfoQuery,
|
|
20
19
|
UserInfoQueryVariables,
|
|
21
|
-
} from '../graphql/generated/stats.
|
|
20
|
+
} from '../graphql/generated/stats.ts';
|
|
22
21
|
import {
|
|
23
22
|
MAX_REPOSITORIES_LIMIT,
|
|
24
23
|
buildReposContributedToDocument,
|
|
25
|
-
} from '../graphql/reposContributedToDocument.
|
|
24
|
+
} from '../graphql/reposContributedToDocument.ts';
|
|
26
25
|
|
|
27
|
-
import type { RepoUserStats, StatsData } from './types.
|
|
26
|
+
import type { RepoUserStats, StatsData } from './types.ts';
|
|
28
27
|
|
|
29
28
|
/** The subset of the stats response `statsFetcher` returns and threads on. */
|
|
30
29
|
type StatsFetcherResponse = Pick<GraphQLResponse<UserInfoQuery>, 'data' | 'statusText'>;
|
|
@@ -36,19 +35,10 @@ const reposFetcher = createGraphQLFetcher(UserReposDocument, 'bearer');
|
|
|
36
35
|
/**
|
|
37
36
|
* Fetch stats information for a given username.
|
|
38
37
|
*
|
|
39
|
-
* @param variables Fetcher variables.
|
|
40
|
-
* @param variables.username GitHub username.
|
|
41
|
-
* @param variables.includeMergedPullRequests Include merged pull requests.
|
|
42
|
-
* @param variables.includeDiscussions Include discussions.
|
|
43
|
-
* @param variables.includeDiscussionsAnswers Include discussions answers.
|
|
44
|
-
* @param variables.startTime Time to start the count of total commits.
|
|
45
|
-
* @param variables.ownerAffiliations The owner affiliations to filter by. Default: OWNER.
|
|
46
|
-
* @param variables.includeUserRepositories Whether to include the user's own repositories in the repos contributed to.
|
|
47
|
-
* @param config Deployment config supplying the PAT pool.
|
|
48
|
-
* @returns The stats response, with every fetched page's repos merged in.
|
|
49
|
-
*
|
|
50
38
|
* @description Supports multi-page fetching when the `FETCH_MULTI_PAGE_STARS`
|
|
51
39
|
* env variable is `true` or a fetch limit.
|
|
40
|
+
*
|
|
41
|
+
* @returns The stats response, with every fetched page's repos merged in.
|
|
52
42
|
*/
|
|
53
43
|
const statsFetcher = async (
|
|
54
44
|
{
|
|
@@ -118,11 +108,11 @@ const statsFetcher = async (
|
|
|
118
108
|
|
|
119
109
|
pageRepositories = page.data.data.user?.repositories;
|
|
120
110
|
extraRepoNodes.push(...(pageRepositories?.nodes ?? []));
|
|
121
|
-
fetchedPages
|
|
111
|
+
fetchedPages += 1;
|
|
122
112
|
}
|
|
123
113
|
|
|
124
114
|
if (extraRepoNodes.length > 0) {
|
|
125
|
-
//
|
|
115
|
+
// defensive: the merge below is the only place a parsed response is mutated
|
|
126
116
|
stats = structuredClone({
|
|
127
117
|
data: stats.data,
|
|
128
118
|
statusText: stats.statusText,
|
|
@@ -136,13 +126,9 @@ const statsFetcher = async (
|
|
|
136
126
|
/**
|
|
137
127
|
* Fetch total items count using the REST search API.
|
|
138
128
|
*
|
|
139
|
-
* @param variables Fetcher variables.
|
|
140
|
-
* @param token GitHub token.
|
|
141
|
-
* @param context What the retryer supplies each attempt.
|
|
142
|
-
* @param context.fetch Transport to send the request with.
|
|
143
|
-
* @returns The search response, carrying `total_count`.
|
|
144
|
-
*
|
|
145
129
|
* @see https://developer.github.com/v3/search/#search-commits
|
|
130
|
+
*
|
|
131
|
+
* @returns The search response, carrying `total_count`.
|
|
146
132
|
*/
|
|
147
133
|
const fetchTotalItems = (
|
|
148
134
|
variables: Record<string, unknown>,
|
|
@@ -155,9 +141,10 @@ const fetchTotalItems = (
|
|
|
155
141
|
const owner = variables['owner'] as Array<string> | string;
|
|
156
142
|
return httpRequest(
|
|
157
143
|
fetch,
|
|
158
|
-
`https://api.github.com/search/${type}?per_page=1&q
|
|
159
|
-
|
|
160
|
-
|
|
144
|
+
`https://api.github.com/search/${type}?per_page=1&q=${buildSearchFilter(repo, owner).replaceAll(
|
|
145
|
+
' ',
|
|
146
|
+
'+',
|
|
147
|
+
)}${filter}`,
|
|
161
148
|
{
|
|
162
149
|
method: 'GET',
|
|
163
150
|
headers: {
|
|
@@ -172,18 +159,11 @@ const fetchTotalItems = (
|
|
|
172
159
|
/**
|
|
173
160
|
* Fetch a total count for a given username via the REST search API.
|
|
174
161
|
*
|
|
175
|
-
* @param props Fetcher props.
|
|
176
|
-
* @param props.username GitHub username.
|
|
177
|
-
* @param props.repo Repositories the search is scoped to.
|
|
178
|
-
* @param props.owner Owners the search is scoped to.
|
|
179
|
-
* @param props.type Search endpoint to hit, `issues` or `commits`.
|
|
180
|
-
* @param props.filter Query fragment appended to the scope filter.
|
|
181
|
-
* @param config Deployment config supplying the PAT pool.
|
|
182
|
-
* @returns Total count.
|
|
183
|
-
*
|
|
184
162
|
* The GraphQL API can't return this.
|
|
185
163
|
* @see https://github.com/anuraghazra/github-readme-stats/issues/92#issuecomment-661026467
|
|
186
164
|
* @see https://github.com/anuraghazra/github-readme-stats/pull/211
|
|
165
|
+
*
|
|
166
|
+
* @returns Total count.
|
|
187
167
|
*/
|
|
188
168
|
const totalItemsFetcher = async (
|
|
189
169
|
{
|
|
@@ -201,7 +181,7 @@ const totalItemsFetcher = async (
|
|
|
201
181
|
},
|
|
202
182
|
config: CardConfig,
|
|
203
183
|
): Promise<number> => {
|
|
204
|
-
if (!
|
|
184
|
+
if (!GITHUB_USERNAME_PATTERN.test(username)) {
|
|
205
185
|
logger.log('Invalid username provided.');
|
|
206
186
|
throw new CardError('Invalid username provided.', {
|
|
207
187
|
code: 'invalid_param',
|
|
@@ -216,14 +196,14 @@ const totalItemsFetcher = async (
|
|
|
216
196
|
{ login: username, repo, owner, type, filter },
|
|
217
197
|
config,
|
|
218
198
|
);
|
|
219
|
-
} catch (
|
|
220
|
-
logger.log(
|
|
221
|
-
throw
|
|
199
|
+
} catch (error) {
|
|
200
|
+
logger.log(error);
|
|
201
|
+
throw error;
|
|
222
202
|
}
|
|
223
203
|
|
|
224
204
|
const totalCount = res.data.total_count;
|
|
225
|
-
if (totalCount
|
|
226
|
-
logger.error(
|
|
205
|
+
if (typeof totalCount !== 'number' || Number.isNaN(totalCount)) {
|
|
206
|
+
logger.error(`GitHub error: ${JSON.stringify(res.data)}`);
|
|
227
207
|
throw new CardError('Could not fetch data from GitHub REST API.', {
|
|
228
208
|
code: 'upstream',
|
|
229
209
|
});
|
|
@@ -234,16 +214,6 @@ const totalItemsFetcher = async (
|
|
|
234
214
|
/**
|
|
235
215
|
* Fetch the per-repository counts the REST search API answers, one request each.
|
|
236
216
|
*
|
|
237
|
-
* @param props Fetcher props.
|
|
238
|
-
* @param props.username GitHub username.
|
|
239
|
-
* @param props.repo Repositories the search is scoped to.
|
|
240
|
-
* @param props.owner Owners the search is scoped to.
|
|
241
|
-
* @param props.include_prs_authored Include count of PRs authored.
|
|
242
|
-
* @param props.include_prs_commented Include count of PRs commented.
|
|
243
|
-
* @param props.include_prs_reviewed Include count of PRs reviewed.
|
|
244
|
-
* @param props.include_issues_authored Include count of issues authored.
|
|
245
|
-
* @param props.include_issues_commented Include count of issues commented.
|
|
246
|
-
* @param config Deployment config supplying the PAT pool.
|
|
247
217
|
* @returns Only the counts that were asked for.
|
|
248
218
|
*/
|
|
249
219
|
const fetchRepoUserStats = async (
|
|
@@ -334,10 +304,6 @@ const fetchRepoUserStats = async (
|
|
|
334
304
|
|
|
335
305
|
/**
|
|
336
306
|
* Turn a GraphQL `errors` payload into the error to throw.
|
|
337
|
-
*
|
|
338
|
-
* @param errors Errors from the response envelope.
|
|
339
|
-
* @param statusText HTTP status text, used as the error type when GitHub gave a message.
|
|
340
|
-
* @param fallback Message when GitHub gave none.
|
|
341
307
|
*/
|
|
342
308
|
const graphqlError = (
|
|
343
309
|
errors: NonNullable<GraphQLResponse<unknown>['data']['errors']>,
|
|
@@ -382,7 +348,7 @@ const fetchTotalContributions = async (
|
|
|
382
348
|
);
|
|
383
349
|
}
|
|
384
350
|
|
|
385
|
-
const user = contribRes.data.data
|
|
351
|
+
const { user } = contribRes.data.data;
|
|
386
352
|
if (!user) {
|
|
387
353
|
return 0;
|
|
388
354
|
}
|
|
@@ -420,39 +386,35 @@ const REPOS_CONTRIBUTED_TO_ERROR =
|
|
|
420
386
|
*
|
|
421
387
|
* Whether private contributions are included depends on the used PAT.
|
|
422
388
|
*
|
|
423
|
-
* @param canonicalUsername Username as GitHub reports it; `nameWithOwner` uses its
|
|
424
|
-
* casing, which the raw query-string username need not match.
|
|
425
|
-
* @param years Contribution years to walk.
|
|
426
|
-
* @param includeOwnRepos Whether to count the user's own repositories.
|
|
427
|
-
* @param config Deployment config supplying the PAT pool.
|
|
428
389
|
* @returns Count of repositories.
|
|
429
390
|
*/
|
|
430
391
|
const fetchAllTimeReposContributedTo = async (
|
|
392
|
+
/** `nameWithOwner` uses GitHub's casing, which the query-string username need not match */
|
|
431
393
|
canonicalUsername: string,
|
|
432
394
|
years: Array<number>,
|
|
433
395
|
includeOwnRepos: boolean,
|
|
434
396
|
config: CardConfig,
|
|
435
397
|
): Promise<number> => {
|
|
436
398
|
const repos = new Set<string>();
|
|
437
|
-
let pending = years.map(getGitHubYearRange);
|
|
399
|
+
let pending = years.map((year) => getGitHubYearRange(year));
|
|
438
400
|
|
|
439
401
|
while (pending.length > 0) {
|
|
440
402
|
const nextPending: Array<GitHubDateRange> = [];
|
|
441
403
|
|
|
442
404
|
for (const chunk of chunkArray(pending, MAX_RANGES_PER_REQUEST)) {
|
|
443
|
-
const
|
|
405
|
+
const chunkFetcher = createGraphQLFetcher(
|
|
444
406
|
buildReposContributedToDocument(chunk, includeOwnRepos),
|
|
445
407
|
'bearer',
|
|
446
408
|
);
|
|
447
409
|
const res = await retryer(
|
|
448
|
-
|
|
410
|
+
chunkFetcher,
|
|
449
411
|
{ login: canonicalUsername, maxRepositories: MAX_REPOSITORIES_LIMIT },
|
|
450
412
|
config,
|
|
451
413
|
);
|
|
452
414
|
if (res.data.errors) {
|
|
453
415
|
throw graphqlError(res.data.errors, res.statusText, REPOS_CONTRIBUTED_TO_ERROR);
|
|
454
416
|
}
|
|
455
|
-
const user = res.data.data
|
|
417
|
+
const { user } = res.data.data;
|
|
456
418
|
if (!user) {
|
|
457
419
|
throw new CardError(REPOS_CONTRIBUTED_TO_ERROR, { code: 'upstream' });
|
|
458
420
|
}
|
|
@@ -515,26 +477,6 @@ const fetchAllTimeReposContributedTo = async (
|
|
|
515
477
|
/**
|
|
516
478
|
* Fetch stats for a given username.
|
|
517
479
|
*
|
|
518
|
-
* @param props Fetcher props.
|
|
519
|
-
* @param props.username GitHub username.
|
|
520
|
-
* @param props.include_all_commits Include all commits.
|
|
521
|
-
* @param props.exclude_repo Repositories to exclude.
|
|
522
|
-
* @param props.include_merged_pull_requests Include merged pull requests.
|
|
523
|
-
* @param props.include_discussions Include discussions.
|
|
524
|
-
* @param props.include_discussions_answers Include discussions answers.
|
|
525
|
-
* @param props.commits_year Year to count total commits.
|
|
526
|
-
* @param props.repo Repositories to scope the REST search to.
|
|
527
|
-
* @param props.owner Owners to scope the REST search to.
|
|
528
|
-
* @param props.include_prs_authored Include count of PRs authored.
|
|
529
|
-
* @param props.include_prs_commented Include count of PRs commented.
|
|
530
|
-
* @param props.include_prs_reviewed Include count of PRs reviewed.
|
|
531
|
-
* @param props.include_issues_authored Include count of issues authored.
|
|
532
|
-
* @param props.include_issues_commented Include count of issues commented.
|
|
533
|
-
* @param props.ownerAffiliations Owner affiliations. Default: OWNER.
|
|
534
|
-
* @param props.include_contributions Include all-time contributions.
|
|
535
|
-
* @param props.include_all_time_contribs Include all-time count of repos contributed to.
|
|
536
|
-
* @param props.contribs_include_own_repos Include user-owned repos in contributed-to counts.
|
|
537
|
-
* @param config Deployment config supplying the PAT pool.
|
|
538
480
|
* @returns Stats data.
|
|
539
481
|
*/
|
|
540
482
|
const fetchStats = async (
|
|
@@ -625,7 +567,7 @@ const fetchStats = async (
|
|
|
625
567
|
// Catch GraphQL errors.
|
|
626
568
|
if (res.data.errors) {
|
|
627
569
|
logger.error(res.data.errors);
|
|
628
|
-
const firstError = res.data.errors
|
|
570
|
+
const [firstError] = res.data.errors;
|
|
629
571
|
if (firstError?.type === 'NOT_FOUND') {
|
|
630
572
|
throw new CardError(firstError.message || 'Could not fetch user.', {
|
|
631
573
|
code: 'not_found',
|
|
@@ -644,7 +586,7 @@ const fetchStats = async (
|
|
|
644
586
|
);
|
|
645
587
|
}
|
|
646
588
|
|
|
647
|
-
const user = res.data.data
|
|
589
|
+
const { user } = res.data.data;
|
|
648
590
|
if (!user) {
|
|
649
591
|
throw new CardError('Could not fetch user.', {
|
|
650
592
|
code: 'not_found',
|