forked from mirrored/github-readme-stats
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d656449265 | ||
|
|
10d8a68ab7 | ||
|
|
80b2d23242 | ||
|
|
1656ec6d3c | ||
|
|
0616df3746 | ||
|
|
7595456677 | ||
|
|
4793de4a6e | ||
|
|
c6c4a88d6d | ||
|
|
1262002624 | ||
|
|
b8983dd697 | ||
|
|
a8663a8025 | ||
|
|
afcb773b11 | ||
|
|
ffe28620ba | ||
|
|
723cdd74d4 | ||
|
|
68c49dd824 | ||
|
|
ddcc30d641 | ||
|
|
d4f1a5d061 | ||
|
|
f26b83d6cd | ||
|
|
3a15963a3d | ||
|
|
49973083b1 | ||
|
|
e50a9b435c | ||
|
|
e04e1397c5 | ||
|
|
36bbea2aab | ||
|
|
034ee0666e | ||
|
|
d654fe5715 | ||
|
|
51eefa2dca | ||
|
|
5ea659579e | ||
|
|
edf9f7413b | ||
|
|
aa90efcae5 | ||
|
|
dda9647c49 | ||
|
|
8d7e9df697 | ||
|
|
843b15ed36 | ||
|
|
a99abe8c24 | ||
|
|
bc7ea33782 | ||
|
|
79cee30ac3 | ||
|
|
e5b5da86e5 | ||
|
|
6d8169fd2c | ||
|
|
90c30f2bd2 | ||
|
|
5b3b920ed2 | ||
|
|
236577f70a | ||
|
|
ff16984988 | ||
|
|
2ddcd9c2bd | ||
|
|
1886fe0379 | ||
|
|
2b90336430 | ||
|
|
6cf9bb8448 | ||
|
|
10236d8cd3 | ||
|
|
91a3ac2705 | ||
|
|
2feebd011d | ||
|
|
5b72dfbc32 | ||
|
|
89fc14609e | ||
|
|
49c0e2da28 | ||
|
|
9c6eb22862 | ||
|
|
db921818be | ||
|
|
4a065a36b5 |
+9
-10
@@ -48,8 +48,8 @@
|
||||
// "dot-notation": "warn",
|
||||
// "eqeqeq": [ "error", "smart" ],
|
||||
// "guard-for-in": "warn",
|
||||
// "no-alert": "error",
|
||||
// "no-caller": "error",
|
||||
"no-alert": "error",
|
||||
"no-caller": "error",
|
||||
// "no-case-declarations": "warn",
|
||||
// "no-div-regex": "warn",
|
||||
// "no-else-return": "warn",
|
||||
@@ -113,7 +113,7 @@
|
||||
|
||||
// "init-declarations": [ "error", "always" ],
|
||||
// "no-catch-shadow": "warn",
|
||||
// "no-delete-var": "error",
|
||||
"no-delete-var": "error",
|
||||
// "no-label-var": "error",
|
||||
// "no-shadow-restricted-names": "error",
|
||||
// "no-shadow": "warn",
|
||||
@@ -150,12 +150,12 @@
|
||||
// "arrow-body-style": [ "error", "always" ],
|
||||
// "arrow-parens": [ "error", "always" ],
|
||||
// "arrow-spacing": [ "error", { "before": true, "after": true }],
|
||||
// "constructor-super": "error",
|
||||
"constructor-super": "error",
|
||||
// "generator-star-spacing": [ "error", "before" ],
|
||||
// "no-arrow-condition": "error",
|
||||
// "no-class-assign": "error",
|
||||
// "no-const-assign": "error",
|
||||
// "no-dupe-class-members": "error",
|
||||
"no-class-assign": "error",
|
||||
"no-const-assign": "error",
|
||||
"no-dupe-class-members": "error",
|
||||
"no-this-before-super": "error",
|
||||
// "no-var": "warn",
|
||||
"object-shorthand": [ "warn" ],
|
||||
@@ -217,14 +217,13 @@
|
||||
"MethodDefinition": true,
|
||||
"ClassDeclaration": false
|
||||
}
|
||||
}]
|
||||
}],
|
||||
// "semi-spacing": [ "warn", { "before": false, "after": true }],
|
||||
// "semi": [ "error", "always" ],
|
||||
// "sort-vars": "off",
|
||||
// "space-after-keywords": [ "warn", "always" ],
|
||||
"keyword-spacing": ["error", { "before": true, "after": true }]
|
||||
// "space-before-blocks": [ "warn", "always" ],
|
||||
// "space-before-function-paren": [ "warn", "never" ],
|
||||
// "space-before-keywords": [ "warn", "always" ],
|
||||
// "space-in-parens": [ "warn", "never" ],
|
||||
// "space-infix-ops": [ "warn", { "int32Hint": true } ],
|
||||
// "space-return-throw-case": "error",
|
||||
|
||||
@@ -6,6 +6,11 @@ updates:
|
||||
schedule:
|
||||
interval: weekly
|
||||
open-pull-requests-limit: 10
|
||||
commit-message:
|
||||
prefix: "build(deps)"
|
||||
prefix-development: "build(deps-dev)"
|
||||
reviewers:
|
||||
- "qwerty541"
|
||||
|
||||
# Maintain dependencies for GitHub Actions
|
||||
- package-ecosystem: github-actions
|
||||
@@ -13,3 +18,8 @@ updates:
|
||||
schedule:
|
||||
interval: weekly
|
||||
open-pull-requests-limit: 10
|
||||
commit-message:
|
||||
prefix: "build(deps)"
|
||||
prefix-development: "build(deps-dev)"
|
||||
reviewers:
|
||||
- "qwerty541"
|
||||
|
||||
@@ -38,6 +38,7 @@ wakatime-card:
|
||||
- src/fetchers/wakatime-fetcher.js
|
||||
- tests/fetchWakatime.test.js
|
||||
- tests/renderWakatimeCard.test.js
|
||||
- tests/wakatime.test.js
|
||||
gist-card:
|
||||
- api/gist.js
|
||||
- src/cards/gist-card.js
|
||||
|
||||
@@ -30,7 +30,7 @@ jobs:
|
||||
- uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
|
||||
|
||||
- name: Run empty issues closer action
|
||||
uses: rickstaa/empty-issues-closer-action@af151e679df0e3b59cda0e10c43010556d6c5bff # v1.1.35
|
||||
uses: rickstaa/empty-issues-closer-action@e0fb1033c83cf13e0c98c2c9d0419d3040a6a1d0 # v1.1.50
|
||||
env:
|
||||
github_token: ${{ secrets.GITHUB_TOKEN }}
|
||||
with:
|
||||
|
||||
@@ -26,7 +26,7 @@ jobs:
|
||||
persist-credentials: false
|
||||
|
||||
- name: "Run analysis"
|
||||
uses: ossf/scorecard-action@483ef80eb98fb506c348f7d62e28055e49fe2398 # v2.3.0
|
||||
uses: ossf/scorecard-action@0864cf19026789058feabb7e87baa5f140aac736 # v2.3.1
|
||||
with:
|
||||
results_file: results.sarif
|
||||
results_format: sarif
|
||||
|
||||
@@ -34,7 +34,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Run top issues action
|
||||
uses: rickstaa/top-issues-action@36df2af30c15ddf48558931420138890ed1b1708 # v1.3.61
|
||||
uses: rickstaa/top-issues-action@04028f4fc0f1c217e571be72102b6ea4827b8468 # v1.3.75
|
||||
env:
|
||||
github_token: ${{ secrets.GITHUB_TOKEN }}
|
||||
with:
|
||||
|
||||
+18
-2
@@ -27,7 +27,15 @@ export default async (req, res) => {
|
||||
res.setHeader("Content-Type", "image/svg+xml");
|
||||
|
||||
if (locale && !isLocaleAvailable(locale)) {
|
||||
return res.send(renderError("Something went wrong", "Language not found"));
|
||||
return res.send(
|
||||
renderError("Something went wrong", "Language not found", {
|
||||
title_color,
|
||||
text_color,
|
||||
bg_color,
|
||||
border_color,
|
||||
theme,
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
try {
|
||||
@@ -83,6 +91,14 @@ export default async (req, res) => {
|
||||
CONSTANTS.ERROR_CACHE_SECONDS
|
||||
}, stale-while-revalidate=${CONSTANTS.ONE_DAY}`,
|
||||
); // Use lower cache period for errors.
|
||||
return res.send(renderError(err.message, err.secondaryMessage));
|
||||
return res.send(
|
||||
renderError(err.message, err.secondaryMessage, {
|
||||
title_color,
|
||||
text_color,
|
||||
bg_color,
|
||||
border_color,
|
||||
theme,
|
||||
}),
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
+50
-7
@@ -8,7 +8,36 @@ import {
|
||||
renderError,
|
||||
} from "../src/common/utils.js";
|
||||
import { fetchStats } from "../src/fetchers/stats-fetcher.js";
|
||||
import { isLocaleAvailable } from "../src/translations.js";
|
||||
import { validateQueryStringParams } from "../src/common/validate.js";
|
||||
|
||||
const QUERYSTRING_PARAMS_DATA_TYPE_MAP = new Map([
|
||||
["username", "string"],
|
||||
["hide", "enum-array"],
|
||||
["hide_title", "boolean"],
|
||||
["hide_border", "boolean"],
|
||||
["card_width", "number"],
|
||||
["hide_rank", "boolean"],
|
||||
["show_icons", "boolean"],
|
||||
["include_all_commits", "boolean"],
|
||||
["line_height", "number"],
|
||||
["title_color", "string"],
|
||||
["ring_color", "string"],
|
||||
["icon_color", "string"],
|
||||
["text_color", "string"],
|
||||
["text_bold", "boolean"],
|
||||
["bg_color", "string"],
|
||||
["theme", "enum"],
|
||||
["cache_seconds", "number"],
|
||||
["exclude_repo", "array"],
|
||||
["custom_title", "string"],
|
||||
["locale", "enum"],
|
||||
["disable_animations", "boolean"],
|
||||
["border_radius", "number"],
|
||||
["border_color", "string"],
|
||||
["number_format", "enum"],
|
||||
["rank_icon", "enum"],
|
||||
["show", "enum-array"],
|
||||
]);
|
||||
|
||||
export default async (req, res) => {
|
||||
const {
|
||||
@@ -42,14 +71,20 @@ export default async (req, res) => {
|
||||
res.setHeader("Content-Type", "image/svg+xml");
|
||||
|
||||
if (blacklist.includes(username)) {
|
||||
return res.send(renderError("Something went wrong"));
|
||||
}
|
||||
|
||||
if (locale && !isLocaleAvailable(locale)) {
|
||||
return res.send(renderError("Something went wrong", "Language not found"));
|
||||
return res.send(
|
||||
renderError("Something went wrong", "This username is blacklisted", {
|
||||
title_color,
|
||||
text_color,
|
||||
bg_color,
|
||||
border_color,
|
||||
theme,
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
try {
|
||||
validateQueryStringParams(req.query, QUERYSTRING_PARAMS_DATA_TYPE_MAP);
|
||||
|
||||
const showStats = parseArray(show);
|
||||
const stats = await fetchStats(
|
||||
username,
|
||||
@@ -111,6 +146,14 @@ export default async (req, res) => {
|
||||
CONSTANTS.ERROR_CACHE_SECONDS
|
||||
}, stale-while-revalidate=${CONSTANTS.ONE_DAY}`,
|
||||
); // Use lower cache period for errors.
|
||||
return res.send(renderError(err.message, err.secondaryMessage));
|
||||
return res.send(
|
||||
renderError(err.message, err.secondaryMessage, {
|
||||
title_color,
|
||||
text_color,
|
||||
bg_color,
|
||||
border_color,
|
||||
theme,
|
||||
}),
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
+29
-3
@@ -24,16 +24,33 @@ export default async (req, res) => {
|
||||
locale,
|
||||
border_radius,
|
||||
border_color,
|
||||
description_lines_count,
|
||||
} = req.query;
|
||||
|
||||
res.setHeader("Content-Type", "image/svg+xml");
|
||||
|
||||
if (blacklist.includes(username)) {
|
||||
return res.send(renderError("Something went wrong"));
|
||||
return res.send(
|
||||
renderError("Something went wrong", "This username is blacklisted", {
|
||||
title_color,
|
||||
text_color,
|
||||
bg_color,
|
||||
border_color,
|
||||
theme,
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
if (locale && !isLocaleAvailable(locale)) {
|
||||
return res.send(renderError("Something went wrong", "Language not found"));
|
||||
return res.send(
|
||||
renderError("Something went wrong", "Language not found", {
|
||||
title_color,
|
||||
text_color,
|
||||
bg_color,
|
||||
border_color,
|
||||
theme,
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
try {
|
||||
@@ -80,6 +97,7 @@ export default async (req, res) => {
|
||||
border_color,
|
||||
show_owner: parseBoolean(show_owner),
|
||||
locale: locale ? locale.toLowerCase() : null,
|
||||
description_lines_count,
|
||||
}),
|
||||
);
|
||||
} catch (err) {
|
||||
@@ -89,6 +107,14 @@ export default async (req, res) => {
|
||||
CONSTANTS.ERROR_CACHE_SECONDS
|
||||
}, stale-while-revalidate=${CONSTANTS.ONE_DAY}`,
|
||||
); // Use lower cache period for errors.
|
||||
return res.send(renderError(err.message, err.secondaryMessage));
|
||||
return res.send(
|
||||
renderError(err.message, err.secondaryMessage, {
|
||||
title_color,
|
||||
text_color,
|
||||
bg_color,
|
||||
border_color,
|
||||
theme,
|
||||
}),
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
+18
-2
@@ -37,7 +37,15 @@ export default async (req, res) => {
|
||||
res.setHeader("Content-Type", "image/svg+xml");
|
||||
|
||||
if (blacklist.includes(username)) {
|
||||
return res.send(renderError("Something went wrong"));
|
||||
return res.send(
|
||||
renderError("Something went wrong", "This username is blacklisted", {
|
||||
title_color,
|
||||
text_color,
|
||||
bg_color,
|
||||
border_color,
|
||||
theme,
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
if (locale && !isLocaleAvailable(locale)) {
|
||||
@@ -105,6 +113,14 @@ export default async (req, res) => {
|
||||
CONSTANTS.ERROR_CACHE_SECONDS
|
||||
}, stale-while-revalidate=${CONSTANTS.ONE_DAY}`,
|
||||
); // Use lower cache period for errors.
|
||||
return res.send(renderError(err.message, err.secondaryMessage));
|
||||
return res.send(
|
||||
renderError(err.message, err.secondaryMessage, {
|
||||
title_color,
|
||||
text_color,
|
||||
bg_color,
|
||||
border_color,
|
||||
theme,
|
||||
}),
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
+22
-2
@@ -30,12 +30,22 @@ export default async (req, res) => {
|
||||
api_domain,
|
||||
border_radius,
|
||||
border_color,
|
||||
display_format,
|
||||
disable_animations,
|
||||
} = req.query;
|
||||
|
||||
res.setHeader("Content-Type", "image/svg+xml");
|
||||
|
||||
if (locale && !isLocaleAvailable(locale)) {
|
||||
return res.send(renderError("Something went wrong", "Language not found"));
|
||||
return res.send(
|
||||
renderError("Something went wrong", "Language not found", {
|
||||
title_color,
|
||||
text_color,
|
||||
bg_color,
|
||||
border_color,
|
||||
theme,
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
try {
|
||||
@@ -75,6 +85,8 @@ export default async (req, res) => {
|
||||
locale: locale ? locale.toLowerCase() : null,
|
||||
layout,
|
||||
langs_count,
|
||||
display_format,
|
||||
disable_animations: parseBoolean(disable_animations),
|
||||
}),
|
||||
);
|
||||
} catch (err) {
|
||||
@@ -84,6 +96,14 @@ export default async (req, res) => {
|
||||
CONSTANTS.ERROR_CACHE_SECONDS
|
||||
}, stale-while-revalidate=${CONSTANTS.ONE_DAY}`,
|
||||
); // Use lower cache period for errors.
|
||||
return res.send(renderError(err.message, err.secondaryMessage));
|
||||
return res.send(
|
||||
renderError(err.message, err.secondaryMessage, {
|
||||
title_color,
|
||||
text_color,
|
||||
bg_color,
|
||||
border_color,
|
||||
theme,
|
||||
}),
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
+1
-2
@@ -1,12 +1,11 @@
|
||||
import "dotenv/config";
|
||||
import statsCard from "./api/index.js";
|
||||
import repoCard from "./api/pin.js";
|
||||
import langCard from "./api/top-langs.js";
|
||||
import wakatimeCard from "./api/wakatime.js";
|
||||
import gistCard from "./api/gist.js";
|
||||
import express from "express";
|
||||
import dotenv from "dotenv";
|
||||
|
||||
dotenv.config();
|
||||
const app = express();
|
||||
app.listen(process.env.port || 9000);
|
||||
|
||||
|
||||
Generated
+64
-64
@@ -9,7 +9,7 @@
|
||||
"version": "1.0.0",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"axios": "^1.5.1",
|
||||
"axios": "^1.6.2",
|
||||
"dotenv": "^16.3.1",
|
||||
"emoji-name-map": "^1.2.8",
|
||||
"github-username-regex": "^1.0.0",
|
||||
@@ -24,7 +24,7 @@
|
||||
"@uppercod/css-to-object": "^1.1.1",
|
||||
"axios-mock-adapter": "^1.22.0",
|
||||
"color-contrast-checker": "^2.1.0",
|
||||
"eslint": "^8.52.0",
|
||||
"eslint": "^8.54.0",
|
||||
"eslint-config-prettier": "^9.0.0",
|
||||
"hjson": "^3.2.2",
|
||||
"husky": "^8.0.3",
|
||||
@@ -32,10 +32,10 @@
|
||||
"jest-bench": "^29.4.1",
|
||||
"jest-environment-jsdom": "^29.7.0",
|
||||
"js-yaml": "^4.1.0",
|
||||
"lint-staged": "^15.0.2",
|
||||
"lint-staged": "^15.1.0",
|
||||
"lodash.snakecase": "^4.1.1",
|
||||
"parse-diff": "^0.11.1",
|
||||
"prettier": "^3.0.3"
|
||||
"prettier": "^3.1.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18.0.0"
|
||||
@@ -83,9 +83,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@adobe/css-tools": {
|
||||
"version": "4.3.1",
|
||||
"resolved": "https://registry.npmjs.org/@adobe/css-tools/-/css-tools-4.3.1.tgz",
|
||||
"integrity": "sha512-/62yikz7NLScCGAAST5SHdnjaDJQBDq0M2muyRTpf2VQhw6StBg2ALiu73zSJQ4fMVLA+0uBhBHAle7Wg+2kSg==",
|
||||
"version": "4.3.2",
|
||||
"resolved": "https://registry.npmjs.org/@adobe/css-tools/-/css-tools-4.3.2.tgz",
|
||||
"integrity": "sha512-DA5a1C0gD/pLOvhv33YMrbf2FK3oUzwNl9oOJqE4XVjuEtt6XIakRcsd7eLiOSPkp1kTRQGICTA8cKra/vFbjw==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/@ampproject/remapping": {
|
||||
@@ -769,9 +769,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@eslint/eslintrc": {
|
||||
"version": "2.1.2",
|
||||
"resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.2.tgz",
|
||||
"integrity": "sha512-+wvgpDsrB1YqAMdEUCcnTlpfVBH7Vqn6A/NT3D8WVXFIaKMlErPIZT3oCIAVCOtarRpMtelZLqJeU3t7WY6X6g==",
|
||||
"version": "2.1.3",
|
||||
"resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.3.tgz",
|
||||
"integrity": "sha512-yZzuIG+jnVu6hNSzFEN07e8BxF3uAzYtQb6uDkaYZLo6oYZDCq454c5kB8zxnzfCYyP4MIuyBn10L0DqwujTmA==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"ajv": "^6.12.4",
|
||||
@@ -792,9 +792,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@eslint/eslintrc/node_modules/globals": {
|
||||
"version": "13.21.0",
|
||||
"resolved": "https://registry.npmjs.org/globals/-/globals-13.21.0.tgz",
|
||||
"integrity": "sha512-ybyme3s4yy/t/3s35bewwXKOf7cvzfreG2lH0lZl0JB7I4GxRP2ghxOK/Nb9EkRXdbBXZLfq/p/0W2JUONB/Gg==",
|
||||
"version": "13.23.0",
|
||||
"resolved": "https://registry.npmjs.org/globals/-/globals-13.23.0.tgz",
|
||||
"integrity": "sha512-XAmF0RjlrjY23MA51q3HltdlGxUpXPvg0GioKiD9X6HD28iMjo2dKC8Vqwm7lne4GNr78+RHTfliktR6ZH09wA==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"type-fest": "^0.20.2"
|
||||
@@ -819,9 +819,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@eslint/js": {
|
||||
"version": "8.52.0",
|
||||
"resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.52.0.tgz",
|
||||
"integrity": "sha512-mjZVbpaeMZludF2fsWLD0Z9gCref1Tk4i9+wddjRvpUNqqcndPkBD09N/Mapey0b3jaXbLm2kICwFv2E64QinA==",
|
||||
"version": "8.54.0",
|
||||
"resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.54.0.tgz",
|
||||
"integrity": "sha512-ut5V+D+fOoWPgGGNj83GGjnntO39xDy6DWxO0wb7Jp3DcMX0TfIqdzHF85VTQkerdyGmuuMD9AKAo5KiNlf/AQ==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": "^12.22.0 || ^14.17.0 || >=16.0.0"
|
||||
@@ -1908,9 +1908,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/axios": {
|
||||
"version": "1.5.1",
|
||||
"resolved": "https://registry.npmjs.org/axios/-/axios-1.5.1.tgz",
|
||||
"integrity": "sha512-Q28iYCWzNHjAm+yEAot5QaAMxhMghWLFVf7rRdwhUI+c2jix2DUXjAHXVi+s1ibs3mjPO/cCgbA++3BjD0vP/A==",
|
||||
"version": "1.6.2",
|
||||
"resolved": "https://registry.npmjs.org/axios/-/axios-1.6.2.tgz",
|
||||
"integrity": "sha512-7i24Ri4pmDRfJTR7LDBhsOTtcm+9kjX5WiY1X3wIisx6G9So3pfMkEiU7emUBe46oceVImccTEM3k6C5dbVW8A==",
|
||||
"dependencies": {
|
||||
"follow-redirects": "^1.15.0",
|
||||
"form-data": "^4.0.0",
|
||||
@@ -2758,15 +2758,15 @@
|
||||
}
|
||||
},
|
||||
"node_modules/eslint": {
|
||||
"version": "8.52.0",
|
||||
"resolved": "https://registry.npmjs.org/eslint/-/eslint-8.52.0.tgz",
|
||||
"integrity": "sha512-zh/JHnaixqHZsolRB/w9/02akBk9EPrOs9JwcTP2ek7yL5bVvXuRariiaAjjoJ5DvuwQ1WAE/HsMz+w17YgBCg==",
|
||||
"version": "8.54.0",
|
||||
"resolved": "https://registry.npmjs.org/eslint/-/eslint-8.54.0.tgz",
|
||||
"integrity": "sha512-NY0DfAkM8BIZDVl6PgSa1ttZbx3xHgJzSNJKYcQglem6CppHyMhRIQkBVSSMaSRnLhig3jsDbEzOjwCVt4AmmA==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@eslint-community/eslint-utils": "^4.2.0",
|
||||
"@eslint-community/regexpp": "^4.6.1",
|
||||
"@eslint/eslintrc": "^2.1.2",
|
||||
"@eslint/js": "8.52.0",
|
||||
"@eslint/eslintrc": "^2.1.3",
|
||||
"@eslint/js": "8.54.0",
|
||||
"@humanwhocodes/config-array": "^0.11.13",
|
||||
"@humanwhocodes/module-importer": "^1.0.1",
|
||||
"@nodelib/fs.walk": "^1.2.8",
|
||||
@@ -5177,9 +5177,9 @@
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/lint-staged": {
|
||||
"version": "15.0.2",
|
||||
"resolved": "https://registry.npmjs.org/lint-staged/-/lint-staged-15.0.2.tgz",
|
||||
"integrity": "sha512-vnEy7pFTHyVuDmCAIFKR5QDO8XLVlPFQQyujQ/STOxe40ICWqJ6knS2wSJ/ffX/Lw0rz83luRDh+ET7toN+rOw==",
|
||||
"version": "15.1.0",
|
||||
"resolved": "https://registry.npmjs.org/lint-staged/-/lint-staged-15.1.0.tgz",
|
||||
"integrity": "sha512-ZPKXWHVlL7uwVpy8OZ7YQjYDAuO5X4kMh0XgZvPNxLcCCngd0PO5jKQyy3+s4TL2EnHoIXIzP1422f/l3nZKMw==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"chalk": "5.3.0",
|
||||
@@ -5191,7 +5191,7 @@
|
||||
"micromatch": "4.0.5",
|
||||
"pidtree": "0.6.0",
|
||||
"string-argv": "0.3.2",
|
||||
"yaml": "2.3.3"
|
||||
"yaml": "2.3.4"
|
||||
},
|
||||
"bin": {
|
||||
"lint-staged": "bin/lint-staged.js"
|
||||
@@ -6122,9 +6122,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/prettier": {
|
||||
"version": "3.0.3",
|
||||
"resolved": "https://registry.npmjs.org/prettier/-/prettier-3.0.3.tgz",
|
||||
"integrity": "sha512-L/4pUDMxcNa8R/EthV08Zt42WBO4h1rarVtK0K+QJG0X187OLo7l699jWw0GKuwzkPQ//jMFA/8Xm6Fh3J/DAg==",
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/prettier/-/prettier-3.1.0.tgz",
|
||||
"integrity": "sha512-TQLvXjq5IAibjh8EpBIkNKxO749UEWABoiIZehEPiY4GNpVdhaFKqSTu+QrlU6D2dPAfubRmtJTi4K4YkQ5eXw==",
|
||||
"dev": true,
|
||||
"bin": {
|
||||
"prettier": "bin/prettier.cjs"
|
||||
@@ -7221,9 +7221,9 @@
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/yaml": {
|
||||
"version": "2.3.3",
|
||||
"resolved": "https://registry.npmjs.org/yaml/-/yaml-2.3.3.tgz",
|
||||
"integrity": "sha512-zw0VAJxgeZ6+++/su5AFoqBbZbrEakwu+X0M5HmcwUiBL7AzcuPKjj5we4xfQLp78LkEMpD0cOnUhmgOVy3KdQ==",
|
||||
"version": "2.3.4",
|
||||
"resolved": "https://registry.npmjs.org/yaml/-/yaml-2.3.4.tgz",
|
||||
"integrity": "sha512-8aAvwVUSHpfEqTQ4w/KMlf3HcRdt50E5ODIQJBw1fQ5RL34xabzxtUlzTXVqc4rkZsPbvrXKWnABCD7kWSmocA==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">= 14"
|
||||
@@ -7309,9 +7309,9 @@
|
||||
}
|
||||
},
|
||||
"@adobe/css-tools": {
|
||||
"version": "4.3.1",
|
||||
"resolved": "https://registry.npmjs.org/@adobe/css-tools/-/css-tools-4.3.1.tgz",
|
||||
"integrity": "sha512-/62yikz7NLScCGAAST5SHdnjaDJQBDq0M2muyRTpf2VQhw6StBg2ALiu73zSJQ4fMVLA+0uBhBHAle7Wg+2kSg==",
|
||||
"version": "4.3.2",
|
||||
"resolved": "https://registry.npmjs.org/@adobe/css-tools/-/css-tools-4.3.2.tgz",
|
||||
"integrity": "sha512-DA5a1C0gD/pLOvhv33YMrbf2FK3oUzwNl9oOJqE4XVjuEtt6XIakRcsd7eLiOSPkp1kTRQGICTA8cKra/vFbjw==",
|
||||
"dev": true
|
||||
},
|
||||
"@ampproject/remapping": {
|
||||
@@ -7826,9 +7826,9 @@
|
||||
"dev": true
|
||||
},
|
||||
"@eslint/eslintrc": {
|
||||
"version": "2.1.2",
|
||||
"resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.2.tgz",
|
||||
"integrity": "sha512-+wvgpDsrB1YqAMdEUCcnTlpfVBH7Vqn6A/NT3D8WVXFIaKMlErPIZT3oCIAVCOtarRpMtelZLqJeU3t7WY6X6g==",
|
||||
"version": "2.1.3",
|
||||
"resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.3.tgz",
|
||||
"integrity": "sha512-yZzuIG+jnVu6hNSzFEN07e8BxF3uAzYtQb6uDkaYZLo6oYZDCq454c5kB8zxnzfCYyP4MIuyBn10L0DqwujTmA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"ajv": "^6.12.4",
|
||||
@@ -7843,9 +7843,9 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"globals": {
|
||||
"version": "13.21.0",
|
||||
"resolved": "https://registry.npmjs.org/globals/-/globals-13.21.0.tgz",
|
||||
"integrity": "sha512-ybyme3s4yy/t/3s35bewwXKOf7cvzfreG2lH0lZl0JB7I4GxRP2ghxOK/Nb9EkRXdbBXZLfq/p/0W2JUONB/Gg==",
|
||||
"version": "13.23.0",
|
||||
"resolved": "https://registry.npmjs.org/globals/-/globals-13.23.0.tgz",
|
||||
"integrity": "sha512-XAmF0RjlrjY23MA51q3HltdlGxUpXPvg0GioKiD9X6HD28iMjo2dKC8Vqwm7lne4GNr78+RHTfliktR6ZH09wA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"type-fest": "^0.20.2"
|
||||
@@ -7860,9 +7860,9 @@
|
||||
}
|
||||
},
|
||||
"@eslint/js": {
|
||||
"version": "8.52.0",
|
||||
"resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.52.0.tgz",
|
||||
"integrity": "sha512-mjZVbpaeMZludF2fsWLD0Z9gCref1Tk4i9+wddjRvpUNqqcndPkBD09N/Mapey0b3jaXbLm2kICwFv2E64QinA==",
|
||||
"version": "8.54.0",
|
||||
"resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.54.0.tgz",
|
||||
"integrity": "sha512-ut5V+D+fOoWPgGGNj83GGjnntO39xDy6DWxO0wb7Jp3DcMX0TfIqdzHF85VTQkerdyGmuuMD9AKAo5KiNlf/AQ==",
|
||||
"dev": true
|
||||
},
|
||||
"@fastify/busboy": {
|
||||
@@ -8728,9 +8728,9 @@
|
||||
"dev": true
|
||||
},
|
||||
"axios": {
|
||||
"version": "1.5.1",
|
||||
"resolved": "https://registry.npmjs.org/axios/-/axios-1.5.1.tgz",
|
||||
"integrity": "sha512-Q28iYCWzNHjAm+yEAot5QaAMxhMghWLFVf7rRdwhUI+c2jix2DUXjAHXVi+s1ibs3mjPO/cCgbA++3BjD0vP/A==",
|
||||
"version": "1.6.2",
|
||||
"resolved": "https://registry.npmjs.org/axios/-/axios-1.6.2.tgz",
|
||||
"integrity": "sha512-7i24Ri4pmDRfJTR7LDBhsOTtcm+9kjX5WiY1X3wIisx6G9So3pfMkEiU7emUBe46oceVImccTEM3k6C5dbVW8A==",
|
||||
"requires": {
|
||||
"follow-redirects": "^1.15.0",
|
||||
"form-data": "^4.0.0",
|
||||
@@ -9355,15 +9355,15 @@
|
||||
}
|
||||
},
|
||||
"eslint": {
|
||||
"version": "8.52.0",
|
||||
"resolved": "https://registry.npmjs.org/eslint/-/eslint-8.52.0.tgz",
|
||||
"integrity": "sha512-zh/JHnaixqHZsolRB/w9/02akBk9EPrOs9JwcTP2ek7yL5bVvXuRariiaAjjoJ5DvuwQ1WAE/HsMz+w17YgBCg==",
|
||||
"version": "8.54.0",
|
||||
"resolved": "https://registry.npmjs.org/eslint/-/eslint-8.54.0.tgz",
|
||||
"integrity": "sha512-NY0DfAkM8BIZDVl6PgSa1ttZbx3xHgJzSNJKYcQglem6CppHyMhRIQkBVSSMaSRnLhig3jsDbEzOjwCVt4AmmA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@eslint-community/eslint-utils": "^4.2.0",
|
||||
"@eslint-community/regexpp": "^4.6.1",
|
||||
"@eslint/eslintrc": "^2.1.2",
|
||||
"@eslint/js": "8.52.0",
|
||||
"@eslint/eslintrc": "^2.1.3",
|
||||
"@eslint/js": "8.54.0",
|
||||
"@humanwhocodes/config-array": "^0.11.13",
|
||||
"@humanwhocodes/module-importer": "^1.0.1",
|
||||
"@nodelib/fs.walk": "^1.2.8",
|
||||
@@ -11116,9 +11116,9 @@
|
||||
"dev": true
|
||||
},
|
||||
"lint-staged": {
|
||||
"version": "15.0.2",
|
||||
"resolved": "https://registry.npmjs.org/lint-staged/-/lint-staged-15.0.2.tgz",
|
||||
"integrity": "sha512-vnEy7pFTHyVuDmCAIFKR5QDO8XLVlPFQQyujQ/STOxe40ICWqJ6knS2wSJ/ffX/Lw0rz83luRDh+ET7toN+rOw==",
|
||||
"version": "15.1.0",
|
||||
"resolved": "https://registry.npmjs.org/lint-staged/-/lint-staged-15.1.0.tgz",
|
||||
"integrity": "sha512-ZPKXWHVlL7uwVpy8OZ7YQjYDAuO5X4kMh0XgZvPNxLcCCngd0PO5jKQyy3+s4TL2EnHoIXIzP1422f/l3nZKMw==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"chalk": "5.3.0",
|
||||
@@ -11130,7 +11130,7 @@
|
||||
"micromatch": "4.0.5",
|
||||
"pidtree": "0.6.0",
|
||||
"string-argv": "0.3.2",
|
||||
"yaml": "2.3.3"
|
||||
"yaml": "2.3.4"
|
||||
},
|
||||
"dependencies": {
|
||||
"chalk": {
|
||||
@@ -11777,9 +11777,9 @@
|
||||
"dev": true
|
||||
},
|
||||
"prettier": {
|
||||
"version": "3.0.3",
|
||||
"resolved": "https://registry.npmjs.org/prettier/-/prettier-3.0.3.tgz",
|
||||
"integrity": "sha512-L/4pUDMxcNa8R/EthV08Zt42WBO4h1rarVtK0K+QJG0X187OLo7l699jWw0GKuwzkPQ//jMFA/8Xm6Fh3J/DAg==",
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/prettier/-/prettier-3.1.0.tgz",
|
||||
"integrity": "sha512-TQLvXjq5IAibjh8EpBIkNKxO749UEWABoiIZehEPiY4GNpVdhaFKqSTu+QrlU6D2dPAfubRmtJTi4K4YkQ5eXw==",
|
||||
"dev": true
|
||||
},
|
||||
"pretty-format": {
|
||||
@@ -12552,9 +12552,9 @@
|
||||
"dev": true
|
||||
},
|
||||
"yaml": {
|
||||
"version": "2.3.3",
|
||||
"resolved": "https://registry.npmjs.org/yaml/-/yaml-2.3.3.tgz",
|
||||
"integrity": "sha512-zw0VAJxgeZ6+++/su5AFoqBbZbrEakwu+X0M5HmcwUiBL7AzcuPKjj5we4xfQLp78LkEMpD0cOnUhmgOVy3KdQ==",
|
||||
"version": "2.3.4",
|
||||
"resolved": "https://registry.npmjs.org/yaml/-/yaml-2.3.4.tgz",
|
||||
"integrity": "sha512-8aAvwVUSHpfEqTQ4w/KMlf3HcRdt50E5ODIQJBw1fQ5RL34xabzxtUlzTXVqc4rkZsPbvrXKWnABCD7kWSmocA==",
|
||||
"dev": true
|
||||
},
|
||||
"yargs": {
|
||||
|
||||
+4
-4
@@ -43,7 +43,7 @@
|
||||
"@uppercod/css-to-object": "^1.1.1",
|
||||
"axios-mock-adapter": "^1.22.0",
|
||||
"color-contrast-checker": "^2.1.0",
|
||||
"eslint": "^8.52.0",
|
||||
"eslint": "^8.54.0",
|
||||
"eslint-config-prettier": "^9.0.0",
|
||||
"hjson": "^3.2.2",
|
||||
"husky": "^8.0.3",
|
||||
@@ -51,13 +51,13 @@
|
||||
"jest-bench": "^29.4.1",
|
||||
"jest-environment-jsdom": "^29.7.0",
|
||||
"js-yaml": "^4.1.0",
|
||||
"lint-staged": "^15.0.2",
|
||||
"lint-staged": "^15.1.0",
|
||||
"lodash.snakecase": "^4.1.1",
|
||||
"parse-diff": "^0.11.1",
|
||||
"prettier": "^3.0.3"
|
||||
"prettier": "^3.1.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"axios": "^1.5.1",
|
||||
"axios": "^1.6.2",
|
||||
"dotenv": "^16.3.1",
|
||||
"emoji-name-map": "^1.2.8",
|
||||
"github-username-regex": "^1.0.0",
|
||||
|
||||
@@ -65,7 +65,7 @@
|
||||
</p>
|
||||
</p>
|
||||
|
||||
<p align="center">Please note that documentation translations may be outdated, try to use english documentation if possible.</p>
|
||||
<p align="center">Please note that documentation translations may be outdated; try to use English documentation if possible.</p>
|
||||
|
||||
<p align="center">Love the project? Please consider <a href="https://www.paypal.me/anuraghazra">donating</a> to help it improve!</p>
|
||||
|
||||
@@ -73,11 +73,11 @@
|
||||
<img src="https://cfstatic.give.do/logo.png" alt="Give india logo" width="200" />
|
||||
</a>
|
||||
|
||||
Are you considering supporting the project by donating to me? Please DO NOT!!
|
||||
Are you considering supporting the project by donating to me? Please DO NOT!!!
|
||||
|
||||
<img src="https://cfstatic.give.do/910ede2a-7892-43fe-8c8a-dea45e96d950.webp" alt="Picture of Coromandel Express train tragedy" width="35%">
|
||||
|
||||
India just suffered one of the most devastating train accident and your help will be immensely valuable for the people who were affected by this tragedy.
|
||||
India has recently suffered one of the most devastating train accidents, and your help will be immensely valuable for the people who were affected by this tragedy.
|
||||
|
||||
Please visit [this link](https://give.do/fundraisers/stand-beside-the-victims-of-the-coromandel-express-train-tragedy-in-odisha-donate-now) and make a small donation to help the people in need. A small donation goes a long way. :heart:
|
||||
|
||||
@@ -129,11 +129,11 @@ Please visit [this link](https://give.do/fundraisers/stand-beside-the-victims-of
|
||||
<img alt="Uptime Badge" src="https://img.shields.io/endpoint?url=https%3A%2F%2Fgithub-readme-stats-git-monitoring-github-readme-stats-team.vercel.app%2Fapi%2Fstatus%2Fup%3Ftype%3Dshields">
|
||||
|
||||
> [!IMPORTANT]\
|
||||
> We're a small team, and to prioritize, we rely on upvotes :+1:. We use Top issues dashboard for tracking community demand (see [#1935](https://github.com/anuraghazra/github-readme-stats/issues/1935)). Do not hesitate to upvote the issues and pull requests you are interested in. We will work on the most upvoted first.
|
||||
> We're a small team, and to prioritize, we rely on upvotes :+1:. We use the Top Issues dashboard for tracking community demand (see [#1935](https://github.com/anuraghazra/github-readme-stats/issues/1935)). Do not hesitate to upvote the issues and pull requests you are interested in. We will work on the most upvoted first.
|
||||
|
||||
# GitHub Stats Card
|
||||
|
||||
Copy-paste this into your markdown content, and that is it. Simple!
|
||||
Copy and paste this into your markdown, and that's it. Simple!
|
||||
|
||||
Change the `?username=` value to your GitHub username.
|
||||
|
||||
@@ -142,10 +142,10 @@ Change the `?username=` value to your GitHub username.
|
||||
```
|
||||
|
||||
> [!WARNING]\
|
||||
> By default, the stats card only shows statistics like stars, commits and pull requests from public repositories. To show private statistics on the stats card, you should [deploy your own instance](#deploy-on-your-own) using your own GitHub API token.
|
||||
> By default, the stats card only shows statistics like stars, commits, and pull requests from public repositories. To show private statistics on the stats card, you should [deploy your own instance](#deploy-on-your-own) using your own GitHub API token.
|
||||
|
||||
> [!NOTE]\
|
||||
> Available ranks are S (top 1%), A+ (12.5%), A (25%), A- (37.5%), B+ (50%), B (62.5%), B- (75%), C+ (87.5%) and C (everyone). This ranking scheme is based on the [Japanese academic grading](https://wikipedia.org/wiki/Academic_grading_in_Japan) system. The global percentile is calculated as a weighted sum of percentiles for each statistic (number of commits, pull requests, reviews, issues, stars and followers), based on the cumulative distribution function of the [exponential](https://wikipedia.org/wiki/exponential_distribution) and the [log-normal](https://wikipedia.org/wiki/Log-normal_distribution) distributions. The implementation can be investigated at [src/calculateRank.js](https://github.com/anuraghazra/github-readme-stats/blob/master/src/calculateRank.js). The circle around the rank shows 100 minus the global percentile.
|
||||
> Available ranks are S (top 1%), A+ (12.5%), A (25%), A- (37.5%), B+ (50%), B (62.5%), B- (75%), C+ (87.5%) and C (everyone). This ranking scheme is based on the [Japanese academic grading](https://wikipedia.org/wiki/Academic_grading_in_Japan) system. The global percentile is calculated as a weighted sum of percentiles for each statistic (number of commits, pull requests, reviews, issues, stars, and followers), based on the cumulative distribution function of the [exponential](https://wikipedia.org/wiki/exponential_distribution) and the [log-normal](https://wikipedia.org/wiki/Log-normal_distribution) distributions. The implementation can be investigated at [src/calculateRank.js](https://github.com/anuraghazra/github-readme-stats/blob/master/src/calculateRank.js). The circle around the rank shows 100 minus the global percentile.
|
||||
|
||||
### Hiding individual stats
|
||||
|
||||
@@ -191,7 +191,7 @@ GitHub Readme Stats comes with several built-in themes (e.g. `dark`, `radical`,
|
||||
|
||||
<img src="https://res.cloudinary.com/anuraghazra/image/upload/v1595174536/grs-themes_l4ynja.png" alt="GitHub Readme Stats Themes" width="600px"/>
|
||||
|
||||
You can look at a preview for [all available themes](themes/README.md) or checkout the [theme config file](themes/index.js). Please note that we paused addition of new themes to decrease maintenance efforts, all pull requests related to new themes will be closed.
|
||||
You can look at a preview for [all available themes](themes/README.md) or checkout the [theme config file](themes/index.js). Please note that we paused the addition of new themes to decrease maintenance efforts; all pull requests related to new themes will be closed.
|
||||
|
||||
#### Responsive Card Theme
|
||||
|
||||
@@ -288,16 +288,18 @@ You can customize the appearance of all your cards however you wish with URL par
|
||||
|
||||
#### Common Options
|
||||
|
||||
* `title_color` - Card's title color *(hex color)*. Default: `2f80ed`.
|
||||
* `text_color` - Body text color *(hex color)*. Default: `434d58`.
|
||||
* `icon_color` - Icons color if available *(hex color)*. Default: `4c71f2`.
|
||||
* `border_color` - Card's border color *(hex color)*. Default: `e4e2e2` (Does not apply when `hide_border` is enabled).
|
||||
* `bg_color` - Card's background color *(hex color)* **or** a gradient in the form of *angle,start,end*. Default: `fffefe`
|
||||
* `hide_border` - Hides the card's border *(boolean)*. Default: `false`
|
||||
* `theme` - Name of the theme, choose from [all available themes](themes/README.md). Default: `default` theme.
|
||||
* `cache_seconds` - Sets the cache header manually *(min: 21600, max: 86400)*. Default: `21600 seconds (6 hours)`.
|
||||
* `locale` - Sets the language in the card, you can check full list of available locales [here](#available-locales). Default: `en`.
|
||||
* `border_radius` - Corner rounding on the card. Default: `4.5`.
|
||||
| Name | Description | Type | Default value |
|
||||
| --- | --- | --- | --- |
|
||||
| `title_color` | Card's title color. | string (hex color) | `2f80ed` |
|
||||
| `text_color` | Body text color. | string (hex color) | `434d58` |
|
||||
| `icon_color` | Icons color if available. | string (hex color) | `4c71f2` |
|
||||
| `border_color` | Card's border color. Does not apply when `hide_border` is enabled. | string (hex color) | `e4e2e2` |
|
||||
| `bg_color` | Card's background color. | string (hex color or a gradient in the form of *angle,start,end*) | `fffefe` |
|
||||
| `hide_border` | Hides the card's border. | boolean | `false` |
|
||||
| `theme` | Name of the theme, choose from [all available themes](themes/README.md). | enum | `default` |
|
||||
| `cache_seconds` | Sets the cache header manually (min: 21600, max: 86400). | integer | `1800` |
|
||||
| `locale` | Sets the language in the card, you can check full list of available locales [here](#available-locales). | enum | `en` |
|
||||
| `border_radius` | Corner rounding on the card. | number | `4.5` |
|
||||
|
||||
> [!WARNING]\
|
||||
> We use caching to decrease the load on our servers (see <https://github.com/anuraghazra/github-readme-stats/issues/1471#issuecomment-1271551425>). Our cards have a default cache of 6 hours (21600 seconds). Also, note that the cache is clamped to a minimum of 6 hours and a maximum of 24 hours. If you want the data on your statistics card to be updated more often you can [deploy your own instance](#deploy-on-your-own) and set [environment variable](#disable-rate-limit-protections) `CACHE_SECONDS` to a value of your choosing.
|
||||
@@ -364,46 +366,55 @@ If we don't support your language, please consider contributing! You can find mo
|
||||
|
||||
#### Stats Card Exclusive Options
|
||||
|
||||
* `hide` - Hides the [specified items](#hiding-individual-stats) from stats *(Comma-separated values)*. Default: `[] (blank array)`.
|
||||
* `hide_title` - *(boolean)*. Default: `false`.
|
||||
* `card_width` - Sets the card's width manually *(number)*. Default: `500px (approx.)`.
|
||||
* `hide_rank` - *(boolean)* hides the rank and automatically resizes the card width. Default: `false`.
|
||||
* `rank_icon` - Shows alternative rank icon (i.e. `github`, `percentile` or `default`). Default: `default`.
|
||||
* `show_icons` - *(boolean)*. Default: `false`.
|
||||
* `include_all_commits` - Counts total commits instead of just the current year commits *(boolean)*. Default: `false`.
|
||||
* `line_height` - Sets the line height between text *(number)*. Default: `25`.
|
||||
* `exclude_repo` - Excludes stars from specified repositories *(Comma-separated values)*. Default: `[] (blank array)`.
|
||||
* `custom_title` - Sets a custom title for the card. Default: `<username> GitHub Stats`.
|
||||
* `text_bold` - Uses bold text *(boolean)*. Default: `true`.
|
||||
* `disable_animations` - Disables all animations in the card *(boolean)*. Default: `false`.
|
||||
* `ring_color` - Color of the rank circle *(hex color)*. Defaults to the theme ring color if it exists and otherwise the title color.
|
||||
* `number_format` - Switches between two available formats for displaying the card values `short` (i.e. `6.6k`) and `long` (i.e. `6626`). Default: `short`.
|
||||
* `show` - Shows [additional items](#showing-additional-individual-stats) on stats card (i.e. `reviews`, `discussions_started`, `discussions_answered`, `prs_merged` or `prs_merged_percentage`) *(Comma-separated values)*. Default: `[] (blank array)`.
|
||||
| Name | Description | Type | Default value |
|
||||
| --- | --- | --- | --- |
|
||||
| `hide` | Hides the [specified items](#hiding-individual-stats) from stats. | string (comma-separated values) | `null` |
|
||||
| `hide_title` | Hides the title of your stats card. | boolean | `false` |
|
||||
| `card_width` | Sets the card's width manually. | number | `500px (approx.)` |
|
||||
| `hide_rank` | Hides the rank and automatically resizes the card width. | boolean | `false` |
|
||||
| `rank_icon` | Shows alternative rank icon (i.e. `github`, `percentile` or `default`). | enum | `default` |
|
||||
| `show_icons` | Shows icons near all stats. | boolean | `false` |
|
||||
| `include_all_commits` | Count total commits instead of just the current year commits. | boolean | `false` |
|
||||
| `line_height` | Sets the line height between text. | integer | `25` |
|
||||
| `exclude_repo` | Excludes specified repositories. | string (comma-separated values) | `null` |
|
||||
| `custom_title` | Sets a custom title for the card. | string | `<username> GitHub Stats` |
|
||||
| `text_bold` | Uses bold text. | boolean | `true` |
|
||||
| `disable_animations` | Disables all animations in the card. | boolean | `false` |
|
||||
| `ring_color` | Color of the rank circle. | string (hex color) | `2f80ed` |
|
||||
| `number_format` | Switches between two available formats for displaying the card values `short` (i.e. `6.6k`) and `long` (i.e. `6626`). | enum | `short` |
|
||||
| `show` | Shows [additional items](#showing-additional-individual-stats) on stats card (i.e. `reviews`, `discussions_started`, `discussions_answered`, `prs_merged` or `prs_merged_percentage`). | string (comma-separated values) | `null` |
|
||||
|
||||
> [!NOTE]\
|
||||
> When hide\_rank=`true`, the minimum card width is 270 px + the title length and padding.
|
||||
|
||||
#### Repo Card Exclusive Options
|
||||
|
||||
* `show_owner` - Shows the repo's owner name *(boolean)*. Default: `false`.
|
||||
| Name | Description | Type | Default value |
|
||||
| --- | --- | --- | --- |
|
||||
| `show_owner` | Shows the repo's owner name. | boolean | `false` |
|
||||
| `description_lines_count` | Manually set the number of lines for the description. Specified value will be clamped between 1 and 3. If this parameter is not specified, the number of lines will be automatically adjusted according to the actual length of the description. | number | `null` |
|
||||
|
||||
#### Gist Card Exclusive Options
|
||||
|
||||
* `show_owner` - Shows the gist's owner name *(boolean)*. Default: `false`.
|
||||
| Name | Description | Type | Default value |
|
||||
| --- | --- | --- | --- |
|
||||
| `show_owner` | Shows the gist's owner name. | boolean | `false` |
|
||||
|
||||
#### Language Card Exclusive Options
|
||||
|
||||
* `hide` - Hides the languages specified from the card *(Comma-separated values)*. Default: `[] (blank array)`.
|
||||
* `hide_title` - *(boolean)*. Default: `false`.
|
||||
* `layout` - Switches between five available layouts `normal` & `compact` & `donut` & `donut-vertical` & `pie`. Default: `normal`.
|
||||
* `card_width` - Sets the card's width manually *(number)*. Default `300`.
|
||||
* `langs_count` - Shows more languages on the card, between 1-20 *(number)*. Default: `5` for `normal` and `donut`, `6` for other layouts.
|
||||
* `exclude_repo` - Excludes specified repositories *(Comma-separated values)*. Default: `[] (blank array)`.
|
||||
* `custom_title` - Sets a custom title for the card *(string)*. Default `Most Used Languages`.
|
||||
* `disable_animations` - Disables all animations in the card *(boolean)*. Default: `false`.
|
||||
* `hide_progress` - Uses the compact layout option, hides percentages, and removes the bars. Default: `false`.
|
||||
* `size_weight` - Configures language stats algorithm *(number)* (see [Language stats algorithm](#Language-stats-algorithm)), defaults to 1.
|
||||
* `count_weight` - Configures language stats algorithm *(number)* (see [Language stats algorithm](#Language-stats-algorithm)), defaults to 0.
|
||||
| Name | Description | Type | Default value |
|
||||
| --- | --- | --- | --- |
|
||||
| `hide` | Hides the [specified languages](#hide-individual-languages) from card. | string (comma-separated values) | `null` |
|
||||
| `hide_title` | Hides the title of your card. | boolean | `false` |
|
||||
| `layout` | Switches between five available layouts `normal` & `compact` & `donut` & `donut-vertical` & `pie`. | enum | `normal` |
|
||||
| `card_width` | Sets the card's width manually. | number | `300` |
|
||||
| `langs_count` | Shows more languages on the card, between 1-20. | integer | `5` for `normal` and `donut`, `6` for other layouts |
|
||||
| `exclude_repo` | Excludes specified repositories. | string (comma-separated values) | `null` |
|
||||
| `custom_title` | Sets a custom title for the card. | string | `Most Used Languages` |
|
||||
| `disable_animations` | Disables all animations in the card. | boolean | `false` |
|
||||
| `hide_progress` | Uses the compact layout option, hides percentages, and removes the bars. | boolean | `false` |
|
||||
| `size_weight` | Configures language stats algorithm (see [Language stats algorithm](#language-stats-algorithm)). | integer | `1` |
|
||||
| `count_weight` | Configures language stats algorithm (see [Language stats algorithm](#language-stats-algorithm)). | integer | `0` |
|
||||
|
||||
> [!WARNING]\
|
||||
> Language names should be URI-escaped, as specified in [Percent Encoding](https://en.wikipedia.org/wiki/Percent-encoding)
|
||||
@@ -412,14 +423,18 @@ If we don't support your language, please consider contributing! You can find mo
|
||||
|
||||
#### WakaTime Card Exclusive Options
|
||||
|
||||
* `hide` - Hides the languages specified from the card *(Comma-separated values)*. Default: `[] (blank array)`.
|
||||
* `hide_title` - *(boolean)*. Default `false`.
|
||||
* `line_height` - Sets the line height between text *(number)*. Default `25`.
|
||||
* `hide_progress` - Hides the progress bar and percentage *(boolean)*. Default `false`.
|
||||
* `custom_title` - Sets a custom title for the card *(string)*. Default `WakaTime Stats`.
|
||||
* `layout` - Switches between two available layouts `default` & `compact`. Default `default`.
|
||||
* `langs_count` - Limits the number of languages on the card, defaults to all reported languages *(number)*.
|
||||
* `api_domain` - Sets a custom API domain for the card, e.g. to use services like [Hakatime](https://github.com/mujx/hakatime) or [Wakapi](https://github.com/muety/wakapi) *(string)*. Default `Waka API`.
|
||||
| Name | Description | Type | Default value |
|
||||
| --- | --- | --- | --- |
|
||||
| `hide` | Hides the languages specified from the card. | string (comma-separated values) | `null` |
|
||||
| `hide_title` | Hides the title of your card. | boolean | `false` |
|
||||
| `line_height` | Sets the line height between text. | integer | `25` |
|
||||
| `hide_progress` | Hides the progress bar and percentage. | boolean | `false` |
|
||||
| `custom_title` | Sets a custom title for the card. | string | `WakaTime Stats` |
|
||||
| `layout` | Switches between two available layouts `default` & `compact`. | enum | `default` |
|
||||
| `langs_count` | Limits the number of languages on the card, defaults to all reported languages. | integer | `null` |
|
||||
| `api_domain` | Sets a custom API domain for the card, e.g. to use services like [Hakatime](https://github.com/mujx/hakatime) or [Wakapi](https://github.com/muety/wakapi) | string | `Waka API` |
|
||||
| `display_format` | Sets the WakaTime stats display format. Choose `time` to display time-based stats or `percent` to show percentages. | enum | `time` |
|
||||
| `disable_animations` | Disables all animations in the card. | boolean | `false` |
|
||||
|
||||
***
|
||||
|
||||
@@ -480,10 +495,10 @@ The top languages card shows a GitHub user's most frequently used languages.
|
||||
> Top Languages does not indicate the user's skill level or anything like that; it's a GitHub metric to determine which languages have the most code on GitHub. It is a new feature of github-readme-stats.
|
||||
|
||||
> [!WARNING]\
|
||||
> This card shows languages usage only inside your own non-forked repositories, not depending from who is the author of the commits. It does not include your contributions into another users/organizations repositories. Currently there are no way to get this data from GitHub API. If you want this behavior to be improved you can support [this feature request](https://github.com/orgs/community/discussions/18230) created by [@rickstaa](https://github.com/rickstaa) inside GitHub Community.
|
||||
> This card shows language usage only inside your own non-forked repositories, not depending on who the author of the commits is. It does not include your contributions into another users/organizations repositories. Currently there are no way to get this data from GitHub API. If you want this behavior to be improved you can support [this feature request](https://github.com/orgs/community/discussions/18230) created by [@rickstaa](https://github.com/rickstaa) inside GitHub Community.
|
||||
|
||||
> [!WARNING]\
|
||||
> Currently this card shows data only about first 100 repositories. This is because GitHub API limitations which cause downtimes of public instance (see [#1471](https://github.com/anuraghazra/github-readme-stats/issues/1471)). In future this behavior will be improved by releasing GitHub action or providing environment variable for user's own instances.
|
||||
> Currently this card shows data only about first 100 repositories. This is because GitHub API limitations which cause downtimes of public instances (see [#1471](https://github.com/anuraghazra/github-readme-stats/issues/1471)). In future this behavior will be improved by releasing GitHub action or providing environment variables for user's own instances.
|
||||
|
||||
### Usage
|
||||
|
||||
@@ -800,7 +815,7 @@ Since the GitHub API only allows 5k requests per hour, my `https://github-readme
|
||||
|
||||
Github Readme Stats contains several Vercel environment variables that can be used to remove the rate limit protections:
|
||||
|
||||
* `CACHE_SECONDS`: This environment variable takes precedence over our cache minimum and maximum values and can circumvent these values for self Hosted Vercel instances.
|
||||
* `CACHE_SECONDS`: This environment variable takes precedence over our cache minimum and maximum values and can circumvent these values for self-hosted Vercel instances.
|
||||
|
||||
See [the Vercel documentation](https://vercel.com/docs/concepts/projects/environment-variables) on adding these environment variables to your Vercel instance.
|
||||
|
||||
@@ -815,9 +830,9 @@ this takes time. You can use this service for free.
|
||||
|
||||
However, if you are using this project and are happy with it or just want to encourage me to continue creating stuff, there are a few ways you can do it:
|
||||
|
||||
* Giving proper credit when you use github-readme-stats on your readme, linking back to it :D
|
||||
* Starring and sharing the project :rocket:
|
||||
* [](https://www.paypal.me/anuraghazra) - You can make one-time donations via PayPal. I'll probably buy a ~~coffee~~ tea. :tea:
|
||||
* Giving proper credit when you use github-readme-stats on your readme, linking back to it. :D
|
||||
* Starring and sharing the project. :rocket:
|
||||
* [](https://www.paypal.me/anuraghazra) - You can make a one-time donations via PayPal. I'll probably buy a ~~coffee~~ tea. :tea:
|
||||
|
||||
Thanks! :heart:
|
||||
|
||||
|
||||
+21
-6
@@ -12,10 +12,13 @@ import {
|
||||
wrapTextMultiline,
|
||||
iconWithLabel,
|
||||
createLanguageNode,
|
||||
clampValue,
|
||||
} from "../common/utils.js";
|
||||
import { repoCardLocales } from "../translations.js";
|
||||
|
||||
const ICON_SIZE = 16;
|
||||
const DESCRIPTION_LINE_WIDTH = 59;
|
||||
const DESCRIPTION_MAX_LINES = 3;
|
||||
|
||||
/**
|
||||
* Retrieves the repository description and wraps it to fit the card width.
|
||||
@@ -73,22 +76,34 @@ const renderRepoCard = (repo, options = {}) => {
|
||||
border_radius,
|
||||
border_color,
|
||||
locale,
|
||||
description_lines_count,
|
||||
} = options;
|
||||
|
||||
const lineHeight = 10;
|
||||
const header = show_owner ? nameWithOwner : name;
|
||||
const langName = (primaryLanguage && primaryLanguage.name) || "Unspecified";
|
||||
const langColor = (primaryLanguage && primaryLanguage.color) || "#333";
|
||||
const descriptionMaxLines = description_lines_count
|
||||
? clampValue(description_lines_count, 1, DESCRIPTION_MAX_LINES)
|
||||
: DESCRIPTION_MAX_LINES;
|
||||
|
||||
const desc = parseEmojis(description || "No description provided");
|
||||
const multiLineDescription = wrapTextMultiline(desc);
|
||||
const descriptionLines = multiLineDescription.length;
|
||||
const multiLineDescription = wrapTextMultiline(
|
||||
desc,
|
||||
DESCRIPTION_LINE_WIDTH,
|
||||
descriptionMaxLines,
|
||||
);
|
||||
const descriptionLinesCount = description_lines_count
|
||||
? clampValue(description_lines_count, 1, DESCRIPTION_MAX_LINES)
|
||||
: multiLineDescription.length;
|
||||
|
||||
const descriptionSvg = multiLineDescription
|
||||
.map((line) => `<tspan dy="1.2em" x="25">${encodeHTML(line)}</tspan>`)
|
||||
.join("");
|
||||
|
||||
const height =
|
||||
(descriptionLines > 1 ? 120 : 110) + descriptionLines * lineHeight;
|
||||
(descriptionLinesCount > 1 ? 120 : 110) +
|
||||
descriptionLinesCount * lineHeight;
|
||||
|
||||
const i18n = new I18n({
|
||||
locale,
|
||||
@@ -160,9 +175,9 @@ const renderRepoCard = (repo, options = {}) => {
|
||||
? // @ts-ignore
|
||||
getBadgeSVG(i18n.t("repocard.template"), colors.textColor)
|
||||
: isArchived
|
||||
? // @ts-ignore
|
||||
getBadgeSVG(i18n.t("repocard.archived"), colors.textColor)
|
||||
: ""
|
||||
? // @ts-ignore
|
||||
getBadgeSVG(i18n.t("repocard.archived"), colors.textColor)
|
||||
: ""
|
||||
}
|
||||
|
||||
<text class="description" x="25" y="-5">
|
||||
|
||||
@@ -76,7 +76,7 @@ const createTextNode = ({
|
||||
};
|
||||
|
||||
/**
|
||||
* Calculates progress along the boundary of the circle i.e it's circumference.
|
||||
* Calculates progress along the boundary of the circle, i.e. its circumference.
|
||||
*
|
||||
* @param {number} value The rank value to calculate progress for.
|
||||
* @returns {number} Progress value.
|
||||
@@ -412,8 +412,8 @@ const renderStatsCard = (stats, options = {}) => {
|
||||
custom_title
|
||||
? custom_title
|
||||
: statItems.length
|
||||
? i18n.t("statcard.title")
|
||||
: i18n.t("statcard.ranktitle"),
|
||||
? i18n.t("statcard.title")
|
||||
: i18n.t("statcard.ranktitle"),
|
||||
);
|
||||
};
|
||||
|
||||
@@ -431,14 +431,14 @@ const renderStatsCard = (stats, options = {}) => {
|
||||
Infinity,
|
||||
)
|
||||
: statItems.length
|
||||
? RANK_CARD_MIN_WIDTH
|
||||
: RANK_ONLY_CARD_MIN_WIDTH) + iconWidth;
|
||||
? RANK_CARD_MIN_WIDTH
|
||||
: RANK_ONLY_CARD_MIN_WIDTH) + iconWidth;
|
||||
const defaultCardWidth =
|
||||
(hide_rank
|
||||
? CARD_DEFAULT_WIDTH
|
||||
: statItems.length
|
||||
? RANK_CARD_DEFAULT_WIDTH
|
||||
: RANK_ONLY_CARD_DEFAULT_WIDTH) + iconWidth;
|
||||
? RANK_CARD_DEFAULT_WIDTH
|
||||
: RANK_ONLY_CARD_DEFAULT_WIDTH) + iconWidth;
|
||||
let width = card_width
|
||||
? isNaN(card_width)
|
||||
? defaultCardWidth
|
||||
|
||||
@@ -755,8 +755,8 @@ const renderTopLanguages = (topLangs, options = {}) => {
|
||||
? isNaN(card_width)
|
||||
? DEFAULT_CARD_WIDTH
|
||||
: card_width < MIN_CARD_WIDTH
|
||||
? MIN_CARD_WIDTH
|
||||
: card_width
|
||||
? MIN_CARD_WIDTH
|
||||
: card_width
|
||||
: DEFAULT_CARD_WIDTH;
|
||||
let height = calculateNormalLayoutHeight(langs.length);
|
||||
|
||||
|
||||
Vendored
+3
@@ -32,6 +32,7 @@ export type StatCardOptions = CommonOptions & {
|
||||
|
||||
export type RepoCardOptions = CommonOptions & {
|
||||
show_owner: boolean;
|
||||
description_lines_count: number;
|
||||
};
|
||||
|
||||
export type TopLangOptions = CommonOptions & {
|
||||
@@ -53,6 +54,8 @@ type WakaTimeOptions = CommonOptions & {
|
||||
custom_title: string;
|
||||
layout: "compact" | "normal";
|
||||
langs_count: number;
|
||||
display_format: "time" | "percent";
|
||||
disable_animations: boolean;
|
||||
};
|
||||
|
||||
export type GistCardOptions = CommonOptions & {
|
||||
|
||||
@@ -46,16 +46,21 @@ const noCodingActivityNode = ({ color, text }) => {
|
||||
* @param {WakaTimeLang} args.lang The languages array.
|
||||
* @param {number} args.x The x position of the language node.
|
||||
* @param {number} args.y The y position of the language node.
|
||||
* @param {"time" | "percent"} args.display_format The display format of the language node.
|
||||
* @returns {string} The compact layout language SVG node.
|
||||
*/
|
||||
const createCompactLangNode = ({ lang, x, y }) => {
|
||||
const createCompactLangNode = ({ lang, x, y, display_format }) => {
|
||||
const color = languageColors[lang.name] || "#858585";
|
||||
const value =
|
||||
display_format === "percent"
|
||||
? `${lang.percent.toFixed(2).toString()} %`
|
||||
: lang.text;
|
||||
|
||||
return `
|
||||
<g transform="translate(${x}, ${y})">
|
||||
<circle cx="5" cy="6" r="5" fill="${color}" />
|
||||
<text data-testid="lang-name" x="15" y="10" class='lang-name'>
|
||||
${lang.name} - ${lang.text}
|
||||
${lang.name} - ${value}
|
||||
</text>
|
||||
</g>
|
||||
`;
|
||||
@@ -67,21 +72,24 @@ const createCompactLangNode = ({ lang, x, y }) => {
|
||||
* @param {Object} args The function arguments.
|
||||
* @param {WakaTimeLang[]} args.langs The language objects.
|
||||
* @param {number} args.y The y position of the language node.
|
||||
* @param {"time" | "percent"} args.display_format The display format of the language node.
|
||||
* @returns {string[]} The language text node items.
|
||||
*/
|
||||
const createLanguageTextNode = ({ langs, y }) => {
|
||||
const createLanguageTextNode = ({ langs, y, display_format }) => {
|
||||
return langs.map((lang, index) => {
|
||||
if (index % 2 === 0) {
|
||||
return createCompactLangNode({
|
||||
lang,
|
||||
x: 25,
|
||||
y: 12.5 * index + y,
|
||||
display_format,
|
||||
});
|
||||
}
|
||||
return createCompactLangNode({
|
||||
lang,
|
||||
x: 230,
|
||||
y: 12.5 + 12.5 * index,
|
||||
display_format,
|
||||
});
|
||||
});
|
||||
};
|
||||
@@ -218,6 +226,8 @@ const renderWakatimeCard = (stats = {}, options = { hide: [] }) => {
|
||||
langs_count = languages.length,
|
||||
border_radius,
|
||||
border_color,
|
||||
display_format = "time",
|
||||
disable_animations,
|
||||
} = options;
|
||||
|
||||
const shouldHideLangs = Array.isArray(hide) && hide.length > 0;
|
||||
@@ -311,6 +321,7 @@ const renderWakatimeCard = (stats = {}, options = { hide: [] }) => {
|
||||
? createLanguageTextNode({
|
||||
y: 25,
|
||||
langs: filteredLanguages,
|
||||
display_format,
|
||||
}).join("")
|
||||
: noCodingActivityNode({
|
||||
// @ts-ignore
|
||||
@@ -330,7 +341,10 @@ const renderWakatimeCard = (stats = {}, options = { hide: [] }) => {
|
||||
return createTextNode({
|
||||
id: language.name,
|
||||
label: language.name,
|
||||
value: language.text,
|
||||
value:
|
||||
display_format === "percent"
|
||||
? `${language.percent.toFixed(2).toString()} %`
|
||||
: language.text,
|
||||
index,
|
||||
percent: language.percent,
|
||||
// @ts-ignore
|
||||
@@ -382,6 +396,10 @@ const renderWakatimeCard = (stats = {}, options = { hide: [] }) => {
|
||||
},
|
||||
});
|
||||
|
||||
if (disable_animations) {
|
||||
card.disableAnimations();
|
||||
}
|
||||
|
||||
card.setHideBorder(hide_border);
|
||||
card.setHideTitle(hide_title);
|
||||
card.setCSS(
|
||||
|
||||
@@ -53,6 +53,7 @@
|
||||
"BlitzBasic": "#00FFAE",
|
||||
"BlitzMax": "#cd6400",
|
||||
"Bluespec": "#12223c",
|
||||
"Bluespec BH": "#12223c",
|
||||
"Boo": "#d4bec1",
|
||||
"Boogie": "#c80fa0",
|
||||
"Brainfuck": "#2F2530",
|
||||
@@ -192,6 +193,7 @@
|
||||
"Gosu": "#82937f",
|
||||
"Grace": "#615f8b",
|
||||
"Gradle": "#02303a",
|
||||
"Gradle Kotlin DSL": "#02303a",
|
||||
"Grammatical Framework": "#ff0000",
|
||||
"GraphQL": "#e10098",
|
||||
"Graphviz (DOT)": "#2596be",
|
||||
@@ -422,6 +424,7 @@
|
||||
"Red": "#f50000",
|
||||
"Regular Expression": "#009a00",
|
||||
"Ren'Py": "#ff7f7f",
|
||||
"Rez": "#FFDAB3",
|
||||
"Ring": "#2D54CB",
|
||||
"Riot": "#A71E49",
|
||||
"RobotFramework": "#00c0b5",
|
||||
@@ -476,6 +479,7 @@
|
||||
"SuperCollider": "#46390b",
|
||||
"Svelte": "#ff3e00",
|
||||
"Sway": "#dea584",
|
||||
"Sweave": "#198ce7",
|
||||
"Swift": "#F05138",
|
||||
"SystemVerilog": "#DAE1C2",
|
||||
"TI Program": "#A0AA87",
|
||||
@@ -496,6 +500,7 @@
|
||||
"Turing": "#cf142b",
|
||||
"Twig": "#c1d026",
|
||||
"TypeScript": "#3178c6",
|
||||
"Typst": "#239dad",
|
||||
"Unified Parallel C": "#4e3617",
|
||||
"Unity3D Asset": "#222c37",
|
||||
"Uno": "#9933cc",
|
||||
|
||||
+75
-55
@@ -39,61 +39,6 @@ class CustomError extends Error {
|
||||
static WAKATIME_ERROR = "WAKATIME_ERROR";
|
||||
}
|
||||
|
||||
// Script parameters.
|
||||
const ERROR_CARD_LENGTH = 576.5;
|
||||
|
||||
/**
|
||||
* Encode string as HTML.
|
||||
*
|
||||
* @see https://stackoverflow.com/a/48073476/10629172
|
||||
*
|
||||
* @param {string} str String to encode.
|
||||
* @returns {string} Encoded string.
|
||||
*/
|
||||
const encodeHTML = (str) => {
|
||||
return str
|
||||
.replace(/[\u00A0-\u9999<>&](?!#)/gim, (i) => {
|
||||
return "&#" + i.charCodeAt(0) + ";";
|
||||
})
|
||||
.replace(/\u0008/gim, "");
|
||||
};
|
||||
|
||||
const UPSTREAM_API_ERRORS = [
|
||||
TRY_AGAIN_LATER,
|
||||
SECONDARY_ERROR_MESSAGES.MAX_RETRY,
|
||||
];
|
||||
|
||||
/**
|
||||
* Renders error message on the card.
|
||||
*
|
||||
* @param {string} message Main error message.
|
||||
* @param {string} secondaryMessage The secondary error message.
|
||||
* @returns {string} The SVG markup.
|
||||
*/
|
||||
const renderError = (message, secondaryMessage = "") => {
|
||||
return `
|
||||
<svg width="${ERROR_CARD_LENGTH}" height="120" viewBox="0 0 ${ERROR_CARD_LENGTH} 120" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<style>
|
||||
.text { font: 600 16px 'Segoe UI', Ubuntu, Sans-Serif; fill: #2F80ED }
|
||||
.small { font: 600 12px 'Segoe UI', Ubuntu, Sans-Serif; fill: #252525 }
|
||||
.gray { fill: #858585 }
|
||||
</style>
|
||||
<rect x="0.5" y="0.5" width="${
|
||||
ERROR_CARD_LENGTH - 1
|
||||
}" height="99%" rx="4.5" fill="#FFFEFE" stroke="#E4E2E2"/>
|
||||
<text x="25" y="45" class="text">Something went wrong!${
|
||||
UPSTREAM_API_ERRORS.includes(secondaryMessage)
|
||||
? ""
|
||||
: " file an issue at https://tiny.one/readme-stats"
|
||||
}</text>
|
||||
<text data-testid="message" x="25" y="55" class="text small">
|
||||
<tspan x="25" dy="18">${encodeHTML(message)}</tspan>
|
||||
<tspan x="25" dy="18" class="gray">${secondaryMessage}</tspan>
|
||||
</text>
|
||||
</svg>
|
||||
`;
|
||||
};
|
||||
|
||||
/**
|
||||
* Auto layout utility, allows us to layout things vertically or horizontally with
|
||||
* proper gaping.
|
||||
@@ -380,6 +325,81 @@ const getCardColors = ({
|
||||
return { titleColor, iconColor, textColor, bgColor, borderColor, ringColor };
|
||||
};
|
||||
|
||||
// Script parameters.
|
||||
const ERROR_CARD_LENGTH = 576.5;
|
||||
|
||||
/**
|
||||
* Encode string as HTML.
|
||||
*
|
||||
* @see https://stackoverflow.com/a/48073476/10629172
|
||||
*
|
||||
* @param {string} str String to encode.
|
||||
* @returns {string} Encoded string.
|
||||
*/
|
||||
const encodeHTML = (str) => {
|
||||
return str
|
||||
.replace(/[\u00A0-\u9999<>&](?!#)/gim, (i) => {
|
||||
return "&#" + i.charCodeAt(0) + ";";
|
||||
})
|
||||
.replace(/\u0008/gim, "");
|
||||
};
|
||||
|
||||
const UPSTREAM_API_ERRORS = [
|
||||
TRY_AGAIN_LATER,
|
||||
SECONDARY_ERROR_MESSAGES.MAX_RETRY,
|
||||
];
|
||||
|
||||
/**
|
||||
* Renders error message on the card.
|
||||
*
|
||||
* @param {string} message Main error message.
|
||||
* @param {string} secondaryMessage The secondary error message.
|
||||
* @param {object} options Function options.
|
||||
* @returns {string} The SVG markup.
|
||||
*/
|
||||
const renderError = (message, secondaryMessage = "", options = {}) => {
|
||||
const {
|
||||
title_color,
|
||||
text_color,
|
||||
bg_color,
|
||||
border_color,
|
||||
theme = "default",
|
||||
} = options;
|
||||
|
||||
// returns theme based colors with proper overrides and defaults
|
||||
const { titleColor, textColor, bgColor, borderColor } = getCardColors({
|
||||
title_color,
|
||||
text_color,
|
||||
icon_color: "",
|
||||
bg_color,
|
||||
border_color,
|
||||
ring_color: "",
|
||||
theme,
|
||||
});
|
||||
|
||||
return `
|
||||
<svg width="${ERROR_CARD_LENGTH}" height="120" viewBox="0 0 ${ERROR_CARD_LENGTH} 120" fill="${bgColor}" xmlns="http://www.w3.org/2000/svg">
|
||||
<style>
|
||||
.text { font: 600 16px 'Segoe UI', Ubuntu, Sans-Serif; fill: ${titleColor} }
|
||||
.small { font: 600 12px 'Segoe UI', Ubuntu, Sans-Serif; fill: ${textColor} }
|
||||
.gray { fill: #858585 }
|
||||
</style>
|
||||
<rect x="0.5" y="0.5" width="${
|
||||
ERROR_CARD_LENGTH - 1
|
||||
}" height="99%" rx="4.5" fill="${bgColor}" stroke="${borderColor}"/>
|
||||
<text x="25" y="45" class="text">Something went wrong!${
|
||||
UPSTREAM_API_ERRORS.includes(secondaryMessage)
|
||||
? ""
|
||||
: " file an issue at https://tiny.one/readme-stats"
|
||||
}</text>
|
||||
<text data-testid="message" x="25" y="55" class="text small">
|
||||
<tspan x="25" dy="18">${encodeHTML(message)}</tspan>
|
||||
<tspan x="25" dy="18" class="gray">${secondaryMessage}</tspan>
|
||||
</text>
|
||||
</svg>
|
||||
`;
|
||||
};
|
||||
|
||||
/**
|
||||
* Split text over multiple lines based on the card width.
|
||||
*
|
||||
|
||||
Vendored
+22
@@ -0,0 +1,22 @@
|
||||
export type DataTypes =
|
||||
| "enum"
|
||||
| "enum-array"
|
||||
| "array"
|
||||
| "string"
|
||||
| "boolean"
|
||||
| "number";
|
||||
|
||||
export function validateQueryStringParam(
|
||||
expectedDataType: DateTypes,
|
||||
param: string,
|
||||
value: string,
|
||||
): boolean;
|
||||
|
||||
export function validateQueryStringParams(
|
||||
queryStringParams: object,
|
||||
queryStringParamsDataTypeMap: Map<string, DataTypes>,
|
||||
): void;
|
||||
|
||||
export class InvalidQueryStringParamsError extends Error {
|
||||
constructor(message: string, secondaryMessage: string);
|
||||
}
|
||||
@@ -0,0 +1,128 @@
|
||||
// @ts-check
|
||||
|
||||
import { availableLocales } from "../translations.js";
|
||||
import { themes } from "../../themes/index.js";
|
||||
import { parseArray } from "./utils.js";
|
||||
|
||||
/**
|
||||
* Class for handling invalid query string param errors.
|
||||
*/
|
||||
export class InvalidQueryStringParamsError extends Error {
|
||||
/**
|
||||
* Constructor for InvalidQueryStringParamsError.
|
||||
*
|
||||
* @param {string} message - The error message.
|
||||
* @param {string} secondaryMessage - The secondary error message.
|
||||
*/
|
||||
constructor(message, secondaryMessage) {
|
||||
super(message);
|
||||
this.secondaryMessage = secondaryMessage;
|
||||
}
|
||||
}
|
||||
|
||||
const QUERYSTRING_PARAMS_ENUM_VALUES = {
|
||||
hide: ["stars", "commits", "prs", "issues", "contribs"],
|
||||
theme: Object.keys(themes),
|
||||
locale: availableLocales,
|
||||
number_format: ["short", "long"],
|
||||
rank_icon: ["github", "percentile", "default"],
|
||||
show: [
|
||||
"reviews",
|
||||
"discussions_started",
|
||||
"discussions_answered",
|
||||
"prs_merged",
|
||||
"prs_merged_percentage",
|
||||
],
|
||||
};
|
||||
|
||||
/**
|
||||
* @typedef {import("./validate").DataTypes} DataTypes The data types.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Returns the secondary error message for an invalid query string param.
|
||||
*
|
||||
* @param {string} param - The invalid query string param.
|
||||
* @param {Map<string, DataTypes>} queryStringParamsDataTypeMap - The query string params data type map.
|
||||
* @returns {string} The secondary error message.
|
||||
*/
|
||||
const getInvalidQueryStringParamsErrorSecondaryMessage = (
|
||||
param,
|
||||
queryStringParamsDataTypeMap,
|
||||
) => {
|
||||
const expectedDataType = queryStringParamsDataTypeMap.get(param);
|
||||
if (expectedDataType === "enum" || expectedDataType === "enum-array") {
|
||||
return `Expected: ${QUERYSTRING_PARAMS_ENUM_VALUES[param].join(", ")}`;
|
||||
} else if (expectedDataType === "number") {
|
||||
return "Expected: a number";
|
||||
} else if (expectedDataType === "boolean") {
|
||||
return "Expected: true or false";
|
||||
} else if (expectedDataType === "array") {
|
||||
return "Expected: an array";
|
||||
} else if (expectedDataType === "string") {
|
||||
return "Expected: a string";
|
||||
} else {
|
||||
throw new Error("Unexpected behavior");
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Validates a query string param.
|
||||
*
|
||||
* @param {DataTypes} expectedDataType - The expected data type of the query string param.
|
||||
* @param {string} param - The query string param.
|
||||
* @param {string} value - The query string param value.
|
||||
* @returns {boolean} Whether the query string param is valid.
|
||||
*/
|
||||
export const validateQueryStringParam = (expectedDataType, param, value) => {
|
||||
if (expectedDataType === "enum") {
|
||||
return QUERYSTRING_PARAMS_ENUM_VALUES[param].includes(value);
|
||||
} else if (expectedDataType === "enum-array") {
|
||||
return parseArray(value).every((value) =>
|
||||
QUERYSTRING_PARAMS_ENUM_VALUES[param].includes(value),
|
||||
);
|
||||
} else if (expectedDataType === "number") {
|
||||
return !isNaN(parseFloat(value));
|
||||
} else if (expectedDataType === "boolean") {
|
||||
return value === "true" || value === "false";
|
||||
} else if (expectedDataType === "array") {
|
||||
return Array.isArray(parseArray(value));
|
||||
} else if (expectedDataType === "string") {
|
||||
return typeof value === "string";
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Validates the query string params.
|
||||
* Throws an error if a query string param is invalid.
|
||||
* Does not return anything.
|
||||
*
|
||||
* @param {object} queryStringParams - The query string params.
|
||||
* @param {Map<string, DataTypes>} queryStringParamsDataTypeMap - The query string params data type map.
|
||||
* @returns {void}
|
||||
*/
|
||||
export const validateQueryStringParams = (
|
||||
queryStringParams,
|
||||
queryStringParamsDataTypeMap,
|
||||
) => {
|
||||
for (const [param, value] of Object.entries(queryStringParams)) {
|
||||
const expectedDataType = queryStringParamsDataTypeMap.get(param);
|
||||
if (!expectedDataType) {
|
||||
// Absence of data type means that the query string param is not supported.
|
||||
// Currently we allow addition of extra query string params.
|
||||
continue;
|
||||
}
|
||||
if (validateQueryStringParam(expectedDataType, param, value)) {
|
||||
continue;
|
||||
}
|
||||
throw new InvalidQueryStringParamsError(
|
||||
`Invalid query string param \`${param}\` value: ${value}`,
|
||||
getInvalidQueryStringParamsErrorSecondaryMessage(
|
||||
param,
|
||||
queryStringParamsDataTypeMap,
|
||||
),
|
||||
);
|
||||
}
|
||||
};
|
||||
@@ -92,7 +92,7 @@ const fetchTopLanguages = async (
|
||||
);
|
||||
}
|
||||
throw new CustomError(
|
||||
"Something went while trying to retrieve the language data using the GraphQL API.",
|
||||
"Something went wrong while trying to retrieve the language data using the GraphQL API.",
|
||||
CustomError.GRAPHQL_ERROR,
|
||||
);
|
||||
}
|
||||
|
||||
+28
-7
@@ -6,6 +6,7 @@ import { calculateRank } from "../src/calculateRank.js";
|
||||
import { renderStatsCard } from "../src/cards/stats-card.js";
|
||||
import { CONSTANTS, renderError } from "../src/common/utils.js";
|
||||
import { expect, it, describe, afterEach } from "@jest/globals";
|
||||
import { availableLocales } from "../src/translations.js";
|
||||
|
||||
const stats = {
|
||||
name: "Anurag Hazra",
|
||||
@@ -120,13 +121,28 @@ describe("Test /api/", () => {
|
||||
);
|
||||
});
|
||||
|
||||
it("should render error card in same theme as requested card", async () => {
|
||||
const { req, res } = faker({ theme: "merko" }, error);
|
||||
|
||||
await api(req, res);
|
||||
|
||||
expect(res.setHeader).toBeCalledWith("Content-Type", "image/svg+xml");
|
||||
expect(res.send).toBeCalledWith(
|
||||
renderError(
|
||||
error.errors[0].message,
|
||||
"Make sure the provided username is not an organization",
|
||||
{ theme: "merko" },
|
||||
),
|
||||
);
|
||||
});
|
||||
|
||||
it("should get the query options", async () => {
|
||||
const { req, res } = faker(
|
||||
{
|
||||
username: "anuraghazra",
|
||||
hide: "issues,prs,contribs",
|
||||
show_icons: true,
|
||||
hide_border: true,
|
||||
show_icons: "true",
|
||||
hide_border: "true",
|
||||
line_height: 100,
|
||||
title_color: "fff",
|
||||
icon_color: "fff",
|
||||
@@ -255,8 +271,8 @@ describe("Test /api/", () => {
|
||||
{
|
||||
username: "anuraghazra",
|
||||
hide: "issues,prs,contribs",
|
||||
show_icons: true,
|
||||
hide_border: true,
|
||||
show_icons: "true",
|
||||
hide_border: "true",
|
||||
line_height: 100,
|
||||
title_color: "fff",
|
||||
ring_color: "0000ff",
|
||||
@@ -291,7 +307,9 @@ describe("Test /api/", () => {
|
||||
await api(req, res);
|
||||
|
||||
expect(res.setHeader).toBeCalledWith("Content-Type", "image/svg+xml");
|
||||
expect(res.send).toBeCalledWith(renderError("Something went wrong"));
|
||||
expect(res.send).toBeCalledWith(
|
||||
renderError("Something went wrong", "This username is blacklisted"),
|
||||
);
|
||||
});
|
||||
|
||||
it("should render error card when wrong locale is provided", async () => {
|
||||
@@ -301,7 +319,10 @@ describe("Test /api/", () => {
|
||||
|
||||
expect(res.setHeader).toBeCalledWith("Content-Type", "image/svg+xml");
|
||||
expect(res.send).toBeCalledWith(
|
||||
renderError("Something went wrong", "Language not found"),
|
||||
renderError(
|
||||
"Invalid query string param `locale` value: asdf",
|
||||
`Expected: ${availableLocales.join(", ")}`,
|
||||
),
|
||||
);
|
||||
});
|
||||
|
||||
@@ -311,7 +332,7 @@ describe("Test /api/", () => {
|
||||
.reply(200, { error: "Some test error message" });
|
||||
|
||||
const { req, res } = faker(
|
||||
{ username: "anuraghazra", include_all_commits: true },
|
||||
{ username: "anuraghazra", include_all_commits: "true" },
|
||||
data_stats,
|
||||
);
|
||||
|
||||
|
||||
@@ -0,0 +1,51 @@
|
||||
import { benchmarkSuite } from "jest-bench";
|
||||
import gist from "../../api/gist.js";
|
||||
import axios from "axios";
|
||||
import MockAdapter from "axios-mock-adapter";
|
||||
import { jest } from "@jest/globals";
|
||||
|
||||
const gist_data = {
|
||||
data: {
|
||||
viewer: {
|
||||
gist: {
|
||||
description:
|
||||
"List of countries and territories in English and Spanish: name, continent, capital, dial code, country codes, TLD, and area in sq km. Lista de países y territorios en Inglés y Español: nombre, continente, capital, código de teléfono, códigos de país, dominio y área en km cuadrados. Updated 2023",
|
||||
owner: {
|
||||
login: "Yizack",
|
||||
},
|
||||
stargazerCount: 33,
|
||||
forks: {
|
||||
totalCount: 11,
|
||||
},
|
||||
files: [
|
||||
{
|
||||
name: "countries.json",
|
||||
language: {
|
||||
name: "JSON",
|
||||
},
|
||||
size: 85858,
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const mock = new MockAdapter(axios);
|
||||
mock.onPost("https://api.github.com/graphql").reply(200, gist_data);
|
||||
|
||||
benchmarkSuite("test /api/gist", {
|
||||
["simple request"]: async () => {
|
||||
const req = {
|
||||
query: {
|
||||
id: "bbfce31e0217a3689c8d961a356cb10d",
|
||||
},
|
||||
};
|
||||
const res = {
|
||||
setHeader: jest.fn(),
|
||||
send: jest.fn(),
|
||||
};
|
||||
|
||||
await gist(req, res);
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,50 @@
|
||||
import { benchmarkSuite } from "jest-bench";
|
||||
import pin from "../../api/pin.js";
|
||||
import axios from "axios";
|
||||
import MockAdapter from "axios-mock-adapter";
|
||||
import { jest } from "@jest/globals";
|
||||
|
||||
const data_repo = {
|
||||
repository: {
|
||||
username: "anuraghazra",
|
||||
name: "convoychat",
|
||||
stargazers: {
|
||||
totalCount: 38000,
|
||||
},
|
||||
description: "Help us take over the world! React + TS + GraphQL Chat App",
|
||||
primaryLanguage: {
|
||||
color: "#2b7489",
|
||||
id: "MDg6TGFuZ3VhZ2UyODc=",
|
||||
name: "TypeScript",
|
||||
},
|
||||
forkCount: 100,
|
||||
isTemplate: false,
|
||||
},
|
||||
};
|
||||
|
||||
const data_user = {
|
||||
data: {
|
||||
user: { repository: data_repo.repository },
|
||||
organization: null,
|
||||
},
|
||||
};
|
||||
|
||||
const mock = new MockAdapter(axios);
|
||||
mock.onPost("https://api.github.com/graphql").reply(200, data_user);
|
||||
|
||||
benchmarkSuite("test /api/pin", {
|
||||
["simple request"]: async () => {
|
||||
const req = {
|
||||
query: {
|
||||
username: "anuraghazra",
|
||||
repo: "convoychat",
|
||||
},
|
||||
};
|
||||
const res = {
|
||||
setHeader: jest.fn(),
|
||||
send: jest.fn(),
|
||||
};
|
||||
|
||||
await pin(req, res);
|
||||
},
|
||||
});
|
||||
+1
-1
@@ -89,7 +89,7 @@ describe("Card", () => {
|
||||
"200",
|
||||
);
|
||||
expect(document.getElementsByTagName("svg")[0]).toHaveAttribute(
|
||||
"height",
|
||||
"width",
|
||||
"200",
|
||||
);
|
||||
});
|
||||
|
||||
@@ -165,7 +165,7 @@ describe("FetchTopLanguages", () => {
|
||||
});
|
||||
|
||||
await expect(fetchTopLanguages("anuraghazra")).rejects.toThrow(
|
||||
"Something went while trying to retrieve the language data using the GraphQL API.",
|
||||
"Something went wrong while trying to retrieve the language data using the GraphQL API.",
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -103,7 +103,7 @@ const wakaTimeData = {
|
||||
};
|
||||
|
||||
describe("WakaTime fetcher", () => {
|
||||
it("should fetch correct wakatime data", async () => {
|
||||
it("should fetch correct WakaTime data", async () => {
|
||||
const username = "anuraghazra";
|
||||
mock
|
||||
.onGet(
|
||||
|
||||
+3
-1
@@ -155,7 +155,9 @@ describe("Test /api/pin", () => {
|
||||
await pin(req, res);
|
||||
|
||||
expect(res.setHeader).toBeCalledWith("Content-Type", "image/svg+xml");
|
||||
expect(res.send).toBeCalledWith(renderError("Something went wrong"));
|
||||
expect(res.send).toBeCalledWith(
|
||||
renderError("Something went wrong", "This username is blacklisted"),
|
||||
);
|
||||
});
|
||||
|
||||
it("should render error card if wrong locale provided", async () => {
|
||||
|
||||
@@ -339,4 +339,34 @@ describe("Test renderRepoCard", () => {
|
||||
"No description provided",
|
||||
);
|
||||
});
|
||||
|
||||
it("should have correct height with specified `description_lines_count` parameter", () => {
|
||||
// Testing short description
|
||||
document.body.innerHTML = renderRepoCard(data_repo.repository, {
|
||||
description_lines_count: 1,
|
||||
});
|
||||
expect(document.querySelector("svg")).toHaveAttribute("height", "120");
|
||||
document.body.innerHTML = renderRepoCard(data_repo.repository, {
|
||||
description_lines_count: 3,
|
||||
});
|
||||
expect(document.querySelector("svg")).toHaveAttribute("height", "150");
|
||||
|
||||
// Testing long description
|
||||
const longDescription =
|
||||
"A tool that will make a lot of iPhone/iPad developers' life easier. It shares your app over-the-air in a WiFi network. Bonjour is used and no configuration is needed.";
|
||||
document.body.innerHTML = renderRepoCard(
|
||||
{ ...data_repo.repository, description: longDescription },
|
||||
{
|
||||
description_lines_count: 3,
|
||||
},
|
||||
);
|
||||
expect(document.querySelector("svg")).toHaveAttribute("height", "150");
|
||||
document.body.innerHTML = renderRepoCard(
|
||||
{ ...data_repo.repository, description: longDescription },
|
||||
{
|
||||
description_lines_count: 1,
|
||||
},
|
||||
);
|
||||
expect(document.querySelector("svg")).toHaveAttribute("height", "120");
|
||||
});
|
||||
});
|
||||
|
||||
@@ -183,7 +183,9 @@ describe("Test /api/top-langs", () => {
|
||||
await topLangs(req, res);
|
||||
|
||||
expect(res.setHeader).toBeCalledWith("Content-Type", "image/svg+xml");
|
||||
expect(res.send).toBeCalledWith(renderError("Something went wrong"));
|
||||
expect(res.send).toBeCalledWith(
|
||||
renderError("Something went wrong", "This username is blacklisted"),
|
||||
);
|
||||
});
|
||||
|
||||
it("should render error card if wrong locale provided", async () => {
|
||||
|
||||
@@ -0,0 +1,256 @@
|
||||
// @ts-check
|
||||
|
||||
import { expect, it, describe } from "@jest/globals";
|
||||
import {
|
||||
validateQueryStringParam,
|
||||
validateQueryStringParams,
|
||||
InvalidQueryStringParamsError,
|
||||
} from "../src/common/validate";
|
||||
import { themes } from "../themes/index.js";
|
||||
|
||||
/**
|
||||
* @typedef {import("../src/common/validate").DataTypes} DataTypes The data types.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @type {Map<string, DataTypes>} The query string params data type map.
|
||||
*/
|
||||
const QUERYSTRING_PARAMS_DATA_TYPE_MAP = new Map([
|
||||
["username", "string"],
|
||||
["hide", "enum-array"],
|
||||
["hide_title", "boolean"],
|
||||
["hide_border", "boolean"],
|
||||
["card_width", "number"],
|
||||
["hide_rank", "boolean"],
|
||||
["show_icons", "boolean"],
|
||||
["include_all_commits", "boolean"],
|
||||
["line_height", "number"],
|
||||
["title_color", "string"],
|
||||
["ring_color", "string"],
|
||||
["icon_color", "string"],
|
||||
["text_color", "string"],
|
||||
["text_bold", "boolean"],
|
||||
["bg_color", "string"],
|
||||
["theme", "enum"],
|
||||
["cache_seconds", "number"],
|
||||
["exclude_repo", "array"],
|
||||
["custom_title", "string"],
|
||||
["locale", "enum"],
|
||||
["disable_animations", "boolean"],
|
||||
["border_radius", "number"],
|
||||
["border_color", "string"],
|
||||
["number_format", "enum"],
|
||||
["rank_icon", "enum"],
|
||||
["show", "enum-array"],
|
||||
]);
|
||||
|
||||
describe("test query string param validation", () => {
|
||||
// Tests for `validateQueryStringParam` function.
|
||||
it("should validate a string", () => {
|
||||
expect(validateQueryStringParam("string", "username", "anuraghazra")).toBe(
|
||||
true,
|
||||
);
|
||||
});
|
||||
it("should validate a boolean", () => {
|
||||
expect(validateQueryStringParam("boolean", "hide_title", "true")).toBe(
|
||||
true,
|
||||
);
|
||||
expect(validateQueryStringParam("boolean", "hide_title", "false")).toBe(
|
||||
true,
|
||||
);
|
||||
expect(validateQueryStringParam("boolean", "hide_title", "invalid")).toBe(
|
||||
false,
|
||||
);
|
||||
});
|
||||
it("should validate a number", () => {
|
||||
expect(validateQueryStringParam("number", "card_width", "300")).toBe(true);
|
||||
expect(validateQueryStringParam("number", "card_width", "300.000")).toBe(
|
||||
true,
|
||||
);
|
||||
expect(validateQueryStringParam("number", "card_width", "invalid")).toBe(
|
||||
false,
|
||||
);
|
||||
});
|
||||
it("should validate an enum", () => {
|
||||
expect(validateQueryStringParam("enum", "theme", "dark")).toBe(true);
|
||||
expect(validateQueryStringParam("enum", "theme", "merko")).toBe(true);
|
||||
expect(validateQueryStringParam("enum", "theme", "invalid")).toBe(false);
|
||||
|
||||
expect(validateQueryStringParam("enum", "locale", "en")).toBe(true);
|
||||
expect(validateQueryStringParam("enum", "locale", "invalid")).toBe(false);
|
||||
|
||||
expect(validateQueryStringParam("enum", "number_format", "short")).toBe(
|
||||
true,
|
||||
);
|
||||
expect(validateQueryStringParam("enum", "number_format", "invalid")).toBe(
|
||||
false,
|
||||
);
|
||||
|
||||
expect(validateQueryStringParam("enum", "rank_icon", "github")).toBe(true);
|
||||
expect(validateQueryStringParam("enum", "rank_icon", "invalid")).toBe(
|
||||
false,
|
||||
);
|
||||
});
|
||||
it("should validate an enum-array", () => {
|
||||
expect(
|
||||
validateQueryStringParam("enum-array", "hide", "stars,commits,prs"),
|
||||
).toBe(true);
|
||||
expect(
|
||||
validateQueryStringParam(
|
||||
"enum-array",
|
||||
"show",
|
||||
"reviews,discussions_started",
|
||||
),
|
||||
).toBe(true);
|
||||
expect(
|
||||
validateQueryStringParam(
|
||||
"enum-array",
|
||||
"hide",
|
||||
"stars,commits,prs,invalid",
|
||||
),
|
||||
).toBe(false);
|
||||
expect(validateQueryStringParam("enum-array", "hide", "invalid")).toBe(
|
||||
false,
|
||||
);
|
||||
});
|
||||
it("should validate an array", () => {
|
||||
expect(
|
||||
validateQueryStringParam("array", "exclude_repo", "repo1,repo2"),
|
||||
).toBe(true);
|
||||
});
|
||||
|
||||
// Tests for `validateQueryStringParams` function.
|
||||
it("should validate query string params", () => {
|
||||
expect(
|
||||
validateQueryStringParams(
|
||||
{
|
||||
username: "anuraghazra",
|
||||
hide: "stars,commits,prs",
|
||||
hide_title: "true",
|
||||
hide_border: "true",
|
||||
card_width: "300",
|
||||
hide_rank: "true",
|
||||
show_icons: "true",
|
||||
include_all_commits: "true",
|
||||
line_height: "25",
|
||||
title_color: "fff",
|
||||
ring_color: "fff",
|
||||
icon_color: "fff",
|
||||
text_color: "fff",
|
||||
text_bold: "true",
|
||||
bg_color: "fff",
|
||||
theme: "dark",
|
||||
cache_seconds: "300",
|
||||
exclude_repo: "repo1,repo2",
|
||||
custom_title: "My Custom Title",
|
||||
locale: "en",
|
||||
disable_animations: "true",
|
||||
border_radius: "0",
|
||||
number_format: "long",
|
||||
border_color: "fff",
|
||||
rank_icon: "github",
|
||||
show: "reviews,discussions_started",
|
||||
},
|
||||
QUERYSTRING_PARAMS_DATA_TYPE_MAP,
|
||||
),
|
||||
// This function returns void on successful run and throws an error on invalid query string params.
|
||||
).toBe(undefined);
|
||||
});
|
||||
it("should validate query string params with extra params", () => {
|
||||
expect(
|
||||
validateQueryStringParams(
|
||||
{
|
||||
username: "anuraghazra",
|
||||
hide: "stars,commits,prs",
|
||||
hide_title: "true",
|
||||
hide_border: "true",
|
||||
card_width: "300",
|
||||
hide_rank: "true",
|
||||
show_icons: "true",
|
||||
include_all_commits: "true",
|
||||
line_height: "25",
|
||||
title_color: "fff",
|
||||
ring_color: "fff",
|
||||
icon_color: "fff",
|
||||
text_color: "fff",
|
||||
text_bold: "true",
|
||||
bg_color: "fff",
|
||||
theme: "dark",
|
||||
cache_seconds: "300",
|
||||
exclude_repo: "repo1,repo2",
|
||||
custom_title: "My Custom Title",
|
||||
locale: "en",
|
||||
disable_animations: "true",
|
||||
border_radius: "0",
|
||||
number_format: "long",
|
||||
border_color: "fff",
|
||||
rank_icon: "github",
|
||||
show: "reviews,discussions_started",
|
||||
invalid: "invalid",
|
||||
},
|
||||
QUERYSTRING_PARAMS_DATA_TYPE_MAP,
|
||||
),
|
||||
// This function returns void on successful run and throws an error on invalid query string params.
|
||||
).toBe(undefined);
|
||||
});
|
||||
it("should throw correct error on invalid boolean param", () => {
|
||||
expect(() =>
|
||||
validateQueryStringParams(
|
||||
{
|
||||
hide_title: "invalid",
|
||||
},
|
||||
QUERYSTRING_PARAMS_DATA_TYPE_MAP,
|
||||
),
|
||||
).toThrow(
|
||||
new InvalidQueryStringParamsError(
|
||||
"Invalid query string param `hide_title` value: invalid",
|
||||
"Expected: true or false",
|
||||
),
|
||||
);
|
||||
});
|
||||
it("should throw correct error on invalid number param", () => {
|
||||
expect(() =>
|
||||
validateQueryStringParams(
|
||||
{
|
||||
card_width: "invalid",
|
||||
},
|
||||
QUERYSTRING_PARAMS_DATA_TYPE_MAP,
|
||||
),
|
||||
).toThrow(
|
||||
new InvalidQueryStringParamsError(
|
||||
"Invalid query string param `card_width` value: invalid",
|
||||
"Expected: a number",
|
||||
),
|
||||
);
|
||||
});
|
||||
it("should throw correct error on invalid enum param", () => {
|
||||
expect(() =>
|
||||
validateQueryStringParams(
|
||||
{
|
||||
theme: "invalid",
|
||||
},
|
||||
QUERYSTRING_PARAMS_DATA_TYPE_MAP,
|
||||
),
|
||||
).toThrow(
|
||||
new InvalidQueryStringParamsError(
|
||||
"Invalid query string param `theme` value: invalid",
|
||||
`Expected: ${Object.keys(themes).join(", ")}`,
|
||||
),
|
||||
);
|
||||
});
|
||||
it("should throw correct error on invalid enum-array param", () => {
|
||||
expect(() =>
|
||||
validateQueryStringParams(
|
||||
{
|
||||
hide: "commits,prs,invalid",
|
||||
},
|
||||
QUERYSTRING_PARAMS_DATA_TYPE_MAP,
|
||||
),
|
||||
).toThrow(
|
||||
new InvalidQueryStringParamsError(
|
||||
"Invalid query string param `hide` value: commits,prs,invalid",
|
||||
"Expected: stars, commits, prs, issues, contribs",
|
||||
),
|
||||
);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,123 @@
|
||||
import { jest } from "@jest/globals";
|
||||
import "@testing-library/jest-dom";
|
||||
import axios from "axios";
|
||||
import MockAdapter from "axios-mock-adapter";
|
||||
import wakatime from "../api/wakatime.js";
|
||||
import { renderWakatimeCard } from "../src/cards/wakatime-card.js";
|
||||
import { expect, it, describe, afterEach } from "@jest/globals";
|
||||
|
||||
const wakaTimeData = {
|
||||
data: {
|
||||
categories: [
|
||||
{
|
||||
digital: "22:40",
|
||||
hours: 22,
|
||||
minutes: 40,
|
||||
name: "Coding",
|
||||
percent: 100,
|
||||
text: "22 hrs 40 mins",
|
||||
total_seconds: 81643.570077,
|
||||
},
|
||||
],
|
||||
daily_average: 16095,
|
||||
daily_average_including_other_language: 16329,
|
||||
days_including_holidays: 7,
|
||||
days_minus_holidays: 5,
|
||||
editors: [
|
||||
{
|
||||
digital: "22:40",
|
||||
hours: 22,
|
||||
minutes: 40,
|
||||
name: "VS Code",
|
||||
percent: 100,
|
||||
text: "22 hrs 40 mins",
|
||||
total_seconds: 81643.570077,
|
||||
},
|
||||
],
|
||||
holidays: 2,
|
||||
human_readable_daily_average: "4 hrs 28 mins",
|
||||
human_readable_daily_average_including_other_language: "4 hrs 32 mins",
|
||||
human_readable_total: "22 hrs 21 mins",
|
||||
human_readable_total_including_other_language: "22 hrs 40 mins",
|
||||
id: "random hash",
|
||||
is_already_updating: false,
|
||||
is_coding_activity_visible: true,
|
||||
is_including_today: false,
|
||||
is_other_usage_visible: true,
|
||||
is_stuck: false,
|
||||
is_up_to_date: true,
|
||||
languages: [
|
||||
{
|
||||
digital: "0:19",
|
||||
hours: 0,
|
||||
minutes: 19,
|
||||
name: "Other",
|
||||
percent: 1.43,
|
||||
text: "19 mins",
|
||||
total_seconds: 1170.434361,
|
||||
},
|
||||
{
|
||||
digital: "0:01",
|
||||
hours: 0,
|
||||
minutes: 1,
|
||||
name: "TypeScript",
|
||||
percent: 0.1,
|
||||
text: "1 min",
|
||||
total_seconds: 83.293809,
|
||||
},
|
||||
{
|
||||
digital: "0:00",
|
||||
hours: 0,
|
||||
minutes: 0,
|
||||
name: "YAML",
|
||||
percent: 0.07,
|
||||
text: "0 secs",
|
||||
total_seconds: 54.975151,
|
||||
},
|
||||
],
|
||||
operating_systems: [
|
||||
{
|
||||
digital: "22:40",
|
||||
hours: 22,
|
||||
minutes: 40,
|
||||
name: "Mac",
|
||||
percent: 100,
|
||||
text: "22 hrs 40 mins",
|
||||
total_seconds: 81643.570077,
|
||||
},
|
||||
],
|
||||
percent_calculated: 100,
|
||||
range: "last_7_days",
|
||||
status: "ok",
|
||||
timeout: 15,
|
||||
total_seconds: 80473.135716,
|
||||
total_seconds_including_other_language: 81643.570077,
|
||||
user_id: "random hash",
|
||||
username: "anuraghazra",
|
||||
writes_only: false,
|
||||
},
|
||||
};
|
||||
|
||||
const mock = new MockAdapter(axios);
|
||||
|
||||
afterEach(() => {
|
||||
mock.reset();
|
||||
});
|
||||
|
||||
describe("Test /api/wakatime", () => {
|
||||
it("should test the request", async () => {
|
||||
const username = "anuraghazra";
|
||||
const req = { query: { username } };
|
||||
const res = { setHeader: jest.fn(), send: jest.fn() };
|
||||
mock
|
||||
.onGet(
|
||||
`https://wakatime.com/api/v1/users/${username}/stats?is_including_today=true`,
|
||||
)
|
||||
.reply(200, wakaTimeData);
|
||||
|
||||
await wakatime(req, res);
|
||||
|
||||
expect(res.setHeader).toBeCalledWith("Content-Type", "image/svg+xml");
|
||||
expect(res.send).toBeCalledWith(renderWakatimeCard(wakaTimeData.data, {}));
|
||||
});
|
||||
});
|
||||
+6
-4
@@ -12,7 +12,7 @@ Use `?theme=THEME_NAME` parameter like so:
|
||||
|
||||
## Stats
|
||||
|
||||
> These themes works with all five our cards: Stats Card, Repo Card, Gist Card, Top languages Card and Wakatime Card.
|
||||
> These themes works with all five our cards: Stats Card, Repo Card, Gist Card, Top languages Card and WakaTime Card.
|
||||
|
||||
| | | |
|
||||
| :--: | :--: | :--: |
|
||||
@@ -40,11 +40,11 @@ Use `?theme=THEME_NAME` parameter like so:
|
||||
| `rose_pine` ![rose_pine][rose_pine] | `catppuccin_latte` ![catppuccin_latte][catppuccin_latte] | `catppuccin_mocha` ![catppuccin_mocha][catppuccin_mocha] |
|
||||
| `date_night` ![date_night][date_night] | `one_dark_pro` ![one_dark_pro][one_dark_pro] | `rose` ![rose][rose] |
|
||||
| `holi` ![holi][holi] | `neon` ![neon][neon] | `blue_navy` ![blue_navy][blue_navy] |
|
||||
| `calm_pink` ![calm_pink][calm_pink] | | [Add your theme][add-theme] |
|
||||
| `calm_pink` ![calm_pink][calm_pink] | `ambient_gradient` ![ambient_gradient][ambient_gradient] | [Add your theme][add-theme] |
|
||||
|
||||
## Repo Card
|
||||
|
||||
> These themes works with all five our cards: Stats Card, Repo Card, Gist Card, Top languages Card and Wakatime Card.
|
||||
> These themes works with all five our cards: Stats Card, Repo Card, Gist Card, Top languages Card and WakaTime Card.
|
||||
|
||||
| | | |
|
||||
| :--: | :--: | :--: |
|
||||
@@ -72,7 +72,7 @@ Use `?theme=THEME_NAME` parameter like so:
|
||||
| `rose_pine` ![rose_pine][rose_pine_repo] | `catppuccin_latte` ![catppuccin_latte][catppuccin_latte_repo] | `catppuccin_mocha` ![catppuccin_mocha][catppuccin_mocha_repo] |
|
||||
| `date_night` ![date_night][date_night_repo] | `one_dark_pro` ![one_dark_pro][one_dark_pro_repo] | `rose` ![rose][rose_repo] |
|
||||
| `holi` ![holi][holi_repo] | `neon` ![neon][neon_repo] | `blue_navy` ![blue_navy][blue_navy_repo] |
|
||||
| `calm_pink` ![calm_pink][calm_pink_repo] | | [Add your theme][add-theme] |
|
||||
| `calm_pink` ![calm_pink][calm_pink_repo] | `ambient_gradient` ![ambient_gradient][ambient_gradient_repo] | [Add your theme][add-theme] |
|
||||
|
||||
|
||||
[default]: https://github-readme-stats.vercel.app/api?username=anuraghazra&show_icons=true&hide=contribs,prs&cache_seconds=86400&theme=default
|
||||
@@ -149,6 +149,7 @@ Use `?theme=THEME_NAME` parameter like so:
|
||||
[neon]: https://github-readme-stats.vercel.app/api?username=anuraghazra&show_icons=true&hide=contribs,prs&cache_seconds=86400&theme=neon
|
||||
[blue_navy]: https://github-readme-stats.vercel.app/api?username=anuraghazra&show_icons=true&hide=contribs,prs&cache_seconds=86400&theme=blue_navy
|
||||
[calm_pink]: https://github-readme-stats.vercel.app/api?username=anuraghazra&show_icons=true&hide=contribs,prs&cache_seconds=86400&theme=calm_pink
|
||||
[ambient_gradient]: https://github-readme-stats.vercel.app/api?username=anuraghazra&show_icons=true&hide=contribs,prs&cache_seconds=86400&theme=ambient_gradient
|
||||
|
||||
|
||||
[default_repo]: https://github-readme-stats.vercel.app/api/pin/?username=anuraghazra&repo=github-readme-stats&cache_seconds=86400&theme=default
|
||||
@@ -225,6 +226,7 @@ Use `?theme=THEME_NAME` parameter like so:
|
||||
[neon_repo]: https://github-readme-stats.vercel.app/api/pin/?username=anuraghazra&repo=github-readme-stats&cache_seconds=86400&theme=neon
|
||||
[blue_navy_repo]: https://github-readme-stats.vercel.app/api/pin/?username=anuraghazra&repo=github-readme-stats&cache_seconds=86400&theme=blue_navy
|
||||
[calm_pink_repo]: https://github-readme-stats.vercel.app/api/pin/?username=anuraghazra&repo=github-readme-stats&cache_seconds=86400&theme=calm_pink
|
||||
[ambient_gradient_repo]: https://github-readme-stats.vercel.app/api/pin/?username=anuraghazra&repo=github-readme-stats&cache_seconds=86400&theme=ambient_gradient
|
||||
|
||||
|
||||
[add-theme]: https://github.com/anuraghazra/github-readme-stats/edit/master/themes/index.js
|
||||
|
||||
@@ -456,6 +456,12 @@ export const themes = {
|
||||
border_color: "e1bc29",
|
||||
bg_color: "2b2d40",
|
||||
},
|
||||
ambient_gradient: {
|
||||
title_color: "ffffff",
|
||||
text_color: "ffffff",
|
||||
icon_color: "ffffff",
|
||||
bg_color: "35,4158d0,c850c0,ffcc70",
|
||||
},
|
||||
};
|
||||
|
||||
export default themes;
|
||||
|
||||
Reference in New Issue
Block a user