@stats-organization/github-readme-stats-core 0.0.1
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/LICENSE +21 -0
- package/README.md +1 -0
- package/build/api/gist.d.ts +19 -0
- package/build/api/gist.d.ts.map +1 -0
- package/build/api/gist.js +77 -0
- package/build/api/index.d.ts +37 -0
- package/build/api/index.d.ts.map +1 -0
- package/build/api/index.js +115 -0
- package/build/api/pin.d.ts +27 -0
- package/build/api/pin.d.ts.map +1 -0
- package/build/api/pin.js +104 -0
- package/build/api/top-langs.d.ts +31 -0
- package/build/api/top-langs.d.ts.map +1 -0
- package/build/api/top-langs.js +121 -0
- package/build/api/wakatime.d.ts +28 -0
- package/build/api/wakatime.d.ts.map +1 -0
- package/build/api/wakatime.js +85 -0
- package/build/calculateRank.d.ts +28 -0
- package/build/calculateRank.d.ts.map +1 -0
- package/build/calculateRank.js +63 -0
- package/build/cards/gist.d.ts +21 -0
- package/build/cards/gist.d.ts.map +1 -0
- package/build/cards/gist.js +124 -0
- package/build/cards/repo.d.ts +21 -0
- package/build/cards/repo.d.ts.map +1 -0
- package/build/cards/repo.js +264 -0
- package/build/cards/stats.d.ts +49 -0
- package/build/cards/stats.d.ts.map +1 -0
- package/build/cards/stats.js +527 -0
- package/build/cards/top-languages.d.ts +143 -0
- package/build/cards/top-languages.d.ts.map +1 -0
- package/build/cards/top-languages.js +795 -0
- package/build/cards/wakatime.d.ts +16 -0
- package/build/cards/wakatime.d.ts.map +1 -0
- package/build/cards/wakatime.js +387 -0
- package/build/common/Card.d.ts +109 -0
- package/build/common/Card.d.ts.map +1 -0
- package/build/common/Card.js +241 -0
- package/build/common/I18n.d.ts +26 -0
- package/build/common/I18n.d.ts.map +1 -0
- package/build/common/I18n.js +34 -0
- package/build/common/color.d.ts +53 -0
- package/build/common/color.d.ts.map +1 -0
- package/build/common/color.js +92 -0
- package/build/common/config.d.ts +3 -0
- package/build/common/config.d.ts.map +1 -0
- package/build/common/config.js +69 -0
- package/build/common/constants.d.ts +6 -0
- package/build/common/constants.d.ts.map +1 -0
- package/build/common/constants.js +7 -0
- package/build/common/error.d.ts +53 -0
- package/build/common/error.d.ts.map +1 -0
- package/build/common/error.js +72 -0
- package/build/common/fmt.d.ts +27 -0
- package/build/common/fmt.d.ts.map +1 -0
- package/build/common/fmt.js +66 -0
- package/build/common/html.d.ts +10 -0
- package/build/common/html.d.ts.map +1 -0
- package/build/common/html.js +18 -0
- package/build/common/http.d.ts +9 -0
- package/build/common/http.d.ts.map +1 -0
- package/build/common/http.js +18 -0
- package/build/common/icons.d.ts +26 -0
- package/build/common/icons.d.ts.map +1 -0
- package/build/common/icons.js +77 -0
- package/build/common/languageColors.json +667 -0
- package/build/common/log.d.ts +9 -0
- package/build/common/log.d.ts.map +1 -0
- package/build/common/log.js +8 -0
- package/build/common/ops.d.ts +65 -0
- package/build/common/ops.d.ts.map +1 -0
- package/build/common/ops.js +137 -0
- package/build/common/render.d.ts +155 -0
- package/build/common/render.d.ts.map +1 -0
- package/build/common/render.js +361 -0
- package/build/common/retryer.d.ts +22 -0
- package/build/common/retryer.d.ts.map +1 -0
- package/build/common/retryer.js +86 -0
- package/build/fetchers/gist.d.ts +26 -0
- package/build/fetchers/gist.d.ts.map +1 -0
- package/build/fetchers/gist.js +99 -0
- package/build/fetchers/repo.d.ts +16 -0
- package/build/fetchers/repo.d.ts.map +1 -0
- package/build/fetchers/repo.js +116 -0
- package/build/fetchers/stats.d.ts +22 -0
- package/build/fetchers/stats.d.ts.map +1 -0
- package/build/fetchers/stats.js +339 -0
- package/build/fetchers/top-languages.d.ts +20 -0
- package/build/fetchers/top-languages.d.ts.map +1 -0
- package/build/fetchers/top-languages.js +139 -0
- package/build/fetchers/wakatime.d.ts +11 -0
- package/build/fetchers/wakatime.d.ts.map +1 -0
- package/build/fetchers/wakatime.js +25 -0
- package/build/index.d.ts +20 -0
- package/build/index.d.ts.map +1 -0
- package/build/index.js +19 -0
- package/build/themes/index.d.ts +469 -0
- package/build/themes/index.d.ts.map +1 -0
- package/build/themes/index.js +468 -0
- package/build/translations.d.ts +533 -0
- package/build/translations.d.ts.map +1 -0
- package/build/translations.js +1140 -0
- package/package.json +94 -0
- package/src/api/gist.js +99 -0
- package/src/api/index.js +185 -0
- package/src/api/pin.js +146 -0
- package/src/api/top-langs.js +168 -0
- package/src/api/wakatime.js +113 -0
- package/src/calculateRank.js +86 -0
- package/src/cards/gist.js +187 -0
- package/src/cards/repo.js +365 -0
- package/src/cards/stats.js +673 -0
- package/src/cards/top-languages.js +1023 -0
- package/src/cards/types.d.ts +78 -0
- package/src/cards/wakatime.js +471 -0
- package/src/common/Card.js +275 -0
- package/src/common/I18n.js +42 -0
- package/src/common/color.js +145 -0
- package/src/common/config.js +78 -0
- package/src/common/constants.js +9 -0
- package/src/common/error.js +90 -0
- package/src/common/fmt.js +80 -0
- package/src/common/html.js +22 -0
- package/src/common/http.js +21 -0
- package/src/common/icons.js +81 -0
- package/src/common/languageColors.json +667 -0
- package/src/common/log.ts +12 -0
- package/src/common/ops.js +172 -0
- package/src/common/render.js +442 -0
- package/src/common/retryer.js +104 -0
- package/src/fetchers/gist.js +114 -0
- package/src/fetchers/repo.js +165 -0
- package/src/fetchers/stats.js +478 -0
- package/src/fetchers/top-languages.js +177 -0
- package/src/fetchers/types.d.ts +128 -0
- package/src/fetchers/wakatime.js +37 -0
- package/src/index.ts +25 -0
- package/src/themes/README.md +229 -0
- package/src/themes/index.ts +476 -0
- package/src/translations.js +1156 -0
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
// @ts-check
|
|
2
|
+
|
|
3
|
+
import { getConfig } from "./config.js";
|
|
4
|
+
import { CustomError } from "./error.js";
|
|
5
|
+
import { logger } from "./log.js";
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Returns a random integer from 0 (inclusive) to `max` (exclusive).
|
|
9
|
+
*
|
|
10
|
+
* The value is generated using `Math.random()` and uniformly distributed
|
|
11
|
+
* across the range.
|
|
12
|
+
*
|
|
13
|
+
* @param {number} max The upper bound (exclusive). Must be a positive number.
|
|
14
|
+
*
|
|
15
|
+
* @returns {number} A random integer `n` such that `0 <= n < max`.
|
|
16
|
+
*/
|
|
17
|
+
function getRandomInt(max) {
|
|
18
|
+
return Math.floor(Math.random() * max);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* @typedef {import("axios").AxiosResponse} AxiosResponse Axios response.
|
|
23
|
+
* @typedef {(variables: any, token: string, retriesForTests?: number) => Promise<AxiosResponse>} FetcherFunction Fetcher function.
|
|
24
|
+
*/
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Try to execute the fetcher function until it succeeds or the max number of retries is reached.
|
|
28
|
+
*
|
|
29
|
+
* @param {FetcherFunction} fetcher The fetcher function.
|
|
30
|
+
* @param {any} variables Object with arguments to pass to the fetcher function.
|
|
31
|
+
* @param {string | null} pat Optional PAT override.
|
|
32
|
+
* @returns {Promise<any>} The response from the fetcher function.
|
|
33
|
+
*/
|
|
34
|
+
const retryer = async (fetcher, variables, pat = null) => {
|
|
35
|
+
let PATs;
|
|
36
|
+
if (pat) {
|
|
37
|
+
PATs = [{ name: "user PAT from database", value: pat }];
|
|
38
|
+
} else {
|
|
39
|
+
PATs = getConfig().pats;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
if (!PATs.length) {
|
|
43
|
+
throw new CustomError("No GitHub API tokens found", CustomError.NO_TOKENS);
|
|
44
|
+
}
|
|
45
|
+
const startPAT = getRandomInt(PATs.length);
|
|
46
|
+
|
|
47
|
+
for (let retries = 0; retries < PATs.length; retries++) {
|
|
48
|
+
const currentPAT = PATs[(startPAT + retries) % PATs.length];
|
|
49
|
+
|
|
50
|
+
try {
|
|
51
|
+
let response = await fetcher(
|
|
52
|
+
variables,
|
|
53
|
+
// @ts-ignore
|
|
54
|
+
currentPAT.value,
|
|
55
|
+
// used in tests for faking rate limit
|
|
56
|
+
retries,
|
|
57
|
+
);
|
|
58
|
+
|
|
59
|
+
// react on both type and message-based rate-limit signals.
|
|
60
|
+
// https://github.com/anuraghazra/github-readme-stats/issues/4425
|
|
61
|
+
const errors = response?.data?.errors;
|
|
62
|
+
const errorType = errors?.[0]?.type;
|
|
63
|
+
const errorMsg = errors?.[0]?.message || "";
|
|
64
|
+
const isRateLimited =
|
|
65
|
+
(errors && errorType === "RATE_LIMITED") ||
|
|
66
|
+
/rate limit/i.test(errorMsg);
|
|
67
|
+
|
|
68
|
+
if (isRateLimited) {
|
|
69
|
+
logger.log(`${currentPAT.name} Failed due to rate limiting`);
|
|
70
|
+
} else {
|
|
71
|
+
return response;
|
|
72
|
+
}
|
|
73
|
+
} catch (err) {
|
|
74
|
+
/** @type {any} */
|
|
75
|
+
const e = err;
|
|
76
|
+
|
|
77
|
+
// network/unexpected error → let caller treat as failure
|
|
78
|
+
if (!e?.response) {
|
|
79
|
+
throw e;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// prettier-ignore
|
|
83
|
+
// also checking for bad credentials if any tokens gets invalidated
|
|
84
|
+
const isBadCredential =
|
|
85
|
+
e?.response?.data?.message === "Bad credentials";
|
|
86
|
+
const isAccountSuspended =
|
|
87
|
+
e?.response?.data?.message === "Sorry. Your account was suspended.";
|
|
88
|
+
|
|
89
|
+
if (isBadCredential || isAccountSuspended) {
|
|
90
|
+
logger.log(`${currentPAT.name} Failed due to bad credentials`);
|
|
91
|
+
} else {
|
|
92
|
+
// HTTP error with a response → return it for caller-side handling
|
|
93
|
+
return e.response;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
throw new CustomError(
|
|
99
|
+
"Downtime due to GitHub API rate limiting",
|
|
100
|
+
CustomError.MAX_RETRY,
|
|
101
|
+
);
|
|
102
|
+
};
|
|
103
|
+
|
|
104
|
+
export { retryer };
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
// @ts-check
|
|
2
|
+
|
|
3
|
+
import { MissingParamError } from "../common/error.js";
|
|
4
|
+
import { request } from "../common/http.js";
|
|
5
|
+
import { retryer } from "../common/retryer.js";
|
|
6
|
+
|
|
7
|
+
const QUERY = `
|
|
8
|
+
query gistInfo($gistName: String!) {
|
|
9
|
+
viewer {
|
|
10
|
+
gist(name: $gistName) {
|
|
11
|
+
description
|
|
12
|
+
owner {
|
|
13
|
+
login
|
|
14
|
+
}
|
|
15
|
+
stargazerCount
|
|
16
|
+
forks {
|
|
17
|
+
totalCount
|
|
18
|
+
}
|
|
19
|
+
files {
|
|
20
|
+
name
|
|
21
|
+
language {
|
|
22
|
+
name
|
|
23
|
+
}
|
|
24
|
+
size
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
`;
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Gist data fetcher.
|
|
33
|
+
*
|
|
34
|
+
* @param {object} variables Fetcher variables.
|
|
35
|
+
* @param {string} token GitHub token.
|
|
36
|
+
* @returns {Promise<import('axios').AxiosResponse>} The response.
|
|
37
|
+
*/
|
|
38
|
+
const fetcher = async (variables, token) => {
|
|
39
|
+
return await request(
|
|
40
|
+
{ query: QUERY, variables },
|
|
41
|
+
{ Authorization: `token ${token}` },
|
|
42
|
+
);
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* @typedef {{ name: string; language: { name: string; }, size: number }} GistFile Gist file.
|
|
47
|
+
*/
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* This function calculates the primary language of a gist by files size.
|
|
51
|
+
*
|
|
52
|
+
* @param {GistFile[]} files Files.
|
|
53
|
+
* @returns {string} Primary language.
|
|
54
|
+
*/
|
|
55
|
+
const calculatePrimaryLanguage = (files) => {
|
|
56
|
+
/** @type {Record<string, number>} */
|
|
57
|
+
const languages = {};
|
|
58
|
+
|
|
59
|
+
for (const file of files) {
|
|
60
|
+
if (file.language) {
|
|
61
|
+
if (languages[file.language.name]) {
|
|
62
|
+
languages[file.language.name] += file.size;
|
|
63
|
+
} else {
|
|
64
|
+
languages[file.language.name] = file.size;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
let primaryLanguage = Object.keys(languages)[0];
|
|
70
|
+
for (const language in languages) {
|
|
71
|
+
if (languages[language] > languages[primaryLanguage]) {
|
|
72
|
+
primaryLanguage = language;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
return primaryLanguage;
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* @typedef {import('./types').GistData} GistData Gist data.
|
|
81
|
+
*/
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Fetch GitHub gist information by given username and ID.
|
|
85
|
+
*
|
|
86
|
+
* @param {string} id GitHub gist ID.
|
|
87
|
+
* @param {string | null} pat Optional PAT override.
|
|
88
|
+
* @returns {Promise<GistData>} Gist data.
|
|
89
|
+
*/
|
|
90
|
+
const fetchGist = async (id, pat = null) => {
|
|
91
|
+
if (!id) {
|
|
92
|
+
throw new MissingParamError(["id"], "/api/gist?id=GIST_ID");
|
|
93
|
+
}
|
|
94
|
+
const res = await retryer(fetcher, { gistName: id }, pat);
|
|
95
|
+
if (res.data.errors) {
|
|
96
|
+
throw new Error(res.data.errors[0].message);
|
|
97
|
+
}
|
|
98
|
+
if (!res.data.data.viewer.gist) {
|
|
99
|
+
throw new Error("Gist not found");
|
|
100
|
+
}
|
|
101
|
+
const data = res.data.data.viewer.gist;
|
|
102
|
+
return {
|
|
103
|
+
name: data.files[Object.keys(data.files)[0]].name,
|
|
104
|
+
nameWithOwner: `${data.owner.login}/${
|
|
105
|
+
data.files[Object.keys(data.files)[0]].name
|
|
106
|
+
}`,
|
|
107
|
+
description: data.description,
|
|
108
|
+
language: calculatePrimaryLanguage(data.files),
|
|
109
|
+
starsCount: data.stargazerCount,
|
|
110
|
+
forksCount: data.forks.totalCount,
|
|
111
|
+
};
|
|
112
|
+
};
|
|
113
|
+
|
|
114
|
+
export { fetchGist };
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
// @ts-check
|
|
2
|
+
|
|
3
|
+
import { MissingParamError } from "../common/error.js";
|
|
4
|
+
import { request } from "../common/http.js";
|
|
5
|
+
import { retryer } from "../common/retryer.js";
|
|
6
|
+
|
|
7
|
+
import { fetchRepoUserStats } from "./stats.js";
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Repo data fetcher.
|
|
11
|
+
*
|
|
12
|
+
* @param {object} variables Fetcher variables.
|
|
13
|
+
* @param {string} token GitHub token.
|
|
14
|
+
* @returns {Promise<import('axios').AxiosResponse>} The response.
|
|
15
|
+
*/
|
|
16
|
+
const fetcher = (variables, token) => {
|
|
17
|
+
return request(
|
|
18
|
+
{
|
|
19
|
+
query: `
|
|
20
|
+
fragment RepoInfo on Repository {
|
|
21
|
+
name
|
|
22
|
+
nameWithOwner
|
|
23
|
+
isPrivate
|
|
24
|
+
isArchived
|
|
25
|
+
isTemplate
|
|
26
|
+
stargazers {
|
|
27
|
+
totalCount
|
|
28
|
+
}
|
|
29
|
+
description
|
|
30
|
+
primaryLanguage {
|
|
31
|
+
color
|
|
32
|
+
id
|
|
33
|
+
name
|
|
34
|
+
}
|
|
35
|
+
forkCount
|
|
36
|
+
}
|
|
37
|
+
query getRepo($login: String!, $repo: String!) {
|
|
38
|
+
user(login: $login) {
|
|
39
|
+
repository(name: $repo) {
|
|
40
|
+
...RepoInfo
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
organization(login: $login) {
|
|
44
|
+
repository(name: $repo) {
|
|
45
|
+
...RepoInfo
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
`,
|
|
50
|
+
variables,
|
|
51
|
+
},
|
|
52
|
+
{
|
|
53
|
+
Authorization: `token ${token}`,
|
|
54
|
+
},
|
|
55
|
+
);
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
const urlExample = "/api/pin?username=USERNAME&repo=REPO_NAME";
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* @typedef {import("./types").RepositoryData} RepositoryData Repository data.
|
|
62
|
+
*/
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Fetch repository data.
|
|
66
|
+
*
|
|
67
|
+
* @param {string} username GitHub username.
|
|
68
|
+
* @param {string} reponame GitHub repository name.
|
|
69
|
+
* @returns {Promise<RepositoryData>} Repository data.
|
|
70
|
+
*/
|
|
71
|
+
const fetchRepo = async (
|
|
72
|
+
username,
|
|
73
|
+
reponame,
|
|
74
|
+
include_prs_authored = false,
|
|
75
|
+
include_prs_commented = false,
|
|
76
|
+
include_prs_reviewed = false,
|
|
77
|
+
include_issues_authored = false,
|
|
78
|
+
include_issues_commented = false,
|
|
79
|
+
pat = null,
|
|
80
|
+
) => {
|
|
81
|
+
let owner = username;
|
|
82
|
+
if (reponame && reponame.includes("/")) {
|
|
83
|
+
const [parsed_owner, parsed_repo] = reponame.split("/");
|
|
84
|
+
owner = parsed_owner;
|
|
85
|
+
reponame = parsed_repo;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
if (owner && !username) {
|
|
89
|
+
username = owner;
|
|
90
|
+
}
|
|
91
|
+
if (username && !owner) {
|
|
92
|
+
owner = username;
|
|
93
|
+
}
|
|
94
|
+
if (!username && !reponame) {
|
|
95
|
+
throw new MissingParamError(["username", "repo"], urlExample);
|
|
96
|
+
}
|
|
97
|
+
if (!username) {
|
|
98
|
+
throw new MissingParamError(["username"], urlExample);
|
|
99
|
+
}
|
|
100
|
+
if (!reponame) {
|
|
101
|
+
throw new MissingParamError(["repo"], urlExample);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
let res = await retryer(fetcher, { login: owner, repo: reponame }, pat);
|
|
105
|
+
|
|
106
|
+
const data = res.data.data;
|
|
107
|
+
|
|
108
|
+
if (!data.user && !data.organization) {
|
|
109
|
+
throw new Error("Not found");
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
const isUser = data.organization === null && data.user;
|
|
113
|
+
const isOrg = data.user === null && data.organization;
|
|
114
|
+
|
|
115
|
+
if (isUser) {
|
|
116
|
+
if (!data.user.repository || data.user.repository.isPrivate) {
|
|
117
|
+
throw new Error("User Repository Not found");
|
|
118
|
+
}
|
|
119
|
+
let repoUserStats = await fetchRepoUserStats(
|
|
120
|
+
username,
|
|
121
|
+
[owner + "/" + reponame],
|
|
122
|
+
[],
|
|
123
|
+
include_prs_authored,
|
|
124
|
+
include_prs_commented,
|
|
125
|
+
include_prs_reviewed,
|
|
126
|
+
include_issues_authored,
|
|
127
|
+
include_issues_commented,
|
|
128
|
+
pat,
|
|
129
|
+
);
|
|
130
|
+
return {
|
|
131
|
+
...repoUserStats,
|
|
132
|
+
...data.user.repository,
|
|
133
|
+
starCount: data.user.repository.stargazers.totalCount,
|
|
134
|
+
};
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
if (isOrg) {
|
|
138
|
+
if (
|
|
139
|
+
!data.organization.repository ||
|
|
140
|
+
data.organization.repository.isPrivate
|
|
141
|
+
) {
|
|
142
|
+
throw new Error("Organization Repository Not found");
|
|
143
|
+
}
|
|
144
|
+
let repoUserStats = await fetchRepoUserStats(
|
|
145
|
+
username,
|
|
146
|
+
[owner + "/" + reponame],
|
|
147
|
+
[],
|
|
148
|
+
include_prs_authored,
|
|
149
|
+
include_prs_commented,
|
|
150
|
+
include_prs_reviewed,
|
|
151
|
+
include_issues_authored,
|
|
152
|
+
include_issues_commented,
|
|
153
|
+
pat,
|
|
154
|
+
);
|
|
155
|
+
return {
|
|
156
|
+
...repoUserStats,
|
|
157
|
+
...data.organization.repository,
|
|
158
|
+
starCount: data.organization.repository.stargazers.totalCount,
|
|
159
|
+
};
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
throw new Error("Unexpected behavior");
|
|
163
|
+
};
|
|
164
|
+
|
|
165
|
+
export { fetchRepo };
|