From ab9c563ec51a0843ab8ad0877d5f9dd491d23e00 Mon Sep 17 00:00:00 2001 From: Rick Staa Date: Mon, 16 Jan 2023 12:21:13 +0100 Subject: [PATCH 01/48] Revert "Revert "ci: fix theme readme generation action (#2271)" (#2394)" (#2418) This reverts commit 70f0264905d9370efa635b6a611c8719b8958efa. --- scripts/push-theme-readme.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/push-theme-readme.sh b/scripts/push-theme-readme.sh index 4a035db3041a0..1ab5de474ea5a 100755 --- a/scripts/push-theme-readme.sh +++ b/scripts/push-theme-readme.sh @@ -9,6 +9,6 @@ git config --global user.name "GitHub Readme Stats Bot" git branch -d $BRANCH_NAME || true git checkout -b $BRANCH_NAME git add --all -git commit --message "docs(theme): Auto update theme readme" || exit 0 +git commit --no-verify --message "docs(theme): Auto update theme readme" git remote add origin-$BRANCH_NAME https://${PERSONAL_TOKEN}@github.com/${GH_REPO}.git git push --force --quiet --set-upstream origin-$BRANCH_NAME $BRANCH_NAME From ad9db67b4c551aba6e19cc5af9030d85ac1c4906 Mon Sep 17 00:00:00 2001 From: Rick Staa Date: Mon, 16 Jan 2023 12:28:31 +0100 Subject: [PATCH 02/48] refactor: update code formatting --- src/common/retryer.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/common/retryer.js b/src/common/retryer.js index 73833ef85b3a4..5351cbe8cf99a 100644 --- a/src/common/retryer.js +++ b/src/common/retryer.js @@ -45,7 +45,9 @@ const retryer = async (fetcher, variables, retries = 0) => { // prettier-ignore // also checking for bad credentials if any tokens gets invalidated const isBadCredential = err.response.data && err.response.data.message === "Bad credentials"; - const isAccountSuspended = err.response.data && err.response.data.message === "Sorry. Your account was suspended."; + const isAccountSuspended = + err.response.data && + err.response.data.message === "Sorry. Your account was suspended."; if (isBadCredential || isAccountSuspended) { logger.log(`PAT_${retries + 1} Failed`); From 06a2a78cfcb1cd29c3b8b46d3899ffac10ab14b8 Mon Sep 17 00:00:00 2001 From: Rick Staa Date: Mon, 16 Jan 2023 12:30:39 +0100 Subject: [PATCH 03/48] Revert "Revert "Add loading Animation to Most used Language card (#2197)" (#2396)" (#2419) This reverts commit 4b8198fa2198da394b71e43fe4c59862a7014287. --- api/top-langs.js | 2 + readme.md | 1 + src/cards/top-languages-card.js | 82 ++++++++++++++++++++++++-------- src/cards/types.d.ts | 1 + src/common/createProgressNode.js | 19 ++++---- 5 files changed, 76 insertions(+), 29 deletions(-) diff --git a/api/top-langs.js b/api/top-langs.js index d183d3b455ca0..19cccb894e33a 100644 --- a/api/top-langs.js +++ b/api/top-langs.js @@ -29,6 +29,7 @@ export default async (req, res) => { locale, border_radius, border_color, + disable_animations, } = req.query; res.setHeader("Content-Type", "image/svg+xml"); @@ -75,6 +76,7 @@ export default async (req, res) => { border_radius, border_color, locale: locale ? locale.toLowerCase() : null, + disable_animations: parseBoolean(disable_animations), }), ); } catch (err) { diff --git a/readme.md b/readme.md index bfe042fcc2032..716bda22758c6 100644 --- a/readme.md +++ b/readme.md @@ -304,6 +304,7 @@ You can provide multiple comma-separated values in the bg_color option to render - `langs_count` - Show more languages on the card, between 1-10 _(number)_. Default `5`. - `exclude_repo` - Exclude 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`. > **Warning** > Language names should be URI-escaped, as specified in [Percent Encoding](https://en.wikipedia.org/wiki/Percent-encoding) diff --git a/src/cards/top-languages-card.js b/src/cards/top-languages-card.js index 602d1b811b5df..9396ff8e73d5e 100644 --- a/src/cards/top-languages-card.js +++ b/src/cards/top-languages-card.js @@ -39,46 +39,53 @@ const getLongestLang = (arr) => * Creates a node to display usage of a programming language in percentage * using text and a horizontal progress bar. * - * @param {object[]} props Function properties. + * @param {object} props Function properties. * @param {number} props.width The card width * @param {string} props.name Name of the programming language. * @param {string} props.color Color of the programming language. * @param {string} props.progress Usage of the programming language in percentage. + * @param {number} props.index Index of the programming language. * @returns {string} Programming language SVG node. */ -const createProgressTextNode = ({ width, color, name, progress }) => { +const createProgressTextNode = ({ width, color, name, progress, index }) => { + const staggerDelay = (index + 3) * 150; const paddingRight = 95; const progressTextX = width - paddingRight + 10; const progressWidth = width - paddingRight; return ` - ${name} - ${progress}% - ${createProgressNode({ - x: 0, - y: 25, - color, - width: progressWidth, - progress, - progressBarBackgroundColor: "#ddd", - })} + + ${name} + ${progress}% + ${createProgressNode({ + x: 0, + y: 25, + color, + width: progressWidth, + progress, + progressBarBackgroundColor: "#ddd", + delay: staggerDelay + 300, + })} + `; }; /** * Creates a text only node to display usage of a programming language in percentage. * - * @param {object[]} props Function properties. + * @param {object} props Function properties. * @param {Lang} props.lang Programming language object. * @param {number} props.totalSize Total size of all languages. + * @param {number} props.index Index of the programming language. * @returns {string} Compact layout programming language SVG node. */ -const createCompactLangNode = ({ lang, totalSize }) => { +const createCompactLangNode = ({ lang, totalSize, index }) => { const percentage = ((lang.size / totalSize) * 100).toFixed(2); + const staggerDelay = (index + 3) * 150; const color = lang.color || "#858585"; return ` - + ${lang.name} ${percentage}% @@ -104,7 +111,6 @@ const createLanguageTextNode = ({ langs, totalSize }) => { createCompactLangNode({ lang, totalSize, - // @ts-ignore index, }), ); @@ -134,12 +140,13 @@ const createLanguageTextNode = ({ langs, totalSize }) => { */ const renderNormalLayout = (langs, width, totalLanguageSize) => { return flexLayout({ - items: langs.map((lang) => { + items: langs.map((lang, index) => { return createProgressTextNode({ - width: width, + width, name: lang.name, color: lang.color || DEFAULT_LANG_COLOR, progress: ((lang.size / totalLanguageSize) * 100).toFixed(2), + index, }); }), gap: 40, @@ -187,7 +194,7 @@ const renderCompactLayout = (langs, width, totalLanguageSize) => { return ` - + ${compactProgressBar} @@ -276,6 +283,7 @@ const renderTopLanguages = (topLangs, options = {}) => { langs_count = DEFAULT_LANGS_COUNT, border_radius, border_color, + disable_animations, } = options; const i18n = new I18n({ @@ -324,11 +332,43 @@ const renderTopLanguages = (topLangs, options = {}) => { colors, }); - card.disableAnimations(); + if (disable_animations) card.disableAnimations(); + card.setHideBorder(hide_border); card.setHideTitle(hide_title); card.setCSS( - `.lang-name { font: 400 11px 'Segoe UI', Ubuntu, Sans-Serif; fill: ${colors.textColor} }`, + ` + @keyframes slideInAnimation { + from { + width: 0; + } + to { + width: calc(100%-100px); + } + } + @keyframes growWidthAnimation { + from { + width: 0; + } + to { + width: 100%; + } + } + .lang-name { + font: 400 11px "Segoe UI", Ubuntu, Sans-Serif; + fill: ${colors.textColor}; + } + .stagger { + opacity: 0; + animation: fadeInAnimation 0.3s ease-in-out forwards; + } + #rect-mask rect{ + animation: slideInAnimation 1s ease-in-out forwards; + } + .lang-progress{ + animation: growWidthAnimation 0.6s ease-in-out forwards; + } + `, ); return card.render(` diff --git a/src/cards/types.d.ts b/src/cards/types.d.ts index 502314c41fa92..c5945d48be71e 100644 --- a/src/cards/types.d.ts +++ b/src/cards/types.d.ts @@ -37,6 +37,7 @@ export type TopLangOptions = CommonOptions & { layout: "compact" | "normal"; custom_title: string; langs_count: number; + disable_animations: boolean; }; type WakaTimeOptions = CommonOptions & { diff --git a/src/common/createProgressNode.js b/src/common/createProgressNode.js index c36818b193b2f..2825583c7406a 100644 --- a/src/common/createProgressNode.js +++ b/src/common/createProgressNode.js @@ -10,6 +10,7 @@ import { clampValue } from "./utils.js"; * @param {string} createProgressNodeParams.color Progress color. * @param {string} createProgressNodeParams.progress Progress value. * @param {string} createProgressNodeParams.progressBarBackgroundColor Progress bar bg color. + * @param {number} createProgressNodeParams.delay Delay before animation starts. * @returns {string} Progress node. */ const createProgressNode = ({ @@ -19,20 +20,22 @@ const createProgressNode = ({ color, progress, progressBarBackgroundColor, + delay, }) => { const progressPercentage = clampValue(progress, 2, 100); return ` - - + + + `; }; From 8bc69e761ff7c922f47e6561d7a2f4fd0cc153c7 Mon Sep 17 00:00:00 2001 From: Zohan Subhash Date: Thu, 19 Jan 2023 14:00:46 +0530 Subject: [PATCH 04/48] Update readme.md (#2442) --- readme.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/readme.md b/readme.md index 716bda22758c6..281c4877f9422 100644 --- a/readme.md +++ b/readme.md @@ -259,7 +259,7 @@ You can customize the appearance of your `Stats Card` or `Repo Card` however you - `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` - set the cache header manually _(min: 7200, max: 86400)_. Default: `14400 seconds (4 hours)`. +- `cache_seconds` - set the cache header manually _(min: 14400, max: 86400)_. Default: `14400 seconds (4 hours)`. - `locale` - set the language in the card _(e.g. cn, de, es, etc.)_. Default: `en`. - `border_radius` - Corner rounding on the card. Default: `4.5`. From d5d1f44e1ccd8f36fc709cae2b42dd0b91f90b07 Mon Sep 17 00:00:00 2001 From: rickstaa Date: Sat, 21 Jan 2023 11:30:50 +0100 Subject: [PATCH 05/48] test: fix e2e tests --- tests/e2e/e2e.test.js | 43 +++++++++++++++++++------------------------ 1 file changed, 19 insertions(+), 24 deletions(-) diff --git a/tests/e2e/e2e.test.js b/tests/e2e/e2e.test.js index 48bf16a0e083a..38b707f8239e9 100644 --- a/tests/e2e/e2e.test.js +++ b/tests/e2e/e2e.test.js @@ -11,27 +11,22 @@ import { renderStatsCard } from "../../src/cards/stats-card.js"; import { renderTopLanguages } from "../../src/cards/top-languages-card.js"; import { renderWakatimeCard } from "../../src/cards/wakatime-card.js"; -const REPO = "dummy-cra"; -const USER = "grsdummy"; +const REPO = "cra-test"; +const USER = "catelinemnemosyne"; const STATS_DATA = { - name: "grsdummy", - totalPRs: 2, - totalCommits: 2, + name: "Cateline Mnemosyne", + totalPRs: 1, + totalCommits: 7, totalIssues: 1, totalStars: 1, - contributedTo: 2, + contributedTo: 1, rank: { level: "A+", - score: 50.900829325065935, + score: 50.893750297869225, }, }; const LANGS_DATA = { - TypeScript: { - color: "#3178c6", - name: "TypeScript", - size: 2049, - }, HTML: { color: "#e34c26", name: "HTML", @@ -42,19 +37,19 @@ const LANGS_DATA = { name: "CSS", size: 930, }, - Python: { + JavaScript: { color: "#3572A5", - name: "Python", - size: 671, + name: "JavaScript", + size: 1912, }, }; const WAKATIME_DATA = { human_readable_range: "last week", is_already_updating: false, - is_coding_activity_visible: false, + is_coding_activity_visible: true, is_including_today: false, - is_other_usage_visible: false, + is_other_usage_visible: true, is_stuck: false, is_up_to_date: false, is_up_to_date_pending_future: false, @@ -62,24 +57,24 @@ const WAKATIME_DATA = { range: "last_7_days", status: "pending_update", timeout: 15, - username: "grsdummy", + username: USER, writes_only: false, }; const REPOSITORY_DATA = { - name: "dummy-cra", - nameWithOwner: "grsdummy/dummy-cra", + name: "cra-test", + nameWithOwner: `${USER}/cra-test`, isPrivate: false, isArchived: false, isTemplate: false, stargazers: { totalCount: 1, }, - description: "Dummy create react app.", + description: "Simple cra test repo.", primaryLanguage: { - color: "#3178c6", - id: "MDg6TGFuZ3VhZ2UyODc=", - name: "TypeScript", + color: "#f1e05a", + id: "MDg6TGFuZ3VhZ2UxNDA=", + name: "JavaScript", }, forkCount: 0, starCount: 1, From cfa84232e2cec0ddaed4a54699cfb8a7311e528e Mon Sep 17 00:00:00 2001 From: rickstaa Date: Sat, 21 Jan 2023 11:32:25 +0100 Subject: [PATCH 06/48] test: update snapshots --- tests/renderStatsCard.test.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/renderStatsCard.test.js b/tests/renderStatsCard.test.js index e39e45b7870e3..5afb1f0218e5d 100644 --- a/tests/renderStatsCard.test.js +++ b/tests/renderStatsCard.test.js @@ -329,7 +329,7 @@ describe("Test renderStatsCard", () => { document.querySelector( 'g[transform="translate(0, 25)"]>.stagger>.stat.bold', ).textContent, - ).toMatchInlineSnapshot(`"累计提交数(commit) (2022):"`); + ).toMatchInlineSnapshot(`"累计提交数(commit) (2023):"`); expect( document.querySelector( 'g[transform="translate(0, 50)"]>.stagger>.stat.bold', From eab140241718725c5de32aeac5f6e8d14c31fbd0 Mon Sep 17 00:00:00 2001 From: rickstaa Date: Sat, 21 Jan 2023 11:37:41 +0100 Subject: [PATCH 07/48] test: fix e2e test data --- tests/e2e/e2e.test.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/e2e/e2e.test.js b/tests/e2e/e2e.test.js index 38b707f8239e9..999d2c96d6578 100644 --- a/tests/e2e/e2e.test.js +++ b/tests/e2e/e2e.test.js @@ -38,7 +38,7 @@ const LANGS_DATA = { size: 930, }, JavaScript: { - color: "#3572A5", + color: "#f1e05a", name: "JavaScript", size: 1912, }, From fbb49e3c80f2c4f84fd86834514bcf9638a011a7 Mon Sep 17 00:00:00 2001 From: rickstaa Date: Sat, 21 Jan 2023 12:15:58 +0100 Subject: [PATCH 08/48] test: update e2e test data --- tests/e2e/e2e.test.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/e2e/e2e.test.js b/tests/e2e/e2e.test.js index 999d2c96d6578..4d00646d02f67 100644 --- a/tests/e2e/e2e.test.js +++ b/tests/e2e/e2e.test.js @@ -11,7 +11,7 @@ import { renderStatsCard } from "../../src/cards/stats-card.js"; import { renderTopLanguages } from "../../src/cards/top-languages-card.js"; import { renderWakatimeCard } from "../../src/cards/wakatime-card.js"; -const REPO = "cra-test"; +const REPO = "curly-fiesta"; const USER = "catelinemnemosyne"; const STATS_DATA = { name: "Cateline Mnemosyne", From 4ff2c2a425dbeccde302e27042d3aa698bc8609c Mon Sep 17 00:00:00 2001 From: rickstaa Date: Sat, 21 Jan 2023 17:52:51 +0100 Subject: [PATCH 09/48] feat: fix e2e tests --- tests/e2e/e2e.test.js | 45 +++++++++++++++++++------------------------ 1 file changed, 20 insertions(+), 25 deletions(-) diff --git a/tests/e2e/e2e.test.js b/tests/e2e/e2e.test.js index 48bf16a0e083a..4d00646d02f67 100644 --- a/tests/e2e/e2e.test.js +++ b/tests/e2e/e2e.test.js @@ -11,27 +11,22 @@ import { renderStatsCard } from "../../src/cards/stats-card.js"; import { renderTopLanguages } from "../../src/cards/top-languages-card.js"; import { renderWakatimeCard } from "../../src/cards/wakatime-card.js"; -const REPO = "dummy-cra"; -const USER = "grsdummy"; +const REPO = "curly-fiesta"; +const USER = "catelinemnemosyne"; const STATS_DATA = { - name: "grsdummy", - totalPRs: 2, - totalCommits: 2, + name: "Cateline Mnemosyne", + totalPRs: 1, + totalCommits: 7, totalIssues: 1, totalStars: 1, - contributedTo: 2, + contributedTo: 1, rank: { level: "A+", - score: 50.900829325065935, + score: 50.893750297869225, }, }; const LANGS_DATA = { - TypeScript: { - color: "#3178c6", - name: "TypeScript", - size: 2049, - }, HTML: { color: "#e34c26", name: "HTML", @@ -42,19 +37,19 @@ const LANGS_DATA = { name: "CSS", size: 930, }, - Python: { - color: "#3572A5", - name: "Python", - size: 671, + JavaScript: { + color: "#f1e05a", + name: "JavaScript", + size: 1912, }, }; const WAKATIME_DATA = { human_readable_range: "last week", is_already_updating: false, - is_coding_activity_visible: false, + is_coding_activity_visible: true, is_including_today: false, - is_other_usage_visible: false, + is_other_usage_visible: true, is_stuck: false, is_up_to_date: false, is_up_to_date_pending_future: false, @@ -62,24 +57,24 @@ const WAKATIME_DATA = { range: "last_7_days", status: "pending_update", timeout: 15, - username: "grsdummy", + username: USER, writes_only: false, }; const REPOSITORY_DATA = { - name: "dummy-cra", - nameWithOwner: "grsdummy/dummy-cra", + name: "cra-test", + nameWithOwner: `${USER}/cra-test`, isPrivate: false, isArchived: false, isTemplate: false, stargazers: { totalCount: 1, }, - description: "Dummy create react app.", + description: "Simple cra test repo.", primaryLanguage: { - color: "#3178c6", - id: "MDg6TGFuZ3VhZ2UyODc=", - name: "TypeScript", + color: "#f1e05a", + id: "MDg6TGFuZ3VhZ2UxNDA=", + name: "JavaScript", }, forkCount: 0, starCount: 1, From c1dc7b850c3ff0a5db4a60564a4eaf8b9fbcdbda Mon Sep 17 00:00:00 2001 From: rickstaa Date: Sat, 21 Jan 2023 17:58:35 +0100 Subject: [PATCH 10/48] fix: fix e2e test data --- tests/e2e/e2e.test.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/e2e/e2e.test.js b/tests/e2e/e2e.test.js index 4d00646d02f67..402e210fcee17 100644 --- a/tests/e2e/e2e.test.js +++ b/tests/e2e/e2e.test.js @@ -62,7 +62,7 @@ const WAKATIME_DATA = { }; const REPOSITORY_DATA = { - name: "cra-test", + name: REPO, nameWithOwner: `${USER}/cra-test`, isPrivate: false, isArchived: false, From 60fae292a349cd3d92942dc56bf67778c213cb0d Mon Sep 17 00:00:00 2001 From: Rick Staa Date: Sat, 21 Jan 2023 18:32:37 +0100 Subject: [PATCH 11/48] feat: enable multi page star fetching for private vercel instances (#2159) * feat: enable multi-page stars' fetching for private vercel instances This commit enables multi-page stars' support from fetching on private Vercel instances. This feature can be disabled on the public Vercel instance by adding the `FETCH_SINGLE_PAGE_STARS=true` as an env variable in the public Vercel instance. This variable will not be present when people deploy their own Vercel instance, causing the code to fetch multiple star pages. * fix: improve stats multi-page fetching behavoir This commit makes sure that the GraphQL api is only called one time per 100 repositories. The old method added one unnecesairy GraphQL call. * docs: update documentation * style: improve code syntax Co-authored-by: Matteo Pierro * lol happy new year * docs: remove rate limit documentation for now Remove the `FETCH_SINGLE_PAGE_STARS` from documentation for now since it might confuse people. * fix: fix error in automatic merge * feat: make sure env variable is read Co-authored-by: Matteo Pierro Co-authored-by: Anurag --- readme.md | 8 +- src/fetchers/stats-fetcher.js | 207 +++++++++++++++++----------------- tests/api.test.js | 35 ++---- tests/fetchStats.test.js | 126 +++++++++++++++------ 4 files changed, 210 insertions(+), 166 deletions(-) diff --git a/readme.md b/readme.md index 281c4877f9422..678c5c0b14af4 100644 --- a/readme.md +++ b/readme.md @@ -93,7 +93,7 @@ Visit and make a small donation to hel - [Language Card Exclusive Options](#language-card-exclusive-options) - [Wakatime Card Exclusive Option](#wakatime-card-exclusive-options) - [Deploy Yourself](#deploy-on-your-own-vercel-instance) - - [Keep your fork up to date](#keep-your-fork-up-to-date) + - [Keep your fork up to date](#keep-your-fork-up-to-date) # GitHub Stats Card @@ -264,7 +264,7 @@ You can customize the appearance of your `Stats Card` or `Repo Card` however you - `border_radius` - Corner rounding on the card. Default: `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 4 hours (14400 seconds). Also, note that the cache is clamped to a minimum of 4 hours and a maximum of 24 hours. +> We use caching to decrease the load on our servers (see ). Our cards have a default cache of 4 hours (14400 seconds). Also, note that the cache is clamped to a minimum of 4 hours and a maximum of 24 hours. ##### Gradient in bg_color @@ -354,7 +354,7 @@ Use [show_owner](#customization) variable to include the repo's owner username The top languages card shows a GitHub user's most frequently used top language. > **Note** -> Top Languages does not indicate my 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._ +> Top Languages does not indicate my 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. ### Usage @@ -498,7 +498,7 @@ By default, GitHub does not lay out the cards side by side. To do that, you can ## Deploy on your own Vercel instance -#### [Check Out Step By Step Video Tutorial By @codeSTACKr](https://youtu.be/n6d4KHSKqGk?t=107) +#### :film_projector: [Check Out Step By Step Video Tutorial By @codeSTACKr](https://youtu.be/n6d4KHSKqGk?t=107) > **Warning** > If you are on the [hobby (i.e. free)](https://vercel.com/pricing) Vercel plan, please make sure you change the `maxDuration` parameter in the [vercel.json](https://github.com/anuraghazra/github-readme-stats/blob/master/vercel.json) file from `30` to `10` (see [#1416](https://github.com/anuraghazra/github-readme-stats/issues/1416#issuecomment-950275476) for more information). diff --git a/src/fetchers/stats-fetcher.js b/src/fetchers/stats-fetcher.js index 7f6cb9e5e95b4..a7df1e504db2f 100644 --- a/src/fetchers/stats-fetcher.js +++ b/src/fetchers/stats-fetcher.js @@ -1,5 +1,6 @@ // @ts-check import axios from "axios"; +import * as dotenv from "dotenv"; import githubUsernameRegex from "github-username-regex"; import { calculateRank } from "../calculateRank.js"; import { retryer } from "../common/retryer.js"; @@ -11,46 +12,74 @@ import { wrapTextMultiline, } from "../common/utils.js"; +dotenv.config(); + +// GraphQL queries. +const GRAPHQL_REPOS_FIELD = ` + repositories(first: 100, ownerAffiliations: OWNER, orderBy: {direction: DESC, field: STARGAZERS}, after: $after) { + totalCount + nodes { + name + stargazers { + totalCount + } + } + pageInfo { + hasNextPage + endCursor + } + } +`; + +const GRAPHQL_REPOS_QUERY = ` + query userInfo($login: String!, $after: String) { + user(login: $login) { + ${GRAPHQL_REPOS_FIELD} + } + } +`; + +const GRAPHQL_STATS_QUERY = ` + query userInfo($login: String!, $after: String) { + user(login: $login) { + name + login + contributionsCollection { + totalCommitContributions + restrictedContributionsCount + } + repositoriesContributedTo(first: 1, contributionTypes: [COMMIT, ISSUE, PULL_REQUEST, REPOSITORY]) { + totalCount + } + pullRequests(first: 1) { + totalCount + } + openIssues: issues(states: OPEN) { + totalCount + } + closedIssues: issues(states: CLOSED) { + totalCount + } + followers { + totalCount + } + ${GRAPHQL_REPOS_FIELD} + } + } +`; + /** * Stats fetcher object. * * @param {import('axios').AxiosRequestHeaders} variables Fetcher variables. * @param {string} token GitHub token. - * @returns {Promise} Stats fetcher response. + * @returns {Promise} Stats fetcher response. */ const fetcher = (variables, token) => { + const query = !variables.after ? GRAPHQL_STATS_QUERY : GRAPHQL_REPOS_QUERY; return request( { - query: ` - query userInfo($login: String!) { - user(login: $login) { - name - login - contributionsCollection { - totalCommitContributions - restrictedContributionsCount - } - repositoriesContributedTo(contributionTypes: [COMMIT, ISSUE, PULL_REQUEST, REPOSITORY]) { - totalCount - } - pullRequests { - totalCount - } - openIssues: issues(states: OPEN) { - totalCount - } - closedIssues: issues(states: CLOSED) { - totalCount - } - followers { - totalCount - } - repositories(ownerAffiliations: OWNER) { - totalCount - } - } - } - `, + query, variables, }, { @@ -60,39 +89,42 @@ const fetcher = (variables, token) => { }; /** - * Fetch first 100 repositories for a given username. + * Fetch stats information for a given username. * - * @param {import('axios').AxiosRequestHeaders} variables Fetcher variables. - * @param {string} token GitHub token. - * @returns {Promise} Repositories fetcher response. + * @param {string} username Github username. + * @returns {Promise} GraphQL Stats object. + * + * @description This function supports multi-page fetching if the 'FETCH_MULTI_PAGE_STARS' environment variable is set to true. */ -const repositoriesFetcher = (variables, token) => { - return request( - { - query: ` - query userInfo($login: String!, $after: String) { - user(login: $login) { - repositories(first: 100, ownerAffiliations: OWNER, orderBy: {direction: DESC, field: STARGAZERS}, after: $after) { - nodes { - name - stargazers { - totalCount - } - } - pageInfo { - hasNextPage - endCursor - } - } - } - } - `, - variables, - }, - { - Authorization: `bearer ${token}`, - }, - ); +const statsFetcher = async (username) => { + let stats; + let hasNextPage = true; + let endCursor = null; + while (hasNextPage) { + const variables = { login: username, first: 100, after: endCursor }; + let res = await retryer(fetcher, variables); + if (res.data.errors) return res; + + // Store stats data. + const repoNodes = res.data.data.user.repositories.nodes; + if (!stats) { + stats = res; + } else { + stats.data.data.user.repositories.nodes.push(...repoNodes); + } + + // Disable multi page fetching on public Vercel instance due to rate limits. + const repoNodesWithStars = repoNodes.filter( + (node) => node.stargazers.totalCount !== 0, + ); + hasNextPage = + process.env.FETCH_MULTI_PAGE_STARS === "true" && + repoNodes.length === repoNodesWithStars.length && + res.data.data.user.repositories.pageInfo.hasNextPage; + endCursor = res.data.data.user.repositories.pageInfo.endCursor; + } + + return stats; }; /** @@ -137,46 +169,6 @@ const totalCommitsFetcher = async (username) => { return 0; }; -/** - * Fetch all the stars for all the repositories of a given username. - * - * @param {string} username GitHub username. - * @param {array} repoToHide Repositories to hide. - * @returns {Promise} Total stars. - */ -const totalStarsFetcher = async (username, repoToHide) => { - let nodes = []; - let hasNextPage = true; - let endCursor = null; - while (hasNextPage) { - const variables = { login: username, first: 100, after: endCursor }; - let res = await retryer(repositoriesFetcher, variables); - - if (res.data.errors) { - logger.error(res.data.errors); - throw new CustomError( - res.data.errors[0].message || "Could not fetch user", - CustomError.USER_NOT_FOUND, - ); - } - - const allNodes = res.data.data.user.repositories.nodes; - const nodesWithStars = allNodes.filter( - (node) => node.stargazers.totalCount !== 0, - ); - nodes.push(...nodesWithStars); - // hasNextPage = - // allNodes.length === nodesWithStars.length && - // res.data.data.user.repositories.pageInfo.hasNextPage; - hasNextPage = false; // NOTE: Temporarily disable fetching of multiple pages. Done because of #2130. - endCursor = res.data.data.user.repositories.pageInfo.endCursor; - } - - return nodes - .filter((data) => !repoToHide[data.name]) - .reduce((prev, curr) => prev + curr.stargazers.totalCount, 0); -}; - /** * Fetch stats for a given username. * @@ -203,7 +195,7 @@ const fetchStats = async ( rank: { level: "C", score: 0 }, }; - let res = await retryer(fetcher, { login: username }); + let res = await statsFetcher(username); // Catch GraphQL errors. if (res.data.errors) { @@ -259,8 +251,15 @@ const fetchStats = async ( stats.contributedTo = user.repositoriesContributedTo.totalCount; // Retrieve stars while filtering out repositories to be hidden - stats.totalStars = await totalStarsFetcher(username, repoToHide); + stats.totalStars = user.repositories.nodes + .filter((data) => { + return !repoToHide[data.name]; + }) + .reduce((prev, curr) => { + return prev + curr.stargazers.totalCount; + }, 0); + // @ts-ignore // TODO: Fix this. stats.rank = calculateRank({ totalCommits: stats.totalCommits, totalRepos: user.repositories.totalCount, diff --git a/tests/api.test.js b/tests/api.test.js index 0037bcdb566b2..461f3e18abb6d 100644 --- a/tests/api.test.js +++ b/tests/api.test.js @@ -25,7 +25,7 @@ stats.rank = calculateRank({ issues: stats.totalIssues, }); -const data = { +const data_stats = { data: { user: { name: stats.name, @@ -40,15 +40,6 @@ const data = { followers: { totalCount: 0 }, repositories: { totalCount: 1, - }, - }, - }, -}; - -const repositoriesData = { - data: { - user: { - repositories: { nodes: [{ stargazers: { totalCount: 100 } }], pageInfo: { hasNextPage: false, @@ -83,11 +74,7 @@ const faker = (query, data) => { setHeader: jest.fn(), send: jest.fn(), }; - mock - .onPost("https://api.github.com/graphql") - .replyOnce(200, data) - .onPost("https://api.github.com/graphql") - .replyOnce(200, repositoriesData); + mock.onPost("https://api.github.com/graphql").replyOnce(200, data); return { req, res }; }; @@ -98,7 +85,7 @@ afterEach(() => { describe("Test /api/", () => { it("should test the request", async () => { - const { req, res } = faker({}, data); + const { req, res } = faker({}, data_stats); await api(req, res); @@ -133,7 +120,7 @@ describe("Test /api/", () => { text_color: "fff", bg_color: "fff", }, - data, + data_stats, ); await api(req, res); @@ -154,7 +141,7 @@ describe("Test /api/", () => { }); it("should have proper cache", async () => { - const { req, res } = faker({}, data); + const { req, res } = faker({}, data_stats); await api(req, res); @@ -170,7 +157,7 @@ describe("Test /api/", () => { }); it("should set proper cache", async () => { - const { req, res } = faker({ cache_seconds: 15000 }, data); + const { req, res } = faker({ cache_seconds: 15000 }, data_stats); await api(req, res); expect(res.setHeader.mock.calls).toEqual([ @@ -196,7 +183,7 @@ describe("Test /api/", () => { it("should set proper cache with clamped values", async () => { { - let { req, res } = faker({ cache_seconds: 200000 }, data); + let { req, res } = faker({ cache_seconds: 200000 }, data_stats); await api(req, res); expect(res.setHeader.mock.calls).toEqual([ @@ -212,7 +199,7 @@ describe("Test /api/", () => { // note i'm using block scoped vars { - let { req, res } = faker({ cache_seconds: 0 }, data); + let { req, res } = faker({ cache_seconds: 0 }, data_stats); await api(req, res); expect(res.setHeader.mock.calls).toEqual([ @@ -227,7 +214,7 @@ describe("Test /api/", () => { } { - let { req, res } = faker({ cache_seconds: -10000 }, data); + let { req, res } = faker({ cache_seconds: -10000 }, data_stats); await api(req, res); expect(res.setHeader.mock.calls).toEqual([ @@ -248,7 +235,7 @@ describe("Test /api/", () => { username: "anuraghazra", count_private: true, }, - data, + data_stats, ); await api(req, res); @@ -288,7 +275,7 @@ describe("Test /api/", () => { text_color: "fff", bg_color: "fff", }, - data, + data_stats, ); await api(req, res); diff --git a/tests/fetchStats.test.js b/tests/fetchStats.test.js index 192146ea5fbe0..04e943a75b50a 100644 --- a/tests/fetchStats.test.js +++ b/tests/fetchStats.test.js @@ -4,7 +4,8 @@ import MockAdapter from "axios-mock-adapter"; import { calculateRank } from "../src/calculateRank.js"; import { fetchStats } from "../src/fetchers/stats-fetcher.js"; -const data = { +// Test parameters. +const data_stats = { data: { user: { name: "Anurag Hazra", @@ -19,15 +20,6 @@ const data = { followers: { totalCount: 100 }, repositories: { totalCount: 5, - }, - }, - }, -}; - -const firstRepositoriesData = { - data: { - user: { - repositories: { nodes: [ { name: "test-repo-1", stargazers: { totalCount: 100 } }, { name: "test-repo-2", stargazers: { totalCount: 100 } }, @@ -42,7 +34,7 @@ const firstRepositoriesData = { }, }; -const secondRepositoriesData = { +const data_repo = { data: { user: { repositories: { @@ -59,7 +51,7 @@ const secondRepositoriesData = { }, }; -const repositoriesWithZeroStarsData = { +const data_repo_zero_stars = { data: { user: { repositories: { @@ -93,13 +85,12 @@ const error = { const mock = new MockAdapter(axios); beforeEach(() => { + process.env.FETCH_MULTI_PAGE_STARS = "false"; // Set to `false` to fetch only one page of stars. mock .onPost("https://api.github.com/graphql") - .replyOnce(200, data) + .replyOnce(200, data_stats) .onPost("https://api.github.com/graphql") - .replyOnce(200, firstRepositoriesData); - // .onPost("https://api.github.com/graphql") // NOTE: Temporarily disable fetching of multiple pages. Done because of #2130. - // .replyOnce(200, secondRepositoriesData); // NOTE: Temporarily disable fetching of multiple pages. Done because of #2130. + .replyOnce(200, data_repo); }); afterEach(() => { @@ -114,8 +105,7 @@ describe("Test fetchStats", () => { totalRepos: 5, followers: 100, contributions: 61, - // stargazers: 400, // NOTE: Temporarily disable fetching of multiple pages. Done because of #2130. - stargazers: 300, // NOTE: Temporarily disable fetching of multiple pages. Done because of #2130. + stargazers: 300, prs: 300, issues: 200, }); @@ -126,8 +116,7 @@ describe("Test fetchStats", () => { totalCommits: 100, totalIssues: 200, totalPRs: 300, - // totalStars: 400, // NOTE: Temporarily disable fetching of multiple pages. Done because of #2130. - totalStars: 300, // NOTE: Temporarily disable fetching of multiple pages. Done because of #2130. + totalStars: 300, rank, }); }); @@ -136,9 +125,9 @@ describe("Test fetchStats", () => { mock.reset(); mock .onPost("https://api.github.com/graphql") - .replyOnce(200, data) + .replyOnce(200, data_stats) .onPost("https://api.github.com/graphql") - .replyOnce(200, repositoriesWithZeroStarsData); + .replyOnce(200, data_repo_zero_stars); let stats = await fetchStats("anuraghazra"); const rank = calculateRank({ @@ -178,8 +167,7 @@ describe("Test fetchStats", () => { totalRepos: 5, followers: 100, contributions: 61, - // stargazers: 400, // NOTE: Temporarily disable fetching of multiple pages. Done because of #2130. - stargazers: 300, // NOTE: Temporarily disable fetching of multiple pages. Done because of #2130. + stargazers: 300, prs: 300, issues: 200, }); @@ -190,8 +178,7 @@ describe("Test fetchStats", () => { totalCommits: 150, totalIssues: 200, totalPRs: 300, - // totalStars: 400, // NOTE: Temporarily disable fetching of multiple pages. Done because of #2130. - totalStars: 300, // NOTE: Temporarily disable fetching of multiple pages. Done because of #2130. + totalStars: 300, rank, }); }); @@ -207,8 +194,7 @@ describe("Test fetchStats", () => { totalRepos: 5, followers: 100, contributions: 61, - // stargazers: 400, // NOTE: Temporarily disable fetching of multiple pages. Done because of #2130. - stargazers: 300, // NOTE: Temporarily disable fetching of multiple pages. Done because of #2130. + stargazers: 300, prs: 300, issues: 200, }); @@ -219,8 +205,7 @@ describe("Test fetchStats", () => { totalCommits: 1050, totalIssues: 200, totalPRs: 300, - // totalStars: 400, // NOTE: Temporarily disable fetching of multiple pages. Done because of #2130. - totalStars: 300, // NOTE: Temporarily disable fetching of multiple pages. Done because of #2130. + totalStars: 300, rank, }); }); @@ -236,8 +221,7 @@ describe("Test fetchStats", () => { totalRepos: 5, followers: 100, contributions: 61, - // stargazers: 300, // NOTE: Temporarily disable fetching of multiple pages. Done because of #2130. - stargazers: 200, // NOTE: Temporarily disable fetching of multiple pages. Done because of #2130. + stargazers: 200, prs: 300, issues: 200, }); @@ -248,8 +232,82 @@ describe("Test fetchStats", () => { totalCommits: 1050, totalIssues: 200, totalPRs: 300, - // totalStars: 300, // NOTE: Temporarily disable fetching of multiple pages. Done because of #2130. - totalStars: 200, // NOTE: Temporarily disable fetching of multiple pages. Done because of #2130. + totalStars: 200, + rank, + }); + }); + + it("should fetch two pages of stars if 'FETCH_MULTI_PAGE_STARS' env variable is set to `true`", async () => { + process.env.FETCH_MULTI_PAGE_STARS = true; + + let stats = await fetchStats("anuraghazra"); + const rank = calculateRank({ + totalCommits: 100, + totalRepos: 5, + followers: 100, + contributions: 61, + stargazers: 400, + prs: 300, + issues: 200, + }); + + expect(stats).toStrictEqual({ + contributedTo: 61, + name: "Anurag Hazra", + totalCommits: 100, + totalIssues: 200, + totalPRs: 300, + totalStars: 400, + rank, + }); + }); + + it("should fetch one page of stars if 'FETCH_MULTI_PAGE_STARS' env variable is set to `false`", async () => { + process.env.FETCH_MULTI_PAGE_STARS = "false"; + + let stats = await fetchStats("anuraghazra"); + const rank = calculateRank({ + totalCommits: 100, + totalRepos: 5, + followers: 100, + contributions: 61, + stargazers: 300, + prs: 300, + issues: 200, + }); + + expect(stats).toStrictEqual({ + contributedTo: 61, + name: "Anurag Hazra", + totalCommits: 100, + totalIssues: 200, + totalPRs: 300, + totalStars: 300, + rank, + }); + }); + + it("should fetch one page of stars if 'FETCH_MULTI_PAGE_STARS' env variable is not set", async () => { + process.env.FETCH_MULTI_PAGE_STARS = undefined; + + let stats = await fetchStats("anuraghazra"); + const rank = calculateRank({ + totalCommits: 100, + totalRepos: 5, + followers: 100, + contributions: 61, + stargazers: 300, + prs: 300, + issues: 200, + }); + + expect(stats).toStrictEqual({ + contributedTo: 61, + name: "Anurag Hazra", + totalCommits: 100, + totalIssues: 200, + totalPRs: 300, + totalStars: 300, rank, }); }); From a17fa1cf5de746f54aadbfea0682a0c58beb4941 Mon Sep 17 00:00:00 2001 From: Anurag Hazra Date: Sun, 22 Jan 2023 00:39:36 +0530 Subject: [PATCH 12/48] chore: resolve conflict (#2453) * test: fix e2e tests * test: update snapshots * test: fix e2e test data * test: update e2e test data Co-authored-by: rickstaa From b2bf4fa4878b585637a7f47d92cb10c62b4d1b06 Mon Sep 17 00:00:00 2001 From: Zohan Subhash Date: Tue, 24 Jan 2023 20:04:26 +0530 Subject: [PATCH 13/48] fix: change prod deployment branch to vercel branch to fix maxDuration bug (#2424) * Create deploy-prep.yml * Create deploy-prep.py * Update vercel.json * Update deploy-prep.yml * Update vercel.json * Added coauthor Co-authored-by: Dou Xiaobo <93511091+douxiaobo@users.noreply.github.com> * Update deploy-prep.yml * refactor: format code * Added if condition to disable deployments on forks Co-authored-by: Rick Staa * Update deploy-prep.yml Co-authored-by: Dou Xiaobo <93511091+douxiaobo@users.noreply.github.com> Co-authored-by: Anurag Hazra Co-authored-by: rickstaa --- .github/workflows/deploy-prep.py | 10 ++++++++++ .github/workflows/deploy-prep.yml | 20 ++++++++++++++++++++ vercel.json | 2 +- 3 files changed, 31 insertions(+), 1 deletion(-) create mode 100644 .github/workflows/deploy-prep.py create mode 100644 .github/workflows/deploy-prep.yml diff --git a/.github/workflows/deploy-prep.py b/.github/workflows/deploy-prep.py new file mode 100644 index 0000000000000..794c19a296122 --- /dev/null +++ b/.github/workflows/deploy-prep.py @@ -0,0 +1,10 @@ +import os + +file = open('./vercel.json', 'r') +str = file.read() +file = open('./vercel.json', 'w') + +str = str.replace('"maxDuration": 10', '"maxDuration": 30') + +file.write(str) +file.close() diff --git a/.github/workflows/deploy-prep.yml b/.github/workflows/deploy-prep.yml new file mode 100644 index 0000000000000..0626e13d575a7 --- /dev/null +++ b/.github/workflows/deploy-prep.yml @@ -0,0 +1,20 @@ +name: Deployment Prep +on: + workflow_dispatch: + push: + branches: + - master + +jobs: + config: + if: github.repository == 'anuraghazra/github-readme-stats' + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - name: Deployment Prep + run: python ./.github/workflows/deploy-prep.py + - uses: stefanzweifel/git-auto-commit-action@v4 + with: + branch: vercel + create_branch: true + push_options: "--force" diff --git a/vercel.json b/vercel.json index aee61ce054fd0..ddf82eb15666f 100644 --- a/vercel.json +++ b/vercel.json @@ -2,7 +2,7 @@ "functions": { "api/*.js": { "memory": 128, - "maxDuration": 30 + "maxDuration": 10 } }, "redirects": [ From cd5cbcdb095d766f5dbab2b8b13ed3b38cab7b2a Mon Sep 17 00:00:00 2001 From: Rick Staa Date: Tue, 24 Jan 2023 15:40:11 +0100 Subject: [PATCH 14/48] fix: fixes card overflow problem #2452 (#2460) This commit makes sure that the card width is formatted correctly. --- src/cards/stats-card.js | 36 ++++++++++++++++++++++++----------- tests/renderStatsCard.test.js | 20 +++++++++++-------- 2 files changed, 37 insertions(+), 19 deletions(-) diff --git a/src/cards/stats-card.js b/src/cards/stats-card.js index a049ecce1c7e3..f39a968f18065 100644 --- a/src/cards/stats-card.js +++ b/src/cards/stats-card.js @@ -12,6 +12,11 @@ import { import { getStyles } from "../getStyles.js"; import { statCardLocales } from "../translations.js"; +const CARD_MIN_WIDTH = 287; +const CARD_DEFAULT_WIDTH = 287; +const RANK_CARD_MIN_WIDTH = 420; +const RANK_CARD_DEFAULT_WIDTH = 450; + /** * Create a stats card text item. * @@ -218,11 +223,17 @@ const renderStatsCard = (stats = {}, options = { hide: [] }) => { When hide_rank=false, the minimum card_width is 340 px + the icon width (if show_icons=true). Numbers are picked by looking at existing dimensions on production. */ - const iconWidth = show_icons ? 16 : 0; - const minCardWidth = hide_rank - ? clampValue(50 /* padding */ + calculateTextWidth() * 2, 270, Infinity) - : 340 + iconWidth; - const defaultCardWidth = hide_rank ? 270 : 495; + const iconWidth = show_icons ? 16 + /* padding */ 1 : 0; + const minCardWidth = + (hide_rank + ? clampValue( + 50 /* padding */ + calculateTextWidth() * 2, + CARD_MIN_WIDTH, + Infinity, + ) + : RANK_CARD_MIN_WIDTH) + iconWidth; + const defaultCardWidth = + (hide_rank ? CARD_DEFAULT_WIDTH : RANK_CARD_DEFAULT_WIDTH) + iconWidth; let width = isNaN(card_width) ? defaultCardWidth : card_width; if (width < minCardWidth) { width = minCardWidth; @@ -251,18 +262,21 @@ const renderStatsCard = (stats = {}, options = { hide: [] }) => { /** * Calculates the right rank circle translation values such that the rank circle - * keeps respecting the padding. + * keeps respecting the following padding: * - * width > 450: The default left padding of 50 px will be used. - * width < 450: The left and right padding will shrink equally. + * width > RANK_CARD_DEFAULT_WIDTH: The default right padding of 70 px will be used. + * width < RANK_CARD_DEFAULT_WIDTH: The left and right padding will be enlarged + * equally from a certain minimum at RANK_CARD_MIN_WIDTH. * * @returns {number} - Rank circle translation value. */ const calculateRankXTranslation = () => { - if (width < 450) { - return width - 95 + (45 * (450 - 340)) / 110; + const minXTranslation = RANK_CARD_MIN_WIDTH + iconWidth - 70; + if (width > RANK_CARD_DEFAULT_WIDTH) { + const xMaxExpansion = minXTranslation + (450 - minCardWidth) / 2; + return xMaxExpansion + width - RANK_CARD_DEFAULT_WIDTH; } else { - return width - 95; + return minXTranslation + (width - minCardWidth) / 2; } }; diff --git a/tests/renderStatsCard.test.js b/tests/renderStatsCard.test.js index 5afb1f0218e5d..748b7a32cd32b 100644 --- a/tests/renderStatsCard.test.js +++ b/tests/renderStatsCard.test.js @@ -78,16 +78,17 @@ describe("Test renderStatsCard", () => { it("should render with custom width set", () => { document.body.innerHTML = renderStatsCard(stats); - expect(document.querySelector("svg")).toHaveAttribute("width", "495"); + expect(document.querySelector("svg")).toHaveAttribute("width", "450"); - document.body.innerHTML = renderStatsCard(stats, { card_width: 400 }); - expect(document.querySelector("svg")).toHaveAttribute("width", "400"); + document.body.innerHTML = renderStatsCard(stats, { card_width: 500 }); + expect(document.querySelector("svg")).toHaveAttribute("width", "500"); }); it("should render with custom width set and limit minimum width", () => { document.body.innerHTML = renderStatsCard(stats, { card_width: 1 }); - expect(document.querySelector("svg")).toHaveAttribute("width", "340"); + expect(document.querySelector("svg")).toHaveAttribute("width", "420"); + // Test default minimum card width without rank circle. document.body.innerHTML = renderStatsCard(stats, { card_width: 1, hide_rank: true, @@ -97,6 +98,7 @@ describe("Test renderStatsCard", () => { "305.81250000000006", ); + // Test minimum card width with rank and icons. document.body.innerHTML = renderStatsCard(stats, { card_width: 1, hide_rank: true, @@ -104,22 +106,24 @@ describe("Test renderStatsCard", () => { }); expect(document.querySelector("svg")).toHaveAttribute( "width", - "305.81250000000006", + "322.81250000000006", ); + // Test minimum card width with icons but without rank. document.body.innerHTML = renderStatsCard(stats, { card_width: 1, hide_rank: false, show_icons: true, }); - expect(document.querySelector("svg")).toHaveAttribute("width", "356"); + expect(document.querySelector("svg")).toHaveAttribute("width", "437"); + // Test minimum card width without icons or rank. document.body.innerHTML = renderStatsCard(stats, { card_width: 1, hide_rank: false, show_icons: false, }); - expect(document.querySelector("svg")).toHaveAttribute("width", "340"); + expect(document.querySelector("svg")).toHaveAttribute("width", "420"); }); it("should render default colors properly", () => { @@ -312,7 +316,7 @@ describe("Test renderStatsCard", () => { expect( document.body.getElementsByTagName("svg")[0].getAttribute("width"), - ).toBe("270"); + ).toBe("287"); }); it("should render translations", () => { From 99d9d3cde0e4f306704a9680738f79002328319c Mon Sep 17 00:00:00 2001 From: Rick Staa Date: Wed, 25 Jan 2023 09:20:36 +0100 Subject: [PATCH 15/48] ci: prevent certain actions from running on forks (#2466) --- .github/workflows/e2e-test.yml | 1 + .github/workflows/empty-issues-closer.yaml | 1 + .github/workflows/label-pr.yml | 1 + .github/workflows/stale-theme-pr-closer.yaml | 1 + .github/workflows/top-issues-dashboard.yml | 1 + 5 files changed, 5 insertions(+) diff --git a/.github/workflows/e2e-test.yml b/.github/workflows/e2e-test.yml index b9d275f5d5828..d45c76ba08e58 100644 --- a/.github/workflows/e2e-test.yml +++ b/.github/workflows/e2e-test.yml @@ -5,6 +5,7 @@ on: jobs: e2eTests: if: + github.repository == 'anuraghazra/github-readme-stats' && github.event_name == 'deployment_status' && github.event.deployment_status.state == 'success' name: Perform 2e2 tests diff --git a/.github/workflows/empty-issues-closer.yaml b/.github/workflows/empty-issues-closer.yaml index b20eef32fefb8..a65ea63b12dd3 100644 --- a/.github/workflows/empty-issues-closer.yaml +++ b/.github/workflows/empty-issues-closer.yaml @@ -8,6 +8,7 @@ on: jobs: closeEmptyIssuesAndTemplates: + if: github.repository == 'anuraghazra/github-readme-stats' name: Close empty issues runs-on: ubuntu-latest steps: diff --git a/.github/workflows/label-pr.yml b/.github/workflows/label-pr.yml index c401d32ee4b9c..03715bfab00d6 100644 --- a/.github/workflows/label-pr.yml +++ b/.github/workflows/label-pr.yml @@ -4,6 +4,7 @@ on: jobs: triage: + if: github.repository == 'anuraghazra/github-readme-stats' runs-on: ubuntu-latest steps: - uses: actions/labeler@v4 diff --git a/.github/workflows/stale-theme-pr-closer.yaml b/.github/workflows/stale-theme-pr-closer.yaml index 9a6249825263e..aa104feb528ca 100644 --- a/.github/workflows/stale-theme-pr-closer.yaml +++ b/.github/workflows/stale-theme-pr-closer.yaml @@ -5,6 +5,7 @@ on: jobs: closeOldThemePrs: + if: github.repository == 'anuraghazra/github-readme-stats' name: Close stale 'invalid' theme PRs runs-on: ubuntu-latest strategy: diff --git a/.github/workflows/top-issues-dashboard.yml b/.github/workflows/top-issues-dashboard.yml index 2b36e529de454..3a9ec82d35149 100644 --- a/.github/workflows/top-issues-dashboard.yml +++ b/.github/workflows/top-issues-dashboard.yml @@ -5,6 +5,7 @@ on: jobs: showAndLabelTopIssues: + if: github.repository == 'anuraghazra/github-readme-stats' name: Update top issues Dashboard. runs-on: ubuntu-latest steps: From 077d40561a4eb74ab56a81942155985a7579103a Mon Sep 17 00:00:00 2001 From: Rick Staa Date: Sat, 28 Jan 2023 15:22:02 +0100 Subject: [PATCH 16/48] feat: add PAT monitoring functions (#2178) * feat: add PAT monitoring functions This commit adds two monitoring functions that can be used to check whether the PATs are functioning correctly: - status/up: Returns whether the PATs are rate limited. - status/pat-info: Returns information about the PATs. * feat: add shields.io dynamic badge json response This commit adds the ability to set the return format of the `/api/status/up` cloud function. When this format is set to `shields` a dynamic shields.io badge json is returned. * feat: add 'json' type to up monitor * feat: cleanup status functions * ci: decrease pat-info rate limiting time * feat: decrease monitoring functions rate limits * refactor: pat code * feat: add PAT monitoring functions This commit adds two monitoring functions that can be used to check whether the PATs are functioning correctly: - status/up: Returns whether the PATs are rate limited. - status/pat-info: Returns information about the PATs. * feat: add shields.io dynamic badge json response This commit adds the ability to set the return format of the `/api/status/up` cloud function. When this format is set to `shields` a dynamic shields.io badge json is returned. * feat: add 'json' type to up monitor * feat: cleanup status functions * ci: decrease pat-info rate limiting time * feat: decrease monitoring functions rate limits * refactor: pat code * test: fix pat-info tests * Update api/status/pat-info.js Co-authored-by: Anurag Hazra * test: fix broken tests * chore: fix suspended account * chore: simplify and refactor * chore: fix test * chore: add resetIn field --------- Co-authored-by: Anurag --- api/status/pat-info.js | 131 ++++++++++++++++++++++ api/status/up.js | 103 +++++++++++++++++ package-lock.json | 2 +- package.json | 2 +- src/common/utils.js | 14 +++ tests/pat-info.test.js | 241 ++++++++++++++++++++++++++++++++++++++++ tests/status.up.test.js | 194 ++++++++++++++++++++++++++++++++ 7 files changed, 685 insertions(+), 2 deletions(-) create mode 100644 api/status/pat-info.js create mode 100644 api/status/up.js create mode 100644 tests/pat-info.test.js create mode 100644 tests/status.up.test.js diff --git a/api/status/pat-info.js b/api/status/pat-info.js new file mode 100644 index 0000000000000..720611d424919 --- /dev/null +++ b/api/status/pat-info.js @@ -0,0 +1,131 @@ +/** + * @file Contains a simple cloud function that can be used to check which PATs are no + * longer working. It returns a list of valid PATs, expired PATs and PATs with errors. + * + * @description This function is currently rate limited to 1 request per 10 minutes. + */ + +import { logger, request, dateDiff } from "../../src/common/utils.js"; +export const RATE_LIMIT_SECONDS = 60 * 10; // 1 request per 10 minutes + +/** + * Simple uptime check fetcher for the PATs. + * + * @param {import('axios').AxiosRequestHeaders} variables + * @param {string} token + */ +const uptimeFetcher = (variables, token) => { + return request( + { + query: ` + query { + rateLimit { + remaining + resetAt + }, + }`, + variables, + }, + { + Authorization: `bearer ${token}`, + }, + ); +}; + +const getAllPATs = () => { + return Object.keys(process.env).filter((key) => /PAT_\d*$/.exec(key)); +}; + +/** + * Check whether any of the PATs is expired. + */ +const getPATInfo = async (fetcher, variables) => { + const details = {}; + const PATs = getAllPATs(); + + for (const pat of PATs) { + try { + const response = await fetcher(variables, process.env[pat]); + const errors = response.data.errors; + const hasErrors = Boolean(errors); + const errorType = errors?.[0]?.type; + const isRateLimited = + (hasErrors && errorType === "RATE_LIMITED") || + response.data.data?.rateLimit?.remaining === 0; + + // Store PATs with errors. + if (hasErrors && errorType !== "RATE_LIMITED") { + details[pat] = { + status: "error", + error: { + type: errors[0].type, + message: errors[0].message, + }, + }; + continue; + } else if (isRateLimited) { + const date1 = new Date(); + const date2 = new Date(response.data?.data?.rateLimit?.resetAt); + details[pat] = { + status: "exhausted", + remaining: 0, + resetIn: dateDiff(date2, date1) + " minutes", + }; + } else { + details[pat] = { + status: "valid", + remaining: response.data.data.rateLimit.remaining, + }; + } + } catch (err) { + // Store the PAT if it is expired. + const errorMessage = err.response?.data?.message?.toLowerCase(); + if (errorMessage === "bad credentials") { + details[pat] = { + status: "expired", + }; + } else if (errorMessage === "sorry. your account was suspended.") { + details[pat] = { + status: "suspended", + }; + } else { + throw err; + } + } + } + + const filterPATsByStatus = (status) => { + return Object.keys(details).filter((pat) => details[pat].status === status); + }; + + return { + validPATs: filterPATsByStatus("valid"), + expiredPATs: filterPATsByStatus("expired"), + exhaustedPATS: filterPATsByStatus("exhausted"), + errorPATs: filterPATsByStatus("error"), + details, + }; +}; + +/** + * Cloud function that returns information about the used PATs. + */ +export default async (_, res) => { + res.setHeader("Content-Type", "application/json"); + try { + // Add header to prevent abuse. + const PATsInfo = await getPATInfo(uptimeFetcher, {}); + if (PATsInfo) { + res.setHeader( + "Cache-Control", + `max-age=0, s-maxage=${RATE_LIMIT_SECONDS}`, + ); + } + res.send(JSON.stringify(PATsInfo, null, 2)); + } catch (err) { + // Throw error if something went wrong. + logger.error(err); + res.setHeader("Cache-Control", "no-store"); + res.send("Something went wrong: " + err.message); + } +}; diff --git a/api/status/up.js b/api/status/up.js new file mode 100644 index 0000000000000..33fe8f900c395 --- /dev/null +++ b/api/status/up.js @@ -0,0 +1,103 @@ +/** + * @file Contains a simple cloud function that can be used to check if the PATs are still + * functional. + * + * @description This function is currently rate limited to 1 request per 10 minutes. + */ + +import retryer from "../../src/common/retryer.js"; +import { logger, request } from "../../src/common/utils.js"; + +export const RATE_LIMIT_SECONDS = 60 * 10; // 1 request per 10 minutes + +/** + * Simple uptime check fetcher for the PATs. + * + * @param {import('axios').AxiosRequestHeaders} variables + * @param {string} token + */ +const uptimeFetcher = (variables, token) => { + return request( + { + query: ` + query { + rateLimit { + remaining + } + } + `, + variables, + }, + { + Authorization: `bearer ${token}`, + }, + ); +}; + +/** + * Creates Json response that can be used for shields.io dynamic card generation. + * + * @param {*} up Whether the PATs are up or not. + * @returns Dynamic shields.io JSON response object. + * + * @see https://shields.io/endpoint. + */ +const shieldsUptimeBadge = (up) => { + const schemaVersion = 1; + const isError = true; + const label = "Public Instance"; + const message = up ? "up" : "down"; + const color = up ? "brightgreen" : "red"; + return { + schemaVersion, + label, + message, + color, + isError, + }; +}; + +/** + * Cloud function that returns whether the PATs are still functional. + */ +export default async (req, res) => { + let { type } = req.query; + type = type ? type.toLowerCase() : "boolean"; + + res.setHeader("Content-Type", "application/json"); + + try { + let PATsValid = true; + try { + await retryer(uptimeFetcher, {}); + } catch (err) { + PATsValid = false; + } + + if (PATsValid) { + res.setHeader( + "Cache-Control", + `max-age=0, s-maxage=${RATE_LIMIT_SECONDS}`, + ); + } else { + res.setHeader("Cache-Control", "no-store"); + } + + switch (type) { + case "shields": + res.send(shieldsUptimeBadge(PATsValid)); + break; + case "json": + res.send({ up: PATsValid }); + break; + default: + res.send(PATsValid); + break; + } + } catch (err) { + // Return fail boolean if something went wrong. + logger.error(err); + res.setHeader("Cache-Control", "no-store"); + res.send("Something went wrong: " + err.message); + } +}; diff --git a/package-lock.json b/package-lock.json index 048c316bfde58..ebc7570a41923 100644 --- a/package-lock.json +++ b/package-lock.json @@ -22,7 +22,7 @@ "@testing-library/dom": "^8.17.1", "@testing-library/jest-dom": "^5.16.5", "@uppercod/css-to-object": "^1.1.1", - "axios-mock-adapter": "^1.18.1", + "axios-mock-adapter": "^1.21.2", "color-contrast-checker": "^2.1.0", "hjson": "^3.2.2", "husky": "^8.0.0", diff --git a/package.json b/package.json index 95b1a11dad93f..606d4f5440a24 100644 --- a/package.json +++ b/package.json @@ -39,7 +39,7 @@ "@testing-library/dom": "^8.17.1", "@testing-library/jest-dom": "^5.16.5", "@uppercod/css-to-object": "^1.1.1", - "axios-mock-adapter": "^1.18.1", + "axios-mock-adapter": "^1.21.2", "color-contrast-checker": "^2.1.0", "hjson": "^3.2.2", "husky": "^8.0.0", diff --git a/src/common/utils.js b/src/common/utils.js index 1215fc9ac8cc2..c600c717ae3e5 100644 --- a/src/common/utils.js +++ b/src/common/utils.js @@ -424,6 +424,19 @@ const parseEmojis = (str) => { }); }; +/** + * Get diff in minutes + * @param {Date} d1 + * @param {Date} d2 + * @returns {number} + */ +const dateDiff = (d1, d2) => { + const date1 = new Date(d1); + const date2 = new Date(d2); + const diff = date1.getTime() - date2.getTime(); + return Math.round(diff / (1000 * 60)); +}; + export { ERROR_CARD_LENGTH, renderError, @@ -447,4 +460,5 @@ export { lowercaseTrim, chunkArray, parseEmojis, + dateDiff, }; diff --git a/tests/pat-info.test.js b/tests/pat-info.test.js new file mode 100644 index 0000000000000..9635ab24c837c --- /dev/null +++ b/tests/pat-info.test.js @@ -0,0 +1,241 @@ +/** + * @file Tests for the status/pat-info cloud function. + */ +import dotenv from "dotenv"; +dotenv.config(); + +import { jest } from "@jest/globals"; +import axios from "axios"; +import MockAdapter from "axios-mock-adapter"; +import patInfo, { RATE_LIMIT_SECONDS } from "../api/status/pat-info.js"; + +const mock = new MockAdapter(axios); + +const successData = { + data: { + rateLimit: { + remaining: 4986, + }, + }, +}; + +const faker = (query) => { + const req = { + query: { ...query }, + }; + const res = { + setHeader: jest.fn(), + send: jest.fn(), + }; + + return { req, res }; +}; + +const rate_limit_error = { + errors: [ + { + type: "RATE_LIMITED", + message: "API rate limit exceeded for user ID.", + }, + ], + data: { + rateLimit: { + resetAt: Date.now(), + }, + }, +}; + +const other_error = { + errors: [ + { + type: "SOME_ERROR", + message: "This is a error", + }, + ], +}; + +const bad_credentials_error = { + message: "Bad credentials", +}; + +afterEach(() => { + mock.reset(); +}); + +describe("Test /api/status/pat-info", () => { + beforeAll(() => { + // reset patenv first so that dotenv doesn't populate them with local envs + process.env = {}; + process.env.PAT_1 = "testPAT1"; + process.env.PAT_2 = "testPAT2"; + process.env.PAT_3 = "testPAT3"; + process.env.PAT_4 = "testPAT4"; + }); + + it("should return only 'validPATs' if all PATs are valid", async () => { + mock + .onPost("https://api.github.com/graphql") + .replyOnce(200, rate_limit_error) + .onPost("https://api.github.com/graphql") + .reply(200, successData); + + const { req, res } = faker({}, {}); + await patInfo(req, res); + + expect(res.setHeader).toBeCalledWith("Content-Type", "application/json"); + expect(res.send).toBeCalledWith( + JSON.stringify( + { + validPATs: ["PAT_2", "PAT_3", "PAT_4"], + expiredPATs: [], + exhaustedPATS: ["PAT_1"], + errorPATs: [], + details: { + PAT_1: { + status: "exhausted", + remaining: 0, + resetIn: "0 minutes", + }, + PAT_2: { + status: "valid", + remaining: 4986, + }, + PAT_3: { + status: "valid", + remaining: 4986, + }, + PAT_4: { + status: "valid", + remaining: 4986, + }, + }, + }, + null, + 2, + ), + ); + }); + + it("should return `errorPATs` if a PAT causes an error to be thrown", async () => { + mock + .onPost("https://api.github.com/graphql") + .replyOnce(200, other_error) + .onPost("https://api.github.com/graphql") + .reply(200, successData); + + const { req, res } = faker({}, {}); + await patInfo(req, res); + + expect(res.setHeader).toBeCalledWith("Content-Type", "application/json"); + expect(res.send).toBeCalledWith( + JSON.stringify( + { + validPATs: ["PAT_2", "PAT_3", "PAT_4"], + expiredPATs: [], + exhaustedPATS: [], + errorPATs: ["PAT_1"], + details: { + PAT_1: { + status: "error", + error: { + type: "SOME_ERROR", + message: "This is a error", + }, + }, + PAT_2: { + status: "valid", + remaining: 4986, + }, + PAT_3: { + status: "valid", + remaining: 4986, + }, + PAT_4: { + status: "valid", + remaining: 4986, + }, + }, + }, + null, + 2, + ), + ); + }); + + it("should return `expiredPaths` if a PAT returns a 'Bad credentials' error", async () => { + mock + .onPost("https://api.github.com/graphql") + .replyOnce(404, bad_credentials_error) + .onPost("https://api.github.com/graphql") + .reply(200, successData); + + const { req, res } = faker({}, {}); + await patInfo(req, res); + + expect(res.setHeader).toBeCalledWith("Content-Type", "application/json"); + expect(res.send).toBeCalledWith( + JSON.stringify( + { + validPATs: ["PAT_2", "PAT_3", "PAT_4"], + expiredPATs: ["PAT_1"], + exhaustedPATS: [], + errorPATs: [], + details: { + PAT_1: { + status: "expired", + }, + PAT_2: { + status: "valid", + remaining: 4986, + }, + PAT_3: { + status: "valid", + remaining: 4986, + }, + PAT_4: { + status: "valid", + remaining: 4986, + }, + }, + }, + null, + 2, + ), + ); + }); + + it("should throw an error if something goes wrong", async () => { + mock.onPost("https://api.github.com/graphql").networkError(); + + const { req, res } = faker({}, {}); + await patInfo(req, res); + + expect(res.setHeader).toBeCalledWith("Content-Type", "application/json"); + expect(res.send).toBeCalledWith("Something went wrong: Network Error"); + }); + + it("should have proper cache when no error is thrown", async () => { + mock.onPost("https://api.github.com/graphql").reply(200, successData); + + const { req, res } = faker({}, {}); + await patInfo(req, res); + + expect(res.setHeader.mock.calls).toEqual([ + ["Content-Type", "application/json"], + ["Cache-Control", `max-age=0, s-maxage=${RATE_LIMIT_SECONDS}`], + ]); + }); + + it("should have proper cache when error is thrown", async () => { + mock.reset(); + mock.onPost("https://api.github.com/graphql").networkError(); + + const { req, res } = faker({}, {}); + await patInfo(req, res); + + expect(res.setHeader.mock.calls).toEqual([ + ["Content-Type", "application/json"], + ["Cache-Control", "no-store"], + ]); + }); +}); diff --git a/tests/status.up.test.js b/tests/status.up.test.js new file mode 100644 index 0000000000000..7cf0144b7112d --- /dev/null +++ b/tests/status.up.test.js @@ -0,0 +1,194 @@ +/** + * @file Tests for the status/up cloud function. + */ +import { jest } from "@jest/globals"; +import axios from "axios"; +import MockAdapter from "axios-mock-adapter"; +import up, { RATE_LIMIT_SECONDS } from "../api/status/up.js"; + +const mock = new MockAdapter(axios); + +const successData = { + rateLimit: { + remaining: 4986, + }, +}; + +const faker = (query) => { + const req = { + query: { ...query }, + }; + const res = { + setHeader: jest.fn(), + send: jest.fn(), + }; + + return { req, res }; +}; + +const rate_limit_error = { + errors: [ + { + type: "RATE_LIMITED", + }, + ], +}; + +const bad_credentials_error = { + message: "Bad credentials", +}; + +const shields_up = { + schemaVersion: 1, + label: "Public Instance", + isError: true, + message: "up", + color: "brightgreen", +}; +const shields_down = { + schemaVersion: 1, + label: "Public Instance", + isError: true, + message: "down", + color: "red", +}; + +afterEach(() => { + mock.reset(); +}); + +describe("Test /api/status/up", () => { + it("should return `true` if request was successful", async () => { + mock.onPost("https://api.github.com/graphql").replyOnce(200, successData); + + const { req, res } = faker({}, {}); + await up(req, res); + + expect(res.setHeader).toBeCalledWith("Content-Type", "application/json"); + expect(res.send).toBeCalledWith(true); + }); + + it("should return `false` if all PATs are rate limited", async () => { + mock.onPost("https://api.github.com/graphql").reply(200, rate_limit_error); + + const { req, res } = faker({}, {}); + await up(req, res); + + expect(res.setHeader).toBeCalledWith("Content-Type", "application/json"); + expect(res.send).toBeCalledWith(false); + }); + + it("should return JSON `true` if request was successful and type='json'", async () => { + mock.onPost("https://api.github.com/graphql").replyOnce(200, successData); + + const { req, res } = faker({ type: "json" }, {}); + await up(req, res); + + expect(res.setHeader).toBeCalledWith("Content-Type", "application/json"); + expect(res.send).toBeCalledWith({ up: true }); + }); + + it("should return JSON `false` if all PATs are rate limited and type='json'", async () => { + mock.onPost("https://api.github.com/graphql").reply(200, rate_limit_error); + + const { req, res } = faker({ type: "json" }, {}); + await up(req, res); + + expect(res.setHeader).toBeCalledWith("Content-Type", "application/json"); + expect(res.send).toBeCalledWith({ up: false }); + }); + + it("should return UP shields.io config if request was successful and type='shields'", async () => { + mock.onPost("https://api.github.com/graphql").replyOnce(200, successData); + + const { req, res } = faker({ type: "shields" }, {}); + await up(req, res); + + expect(res.setHeader).toBeCalledWith("Content-Type", "application/json"); + expect(res.send).toBeCalledWith(shields_up); + }); + + it("should return DOWN shields.io config if all PATs are rate limited and type='shields'", async () => { + mock.onPost("https://api.github.com/graphql").reply(200, rate_limit_error); + + const { req, res } = faker({ type: "shields" }, {}); + await up(req, res); + + expect(res.setHeader).toBeCalledWith("Content-Type", "application/json"); + expect(res.send).toBeCalledWith(shields_down); + }); + + it("should return `true` if the first PAT is rate limited but the second PATs works", async () => { + mock + .onPost("https://api.github.com/graphql") + .replyOnce(200, rate_limit_error) + .onPost("https://api.github.com/graphql") + .replyOnce(200, successData); + + const { req, res } = faker({}, {}); + await up(req, res); + + expect(res.setHeader).toBeCalledWith("Content-Type", "application/json"); + expect(res.send).toBeCalledWith(true); + }); + + it("should return `true` if the first PAT has 'Bad credentials' but the second PAT works", async () => { + mock + .onPost("https://api.github.com/graphql") + .replyOnce(404, bad_credentials_error) + .onPost("https://api.github.com/graphql") + .replyOnce(200, successData); + + const { req, res } = faker({}, {}); + await up(req, res); + + expect(res.setHeader).toBeCalledWith("Content-Type", "application/json"); + expect(res.send).toBeCalledWith(true); + }); + + it("should return `false` if all pats have 'Bad credentials'", async () => { + mock + .onPost("https://api.github.com/graphql") + .reply(404, bad_credentials_error); + + const { req, res } = faker({}, {}); + await up(req, res); + + expect(res.setHeader).toBeCalledWith("Content-Type", "application/json"); + expect(res.send).toBeCalledWith(false); + }); + + it("should throw an error if the request fails", async () => { + mock.onPost("https://api.github.com/graphql").networkError(); + + const { req, res } = faker({}, {}); + await up(req, res); + + expect(res.setHeader).toBeCalledWith("Content-Type", "application/json"); + expect(res.send).toBeCalledWith(false); + }); + + it("should have proper cache when no error is thrown", async () => { + mock.onPost("https://api.github.com/graphql").replyOnce(200, successData); + + const { req, res } = faker({}, {}); + await up(req, res); + + expect(res.setHeader.mock.calls).toEqual([ + ["Content-Type", "application/json"], + ["Cache-Control", `max-age=0, s-maxage=${RATE_LIMIT_SECONDS}`], + ]); + }); + + it("should have proper cache when error is thrown", async () => { + mock.onPost("https://api.github.com/graphql").networkError(); + + const { req, res } = faker({}, {}); + await up(req, res); + + expect(res.setHeader.mock.calls).toEqual([ + ["Content-Type", "application/json"], + ["Cache-Control", "no-store"], + ]); + }); +}); From 7aa502d45377c73f89c6d1096c29cd862188aa2e Mon Sep 17 00:00:00 2001 From: Anurag Hazra Date: Sat, 28 Jan 2023 20:32:40 +0530 Subject: [PATCH 17/48] chore: minor changes in pat info (#2481) --- api/status/pat-info.js | 14 +++++++++++--- tests/pat-info.test.js | 9 ++++++--- 2 files changed, 17 insertions(+), 6 deletions(-) diff --git a/api/status/pat-info.js b/api/status/pat-info.js index 720611d424919..775e06896427c 100644 --- a/api/status/pat-info.js +++ b/api/status/pat-info.js @@ -6,7 +6,7 @@ */ import { logger, request, dateDiff } from "../../src/common/utils.js"; -export const RATE_LIMIT_SECONDS = 60 * 10; // 1 request per 10 minutes +export const RATE_LIMIT_SECONDS = 60 * 5; // 1 request per 10 minutes /** * Simple uptime check fetcher for the PATs. @@ -98,12 +98,20 @@ const getPATInfo = async (fetcher, variables) => { return Object.keys(details).filter((pat) => details[pat].status === status); }; + const sortedDetails = Object.keys(details) + .sort() + .reduce((obj, key) => { + obj[key] = details[key]; + return obj; + }, {}); + return { validPATs: filterPATsByStatus("valid"), expiredPATs: filterPATsByStatus("expired"), - exhaustedPATS: filterPATsByStatus("exhausted"), + exhaustedPATs: filterPATsByStatus("exhausted"), + suspendedPATs: filterPATsByStatus("suspended"), errorPATs: filterPATsByStatus("error"), - details, + details: sortedDetails, }; }; diff --git a/tests/pat-info.test.js b/tests/pat-info.test.js index 9635ab24c837c..23aca8c40e5ca 100644 --- a/tests/pat-info.test.js +++ b/tests/pat-info.test.js @@ -88,7 +88,8 @@ describe("Test /api/status/pat-info", () => { { validPATs: ["PAT_2", "PAT_3", "PAT_4"], expiredPATs: [], - exhaustedPATS: ["PAT_1"], + exhaustedPATs: ["PAT_1"], + suspendedPATs: [], errorPATs: [], details: { PAT_1: { @@ -132,7 +133,8 @@ describe("Test /api/status/pat-info", () => { { validPATs: ["PAT_2", "PAT_3", "PAT_4"], expiredPATs: [], - exhaustedPATS: [], + exhaustedPATs: [], + suspendedPATs: [], errorPATs: ["PAT_1"], details: { PAT_1: { @@ -178,7 +180,8 @@ describe("Test /api/status/pat-info", () => { { validPATs: ["PAT_2", "PAT_3", "PAT_4"], expiredPATs: ["PAT_1"], - exhaustedPATS: [], + exhaustedPATs: [], + suspendedPATs: [], errorPATs: [], details: { PAT_1: { From 112000667c01f18fd161f204ae3ee796ec2e3011 Mon Sep 17 00:00:00 2001 From: Rick Staa Date: Sun, 29 Jan 2023 15:19:01 +0100 Subject: [PATCH 18/48] ci: add update languages action (#2484) * ci: add update languages action * ci: make sure PR is created when upstream languages are updated --- .github/workflows/update-langs.yaml | 44 +++++++++++++++++++++++++++++ src/common/languageColors.json | 18 ++++++++++++ 2 files changed, 62 insertions(+) create mode 100644 .github/workflows/update-langs.yaml diff --git a/.github/workflows/update-langs.yaml b/.github/workflows/update-langs.yaml new file mode 100644 index 0000000000000..ad6bfb6213b8f --- /dev/null +++ b/.github/workflows/update-langs.yaml @@ -0,0 +1,44 @@ +name: Update supported languages +on: + schedule: + - cron: "0 0 */30 * *" + +jobs: + updateLanguages: + if: github.repository == 'anuraghazra/github-readme-stats' + name: Update supported languages + runs-on: ubuntu-latest + strategy: + matrix: + node-version: [16.x] + + steps: + - uses: actions/checkout@v3 + + - name: Setup Node + uses: actions/setup-node@v3 + with: + node-version: ${{ matrix.node-version }} + cache: npm + + - name: Install dependencies + run: npm ci + env: + CI: true + + - name: Run update-languages-json.js script + run: npm run generate-langs-json + + - name: Create Pull Request if upstream language file is changed + uses: peter-evans/create-pull-request@v4 + with: + commit-message: "refactor: update languages JSON" + branch: "update_langs/patch" + delete-branch: true + title: Update languages JSON + body: + "The + [update-langs](https://github.com/anuraghazra/github-readme-stats/actions/workflows/update-langs.yaml) + action found new/updated languages in the [upstream languages JSON + file](https://raw.githubusercontent.com/github/linguist/master/lib/linguist/languages.yml)." + labels: "ci, lang-card" diff --git a/src/common/languageColors.json b/src/common/languageColors.json index 7e8cd551264b8..47bfb1cfa3435 100644 --- a/src/common/languageColors.json +++ b/src/common/languageColors.json @@ -102,6 +102,7 @@ "Csound Score": "#1a1a1a", "Cuda": "#3A4E3A", "Curry": "#531242", + "Cypher": "#34c0eb", "Cython": "#fedf5b", "D": "#ba595e", "DM": "#447265", @@ -124,6 +125,7 @@ "Earthly": "#2af0ff", "Easybuild": "#069406", "Ecere Projects": "#913960", + "Ecmarkup": "#eb8131", "EditorConfig": "#fff1f2", "Eiffel": "#4d6977", "Elixir": "#6e4a7e", @@ -215,6 +217,7 @@ "Idris": "#b30000", "Ignore List": "#000000", "ImageJ Macro": "#99AAFF", + "Imba": "#16cec6", "Inno Setup": "#264b99", "Io": "#a9188d", "Ioke": "#078193", @@ -286,6 +289,7 @@ "Mathematica": "#dd1100", "Max": "#c4a79c", "Mercury": "#ff2b2b", + "Mermaid": "#ff3670", "Meson": "#007800", "Metal": "#8f14e9", "MiniYAML": "#ff1111", @@ -318,6 +322,10 @@ "Nu": "#c9df40", "NumPy": "#9C8AF9", "Nunjucks": "#3d8137", + "OASv2-json": "#85ea2d", + "OASv2-yaml": "#85ea2d", + "OASv3-json": "#85ea2d", + "OASv3-yaml": "#85ea2d", "OCaml": "#3be133", "ObjectScript": "#424893", "Objective-C": "#438eff", @@ -327,14 +335,18 @@ "Omgrofl": "#cabbff", "Opal": "#f7ede0", "Open Policy Agent": "#7d9199", + "OpenAPI Specification v2": "#85ea2d", + "OpenAPI Specification v3": "#85ea2d", "OpenCL": "#ed2e2d", "OpenEdge ABL": "#5ce600", "OpenQASM": "#AA70FF", "OpenSCAD": "#e5cd45", + "Option List": "#476732", "Org": "#77aa99", "Oxygene": "#cdd0e3", "Oz": "#fab738", "P4": "#7055b5", + "PDDL": "#0d00ff", "PEG.js": "#234d6b", "PHP": "#4F5D95", "PLSQL": "#dad8d8", @@ -351,6 +363,7 @@ "PigLatin": "#fcd7de", "Pike": "#005390", "PogoScript": "#d80074", + "Polar": "#ae81ff", "Portugol": "#f8bd00", "PostCSS": "#dc3a0c", "PostScript": "#da291c", @@ -414,6 +427,7 @@ "Sass": "#a53b70", "Scala": "#c22d40", "Scaml": "#bd181a", + "Scenic": "#fdc700", "Scheme": "#1e4aec", "Scilab": "#ca0f21", "Self": "#0579aa", @@ -421,6 +435,7 @@ "Shell": "#89e051", "ShellCheck Config": "#cecfcb", "Shen": "#120F14", + "Simple File Verification": "#C9BFED", "Singularity": "#64E6AD", "Slash": "#007eff", "Slice": "#003fa2", @@ -428,6 +443,7 @@ "SmPL": "#c94949", "Smalltalk": "#596706", "Smarty": "#f0c040", + "Smithy": "#c44536", "Solidity": "#AA6746", "SourcePawn": "#f69e1d", "Squirrel": "#800000", @@ -478,6 +494,7 @@ "Vim Script": "#199f4b", "Vim Snippet": "#199f4b", "Visual Basic .NET": "#945db7", + "Visual Basic 6.0": "#2c6353", "Volt": "#1F1F1F", "Vue": "#41b883", "Vyper": "#2980b9", @@ -514,6 +531,7 @@ "fish": "#4aae47", "hoon": "#00b171", "jq": "#c7254e", + "just": "#384d54", "kvlang": "#1da6e0", "mIRC Script": "#3d57c3", "mcfunction": "#E22837", From 888663a47728a4e82a78dcc6fd95ce53bdaffc70 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B0=95=EB=AF=BC=EC=A7=80?= <68285922+Meezzi@users.noreply.github.com> Date: Tue, 14 Feb 2023 09:50:12 +0900 Subject: [PATCH 19/48] Add `rose` theme (#2480) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: 강민지 <68285922+Kminzzi@users.noreply.github.com> --- themes/index.js | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/themes/index.js b/themes/index.js index a5d3abae8cb6f..60825d132a119 100644 --- a/themes/index.js +++ b/themes/index.js @@ -374,6 +374,13 @@ export const themes = { border_color: "170F0C", bg_color: "170F0C", }, + rose: { + title_color: "8d192b", + text_color: "862931", + icon_color: "B71F36", + border_color: "e9d8d4", + bg_color: "e9d8d4", + }, }; export default themes; From ba7c2f8b55eac8452e479c8bd38b044d204d0424 Mon Sep 17 00:00:00 2001 From: Amir Date: Thu, 16 Feb 2023 04:53:11 +0330 Subject: [PATCH 20/48] Support hide_progress for top-langs feature (#2514) * Add support for hide_progress in top languages feature * Fix mistake * Add documents for all languages * Remove unnecessary value check * Update top-languages-card.js * Revert document for all languages except English * Update documentation * Update documentation --------- Co-authored-by: Zohan Subhash --- api/top-langs.js | 2 ++ readme.md | 13 +++++++++++ src/cards/top-languages-card.js | 39 +++++++++++++++++++++++---------- src/cards/types.d.ts | 1 + 4 files changed, 44 insertions(+), 11 deletions(-) diff --git a/api/top-langs.js b/api/top-langs.js index 19cccb894e33a..e67d953323441 100644 --- a/api/top-langs.js +++ b/api/top-langs.js @@ -30,6 +30,7 @@ export default async (req, res) => { border_radius, border_color, disable_animations, + hide_progress, } = req.query; res.setHeader("Content-Type", "image/svg+xml"); @@ -77,6 +78,7 @@ export default async (req, res) => { border_color, locale: locale ? locale.toLowerCase() : null, disable_animations: parseBoolean(disable_animations), + hide_progress: parseBoolean(hide_progress), }), ); } catch (err) { diff --git a/readme.md b/readme.md index 678c5c0b14af4..29852bf9c6515 100644 --- a/readme.md +++ b/readme.md @@ -305,6 +305,7 @@ You can provide multiple comma-separated values in the bg_color option to render - `exclude_repo` - Exclude 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` - It uses the compact layout option, hides percentages, and removes the bars. Default: `false`. > **Warning** > Language names should be URI-escaped, as specified in [Percent Encoding](https://en.wikipedia.org/wiki/Percent-encoding) @@ -398,6 +399,14 @@ You can use the `&layout=compact` option to change the card design. [![Top Langs](https://github-readme-stats.vercel.app/api/top-langs/?username=anuraghazra&layout=compact)](https://github.com/anuraghazra/github-readme-stats) ``` +### Hide Progress Bars + +You can use the `&hide_progress=true` option to hide the percentages and the progress bars (layout will be automatically set to `compact`). + +```md +[![Top Langs](https://github-readme-stats.vercel.app/api/top-langs/?username=anuraghazra&hide_progress=true)](https://github.com/anuraghazra/github-readme-stats) +``` + ### Demo [![Top Langs](https://github-readme-stats.vercel.app/api/top-langs/?username=anuraghazra)](https://github.com/anuraghazra/github-readme-stats) @@ -406,6 +415,10 @@ You can use the `&layout=compact` option to change the card design. [![Top Langs](https://github-readme-stats.vercel.app/api/top-langs/?username=anuraghazra&layout=compact)](https://github.com/anuraghazra/github-readme-stats) +- Hidden progress bars + +[![Top Langs](https://github-readme-stats.vercel.app/api/top-langs/?username=anuraghazra&hide_progress=true)](https://github.com/anuraghazra/github-readme-stats) + # Wakatime Week Stats Change the `?username=` value to your [Wakatime](https://wakatime.com) username. diff --git a/src/cards/top-languages-card.js b/src/cards/top-languages-card.js index 9396ff8e73d5e..be1328c0c8fe3 100644 --- a/src/cards/top-languages-card.js +++ b/src/cards/top-languages-card.js @@ -76,10 +76,11 @@ const createProgressTextNode = ({ width, color, name, progress, index }) => { * @param {object} props Function properties. * @param {Lang} props.lang Programming language object. * @param {number} props.totalSize Total size of all languages. + * @param {boolean} props.hideProgress Whether to hide percentage. * @param {number} props.index Index of the programming language. * @returns {string} Compact layout programming language SVG node. */ -const createCompactLangNode = ({ lang, totalSize, index }) => { +const createCompactLangNode = ({ lang, totalSize, hideProgress, index }) => { const percentage = ((lang.size / totalSize) * 100).toFixed(2); const staggerDelay = (index + 3) * 150; const color = lang.color || "#858585"; @@ -88,7 +89,7 @@ const createCompactLangNode = ({ lang, totalSize, index }) => { - ${lang.name} ${percentage}% + ${lang.name} ${hideProgress ? "" : percentage + "%"} `; @@ -100,9 +101,10 @@ const createCompactLangNode = ({ lang, totalSize, index }) => { * @param {object[]} props Function properties. * @param {Lang[]} props.langs Array of programming languages. * @param {number} props.totalSize Total size of all languages. + * @param {boolean} props.hideProgress Whether to hide percentage. * @returns {string} Programming languages SVG node. */ -const createLanguageTextNode = ({ langs, totalSize }) => { +const createLanguageTextNode = ({ langs, totalSize, hideProgress }) => { const longestLang = getLongestLang(langs); const chunked = chunkArray(langs, langs.length / 2); const layouts = chunked.map((array) => { @@ -111,6 +113,7 @@ const createLanguageTextNode = ({ langs, totalSize }) => { createCompactLangNode({ lang, totalSize, + hideProgress, index, }), ); @@ -160,9 +163,10 @@ const renderNormalLayout = (langs, width, totalLanguageSize) => { * @param {Lang[]} langs Array of programming languages. * @param {number} width Card width. * @param {number} totalLanguageSize Total size of all languages. + * @param {boolean} hideProgress Whether to hide progress bar. * @returns {string} Compact layout card SVG object. */ -const renderCompactLayout = (langs, width, totalLanguageSize) => { +const renderCompactLayout = (langs, width, totalLanguageSize, hideProgress) => { const paddingRight = 50; const offsetWidth = width - paddingRight; // progressOffset holds the previous language's width and used to offset the next language @@ -193,15 +197,21 @@ const renderCompactLayout = (langs, width, totalLanguageSize) => { .join(""); return ` - + ${ + !hideProgress + ? ` + ${compactProgressBar} - - + ` + : "" + } + ${createLanguageTextNode({ langs, totalSize: totalLanguageSize, + hideProgress: hideProgress, })} `; @@ -276,6 +286,7 @@ const renderTopLanguages = (topLangs, options = {}) => { text_color, bg_color, hide, + hide_progress, theme, layout, custom_title, @@ -305,11 +316,17 @@ const renderTopLanguages = (topLangs, options = {}) => { let height = calculateNormalLayoutHeight(langs.length); let finalLayout = ""; - if (layout === "compact") { + if (layout === "compact" || hide_progress == true) { width = width + 50; // padding - height = calculateCompactLayoutHeight(langs.length); - - finalLayout = renderCompactLayout(langs, width, totalLanguageSize); + height = + calculateCompactLayoutHeight(langs.length) + (hide_progress ? -25 : 0); + + finalLayout = renderCompactLayout( + langs, + width, + totalLanguageSize, + hide_progress, + ); } else { finalLayout = renderNormalLayout(langs, width, totalLanguageSize); } diff --git a/src/cards/types.d.ts b/src/cards/types.d.ts index c5945d48be71e..52ee0edb6a459 100644 --- a/src/cards/types.d.ts +++ b/src/cards/types.d.ts @@ -38,6 +38,7 @@ export type TopLangOptions = CommonOptions & { custom_title: string; langs_count: number; disable_animations: boolean; + hide_progress: boolean; }; type WakaTimeOptions = CommonOptions & { From 5f20e6c97a35d77c4b2839b340bfc188ed23a056 Mon Sep 17 00:00:00 2001 From: Mohamed Hassan Date: Sat, 25 Feb 2023 08:49:30 +0200 Subject: [PATCH 21/48] add holi_theme (#2539) * add holi_theme * add holi_theme --- themes/index.js | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/themes/index.js b/themes/index.js index 60825d132a119..5ed0f782fd90a 100644 --- a/themes/index.js +++ b/themes/index.js @@ -381,6 +381,13 @@ export const themes = { border_color: "e9d8d4", bg_color: "e9d8d4", }, + holi_theme: { + title_color: "5FABEE", + text_color: "D6E7FF", + icon_color: "5FABEE", + border_color: "85A4C0", + bg_color: "030314", + }, }; export default themes; From a6ff0fa521f87f112f124bc7a9d0b435d4d166b4 Mon Sep 17 00:00:00 2001 From: Oleksandr Perlov Date: Sat, 25 Feb 2023 08:50:00 +0200 Subject: [PATCH 22/48] Add one_dark_pro (#2507) This colors were taken from One Dark Pro theme in VSCode extention. Please add it and I will use it in my profile Co-authored-by: Zohan Subhash --- themes/index.js | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/themes/index.js b/themes/index.js index 5ed0f782fd90a..e55c12f89bdce 100644 --- a/themes/index.js +++ b/themes/index.js @@ -374,6 +374,12 @@ export const themes = { border_color: "170F0C", bg_color: "170F0C", }, + one_dark_pro: { + title_color: "61AFEF", + text_color: "E5C06E", + icon_color: "C678DD", + border_color: "3B4048", + bg_color: "23272E", rose: { title_color: "8d192b", text_color: "862931", From f3f7a4837d48cb82abd61e8b16631f0f161fca9f Mon Sep 17 00:00:00 2001 From: Cateline Mnemosyne <123184375+catelinemnemosyne@users.noreply.github.com> Date: Sat, 25 Feb 2023 09:55:12 +0100 Subject: [PATCH 23/48] fix: fix JSON themes bug. (#2544) This fixes a JSON bug that was introduced in https://github.com/anuraghazra/github-readme-stats/pull/2507. --- themes/index.js | 1 + 1 file changed, 1 insertion(+) diff --git a/themes/index.js b/themes/index.js index e55c12f89bdce..01c9a8eee79d6 100644 --- a/themes/index.js +++ b/themes/index.js @@ -380,6 +380,7 @@ export const themes = { icon_color: "C678DD", border_color: "3B4048", bg_color: "23272E", + }, rose: { title_color: "8d192b", text_color: "862931", From 55a303b4a621b9f1ac1ce0c6fe298139df7d2ac1 Mon Sep 17 00:00:00 2001 From: Zohan Subhash Date: Sat, 25 Feb 2023 17:20:14 +0530 Subject: [PATCH 24/48] Add auto-labelling for documentation updates (#2526) --- .github/labeler.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/labeler.yml b/.github/labeler.yml index be97765f07e42..fad3eeeb8d101 100644 --- a/.github/labeler.yml +++ b/.github/labeler.yml @@ -1,3 +1,4 @@ themes: themes/index.js doc-translation: docs/* card-i18n: src/translations.js +documentation: readme.md From 91345ed55fab44acac016a25d7083fc74b0b1592 Mon Sep 17 00:00:00 2001 From: Rick Staa Date: Sat, 25 Feb 2023 12:51:54 +0100 Subject: [PATCH 25/48] ci: fix unsafe directory bug (#2518) This commit fixes a bug that was introduced due to a upstream change in the git package. See https://stackoverflow.com/questions/71849415/i-cannot-add-the-parent-directory-to-safe-directory-in-git/71904131#71904131 for more information. --- .github/workflows/generate-theme-doc.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.github/workflows/generate-theme-doc.yml b/.github/workflows/generate-theme-doc.yml index d5fac06381943..75f6511f09015 100644 --- a/.github/workflows/generate-theme-doc.yml +++ b/.github/workflows/generate-theme-doc.yml @@ -23,6 +23,10 @@ jobs: node-version: ${{ matrix.node-version }} cache: npm + # Fix the unsafe repo error which was introduced by the CVE-2022-24765 git patches. + - name: Fix unsafe repo error + run: git config --global --add safe.directory ${{ github.workspace }} + - name: npm install, generate readme run: | npm ci From 8898d013b67481844213e065407cbe64ef6f3292 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Raphael=20Gon=C3=A7alves?= <89359384+raphaelricardo10@users.noreply.github.com> Date: Sat, 25 Feb 2023 09:02:42 -0300 Subject: [PATCH 26/48] Update readme.md (#2414) fix: missing "&" in show_icons=true in Showing icons section --- readme.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/readme.md b/readme.md index 29852bf9c6515..83f573eddef12 100644 --- a/readme.md +++ b/readme.md @@ -133,7 +133,7 @@ You can add the count of all your private contributions to the total commits cou ### Showing icons -To enable icons, you can pass `show_icons=true` in the query param, like so: +To enable icons, you can pass `&show_icons=true` in the query param, like so: ```md ![Anurag's GitHub stats](https://github-readme-stats.vercel.app/api?username=anuraghazra&show_icons=true) From a3c6f874af5c7140c67d3db4e15f85fb92171fd8 Mon Sep 17 00:00:00 2001 From: Rick Staa Date: Sat, 25 Feb 2023 13:32:08 +0100 Subject: [PATCH 27/48] test: update snapshots (#2519) --- src/cards/wakatime-card.js | 26 ++++++++++- .../renderWakatimeCard.test.js.snap | 44 +++++++++++++++++++ 2 files changed, 69 insertions(+), 1 deletion(-) diff --git a/src/cards/wakatime-card.js b/src/cards/wakatime-card.js index e7af1df710f9c..2c329558b8a35 100644 --- a/src/cards/wakatime-card.js +++ b/src/cards/wakatime-card.js @@ -118,6 +118,7 @@ const createTextNode = ({ // @ts-ignore name: label, progressBarBackgroundColor, + delay: staggerDelay + 300, }); return ` @@ -276,11 +277,12 @@ const renderWakatimeCard = (stats = {}, options = { hide: [] }) => { } else { finalLayout = flexLayout({ items: filteredLanguages.length - ? filteredLanguages.map((language) => { + ? filteredLanguages.map((language, index) => { return createTextNode({ id: language.name, label: language.name, value: language.text, + index: index, percent: language.percent, // @ts-ignore progressBarColor: titleColor, @@ -321,7 +323,29 @@ const renderWakatimeCard = (stats = {}, options = { hide: [] }) => { card.setCSS( ` ${cssStyles} + @keyframes slideInAnimation { + from { + width: 0; + } + to { + width: calc(100%-100px); + } + } + @keyframes growWidthAnimation { + from { + width: 0; + } + to { + width: 100%; + } + } .lang-name { font: 400 11px 'Segoe UI', Ubuntu, Sans-Serif; fill: ${textColor} } + #rect-mask rect{ + animation: slideInAnimation 1s ease-in-out forwards; + } + .lang-progress{ + animation: growWidthAnimation 0.6s ease-in-out forwards; + } `, ); diff --git a/tests/__snapshots__/renderWakatimeCard.test.js.snap b/tests/__snapshots__/renderWakatimeCard.test.js.snap index 1c0bd701fbbfe..6dfaf98e9742a 100644 --- a/tests/__snapshots__/renderWakatimeCard.test.js.snap +++ b/tests/__snapshots__/renderWakatimeCard.test.js.snap @@ -69,7 +69,29 @@ exports[`Test Render Wakatime Card should render correctly with compact layout 1 } + @keyframes slideInAnimation { + from { + width: 0; + } + to { + width: calc(100%-100px); + } + } + @keyframes growWidthAnimation { + from { + width: 0; + } + to { + width: 100%; + } + } .lang-name { font: 400 11px 'Segoe UI', Ubuntu, Sans-Serif; fill: #434d58 } + #rect-mask rect{ + animation: slideInAnimation 1s ease-in-out forwards; + } + .lang-progress{ + animation: growWidthAnimation 0.6s ease-in-out forwards; + } @@ -227,7 +249,29 @@ exports[`Test Render Wakatime Card should render correctly with compact layout w } + @keyframes slideInAnimation { + from { + width: 0; + } + to { + width: calc(100%-100px); + } + } + @keyframes growWidthAnimation { + from { + width: 0; + } + to { + width: 100%; + } + } .lang-name { font: 400 11px 'Segoe UI', Ubuntu, Sans-Serif; fill: #434d58 } + #rect-mask rect{ + animation: slideInAnimation 1s ease-in-out forwards; + } + .lang-progress{ + animation: growWidthAnimation 0.6s ease-in-out forwards; + } From 2ab8b85ae39e7b3307c27328916b97e96f1da00f Mon Sep 17 00:00:00 2001 From: Rehman Date: Sat, 25 Feb 2023 19:01:49 +0530 Subject: [PATCH 28/48] fix: for issue #2534 (#2536) --- src/cards/top-languages-card.js | 3 +-- tests/renderTopLanguages.test.js | 6 +++--- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/src/cards/top-languages-card.js b/src/cards/top-languages-card.js index be1328c0c8fe3..ce8e12a839c77 100644 --- a/src/cards/top-languages-card.js +++ b/src/cards/top-languages-card.js @@ -13,7 +13,7 @@ import { import { langCardLocales } from "../translations.js"; const DEFAULT_CARD_WIDTH = 300; -const MIN_CARD_WIDTH = 230; +const MIN_CARD_WIDTH = 280; const DEFAULT_LANGS_COUNT = 5; const DEFAULT_LANG_COLOR = "#858585"; const CARD_PADDING = 25; @@ -317,7 +317,6 @@ const renderTopLanguages = (topLangs, options = {}) => { let finalLayout = ""; if (layout === "compact" || hide_progress == true) { - width = width + 50; // padding height = calculateCompactLayoutHeight(langs.length) + (hide_progress ? -25 : 0); diff --git a/tests/renderTopLanguages.test.js b/tests/renderTopLanguages.test.js index 8ae4bbd0c16e6..de9e21f129bdf 100644 --- a/tests/renderTopLanguages.test.js +++ b/tests/renderTopLanguages.test.js @@ -216,7 +216,7 @@ describe("Test renderTopLanguages", () => { ); expect(queryAllByTestId(document.body, "lang-progress")[0]).toHaveAttribute( "width", - "120", + "100", ); expect(queryAllByTestId(document.body, "lang-name")[1]).toHaveTextContent( @@ -224,7 +224,7 @@ describe("Test renderTopLanguages", () => { ); expect(queryAllByTestId(document.body, "lang-progress")[1]).toHaveAttribute( "width", - "120", + "100", ); expect(queryAllByTestId(document.body, "lang-name")[2]).toHaveTextContent( @@ -232,7 +232,7 @@ describe("Test renderTopLanguages", () => { ); expect(queryAllByTestId(document.body, "lang-progress")[2]).toHaveAttribute( "width", - "60", + "50", ); }); From 1d528da1dcfe301861810e7e559e70da262c4d4f Mon Sep 17 00:00:00 2001 From: Zohan Subhash Date: Sat, 25 Feb 2023 19:34:00 +0530 Subject: [PATCH 29/48] Add option to deploy using other services (#2525) * Create express.js * Update readme.md * Update readme.md --- express.js | 15 +++++++++++++++ readme.md | 25 +++++++++++++++++++++++-- 2 files changed, 38 insertions(+), 2 deletions(-) create mode 100644 express.js diff --git a/express.js b/express.js new file mode 100644 index 0000000000000..0a139625e06bb --- /dev/null +++ b/express.js @@ -0,0 +1,15 @@ +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 express from 'express' +import dotenv from 'dotenv' + +dotenv.config() +const app = express() +app.listen(process.env.port || 9000) + +app.get('/', statsCard) +app.get('/pin', repoCard) +app.get('/top-langs', langCard) +app.get('/wakatime', wakatimeCard) diff --git a/readme.md b/readme.md index 83f573eddef12..7a60228460126 100644 --- a/readme.md +++ b/readme.md @@ -92,7 +92,9 @@ Visit and make a small donation to hel - [Repo Card Exclusive Options](#repo-card-exclusive-options) - [Language Card Exclusive Options](#language-card-exclusive-options) - [Wakatime Card Exclusive Option](#wakatime-card-exclusive-options) -- [Deploy Yourself](#deploy-on-your-own-vercel-instance) +- [Deploy Yourself](#deploy-on-your-own) + - [On Vercel](#on-vercel) + - [On other platforms](#on-other-platforms) - [Keep your fork up to date](#keep-your-fork-up-to-date) # GitHub Stats Card @@ -509,7 +511,9 @@ By default, GitHub does not lay out the cards side by side. To do that, you can ``` -## Deploy on your own Vercel instance +## Deploy on your own + +### On Vercel #### :film_projector: [Check Out Step By Step Video Tutorial By @codeSTACKr](https://youtu.be/n6d4KHSKqGk?t=107) @@ -546,6 +550,23 @@ Since the GitHub API only allows 5k requests per hour, my `https://github-readme +### On other platforms + +> **Warning** +> This way of using GRS is not officially supported and was added to cater to some particular use cases where Vercel could not be used (e.g. #2341). The support for this method, therefore, is limited. + +
+:hammer_and_wrench: Step-by-step guide for deploying on other platforms + +1. Fork or clone this repo as per your needs +2. Add `express` to the dependencies section of `package.json` +https://github.com/anuraghazra/github-readme-stats/blob/ba7c2f8b55eac8452e479c8bd38b044d204d0424/package.json#L54-L61 +3. Run `npm i` if needed (initial setup) +4. Run `node express.js` to start the server, or set the entry point to `express.js` in `package.json` if you're deploying on a managed service +https://github.com/anuraghazra/github-readme-stats/blob/ba7c2f8b55eac8452e479c8bd38b044d204d0424/package.json#L11 +5. You're done 🎉 +
+ ### Keep your fork up to date You can keep your fork, and thus your private Vercel instance up to date with the upstream using GitHubs' [Sync Fork button](https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/working-with-forks/syncing-a-fork). You can also use the [pull](https://github.com/wei/pull) package created by [@wei](https://github.com/wei) to automate this process. From 82224fa68a453c82b7d5458c5431d3efb01f1853 Mon Sep 17 00:00:00 2001 From: Rick Staa Date: Sat, 25 Feb 2023 15:12:11 +0100 Subject: [PATCH 30/48] ci: update e2e tests (#2548) --- .gitignore | 2 ++ tests/e2e/e2e.test.js | 6 +++--- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/.gitignore b/.gitignore index 2cdfa3d334808..25017502d486a 100644 --- a/.gitignore +++ b/.gitignore @@ -10,3 +10,5 @@ vercel_token # IDE .vscode *.code-workspace + +.vercel diff --git a/tests/e2e/e2e.test.js b/tests/e2e/e2e.test.js index 402e210fcee17..f34859d4c8be0 100644 --- a/tests/e2e/e2e.test.js +++ b/tests/e2e/e2e.test.js @@ -15,14 +15,14 @@ const REPO = "curly-fiesta"; const USER = "catelinemnemosyne"; const STATS_DATA = { name: "Cateline Mnemosyne", - totalPRs: 1, - totalCommits: 7, + totalPRs: 2, + totalCommits: 8, totalIssues: 1, totalStars: 1, contributedTo: 1, rank: { level: "A+", - score: 50.893750297869225, + score: 50.88831151384285, }, }; From d5fbfb4345a89c264b8b07b16dedf933fa60691a Mon Sep 17 00:00:00 2001 From: Rick Staa Date: Sat, 25 Feb 2023 15:14:47 +0100 Subject: [PATCH 31/48] ci: fix a bug in the theme preview action (#2549) --- scripts/preview-theme.js | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/scripts/preview-theme.js b/scripts/preview-theme.js index 38faf873ce3d5..aae892c602fdb 100644 --- a/scripts/preview-theme.js +++ b/scripts/preview-theme.js @@ -298,12 +298,13 @@ const themeNameAlreadyExists = (name) => { return themes[name] !== undefined; }; +const DRY_RUN = process.env.DRY_RUN === "true" || false; + /** * Main function. */ export const run = async (prNumber) => { try { - const dryRun = process.env.DRY_RUN === "true" || false; debug("Retrieve action information from context..."); debug(`Context: ${inspect(github.context)}`); let commentBody = ` @@ -513,7 +514,7 @@ export const run = async (prNumber) => { // Create or update theme-preview comment. debug("Create or update theme-preview comment..."); let comment_url; - if (!dryRun) { + if (!DRY_RUN) { comment_url = await upsertComment(octokit, { comment_id: comment?.id, issue_number: pullRequestId, @@ -535,7 +536,7 @@ export const run = async (prNumber) => { const reviewReason = themesValid ? undefined : INVALID_REVIEW_COMMENT(comment_url); - if (!dryRun) { + if (!DRY_RUN) { await addReview( octokit, pullRequestId, @@ -558,7 +559,7 @@ export const run = async (prNumber) => { } } catch (error) { debug("Set review state to `REQUEST_CHANGES` and add `invalid` label..."); - if (!dryRun) { + if (!DRY_RUN) { await addReview( octokit, pullRequestId, From 252c2b419d8adbba76e02463b198409f7d1d5977 Mon Sep 17 00:00:00 2001 From: Rick Staa Date: Sun, 26 Feb 2023 03:44:42 +0100 Subject: [PATCH 32/48] refactor: format code (#2550) --- express.js | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/express.js b/express.js index 0a139625e06bb..6ce92ff035181 100644 --- a/express.js +++ b/express.js @@ -1,15 +1,15 @@ -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 express from 'express' -import dotenv from 'dotenv' +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 express from "express"; +import dotenv from "dotenv"; -dotenv.config() -const app = express() -app.listen(process.env.port || 9000) +dotenv.config(); +const app = express(); +app.listen(process.env.port || 9000); -app.get('/', statsCard) -app.get('/pin', repoCard) -app.get('/top-langs', langCard) -app.get('/wakatime', wakatimeCard) +app.get("/", statsCard); +app.get("/pin", repoCard); +app.get("/top-langs", langCard); +app.get("/wakatime", wakatimeCard); From d60d53cdb43c27d48ba2fc2973ae8143579b6dd9 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Wed, 1 Mar 2023 06:47:56 +0530 Subject: [PATCH 33/48] refactor: update languages JSON (#2554) Co-authored-by: rickstaa --- src/common/languageColors.json | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/src/common/languageColors.json b/src/common/languageColors.json index 47bfb1cfa3435..b50cba6f30b08 100644 --- a/src/common/languageColors.json +++ b/src/common/languageColors.json @@ -79,6 +79,7 @@ "Ceylon": "#dfa535", "Chapel": "#8dc63f", "ChucK": "#3f8000", + "Circom": "#707575", "Cirru": "#ccccff", "Clarion": "#db901e", "Clarity": "#5546ff", @@ -116,6 +117,7 @@ "DirectX 3D File": "#aace60", "Dockerfile": "#384d54", "Dogescript": "#cca760", + "Dotenv": "#e5d559", "Dylan": "#6c616e", "E": "#ccce35", "ECL": "#8a1267", @@ -149,7 +151,7 @@ "Forth": "#341708", "Fortran": "#4d41b1", "Fortran Free Form": "#4d41b1", - "FreeBasic": "#867db1", + "FreeBasic": "#141AC9", "FreeMarker": "#0050b2", "Frege": "#00cafe", "Futhark": "#5f021f", @@ -182,6 +184,7 @@ "Go": "#00ADD8", "Go Checksums": "#00ADD8", "Go Module": "#00ADD8", + "Godot Resource": "#355570", "Golo": "#88562A", "Gosu": "#82937f", "Grace": "#615f8b", @@ -192,6 +195,7 @@ "Groovy": "#4298b8", "Groovy Server Pages": "#4298b8", "HAProxy": "#106da9", + "HCL": "#844FBA", "HLSL": "#aace60", "HOCON": "#9ff8ee", "HTML": "#e34c26", @@ -225,6 +229,7 @@ "Isabelle ROOT": "#FEFE00", "J": "#9EEDFF", "JAR Manifest": "#b07219", + "JCL": "#d90e09", "JFlex": "#DBCA00", "JSON": "#292929", "JSON with Comments": "#292929", @@ -247,9 +252,11 @@ "Jsonnet": "#0064bd", "Julia": "#a270ba", "Jupyter Notebook": "#DA5B0B", + "Just": "#384d54", "KRL": "#28430A", "Kaitai Struct": "#773b37", "KakouneScript": "#6f8042", + "KerboScript": "#41adf0", "KiCad Layout": "#2f4aab", "KiCad Legacy Layout": "#2f4aab", "KiCad Schematic": "#2f4aab", @@ -362,6 +369,7 @@ "PicoLisp": "#6067af", "PigLatin": "#fcd7de", "Pike": "#005390", + "PlantUML": "#fbbd16", "PogoScript": "#d80074", "Polar": "#ae81ff", "Portugol": "#f8bd00", @@ -379,6 +387,7 @@ "Puppet": "#302B6D", "PureBasic": "#5a6986", "PureScript": "#1D222D", + "Pyret": "#ee1e10", "Python": "#3572A5", "Python console": "#3572A5", "Python traceback": "#3572A5", @@ -531,7 +540,6 @@ "fish": "#4aae47", "hoon": "#00b171", "jq": "#c7254e", - "just": "#384d54", "kvlang": "#1da6e0", "mIRC Script": "#3d57c3", "mcfunction": "#E22837", From 8849b5f5fc708ea2f64d5c7176580252e17bc81a Mon Sep 17 00:00:00 2001 From: Zohan Subhash Date: Wed, 1 Mar 2023 15:03:49 +0530 Subject: [PATCH 34/48] Preview theme workflow fix (#2557) * Fix octokit error * ci: make octokit instance global --------- Co-authored-by: rickstaa --- scripts/preview-theme.js | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/scripts/preview-theme.js b/scripts/preview-theme.js index aae892c602fdb..d94d084aa85e7 100644 --- a/scripts/preview-theme.js +++ b/scripts/preview-theme.js @@ -44,6 +44,9 @@ const REQUIRED_COLOR_PROPS = ACCEPTED_COLOR_PROPS.slice(0, 4); const INVALID_REVIEW_COMMENT = (commentUrl) => `Some themes are invalid. See the [Automated Theme Preview](${commentUrl}) comment above for more information.`; +// Retrieve octokit instance. +const OCTOKIT = github.getOctokit(getGithubToken()); + /** * Retrieve PR number from the event payload. * @@ -312,7 +315,6 @@ export const run = async (prNumber) => { \r${THEME_CONTRIB_GUIDELINESS} `; const ccc = new ColorContrastChecker(); - const octokit = github.getOctokit(getGithubToken()); const pullRequestId = prNumber ? prNumber : getPrNumber(); const commenter = getCommenter(); const { owner, repo } = getRepoInfo(github.context); @@ -322,7 +324,7 @@ export const run = async (prNumber) => { // Retrieve the PR diff and preview-theme comment. debug("Retrieve PR diff..."); - const res = await octokit.pulls.get({ + const res = await OCTOKIT.pulls.get({ owner, repo, pull_number: pullRequestId, @@ -332,7 +334,7 @@ export const run = async (prNumber) => { }); debug("Retrieve preview-theme comment..."); const comment = await findComment( - octokit, + OCTOKIT, pullRequestId, owner, repo, @@ -515,7 +517,7 @@ export const run = async (prNumber) => { debug("Create or update theme-preview comment..."); let comment_url; if (!DRY_RUN) { - comment_url = await upsertComment(octokit, { + comment_url = await upsertComment(OCTOKIT, { comment_id: comment?.id, issue_number: pullRequestId, owner, @@ -538,7 +540,7 @@ export const run = async (prNumber) => { : INVALID_REVIEW_COMMENT(comment_url); if (!DRY_RUN) { await addReview( - octokit, + OCTOKIT, pullRequestId, owner, repo, @@ -546,7 +548,7 @@ export const run = async (prNumber) => { reviewReason, ); await addRemoveLabel( - octokit, + OCTOKIT, pullRequestId, owner, repo, @@ -561,7 +563,7 @@ export const run = async (prNumber) => { debug("Set review state to `REQUEST_CHANGES` and add `invalid` label..."); if (!DRY_RUN) { await addReview( - octokit, + OCTOKIT, pullRequestId, owner, repo, @@ -569,7 +571,7 @@ export const run = async (prNumber) => { error.message, ); await addRemoveLabel( - octokit, + OCTOKIT, pullRequestId, owner, repo, From a1c3c6accc1e8a0be00dd0db5a32d63d86055daa Mon Sep 17 00:00:00 2001 From: Zohan Subhash Date: Wed, 1 Mar 2023 21:21:25 +0530 Subject: [PATCH 35/48] ci: preview theme workflow fix (#2559) * Fix octokit error * ci: make octokit instance global * Fix preview theme (move declarations to global) * refactor: make constants uppercase --------- Co-authored-by: rickstaa --- scripts/preview-theme.js | 50 ++++++++++++++++++++-------------------- 1 file changed, 25 insertions(+), 25 deletions(-) diff --git a/scripts/preview-theme.js b/scripts/preview-theme.js index d94d084aa85e7..f5a24a80a326d 100644 --- a/scripts/preview-theme.js +++ b/scripts/preview-theme.js @@ -46,6 +46,8 @@ const INVALID_REVIEW_COMMENT = (commentUrl) => // Retrieve octokit instance. const OCTOKIT = github.getOctokit(getGithubToken()); +const PULL_REQUEST_ID = prNumber ? prNumber : getPrNumber(); +const { OWNER, REPO } = getRepoInfo(github.context); /** * Retrieve PR number from the event payload. @@ -315,19 +317,17 @@ export const run = async (prNumber) => { \r${THEME_CONTRIB_GUIDELINESS} `; const ccc = new ColorContrastChecker(); - const pullRequestId = prNumber ? prNumber : getPrNumber(); const commenter = getCommenter(); - const { owner, repo } = getRepoInfo(github.context); - debug(`Owner: ${owner}`); - debug(`Repo: ${repo}`); + debug(`Owner: ${OWNER}`); + debug(`Repo: ${REPO}`); debug(`Commenter: ${commenter}`); // Retrieve the PR diff and preview-theme comment. debug("Retrieve PR diff..."); const res = await OCTOKIT.pulls.get({ - owner, - repo, - pull_number: pullRequestId, + OWNER, + REPO, + pull_number: PULL_REQUEST_ID, mediaType: { format: "diff", }, @@ -335,9 +335,9 @@ export const run = async (prNumber) => { debug("Retrieve preview-theme comment..."); const comment = await findComment( OCTOKIT, - pullRequestId, - owner, - repo, + PULL_REQUEST_ID, + OWNER, + REPO, commenter, ); @@ -519,9 +519,9 @@ export const run = async (prNumber) => { if (!DRY_RUN) { comment_url = await upsertComment(OCTOKIT, { comment_id: comment?.id, - issue_number: pullRequestId, - owner, - repo, + issue_number: PULL_REQUEST_ID, + OWNER, + REPO, body: commentBody, }); } else { @@ -541,17 +541,17 @@ export const run = async (prNumber) => { if (!DRY_RUN) { await addReview( OCTOKIT, - pullRequestId, - owner, - repo, + PULL_REQUEST_ID, + OWNER, + REPO, reviewState, reviewReason, ); await addRemoveLabel( OCTOKIT, - pullRequestId, - owner, - repo, + PULL_REQUEST_ID, + OWNER, + REPO, "invalid", !themesValid, ); @@ -564,17 +564,17 @@ export const run = async (prNumber) => { if (!DRY_RUN) { await addReview( OCTOKIT, - pullRequestId, - owner, - repo, + PULL_REQUEST_ID, + OWNER, + REPO, "REQUEST_CHANGES", error.message, ); await addRemoveLabel( OCTOKIT, - pullRequestId, - owner, - repo, + PULL_REQUEST_ID, + OWNER, + REPO, "invalid", true, ); From 9ec2c8367a38e008a1c4ea740d2408bdc3c20842 Mon Sep 17 00:00:00 2001 From: Rick Staa Date: Thu, 2 Mar 2023 03:14:43 +0100 Subject: [PATCH 36/48] refactor: fix code comments and change 'up' rate limit (#2560) --- api/status/pat-info.js | 4 ++-- api/status/up.js | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/api/status/pat-info.js b/api/status/pat-info.js index 775e06896427c..69d869ea2553e 100644 --- a/api/status/pat-info.js +++ b/api/status/pat-info.js @@ -2,11 +2,11 @@ * @file Contains a simple cloud function that can be used to check which PATs are no * longer working. It returns a list of valid PATs, expired PATs and PATs with errors. * - * @description This function is currently rate limited to 1 request per 10 minutes. + * @description This function is currently rate limited to 1 request per 5 minutes. */ import { logger, request, dateDiff } from "../../src/common/utils.js"; -export const RATE_LIMIT_SECONDS = 60 * 5; // 1 request per 10 minutes +export const RATE_LIMIT_SECONDS = 60 * 5; // 1 request per 5 minutes /** * Simple uptime check fetcher for the PATs. diff --git a/api/status/up.js b/api/status/up.js index 33fe8f900c395..678a20b0b5c14 100644 --- a/api/status/up.js +++ b/api/status/up.js @@ -2,13 +2,13 @@ * @file Contains a simple cloud function that can be used to check if the PATs are still * functional. * - * @description This function is currently rate limited to 1 request per 10 minutes. + * @description This function is currently rate limited to 1 request per 5 minutes. */ import retryer from "../../src/common/retryer.js"; import { logger, request } from "../../src/common/utils.js"; -export const RATE_LIMIT_SECONDS = 60 * 10; // 1 request per 10 minutes +export const RATE_LIMIT_SECONDS = 60 * 5; // 1 request per 5 minutes /** * Simple uptime check fetcher for the PATs. From 7bc8f19a7fd7787287c02e42761602746e55c1d2 Mon Sep 17 00:00:00 2001 From: Zohan Subhash Date: Thu, 2 Mar 2023 22:51:39 +0530 Subject: [PATCH 37/48] Preview action fix (#2561) * Fix error * refactor: remove unused code --------- Co-authored-by: Rick Staa --- scripts/preview-theme.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/scripts/preview-theme.js b/scripts/preview-theme.js index f5a24a80a326d..baccaa61a3a78 100644 --- a/scripts/preview-theme.js +++ b/scripts/preview-theme.js @@ -46,7 +46,7 @@ const INVALID_REVIEW_COMMENT = (commentUrl) => // Retrieve octokit instance. const OCTOKIT = github.getOctokit(getGithubToken()); -const PULL_REQUEST_ID = prNumber ? prNumber : getPrNumber(); +const PULL_REQUEST_ID = getPrNumber(); const { OWNER, REPO } = getRepoInfo(github.context); /** @@ -308,7 +308,7 @@ const DRY_RUN = process.env.DRY_RUN === "true" || false; /** * Main function. */ -export const run = async (prNumber) => { +export const run = async () => { try { debug("Retrieve action information from context..."); debug(`Context: ${inspect(github.context)}`); From 976771080facb86c3e4b455fef18278021f7f984 Mon Sep 17 00:00:00 2001 From: Rick Staa Date: Fri, 3 Mar 2023 09:07:02 +0100 Subject: [PATCH 38/48] ci: fix theme preview action (#2563) --- scripts/preview-theme.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/scripts/preview-theme.js b/scripts/preview-theme.js index baccaa61a3a78..2cfe0f25cad3a 100644 --- a/scripts/preview-theme.js +++ b/scripts/preview-theme.js @@ -46,8 +46,8 @@ const INVALID_REVIEW_COMMENT = (commentUrl) => // Retrieve octokit instance. const OCTOKIT = github.getOctokit(getGithubToken()); -const PULL_REQUEST_ID = getPrNumber(); const { OWNER, REPO } = getRepoInfo(github.context); +var PULL_REQUEST_ID; /** * Retrieve PR number from the event payload. @@ -318,6 +318,7 @@ export const run = async () => { `; const ccc = new ColorContrastChecker(); const commenter = getCommenter(); + PULL_REQUEST_ID = getPrNumber(); debug(`Owner: ${OWNER}`); debug(`Repo: ${REPO}`); debug(`Commenter: ${commenter}`); From 1e61f9f3fe955fa25ab27dd1e06ddbfd788f4fda Mon Sep 17 00:00:00 2001 From: Rick Staa Date: Fri, 3 Mar 2023 15:57:11 +0100 Subject: [PATCH 39/48] fix theme preview (#2564) * ci: fix theme preview action * fix: fix some bugs in the 'theme-preveiw' action --- scripts/preview-theme.js | 41 +++++++++++++++++++++++++++++++--------- 1 file changed, 32 insertions(+), 9 deletions(-) diff --git a/scripts/preview-theme.js b/scripts/preview-theme.js index 2cfe0f25cad3a..c0bb9eb5522d8 100644 --- a/scripts/preview-theme.js +++ b/scripts/preview-theme.js @@ -43,12 +43,24 @@ const ACCEPTED_COLOR_PROPS = Object.keys(COLOR_PROPS); const REQUIRED_COLOR_PROPS = ACCEPTED_COLOR_PROPS.slice(0, 4); const INVALID_REVIEW_COMMENT = (commentUrl) => `Some themes are invalid. See the [Automated Theme Preview](${commentUrl}) comment above for more information.`; - -// Retrieve octokit instance. -const OCTOKIT = github.getOctokit(getGithubToken()); -const { OWNER, REPO } = getRepoInfo(github.context); +var OCTOKIT; +var OWNER; +var REPO; var PULL_REQUEST_ID; +/** + * Incorrect JSON format error. + * @extends Error + * @param {string} message Error message. + * @returns {Error} IncorrectJsonFormatError. + */ +class IncorrectJsonFormatError extends Error { + constructor(message) { + super(message); + this.name = "IncorrectJsonFormatError"; + } +} + /** * Retrieve PR number from the event payload. * @@ -274,7 +286,9 @@ const parseJSON = (json) => { if (typeof parsedJson === "object") { return parsedJson; } else { - throw new Error("PR diff is not a valid theme JSON object."); + throw new IncorrectJsonFormatError( + "PR diff is not a valid theme JSON object.", + ); } } catch (error) { let parsedJson = json @@ -289,7 +303,9 @@ const parseJSON = (json) => { } return Hjson.parse(parsedJson.join("")); } else { - throw error; + throw new IncorrectJsonFormatError( + `Theme JSON file could not be parsed: ${error.message}`, + ); } } }; @@ -317,6 +333,11 @@ export const run = async () => { \r${THEME_CONTRIB_GUIDELINESS} `; const ccc = new ColorContrastChecker(); + OCTOKIT = github.getOctokit(getGithubToken()); + PULL_REQUEST_ID = getPrNumber(); + const { owner, repo } = getRepoInfo(github.context); + OWNER = owner; + REPO = repo; const commenter = getCommenter(); PULL_REQUEST_ID = getPrNumber(); debug(`Owner: ${OWNER}`); @@ -326,8 +347,8 @@ export const run = async () => { // Retrieve the PR diff and preview-theme comment. debug("Retrieve PR diff..."); const res = await OCTOKIT.pulls.get({ - OWNER, - REPO, + owner: OWNER, + repo: REPO, pull_number: PULL_REQUEST_ID, mediaType: { format: "diff", @@ -569,7 +590,9 @@ export const run = async () => { OWNER, REPO, "REQUEST_CHANGES", - error.message, + "**Something went wrong in the theme preview action:** `" + + error.message + + "`", ); await addRemoveLabel( OCTOKIT, From ed18914fa4c131b076cbc6cff29db3ffbdc48787 Mon Sep 17 00:00:00 2001 From: Rick Staa Date: Sun, 5 Mar 2023 11:22:08 +0100 Subject: [PATCH 40/48] ci: fixes theme preview action (#2566) --- scripts/preview-theme.js | 44 ++++++++++++++++++++++++++++++---------- 1 file changed, 33 insertions(+), 11 deletions(-) diff --git a/scripts/preview-theme.js b/scripts/preview-theme.js index c0bb9eb5522d8..3179780d8438c 100644 --- a/scripts/preview-theme.js +++ b/scripts/preview-theme.js @@ -143,15 +143,36 @@ const findComment = async (octokit, issueNumber, owner, repo, commenter) => { * Create or update the preview comment. * * @param {Object} octokit Octokit instance. - * @param {Object} props Comment properties. + * @param {number} issueNumber Issue number. + * @param {Object} repo Repository name. + * @param {Object} owner Owner of the repository. + * @param {number} commentId Comment ID. + * @param {string} body Comment body. * @return {string} The comment URL. */ -const upsertComment = async (octokit, props) => { +const upsertComment = async ( + octokit, + issueNumber, + repo, + owner, + commentId, + body, +) => { let resp; - if (props.comment_id !== undefined) { - resp = await octokit.issues.updateComment(props); + if (commentId !== undefined) { + resp = await octokit.issues.updateComment({ + owner, + repo, + comment_id: commentId, + body, + }); } else { - resp = await octokit.issues.createComment(props); + resp = await octokit.issues.createComment({ + owner, + repo, + issue_number: issueNumber, + body, + }); } return resp.data.html_url; }; @@ -539,13 +560,14 @@ export const run = async () => { debug("Create or update theme-preview comment..."); let comment_url; if (!DRY_RUN) { - comment_url = await upsertComment(OCTOKIT, { - comment_id: comment?.id, - issue_number: PULL_REQUEST_ID, - OWNER, + comment_url = await upsertComment( + OCTOKIT, + PULL_REQUEST_ID, REPO, - body: commentBody, - }); + OWNER, + comment?.id, + commentBody, + ); } else { info(`DRY_RUN: Comment body: ${commentBody}`); comment_url = ""; From b93aee34d0101aed1de0b5d7cc68c3bb19614d52 Mon Sep 17 00:00:00 2001 From: Rick Staa Date: Mon, 6 Mar 2023 05:03:06 +0100 Subject: [PATCH 41/48] ci: improve theme preview action (#2572) --- scripts/preview-theme.js | 41 +++++++++++++++++++++++++++++----------- 1 file changed, 30 insertions(+), 11 deletions(-) diff --git a/scripts/preview-theme.js b/scripts/preview-theme.js index 3179780d8438c..e18c01b8615af 100644 --- a/scripts/preview-theme.js +++ b/scripts/preview-theme.js @@ -312,18 +312,30 @@ const parseJSON = (json) => { ); } } catch (error) { - let parsedJson = json + // Remove trailing commas (if any). + let parsedJson = json.replace(/(,\s*})/g, "}"); + + // Remove JS comments (if any). + parsedJson = parsedJson.replace(/\/\/[A-z\s]*\s/g, ""); + + // Fix incorrect open bracket (if any). + const splitJson = parsedJson .split(/([\s\r\s]*}[\s\r\s]*,[\s\r\s]*)(?=[\w"-]+:)/) - .filter((x) => typeof x !== "string" || !!x.trim()); - if (parsedJson[0].replace(/\s+/g, "") === "},") { - parsedJson[0] = "},"; - if (!/\s*}\s*,?\s*$/.test(parsedJson[1])) { - parsedJson.push(parsedJson.shift()); + .filter((x) => typeof x !== "string" || !!x.trim()); // Split json into array of strings and objects. + if (splitJson[0].replace(/\s+/g, "") === "},") { + splitJson[0] = "},"; + if (!/\s*}\s*,?\s*$/.test(splitJson[1])) { + splitJson.push(splitJson.shift()); } else { - parsedJson.shift(); + splitJson.shift(); } - return Hjson.parse(parsedJson.join("")); - } else { + parsedJson = splitJson.join(""); + } + + // Try to parse the fixed json. + try { + return Hjson.parse(parsedJson); + } catch (error) { throw new IncorrectJsonFormatError( `Theme JSON file could not be parsed: ${error.message}`, ); @@ -387,10 +399,17 @@ export const run = async () => { // Retrieve theme changes from the PR diff. debug("Retrieve themes..."); const diff = parse(res.data); + + // Retrieve all theme changes from the PR diff and convert to JSON. + debug("Retrieve theme changes..."); const content = diff .find((file) => file.to === "themes/index.js") - .chunks[0].changes.filter((c) => c.type === "add") - .map((c) => c.content.replace("+", "")) + .chunks.map((chunk) => + chunk.changes + .filter((c) => c.type === "add") + .map((c) => c.content.replace("+", "")) + .join(""), + ) .join(""); const themeObject = parseJSON(content); if ( From c5063b92b6e260dcc405a0a4cd27552dc103f8f5 Mon Sep 17 00:00:00 2001 From: Etanarvazac Revorix Date: Tue, 7 Mar 2023 20:06:04 -0500 Subject: [PATCH 42/48] Added "Shadow" set (Red, Green, Blue, transparent BG) (#2529) * Added "Shadow" set (Red, Green, Blue, transparent BG) 3 additional themes sticking primarily to flat colors, which the exception of icons and border being slightly darker. All 3 themes also have transparent backgrounds that will show differently per-user via GiHub's own light and dark themes. Transparency should also still provide easy readability for both. * Test Just want to see if we can make the themes have a transparent background. * Shadows moved under Transparent --------- Co-authored-by: Zohan Subhash --- themes/index.js | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/themes/index.js b/themes/index.js index 01c9a8eee79d6..d348a2ba1a769 100644 --- a/themes/index.js +++ b/themes/index.js @@ -18,6 +18,27 @@ export const themes = { text_color: "417E87", bg_color: "ffffff00", }, + shadow_red: { + title_color: "9A0000", + text_color: "444", + icon_color: "4F0000", + border_color: "4F0000", + bg_color: "ffffff00", + }, + shadow_green: { + title_color: "007A00", + text_color: "444", + icon_color: "003D00", + border_color: "003D00", + bg_color: "ffffff00", + }, + shadow_blue: { + title_color: "00779A", + text_color: "444", + icon_color: "004450", + border_color: "004490", + bg_color: "ffffff00", + }, dark: { title_color: "fff", icon_color: "79ff97", From 2bd9d457ac7f10bd5f1303c6a14f3ec22c2ee9c6 Mon Sep 17 00:00:00 2001 From: Rick Staa Date: Thu, 9 Mar 2023 09:45:50 +0000 Subject: [PATCH 43/48] ci: fix theme docs generate bug (#2573) --- scripts/push-theme-readme.sh | 1 + 1 file changed, 1 insertion(+) diff --git a/scripts/push-theme-readme.sh b/scripts/push-theme-readme.sh index 1ab5de474ea5a..132a4b508e8e4 100755 --- a/scripts/push-theme-readme.sh +++ b/scripts/push-theme-readme.sh @@ -6,6 +6,7 @@ export BRANCH_NAME=updated-theme-readme git --version git config --global user.email "no-reply@githubreadmestats.com" git config --global user.name "GitHub Readme Stats Bot" +git config --global --add safe.directory ${GITHUB_WORKSPACE} git branch -d $BRANCH_NAME || true git checkout -b $BRANCH_NAME git add --all From 32998295b7ef9742ea0168213807d7e8ec5c72a6 Mon Sep 17 00:00:00 2001 From: Eduardo Zaniboni <67515606+eduardozaniboni@users.noreply.github.com> Date: Tue, 14 Mar 2023 06:12:29 -0300 Subject: [PATCH 44/48] update my theme (#2576) --- themes/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/themes/index.js b/themes/index.js index d348a2ba1a769..0121cf0bc14d1 100644 --- a/themes/index.js +++ b/themes/index.js @@ -409,7 +409,7 @@ export const themes = { border_color: "e9d8d4", bg_color: "e9d8d4", }, - holi_theme: { + holi: { title_color: "5FABEE", text_color: "D6E7FF", icon_color: "5FABEE", From 857aecc8bc17a177600d80fc66355fa06e39ce41 Mon Sep 17 00:00:00 2001 From: Meyer Pidiache Date: Sun, 19 Mar 2023 20:06:20 -0500 Subject: [PATCH 45/48] User data --- .github/workflows/e2e-test.yml | 2 +- .github/workflows/label-pr.yml | 2 +- .github/workflows/stale-theme-pr-closer.yaml | 2 +- package.json | 6 +++--- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/.github/workflows/e2e-test.yml b/.github/workflows/e2e-test.yml index d45c76ba08e58..27fd571f0fa05 100644 --- a/.github/workflows/e2e-test.yml +++ b/.github/workflows/e2e-test.yml @@ -5,7 +5,7 @@ on: jobs: e2eTests: if: - github.repository == 'anuraghazra/github-readme-stats' && + github.repository == 'meyer-pidiache/github-readme-stats' && github.event_name == 'deployment_status' && github.event.deployment_status.state == 'success' name: Perform 2e2 tests diff --git a/.github/workflows/label-pr.yml b/.github/workflows/label-pr.yml index 03715bfab00d6..622c60cfe4317 100644 --- a/.github/workflows/label-pr.yml +++ b/.github/workflows/label-pr.yml @@ -4,7 +4,7 @@ on: jobs: triage: - if: github.repository == 'anuraghazra/github-readme-stats' + if: github.repository == 'meyer-pidiache/github-readme-stats' runs-on: ubuntu-latest steps: - uses: actions/labeler@v4 diff --git a/.github/workflows/stale-theme-pr-closer.yaml b/.github/workflows/stale-theme-pr-closer.yaml index aa104feb528ca..e96901ca57898 100644 --- a/.github/workflows/stale-theme-pr-closer.yaml +++ b/.github/workflows/stale-theme-pr-closer.yaml @@ -5,7 +5,7 @@ on: jobs: closeOldThemePrs: - if: github.repository == 'anuraghazra/github-readme-stats' + if: github.repository == 'meyer-pidiache/github-readme-stats' name: Close stale 'invalid' theme PRs runs-on: ubuntu-latest strategy: diff --git a/package.json b/package.json index 606d4f5440a24..cb515917c32e9 100644 --- a/package.json +++ b/package.json @@ -10,13 +10,13 @@ ], "main": "src/index.js", "type": "module", - "homepage": "https://github.com/anuraghazra/github-readme-stats", + "homepage": "https://github.com/meyer-pidiache/github-readme-stats", "bugs": { - "url": "https://github.com/anuraghazra/github-readme-stats/issues" + "url": "https://github.com/meyer-pidiache/github-readme-stats/issues" }, "repository": { "type": "git", - "url": "https://github.com/anuraghazra/github-readme-stats.git" + "url": "https://github.com/meyer-pidiache/github-readme-stats.git" }, "scripts": { "test": "node --experimental-vm-modules node_modules/jest/bin/jest.js --coverage", From b928f51442ff224507f519ceaca67d09afffa2b1 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Fri, 31 Mar 2023 06:40:22 +0530 Subject: [PATCH 46/48] refactor: update languages JSON (#2596) Co-authored-by: rickstaa --- src/common/languageColors.json | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/common/languageColors.json b/src/common/languageColors.json index b50cba6f30b08..3937eec5a2bf5 100644 --- a/src/common/languageColors.json +++ b/src/common/languageColors.json @@ -133,6 +133,7 @@ "Elixir": "#6e4a7e", "Elm": "#60B5CC", "Elvish": "#55BB55", + "Elvish Transcript": "#55BB55", "Emacs Lisp": "#c065db", "EmberScript": "#FFF4F3", "Erlang": "#B83998", @@ -453,6 +454,7 @@ "Smalltalk": "#596706", "Smarty": "#f0c040", "Smithy": "#c44536", + "Snakemake": "#419179", "Solidity": "#AA6746", "SourcePawn": "#f69e1d", "Squirrel": "#800000", @@ -466,6 +468,7 @@ "SugarSS": "#2fcc9f", "SuperCollider": "#46390b", "Svelte": "#ff3e00", + "Sway": "#dea584", "Swift": "#F05138", "SystemVerilog": "#DAE1C2", "TI Program": "#A0AA87", From d8244a7fe5eaffaf7264e592290b9b1f9aaf849d Mon Sep 17 00:00:00 2001 From: Caeden Perelli-Harris Date: Sat, 1 Apr 2023 05:47:56 +0100 Subject: [PATCH 47/48] Add format stats option (#2155) * feat: added `format_stats` option (#2128) * refactor: change `format_stats` to `short_values` (#2128) * test: create shorten values test (#2128) * Update readme.md Co-authored-by: Rick Staa * refactor: rename ``short_values`` to ``number_format`` * Update readme.md Co-authored-by: Rick Staa * Update src/cards/stats-card.js Co-authored-by: Rick Staa * refactor: format codebase --------- Co-authored-by: Rick Staa --- api/index.js | 2 ++ readme.md | 1 + src/cards/stats-card.js | 6 +++++- src/cards/types.d.ts | 1 + tests/renderStatsCard.test.js | 9 +++++++++ 5 files changed, 18 insertions(+), 1 deletion(-) diff --git a/api/index.js b/api/index.js index b449d43b49080..e89c74688d116 100644 --- a/api/index.js +++ b/api/index.js @@ -35,6 +35,7 @@ export default async (req, res) => { locale, disable_animations, border_radius, + number_format, border_color, } = req.query; res.setHeader("Content-Type", "image/svg+xml"); @@ -88,6 +89,7 @@ export default async (req, res) => { custom_title, border_radius, border_color, + number_format, locale: locale ? locale.toLowerCase() : null, disable_animations: parseBoolean(disable_animations), }), diff --git a/readme.md b/readme.md index 7a60228460126..91a1cfa9f8870 100644 --- a/readme.md +++ b/readme.md @@ -289,6 +289,7 @@ You can provide multiple comma-separated values in the bg_color option to render - `text_bold` - Use 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` - Switch between two available formats for displaying the card values `short` (i.e. `6.6k`) and `long` (i.e. `6626`). Default: `short`. > **Note** > When hide_rank=`true`, the minimum card width is 270 px + the title length and padding. diff --git a/src/cards/stats-card.js b/src/cards/stats-card.js index f39a968f18065..c60ea51d5119c 100644 --- a/src/cards/stats-card.js +++ b/src/cards/stats-card.js @@ -39,8 +39,10 @@ const createTextNode = ({ showIcons, shiftValuePos, bold, + number_format, }) => { - const kValue = kFormatter(value); + const kValue = + number_format.toLowerCase() === "long" ? value : kFormatter(value); const staggerDelay = (index + 3) * 150; const labelOffset = showIcons ? `x="25"` : ""; @@ -103,6 +105,7 @@ const renderStatsCard = (stats = {}, options = { hide: [] }) => { custom_title, border_radius, border_color, + number_format = "short", locale, disable_animations = false, } = options; @@ -192,6 +195,7 @@ const renderStatsCard = (stats = {}, options = { hide: [] }) => { showIcons: show_icons, shiftValuePos: 79.01 + (isLongLocale ? 50 : 0), bold: text_bold, + number_format, }), ); diff --git a/src/cards/types.d.ts b/src/cards/types.d.ts index 52ee0edb6a459..a3abc23e98a36 100644 --- a/src/cards/types.d.ts +++ b/src/cards/types.d.ts @@ -22,6 +22,7 @@ export type StatCardOptions = CommonOptions & { line_height: number | string; custom_title: string; disable_animations: boolean; + number_format: string; }; export type RepoCardOptions = CommonOptions & { diff --git a/tests/renderStatsCard.test.js b/tests/renderStatsCard.test.js index 748b7a32cd32b..110121ac9e4b6 100644 --- a/tests/renderStatsCard.test.js +++ b/tests/renderStatsCard.test.js @@ -357,4 +357,13 @@ describe("Test renderStatsCard", () => { document.body.innerHTML = renderStatsCard(stats, {}); expect(document.querySelector("rect")).toHaveAttribute("rx", "4.5"); }); + + it("should shorten values", () => { + stats["totalCommits"] = 1999; + + document.body.innerHTML = renderStatsCard(stats); + expect(getByTestId(document.body, "commits").textContent).toBe("2k"); + document.body.innerHTML = renderStatsCard(stats, { number_format: "long" }); + expect(getByTestId(document.body, "commits").textContent).toBe("1999"); + }); }); From 4d1d83d5e5691a5aaa8a65d230924d6072bbc16d Mon Sep 17 00:00:00 2001 From: Fabiano Couto Date: Sat, 1 Apr 2023 08:22:00 -0300 Subject: [PATCH 48/48] add github_dark_dimmed theme (#2594) * feat(theme): add github_dark_dimmed theme * feat(theme): change github_dark_dimmed icon color * contrast ratio adjustment contrast ratio adjustment on github_dark_dimmed theme * feat(theme): readme preview * feat(theme): github themes next to each other * github themes next to each other --- themes/README.md | 14 ++++++++------ themes/index.js | 7 +++++++ 2 files changed, 15 insertions(+), 6 deletions(-) diff --git a/themes/README.md b/themes/README.md index b8649d43b9564..5993b6a07a9b8 100644 --- a/themes/README.md +++ b/themes/README.md @@ -32,10 +32,11 @@ Use `?theme=THEME_NAME` parameter like so :- | `jolly` ![jolly][jolly] | `maroongold` ![maroongold][maroongold] | `yeblu` ![yeblu][yeblu] | | `blueberry` ![blueberry][blueberry] | `slateorange` ![slateorange][slateorange] | `kacho_ga` ![kacho_ga][kacho_ga] | | `outrun` ![outrun][outrun] | `ocean_dark` ![ocean_dark][ocean_dark] | `city_lights` ![city_lights][city_lights] | -| `github_dark` ![github_dark][github_dark] | `discord_old_blurple` ![discord_old_blurple][discord_old_blurple] | `aura_dark` ![aura_dark][aura_dark] | +| `github_dark` ![github_dark][github_dark] | `github_dark_dimmed` ![github_dark_dimmed][github_dark_dimmed] | `discord_old_blurple` ![discord_old_blurple][discord_old_blurple] | | `panda` ![panda][panda] | `noctis_minimus` ![noctis_minimus][noctis_minimus] | `cobalt2` ![cobalt2][cobalt2] | | `swift` ![swift][swift] | `aura` ![aura][aura] | `apprentice` ![apprentice][apprentice] | | `moltack` ![moltack][moltack] | `codeSTACKr` ![codeSTACKr][codeSTACKr] | `rose_pine` ![rose_pine][rose_pine] | +| `aura_dark` ![aura_dark][aura_dark] | | | | [Add your theme][add-theme] | | | ## Repo Card @@ -60,10 +61,11 @@ Use `?theme=THEME_NAME` parameter like so :- | `jolly` ![jolly][jolly_repo] | `maroongold` ![maroongold][maroongold_repo] | `yeblu` ![yeblu][yeblu_repo] | | `blueberry` ![blueberry][blueberry_repo] | `slateorange` ![slateorange][slateorange_repo] | `kacho_ga` ![kacho_ga][kacho_ga_repo] | | `outrun` ![outrun][outrun_repo] | `ocean_dark` ![ocean_dark][ocean_dark_repo] | `city_lights` ![city_lights][city_lights_repo] | -| `github_dark` ![github_dark][github_dark_repo] | `discord_old_blurple` ![discord_old_blurple][discord_old_blurple_repo] | `aura_dark` ![aura_dark][aura_dark_repo] | +| `github_dark` ![github_dark][github_dark_repo] | `github_dark_dimmed` ![github_dark_dimmed][github_dark_dimmed_repo] | `discord_old_blurple` ![discord_old_blurple][discord_old_blurple_repo] | | `panda` ![panda][panda_repo] | `noctis_minimus` ![noctis_minimus][noctis_minimus_repo] | `cobalt2` ![cobalt2][cobalt2_repo] | | `swift` ![swift][swift_repo] | `aura` ![aura][aura_repo] | `apprentice` ![apprentice][apprentice_repo] | | `moltack` ![moltack][moltack_repo] | `codeSTACKr` ![codeSTACKr][codeSTACKr_repo] | `rose_pine` ![rose_pine][rose_pine_repo] | +| `aura_dark` ![aura_dark][aura_dark_repo] | | | | [Add your theme][add-theme] | | | @@ -117,8 +119,8 @@ Use `?theme=THEME_NAME` parameter like so :- [ocean_dark]: https://github-readme-stats.vercel.app/api?username=anuraghazra&show_icons=true&hide=contribs,prs&cache_seconds=86400&theme=ocean_dark [city_lights]: https://github-readme-stats.vercel.app/api?username=anuraghazra&show_icons=true&hide=contribs,prs&cache_seconds=86400&theme=city_lights [github_dark]: https://github-readme-stats.vercel.app/api?username=anuraghazra&show_icons=true&hide=contribs,prs&cache_seconds=86400&theme=github_dark +[github_dark_dimmed]: https://github-readme-stats.vercel.app/api?username=anuraghazra&show_icons=true&hide=contribs,prs&cache_seconds=86400&theme=github_dark_dimmed [discord_old_blurple]: https://github-readme-stats.vercel.app/api?username=anuraghazra&show_icons=true&hide=contribs,prs&cache_seconds=86400&theme=discord_old_blurple -[aura_dark]: https://github-readme-stats.vercel.app/api?username=anuraghazra&show_icons=true&hide=contribs,prs&cache_seconds=86400&theme=aura_dark [panda]: https://github-readme-stats.vercel.app/api?username=anuraghazra&show_icons=true&hide=contribs,prs&cache_seconds=86400&theme=panda [noctis_minimus]: https://github-readme-stats.vercel.app/api?username=anuraghazra&show_icons=true&hide=contribs,prs&cache_seconds=86400&theme=noctis_minimus [cobalt2]: https://github-readme-stats.vercel.app/api?username=anuraghazra&show_icons=true&hide=contribs,prs&cache_seconds=86400&theme=cobalt2 @@ -128,7 +130,7 @@ Use `?theme=THEME_NAME` parameter like so :- [moltack]: https://github-readme-stats.vercel.app/api?username=anuraghazra&show_icons=true&hide=contribs,prs&cache_seconds=86400&theme=moltack [codeSTACKr]: https://github-readme-stats.vercel.app/api?username=anuraghazra&show_icons=true&hide=contribs,prs&cache_seconds=86400&theme=codeSTACKr [rose_pine]: https://github-readme-stats.vercel.app/api?username=anuraghazra&show_icons=true&hide=contribs,prs&cache_seconds=86400&theme=rose_pine - +[aura_dark]: https://github-readme-stats.vercel.app/api?username=anuraghazra&show_icons=true&hide=contribs,prs&cache_seconds=86400&theme=aura_dark [default_repo]: https://github-readme-stats.vercel.app/api/pin/?username=anuraghazra&repo=github-readme-stats&cache_seconds=86400&theme=default [default_repocard_repo]: https://github-readme-stats.vercel.app/api/pin/?username=anuraghazra&repo=github-readme-stats&cache_seconds=86400&theme=default_repocard @@ -180,8 +182,8 @@ Use `?theme=THEME_NAME` parameter like so :- [ocean_dark_repo]: https://github-readme-stats.vercel.app/api/pin/?username=anuraghazra&repo=github-readme-stats&cache_seconds=86400&theme=ocean_dark [city_lights_repo]: https://github-readme-stats.vercel.app/api/pin/?username=anuraghazra&repo=github-readme-stats&cache_seconds=86400&theme=city_lights [github_dark_repo]: https://github-readme-stats.vercel.app/api/pin/?username=anuraghazra&repo=github-readme-stats&cache_seconds=86400&theme=github_dark +[github_dark_dimmed_repo]: https://github-readme-stats.vercel.app/api/pin/?username=anuraghazra&repo=github-readme-stats&cache_seconds=86400&theme=github_dark_dimmed [discord_old_blurple_repo]: https://github-readme-stats.vercel.app/api/pin/?username=anuraghazra&repo=github-readme-stats&cache_seconds=86400&theme=discord_old_blurple -[aura_dark_repo]: https://github-readme-stats.vercel.app/api/pin/?username=anuraghazra&repo=github-readme-stats&cache_seconds=86400&theme=aura_dark [panda_repo]: https://github-readme-stats.vercel.app/api/pin/?username=anuraghazra&repo=github-readme-stats&cache_seconds=86400&theme=panda [noctis_minimus_repo]: https://github-readme-stats.vercel.app/api/pin/?username=anuraghazra&repo=github-readme-stats&cache_seconds=86400&theme=noctis_minimus [cobalt2_repo]: https://github-readme-stats.vercel.app/api/pin/?username=anuraghazra&repo=github-readme-stats&cache_seconds=86400&theme=cobalt2 @@ -191,7 +193,7 @@ Use `?theme=THEME_NAME` parameter like so :- [moltack_repo]: https://github-readme-stats.vercel.app/api/pin/?username=anuraghazra&repo=github-readme-stats&cache_seconds=86400&theme=moltack [codeSTACKr_repo]: https://github-readme-stats.vercel.app/api/pin/?username=anuraghazra&repo=github-readme-stats&cache_seconds=86400&theme=codeSTACKr [rose_pine_repo]: https://github-readme-stats.vercel.app/api/pin/?username=anuraghazra&repo=github-readme-stats&cache_seconds=86400&theme=rose_pine - +[aura_dark_repo]: https://github-readme-stats.vercel.app/api/pin/?username=anuraghazra&repo=github-readme-stats&cache_seconds=86400&theme=aura_dark [add-theme]: https://github.com/anuraghazra/github-readme-stats/edit/master/themes/index.js diff --git a/themes/index.js b/themes/index.js index 0121cf0bc14d1..ab8eab6a0d0e9 100644 --- a/themes/index.js +++ b/themes/index.js @@ -321,6 +321,13 @@ export const themes = { text_color: "C3D1D9", bg_color: "0D1117", }, + github_dark_dimmed: { + title_color: "539bf5", + icon_color: "539bf5", + text_color: "ADBAC7", + bg_color: "24292F", + border_color: "373E47", + }, discord_old_blurple: { title_color: "7289DA", icon_color: "7289DA",