forked from mirrored/github-readme-stats
Compare commits
60
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
94f4f0d39a | ||
|
|
a7159aae49 | ||
|
|
8ad4eee3a7 | ||
|
|
3ba76665c0 | ||
|
|
5f411be854 | ||
|
|
1be7d06fd3 | ||
|
|
11e9cce178 | ||
|
|
3e14bafe6b | ||
|
|
62c7101183 | ||
|
|
91d0ed68c8 | ||
|
|
95333194c5 | ||
|
|
5a05bd2180 | ||
|
|
13c25c7a62 | ||
|
|
547660b0f1 | ||
|
|
319e3d401f | ||
|
|
0704b5a58d | ||
|
|
26f6d7f759 | ||
|
|
b9fa27bf22 | ||
|
|
8b610da0c0 | ||
|
|
734c69a70b | ||
|
|
4f567cc334 | ||
|
|
91db5356fc | ||
|
|
533469d677 | ||
|
|
fa50161a65 | ||
|
|
9a11163c49 | ||
|
|
eb3b4d1f6a | ||
|
|
3d996b940b | ||
|
|
14fe8cd650 | ||
|
|
9cce439520 | ||
|
|
d1540efc1b | ||
|
|
39e8c20768 | ||
|
|
62662be9cf | ||
|
|
940646697f | ||
|
|
258d54d134 | ||
|
|
69f58261d8 | ||
|
|
5dd50ee847 | ||
|
|
1852db0635 | ||
|
|
53a1e349e3 | ||
|
|
d2dbffeb2d | ||
|
|
4710f24670 | ||
|
|
7d4c49b7f7 | ||
|
|
59367dc6b0 | ||
|
|
c1e8037973 | ||
|
|
f44dddef0b | ||
|
|
e30da927c7 | ||
|
|
3ea55bb73e | ||
|
|
82699d6ba6 | ||
|
|
beb0172465 | ||
|
|
9c1bb80fdb | ||
|
|
f831dadf79 | ||
|
|
c402a23360 | ||
|
|
2259870e8e | ||
|
|
c31f3e81bd | ||
|
|
23472f40e8 | ||
|
|
6633d68f79 | ||
|
|
34c67ffa6d | ||
|
|
4412af8b53 | ||
|
|
91889a3319 | ||
|
|
105e136c5e | ||
|
|
1cba9c13f3 |
+86
-44
@@ -1,53 +1,95 @@
|
||||
themes: themes/index.js
|
||||
doc-translation: docs/*
|
||||
themes:
|
||||
- changed-files:
|
||||
- any-glob-to-any-file:
|
||||
- themes/index.js
|
||||
|
||||
doc-translation:
|
||||
- changed-files:
|
||||
- any-glob-to-any-file:
|
||||
- docs/*
|
||||
|
||||
card-i18n:
|
||||
- src/translations.js
|
||||
- src/common/I18n.js
|
||||
- changed-files:
|
||||
- any-glob-to-any-file:
|
||||
- src/translations.js
|
||||
- src/common/I18n.js
|
||||
|
||||
documentation:
|
||||
- readme.md
|
||||
- CONTRIBUTING.md
|
||||
- CODE_OF_CONDUCT.md
|
||||
- SECURITY.md
|
||||
- changed-files:
|
||||
- any-glob-to-any-file:
|
||||
- readme.md
|
||||
- CONTRIBUTING.md
|
||||
- CODE_OF_CONDUCT.md
|
||||
- SECURITY.md
|
||||
|
||||
dependencies:
|
||||
- package.json
|
||||
- package-lock.json
|
||||
- changed-files:
|
||||
- any-glob-to-any-file:
|
||||
- package.json
|
||||
- package-lock.json
|
||||
|
||||
lang-card:
|
||||
- api/top-langs.js
|
||||
- src/cards/top-languages-card.js
|
||||
- src/fetchers/top-languages-fetcher.js
|
||||
- tests/fetchTopLanguages.test.js
|
||||
- tests/renderTopLanguagesCard.test.js
|
||||
- tests/top-langs.test.js
|
||||
- changed-files:
|
||||
- any-glob-to-any-file:
|
||||
- api/top-langs.js
|
||||
- src/cards/top-languages-card.js
|
||||
- src/fetchers/top-languages-fetcher.js
|
||||
- tests/fetchTopLanguages.test.js
|
||||
- tests/renderTopLanguagesCard.test.js
|
||||
- tests/top-langs.test.js
|
||||
|
||||
repo-card:
|
||||
- api/pin.js
|
||||
- src/cards/repo-card.js
|
||||
- src/fetchers/repo-fetcher.js
|
||||
- tests/fetchRepo.test.js
|
||||
- tests/renderRepoCard.test.js
|
||||
- tests/pin.test.js
|
||||
- changed-files:
|
||||
- any-glob-to-any-file:
|
||||
- api/pin.js
|
||||
- src/cards/repo-card.js
|
||||
- src/fetchers/repo-fetcher.js
|
||||
- tests/fetchRepo.test.js
|
||||
- tests/renderRepoCard.test.js
|
||||
- tests/pin.test.js
|
||||
|
||||
stats-card:
|
||||
- api/index.js
|
||||
- src/cards/stats-card.js
|
||||
- src/fetchers/stats-fetcher.js
|
||||
- tests/fetchStats.test.js
|
||||
- tests/renderStatsCard.test.js
|
||||
- tests/api.test.js
|
||||
- changed-files:
|
||||
- any-glob-to-any-file:
|
||||
- api/index.js
|
||||
- src/cards/stats-card.js
|
||||
- src/fetchers/stats-fetcher.js
|
||||
- tests/fetchStats.test.js
|
||||
- tests/renderStatsCard.test.js
|
||||
- tests/api.test.js
|
||||
|
||||
wakatime-card:
|
||||
- api/wakatime.js
|
||||
- src/cards/wakatime-card.js
|
||||
- src/fetchers/wakatime-fetcher.js
|
||||
- tests/fetchWakatime.test.js
|
||||
- tests/renderWakatimeCard.test.js
|
||||
- tests/wakatime.test.js
|
||||
- changed-files:
|
||||
- any-glob-to-any-file:
|
||||
- api/wakatime.js
|
||||
- src/cards/wakatime-card.js
|
||||
- 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
|
||||
- src/fetchers/gist-fetcher.js
|
||||
- tests/fetchGist.test.js
|
||||
- tests/renderGistCard.test.js
|
||||
- tests/gist.test.js
|
||||
ranks: src/calculateRank.js
|
||||
- changed-files:
|
||||
- any-glob-to-any-file:
|
||||
- api/gist.js
|
||||
- src/cards/gist-card.js
|
||||
- src/fetchers/gist-fetcher.js
|
||||
- tests/fetchGist.test.js
|
||||
- tests/renderGistCard.test.js
|
||||
- tests/gist.test.js
|
||||
|
||||
ranks:
|
||||
- changed-files:
|
||||
- any-glob-to-any-file:
|
||||
- src/calculateRank.js
|
||||
|
||||
ci:
|
||||
- .github/workflows/*
|
||||
- scripts/*
|
||||
infrastructure: .eslintrc.json
|
||||
- changed-files:
|
||||
- any-glob-to-any-file:
|
||||
- .github/workflows/*
|
||||
- scripts/*
|
||||
|
||||
infrastructure:
|
||||
- changed-files:
|
||||
- any-glob-to-any-file:
|
||||
- .eslintrc.json
|
||||
|
||||
@@ -20,7 +20,7 @@ jobs:
|
||||
- uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
|
||||
|
||||
- name: Setup Node
|
||||
uses: actions/setup-node@8f152de45cc393bb48ce5d89d36b731f54556e65 # v4.0.0
|
||||
uses: actions/setup-node@b39b52d1213e96004bfcb1c61a8a6fa8ab84f3e8 # v4.0.1
|
||||
with:
|
||||
node-version: ${{ matrix.node-version }}
|
||||
cache: npm
|
||||
|
||||
+1
-1
@@ -30,7 +30,7 @@ jobs:
|
||||
- uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
|
||||
|
||||
- name: Run empty issues closer action
|
||||
uses: rickstaa/empty-issues-closer-action@e0fb1033c83cf13e0c98c2c9d0419d3040a6a1d0 # v1.1.50
|
||||
uses: rickstaa/empty-issues-closer-action@5de6805e71f0a2b80e5e6c174467feceb94b935a # v1.1.70
|
||||
env:
|
||||
github_token: ${{ secrets.GITHUB_TOKEN }}
|
||||
with:
|
||||
@@ -32,7 +32,7 @@ jobs:
|
||||
- uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
|
||||
|
||||
- name: Setup Node
|
||||
uses: actions/setup-node@8f152de45cc393bb48ce5d89d36b731f54556e65 # v4.0.0
|
||||
uses: actions/setup-node@b39b52d1213e96004bfcb1c61a8a6fa8ab84f3e8 # v4.0.1
|
||||
with:
|
||||
node-version: ${{ matrix.node-version }}
|
||||
cache: npm
|
||||
|
||||
@@ -21,6 +21,7 @@ jobs:
|
||||
if: github.repository == 'anuraghazra/github-readme-stats'
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/labeler@ac9175f8a1f3625fd0d4fb234536d26811351594 # v4.3.0
|
||||
- uses: actions/labeler@8558fd74291d67161a8a78ce36a881fa63b766a9 # v5.0.0
|
||||
with:
|
||||
repo-token: "${{ secrets.GITHUB_TOKEN }}"
|
||||
sync-labels: true
|
||||
|
||||
@@ -35,7 +35,7 @@ jobs:
|
||||
# Upload the results as artifacts (optional). Commenting out will disable uploads of run results in SARIF
|
||||
# format to the repository Actions tab.
|
||||
- name: "Upload artifact"
|
||||
uses: actions/upload-artifact@a8a3f3ad30e3422c9c7b888a15615d19a852ae32 # v3.1.3
|
||||
uses: actions/upload-artifact@26f96dfa697d77e81fd5907df203aa23a56210a8 # v4.3.0
|
||||
with:
|
||||
name: SARIF file
|
||||
path: results.sarif
|
||||
|
||||
@@ -33,12 +33,12 @@ jobs:
|
||||
- uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
|
||||
|
||||
- name: Setup Node
|
||||
uses: actions/setup-node@8f152de45cc393bb48ce5d89d36b731f54556e65 # v4.0.0
|
||||
uses: actions/setup-node@b39b52d1213e96004bfcb1c61a8a6fa8ab84f3e8 # v4.0.1
|
||||
with:
|
||||
node-version: ${{ matrix.node-version }}
|
||||
cache: npm
|
||||
|
||||
- uses: bahmutov/npm-install@2509f13e8485d88340a789a3f7ca11aaac47c9fc # v1.8.36
|
||||
- uses: bahmutov/npm-install@d476752204653fb5cce6c09db0eaf220761f5d9e # v1.8.37
|
||||
with:
|
||||
useLockFile: false
|
||||
|
||||
|
||||
+2
-2
@@ -39,12 +39,12 @@ jobs:
|
||||
- uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
|
||||
|
||||
- name: Setup Node
|
||||
uses: actions/setup-node@8f152de45cc393bb48ce5d89d36b731f54556e65 # v4.0.0
|
||||
uses: actions/setup-node@b39b52d1213e96004bfcb1c61a8a6fa8ab84f3e8 # v4.0.1
|
||||
with:
|
||||
node-version: ${{ matrix.node-version }}
|
||||
cache: npm
|
||||
|
||||
- uses: bahmutov/npm-install@2509f13e8485d88340a789a3f7ca11aaac47c9fc # v1.8.36
|
||||
- uses: bahmutov/npm-install@d476752204653fb5cce6c09db0eaf220761f5d9e # v1.8.37
|
||||
with:
|
||||
useLockFile: false
|
||||
|
||||
@@ -21,7 +21,7 @@ jobs:
|
||||
- uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
|
||||
|
||||
- name: Setup Node
|
||||
uses: actions/setup-node@8f152de45cc393bb48ce5d89d36b731f54556e65 # v4.0.0
|
||||
uses: actions/setup-node@b39b52d1213e96004bfcb1c61a8a6fa8ab84f3e8 # v4.0.1
|
||||
with:
|
||||
node-version: ${{ matrix.node-version }}
|
||||
cache: npm
|
||||
@@ -44,4 +44,4 @@ jobs:
|
||||
npm run format:check
|
||||
|
||||
- name: Code Coverage
|
||||
uses: codecov/codecov-action@eaaf4bedf32dbdc6b720b63067d99c4d77d6047d # v3.1.4
|
||||
uses: codecov/codecov-action@e0b68c6749509c5f83f984dd99a76a1c1a231044 # v4.0.1
|
||||
|
||||
@@ -34,7 +34,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Run top issues action
|
||||
uses: rickstaa/top-issues-action@04028f4fc0f1c217e571be72102b6ea4827b8468 # v1.3.75
|
||||
uses: rickstaa/top-issues-action@c5580f47f44c69df300606554f428f040026d72a # v1.3.95
|
||||
env:
|
||||
github_token: ${{ secrets.GITHUB_TOKEN }}
|
||||
with:
|
||||
|
||||
@@ -39,7 +39,7 @@ jobs:
|
||||
- uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
|
||||
|
||||
- name: Setup Node
|
||||
uses: actions/setup-node@8f152de45cc393bb48ce5d89d36b731f54556e65 # v4.0.0
|
||||
uses: actions/setup-node@b39b52d1213e96004bfcb1c61a8a6fa8ab84f3e8 # v4.0.1
|
||||
with:
|
||||
node-version: ${{ matrix.node-version }}
|
||||
cache: npm
|
||||
@@ -1,6 +1,3 @@
|
||||
#!/usr/bin/env sh
|
||||
. "$(dirname -- "$0")/_/husky.sh"
|
||||
|
||||
npm test
|
||||
npm run lint
|
||||
npx lint-staged
|
||||
|
||||
+13
-32
@@ -8,36 +8,7 @@ import {
|
||||
renderError,
|
||||
} from "../src/common/utils.js";
|
||||
import { fetchStats } from "../src/fetchers/stats-fetcher.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"],
|
||||
]);
|
||||
import { isLocaleAvailable } from "../src/translations.js";
|
||||
|
||||
export default async (req, res) => {
|
||||
const {
|
||||
@@ -82,9 +53,19 @@ export default async (req, res) => {
|
||||
);
|
||||
}
|
||||
|
||||
try {
|
||||
validateQueryStringParams(req.query, QUERYSTRING_PARAMS_DATA_TYPE_MAP);
|
||||
if (locale && !isLocaleAvailable(locale)) {
|
||||
return res.send(
|
||||
renderError("Something went wrong", "Language not found", {
|
||||
title_color,
|
||||
text_color,
|
||||
bg_color,
|
||||
border_color,
|
||||
theme,
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
try {
|
||||
const showStats = parseArray(show);
|
||||
const stats = await fetchStats(
|
||||
username,
|
||||
|
||||
Generated
+350
-278
File diff suppressed because it is too large
Load Diff
+11
-11
@@ -29,7 +29,7 @@
|
||||
"generate-langs-json": "node scripts/generate-langs-json",
|
||||
"format": "prettier --write .",
|
||||
"format:check": "prettier --check .",
|
||||
"prepare": "husky install",
|
||||
"prepare": "husky",
|
||||
"lint": "npx eslint --max-warnings 0 \"./src/**/*.js\" \"./scripts/**/*.js\" \"./tests/**/*.js\" \"./api/**/*.js\" \"./themes/**/*.js\"",
|
||||
"bench": "node --experimental-vm-modules node_modules/jest/bin/jest.js --config jest.bench.config.js"
|
||||
},
|
||||
@@ -38,27 +38,27 @@
|
||||
"devDependencies": {
|
||||
"@actions/core": "^1.10.1",
|
||||
"@actions/github": "^6.0.0",
|
||||
"@testing-library/dom": "^9.3.3",
|
||||
"@testing-library/jest-dom": "^6.1.4",
|
||||
"@testing-library/dom": "^9.3.4",
|
||||
"@testing-library/jest-dom": "^6.4.2",
|
||||
"@uppercod/css-to-object": "^1.1.1",
|
||||
"axios-mock-adapter": "^1.22.0",
|
||||
"color-contrast-checker": "^2.1.0",
|
||||
"eslint": "^8.54.0",
|
||||
"eslint-config-prettier": "^9.0.0",
|
||||
"eslint": "^8.56.0",
|
||||
"eslint-config-prettier": "^9.1.0",
|
||||
"hjson": "^3.2.2",
|
||||
"husky": "^8.0.3",
|
||||
"husky": "^9.0.10",
|
||||
"jest": "^29.7.0",
|
||||
"jest-bench": "^29.4.1",
|
||||
"jest-bench": "^29.7.1",
|
||||
"jest-environment-jsdom": "^29.7.0",
|
||||
"js-yaml": "^4.1.0",
|
||||
"lint-staged": "^15.1.0",
|
||||
"lint-staged": "^15.2.2",
|
||||
"lodash.snakecase": "^4.1.1",
|
||||
"parse-diff": "^0.11.1",
|
||||
"prettier": "^3.1.0"
|
||||
"prettier": "^3.2.5"
|
||||
},
|
||||
"dependencies": {
|
||||
"axios": "^1.6.2",
|
||||
"dotenv": "^16.3.1",
|
||||
"axios": "^1.6.7",
|
||||
"dotenv": "^16.4.1",
|
||||
"emoji-name-map": "^1.2.8",
|
||||
"github-username-regex": "^1.0.0",
|
||||
"upgrade": "^1.1.0",
|
||||
|
||||
@@ -432,7 +432,7 @@ If we don't support your language, please consider contributing! You can find mo
|
||||
| `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` |
|
||||
| `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 | `wakatime.com` |
|
||||
| `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` |
|
||||
|
||||
|
||||
@@ -518,7 +518,7 @@ const renderStatsCard = (stats, options = {}) => {
|
||||
if (key === "commits") {
|
||||
return `${i18n.t("statcard.commits")} ${
|
||||
include_all_commits ? "" : `in ${new Date().getFullYear()}`
|
||||
} : ${totalStars}`;
|
||||
} : ${STATS[key].value}`;
|
||||
}
|
||||
return `${STATS[key].label}: ${STATS[key].value}`;
|
||||
})
|
||||
|
||||
Vendored
+1
-1
@@ -46,7 +46,7 @@ export type TopLangOptions = CommonOptions & {
|
||||
hide_progress: boolean;
|
||||
};
|
||||
|
||||
type WakaTimeOptions = CommonOptions & {
|
||||
export type WakaTimeOptions = CommonOptions & {
|
||||
hide_title: boolean;
|
||||
hide: string[];
|
||||
line_height: string;
|
||||
|
||||
@@ -39,6 +39,20 @@ const noCodingActivityNode = ({ color, text }) => {
|
||||
* @typedef {import('../fetchers/types').WakaTimeLang} WakaTimeLang
|
||||
*/
|
||||
|
||||
/**
|
||||
* Format language value.
|
||||
*
|
||||
* @param {Object} args The function arguments.
|
||||
* @param {WakaTimeLang} args.lang The language object.
|
||||
* @param {"time" | "percent"} args.display_format The display format of the language node.
|
||||
* @returns {string} The formatted language value.
|
||||
*/
|
||||
const formatLanguageValue = ({ display_format, lang }) => {
|
||||
return display_format === "percent"
|
||||
? `${lang.percent.toFixed(2).toString()} %`
|
||||
: lang.text;
|
||||
};
|
||||
|
||||
/**
|
||||
* Create compact WakaTime layout.
|
||||
*
|
||||
@@ -51,10 +65,7 @@ const noCodingActivityNode = ({ color, text }) => {
|
||||
*/
|
||||
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;
|
||||
const value = formatLanguageValue({ display_format, lang });
|
||||
|
||||
return `
|
||||
<g transform="translate(${x}, ${y})">
|
||||
@@ -341,10 +352,7 @@ const renderWakatimeCard = (stats = {}, options = { hide: [] }) => {
|
||||
return createTextNode({
|
||||
id: language.name,
|
||||
label: language.name,
|
||||
value:
|
||||
display_format === "percent"
|
||||
? `${language.percent.toFixed(2).toString()} %`
|
||||
: language.text,
|
||||
value: formatLanguageValue({ display_format, lang: language }),
|
||||
index,
|
||||
percent: language.percent,
|
||||
// @ts-ignore
|
||||
|
||||
@@ -130,6 +130,7 @@
|
||||
"Easybuild": "#069406",
|
||||
"Ecere Projects": "#913960",
|
||||
"Ecmarkup": "#eb8131",
|
||||
"EdgeQL": "#31A7FF",
|
||||
"EditorConfig": "#fff1f2",
|
||||
"Eiffel": "#4d6977",
|
||||
"Elixir": "#6e4a7e",
|
||||
@@ -170,8 +171,8 @@
|
||||
"Game Maker Language": "#71b417",
|
||||
"Gemfile.lock": "#701516",
|
||||
"Gemini": "#ff6900",
|
||||
"Genero": "#63408e",
|
||||
"Genero Forms": "#d8df39",
|
||||
"Genero 4gl": "#63408e",
|
||||
"Genero per": "#d8df39",
|
||||
"Genie": "#fb855d",
|
||||
"Genshi": "#951531",
|
||||
"Gentoo Ebuild": "#9400ff",
|
||||
@@ -182,6 +183,7 @@
|
||||
"Git Config": "#F44D27",
|
||||
"Git Revision List": "#F44D27",
|
||||
"Gleam": "#ffaff3",
|
||||
"Glimmer JS": "#F5835F",
|
||||
"Glyph": "#c1ac7f",
|
||||
"Gnuplot": "#f0a9f0",
|
||||
"Go": "#00ADD8",
|
||||
@@ -386,6 +388,7 @@
|
||||
"PostScript": "#da291c",
|
||||
"PowerBuilder": "#8f0f8d",
|
||||
"PowerShell": "#012456",
|
||||
"Praat": "#c8506d",
|
||||
"Prisma": "#0c344b",
|
||||
"Processing": "#0096D8",
|
||||
"Procfile": "#3B2F63",
|
||||
@@ -478,7 +481,7 @@
|
||||
"SugarSS": "#2fcc9f",
|
||||
"SuperCollider": "#46390b",
|
||||
"Svelte": "#ff3e00",
|
||||
"Sway": "#dea584",
|
||||
"Sway": "#00F58C",
|
||||
"Sweave": "#198ce7",
|
||||
"Swift": "#F05138",
|
||||
"SystemVerilog": "#DAE1C2",
|
||||
@@ -494,9 +497,11 @@
|
||||
"Tcl": "#e4cc98",
|
||||
"TeX": "#3D6117",
|
||||
"Terra": "#00004c",
|
||||
"Terraform Template": "#7b42bb",
|
||||
"TextMate Properties": "#df66e4",
|
||||
"Textile": "#ffe7ac",
|
||||
"Thrift": "#D12127",
|
||||
"Toit": "#c2c9fb",
|
||||
"Turing": "#cf142b",
|
||||
"Twig": "#c1d026",
|
||||
"TypeScript": "#3178c6",
|
||||
|
||||
Vendored
-22
@@ -1,22 +0,0 @@
|
||||
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);
|
||||
}
|
||||
@@ -1,128 +0,0 @@
|
||||
// @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,
|
||||
),
|
||||
);
|
||||
}
|
||||
};
|
||||
+6
-10
@@ -6,7 +6,6 @@ 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",
|
||||
@@ -141,8 +140,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",
|
||||
icon_color: "fff",
|
||||
@@ -271,8 +270,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",
|
||||
@@ -319,10 +318,7 @@ describe("Test /api/", () => {
|
||||
|
||||
expect(res.setHeader).toBeCalledWith("Content-Type", "image/svg+xml");
|
||||
expect(res.send).toBeCalledWith(
|
||||
renderError(
|
||||
"Invalid query string param `locale` value: asdf",
|
||||
`Expected: ${availableLocales.join(", ")}`,
|
||||
),
|
||||
renderError("Something went wrong", "Language not found"),
|
||||
);
|
||||
});
|
||||
|
||||
@@ -332,7 +328,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,
|
||||
);
|
||||
|
||||
|
||||
+11
-8
@@ -14,19 +14,20 @@ import { expect, describe, beforeAll, test } from "@jest/globals";
|
||||
|
||||
const REPO = "curly-fiesta";
|
||||
const USER = "catelinemnemosyne";
|
||||
const STATS_CARD_USER = "e2eninja";
|
||||
const GIST_ID = "372cef55fd897b31909fdeb3a7262758";
|
||||
|
||||
const STATS_DATA = {
|
||||
name: "Cateline Mnemosyne",
|
||||
totalPRs: 2,
|
||||
name: "E2ENinja",
|
||||
totalPRs: 1,
|
||||
totalReviews: 0,
|
||||
totalCommits: 8,
|
||||
totalCommits: 3,
|
||||
totalIssues: 1,
|
||||
totalStars: 1,
|
||||
contributedTo: 1,
|
||||
contributedTo: 0,
|
||||
rank: {
|
||||
level: "C",
|
||||
percentile: 98.06929469995667,
|
||||
percentile: 98.73972605284538,
|
||||
},
|
||||
};
|
||||
|
||||
@@ -116,15 +117,17 @@ describe("Fetch Cards", () => {
|
||||
|
||||
// Check if the Vercel preview instance stats card function is up and running.
|
||||
await expect(
|
||||
axios.get(`${VERCEL_PREVIEW_URL}/api?username=${USER}`),
|
||||
axios.get(`${VERCEL_PREVIEW_URL}/api?username=${STATS_CARD_USER}`),
|
||||
).resolves.not.toThrow();
|
||||
|
||||
// Get local stats card.
|
||||
const localStatsCardSVG = renderStatsCard(STATS_DATA);
|
||||
const localStatsCardSVG = renderStatsCard(STATS_DATA, {
|
||||
include_all_commits: true,
|
||||
});
|
||||
|
||||
// Get the Vercel preview stats card response.
|
||||
const serverStatsSvg = await axios.get(
|
||||
`${VERCEL_PREVIEW_URL}/api?username=${USER}&${CACHE_BURST_STRING}`,
|
||||
`${VERCEL_PREVIEW_URL}/api?username=${STATS_CARD_USER}&include_all_commits=true&${CACHE_BURST_STRING}`,
|
||||
);
|
||||
|
||||
// Check if stats card from deployment matches the stats card from local.
|
||||
|
||||
@@ -387,7 +387,9 @@ describe("Test renderStatsCard", () => {
|
||||
document.querySelector(
|
||||
'g[transform="translate(0, 25)"]>.stagger>.stat.bold',
|
||||
).textContent,
|
||||
).toMatchInlineSnapshot(`"累计提交数(commit) (2023):"`);
|
||||
).toMatchInlineSnapshot(
|
||||
`"累计提交数(commit) (${new Date().getFullYear()}):"`,
|
||||
);
|
||||
expect(
|
||||
document.querySelector(
|
||||
'g[transform="translate(0, 50)"]>.stagger>.stat.bold',
|
||||
|
||||
@@ -1,256 +0,0 @@
|
||||
// @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",
|
||||
),
|
||||
);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user