Repository: anuraghazra/github-readme-stats
Pull request #3141 (2023-08-25 06:00:35)

Add performance tests base
This is basic implementation of bench tests which can show us how some specific changes affect on project performance. I think it can be quite useful ahead of updating ranking system in https://github.com/anuraghazra/github-readme-stats/pull/2962.

What do you think about this @rickstaa?

![image](https://github.com/anuraghazra/github-readme-stats/assets/53787217/24bbc93a-f54f-468b-99a6-24f057204207)


vercel[bot]: [vc]: #ZHNCvl0p4zYj8hYfoFEg0gm+JeDaxctLIbnn5lRQ8J8=:eyJpc01vbm9yZXBvIjp0cnVlLCJ0eXBlIjoiZ2l0aHViIiwicHJvamVjdHMiOlt7Im5hbWUiOiJnaXRodWItcmVhZG1lLXN0YXRzIiwiaW5zcGVjdG9yVXJsIjoiaHR0cHM6Ly92ZXJjZWwuY29tL2dpdGh1Yi1yZWFkbWUtc3RhdHMtdGVhbS9naXRodWItcmVhZG1lLXN0YXRzL0I0OW9DS0t3UkpnaTdQYkdiWFBSZHl4ZEJkWkQiLCJwcmV2aWV3VXJsIjoiZ2l0aHViLXJlYWRtZS1zdGF0cy1naXQtYmFzaWNiZS1iZDgxNTQtZ2l0aHViLXJlYWRtZS1zdGF0cy10ZWFtLnZlcmNlbC5hcHAiLCJuZXh0Q29tbWl0U3RhdHVzIjoiREVQTE9ZRUQifV19
**The latest updates on your projects**. Learn more about [Vercel for Git ↗︎](https://vercel.link/github-learn-more)

| Name | Status | Preview | Updated (UTC) |
| :--- | :----- | :------ | :------ |
| **github-readme-stats** | ✅ Ready ([Inspect](https://vercel.com/github-readme-stats-team/github-readme-stats/B49oCKKwRJgi7PbGbXPRdyxdBdZD)) | [Visit Preview](https://github-readme-stats-git-basicbe-bd8154-github-readme-stats-team.vercel.app) | Aug 25, 2023 6:00am |


codecov[bot]: ## [Codecov](https://app.codecov.io/gh/anuraghazra/github-readme-stats/pull/3141?src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Anurag+Hazra) Report
Patch and project coverage have no change.
> Comparison is base [(`e8b9ccf`)](https://app.codecov.io/gh/anuraghazra/github-readme-stats/commit/e8b9ccf20306fb5bb03e70e68d2854850096d58b?el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Anurag+Hazra) 98.44% compared to head [(`1d2c738`)](https://app.codecov.io/gh/anuraghazra/github-readme-stats/pull/3141?src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Anurag+Hazra) 98.44%.

<details><summary>Additional details and impacted files</summary>


```diff
@@           Coverage Diff           @@
##           master    #3141   +/-   ##
=======================================
  Coverage   98.44%   98.44%           
=======================================
  Files          27       27           
  Lines        5806     5806           
  Branches      517      517           
=======================================
  Hits         5716     5716           
  Misses         87       87           
  Partials        3        3           
```




</details>

[:umbrella: View full report in Codecov by Sentry](https://app.codecov.io/gh/anuraghazra/github-readme-stats/pull/3141?src=pr&el=continue&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Anurag+Hazra).   
:loudspeaker: Have feedback on the report? [Share it here](https://about.codecov.io/codecov-pr-comment-feedback/?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Anurag+Hazra).


Patch:
diff --git a/.eslintrc.json b/.eslintrc.json
index d8c31c7..2edbe53 100644
--- a/.eslintrc.json
+++ b/.eslintrc.json
@@ -98,7 +98,7 @@
             "terms": [ "TODO", "FIXME" ],
             "location": "start"
         }],
-        // "no-with": "warn",
+        "no-with": "warn",
         // "radix": "warn",
         // "vars-on-top": "error",
 
@@ -194,7 +194,7 @@
         // "no-inline-comments": "off",
         // "no-lonely-if": "warn",
         // "no-mixed-spaces-and-tabs": "warn",
-        // "no-multiple-empty-lines": "warn",
+        "no-multiple-empty-lines": "warn",
         // "no-negated-condition": "off",
         // "no-nested-ternary": "warn",
         // "no-new-object": "warn",
diff --git a/.github/labeler.yml b/.github/labeler.yml
index 5e0475a..aa276c6 100644
--- a/.github/labeler.yml
+++ b/.github/labeler.yml
@@ -49,4 +49,4 @@ ranks: src/calculateRank.js
 ci:
   - .github/workflows/*
   - scripts/*
-infrastructure: .eslintrc.js
+infrastructure: .eslintrc.json
diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml
new file mode 100644
index 0000000..da4545a
--- /dev/null
+++ b/.github/workflows/codeql-analysis.yml
@@ -0,0 +1,39 @@
+name: "Static code analysis workflow (CodeQL)"
+
+on:
+  push:
+    branches: [main]
+  pull_request:
+    branches: [main]
+
+permissions:
+  actions: read
+  checks: read
+  contents: read
+  deployments: read
+  issues: read
+  discussions: read
+  packages: read
+  pages: read
+  pull-requests: read
+  repository-projects: read
+  security-events: write
+  statuses: read
+
+jobs:
+  CodeQL-Build:
+    # CodeQL runs on ubuntu-latest, windows-latest, and macos-latest
+    runs-on: ubuntu-latest
+
+    steps:
+      - name: Checkout repository
+        uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.5.3
+
+      # Initializes the CodeQL tools for scanning.
+      - name: Initialize CodeQL
+        uses: github/codeql-action/init@46a6823b81f2d7c67ddf123851eea88365bc8a67 # v2.13.5
+        with:
+          languages: javascript
+
+      - name: Perform CodeQL Analysis
+        uses: github/codeql-action/analyze@46a6823b81f2d7c67ddf123851eea88365bc8a67 # v2.13.5
diff --git a/.github/workflows/deploy-prep.yml b/.github/workflows/deploy-prep.yml
index 759c97a..c50c04e 100644
--- a/.github/workflows/deploy-prep.yml
+++ b/.github/workflows/deploy-prep.yml
@@ -10,10 +10,10 @@ jobs:
     if: github.repository == 'anuraghazra/github-readme-stats'
     runs-on: ubuntu-latest
     steps:
-      - uses: actions/checkout@3df4ab11eba7bda6032a0b82a6bb43b11571feac # v4.0.0
+      - uses: actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608 # v4.1.0
       - name: Deployment Prep
         run: python ./.github/workflows/deploy-prep.py
-      - uses: stefanzweifel/git-auto-commit-action@3ea6ae190baf489ba007f7c92608f33ce20ef04a # v4.16.0
+      - uses: stefanzweifel/git-auto-commit-action@8756aa072ef5b4a080af5dc8fef36c5d586e521d # v5.0.0
         with:
           branch: vercel
           create_branch: true
diff --git a/.github/workflows/empty-issues-closer.yaml b/.github/workflows/empty-issues-closer.yaml
index 54bbf60..e1a325b 100644
--- a/.github/workflows/empty-issues-closer.yaml
+++ b/.github/workflows/empty-issues-closer.yaml
@@ -27,10 +27,10 @@ jobs:
     runs-on: ubuntu-latest
     steps:
       # NOTE: Retrieve issue templates.
-      - uses: actions/checkout@3df4ab11eba7bda6032a0b82a6bb43b11571feac # v4.0.0
+      - uses: actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608 # v4.1.0
 
       - name: Run empty issues closer action
-        uses: rickstaa/empty-issues-closer-action@e9f0932401f4a8615cfe8bc22aacf06330c910c2 # v1.1.24
+        uses: rickstaa/empty-issues-closer-action@e9e79c66b85961ebc9dc11493b65b6e4bb355ed0 # v1.1.28
         env:
           github_token: ${{ secrets.GITHUB_TOKEN }}
         with:
diff --git a/.github/workflows/generate-theme-doc.yml b/.github/workflows/generate-theme-doc.yml
index 2968f39..b942089 100644
--- a/.github/workflows/generate-theme-doc.yml
+++ b/.github/workflows/generate-theme-doc.yml
@@ -29,7 +29,7 @@ jobs:
         node-version: [18.x]
 
     steps:
-      - uses: actions/checkout@3df4ab11eba7bda6032a0b82a6bb43b11571feac # v4.0.0
+      - uses: actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608 # v4.1.0
 
       - name: Setup Node
         uses: actions/setup-node@5e21ff4d9bc1a8cf6de233a3057d20ec6b3fb69d # v3.8.1
diff --git a/.github/workflows/ossf-analysis.yml b/.github/workflows/ossf-analysis.yml
index 15bc1a9..34ce9c6 100644
--- a/.github/workflows/ossf-analysis.yml
+++ b/.github/workflows/ossf-analysis.yml
@@ -21,12 +21,12 @@ jobs:
 
     steps:
       - name: "Checkout code"
-        uses: actions/checkout@3df4ab11eba7bda6032a0b82a6bb43b11571feac # v4.0.0
+        uses: actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608 # v4.1.0
         with:
           persist-credentials: false
 
       - name: "Run analysis"
-        uses: ossf/scorecard-action@08b4669551908b1024bb425080c797723083c031 # v2.2.0
+        uses: ossf/scorecard-action@483ef80eb98fb506c348f7d62e28055e49fe2398 # v2.3.0
         with:
           results_file: results.sarif
           results_format: sarif
@@ -43,6 +43,6 @@ jobs:
 
       # required for Code scanning alerts
       - name: "Upload SARIF results to code scanning"
-        uses: github/codeql-action/upload-sarif@04daf014b50eaf774287bf3f0f1869d4b4c4b913 # v2.21.7
+        uses: github/codeql-action/upload-sarif@fdcae64e1484d349b3366718cdfef3d404390e85 # v2.22.1
         with:
           sarif_file: results.sarif
diff --git a/.github/workflows/preview-theme.yml b/.github/workflows/preview-theme.yml
index 50502bc..87932c7 100644
--- a/.github/workflows/preview-theme.yml
+++ b/.github/workflows/preview-theme.yml
@@ -30,7 +30,7 @@ jobs:
         node-version: [18.x]
 
     steps:
-      - uses: actions/checkout@3df4ab11eba7bda6032a0b82a6bb43b11571feac # v4.0.0
+      - uses: actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608 # v4.1.0
 
       - name: Setup Node
         uses: actions/setup-node@5e21ff4d9bc1a8cf6de233a3057d20ec6b3fb69d # v3.8.1
diff --git a/.github/workflows/prs-cache-clean.yml b/.github/workflows/prs-cache-clean.yml
index cec9a9b..f894fcf 100644
--- a/.github/workflows/prs-cache-clean.yml
+++ b/.github/workflows/prs-cache-clean.yml
@@ -23,7 +23,7 @@ jobs:
     runs-on: ubuntu-latest
     steps:
       - name: Checkout
-        uses: actions/checkout@3df4ab11eba7bda6032a0b82a6bb43b11571feac # v4.0.0
+        uses: actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608 # v4.1.0
 
       - name: Cleanup
         run: |
diff --git a/.github/workflows/stale-theme-pr-closer.yaml b/.github/workflows/stale-theme-pr-closer.yaml
index e388786..dc199ac 100644
--- a/.github/workflows/stale-theme-pr-closer.yaml
+++ b/.github/workflows/stale-theme-pr-closer.yaml
@@ -36,7 +36,7 @@ jobs:
         node-version: [18.x]
 
     steps:
-      - uses: actions/checkout@3df4ab11eba7bda6032a0b82a6bb43b11571feac # v4.0.0
+      - uses: actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608 # v4.1.0
 
       - name: Setup Node
         uses: actions/setup-node@5e21ff4d9bc1a8cf6de233a3057d20ec6b3fb69d # v3.8.1
diff --git a/.github/workflows/top-issues-dashboard.yml b/.github/workflows/top-issues-dashboard.yml
index b443ded..9e70319 100644
--- a/.github/workflows/top-issues-dashboard.yml
+++ b/.github/workflows/top-issues-dashboard.yml
@@ -34,7 +34,7 @@ jobs:
     runs-on: ubuntu-latest
     steps:
       - name: Run top issues action
-        uses: rickstaa/top-issues-action@1b36bbbc747a2e05808a88a57c15f3ba53ccfffb # v1.3.50
+        uses: rickstaa/top-issues-action@a2f94d3653b3c2490e0d997c8ec4a5e7beba4c7d # v1.3.54
         env:
           github_token: ${{ secrets.GITHUB_TOKEN }}
         with:
diff --git a/.github/workflows/update-langs.yaml b/.github/workflows/update-langs.yaml
index 7a4f198..e1c6db6 100644
--- a/.github/workflows/update-langs.yaml
+++ b/.github/workflows/update-langs.yaml
@@ -36,7 +36,7 @@ jobs:
         node-version: [18.x]
 
     steps:
-      - uses: actions/checkout@3df4ab11eba7bda6032a0b82a6bb43b11571feac # v4.0.0
+      - uses: actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608 # v4.1.0
 
       - name: Setup Node
         uses: actions/setup-node@5e21ff4d9bc1a8cf6de233a3057d20ec6b3fb69d # v3.8.1
diff --git a/.gitignore b/.gitignore
index 80db778..b1d9a01 100644
--- a/.gitignore
+++ b/.gitignore
@@ -4,6 +4,7 @@ node_modules
 *.lock
 .idea/
 coverage
+benchmarks
 vercel_token
 
 # IDE
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index 9693c4b..8fdbf1f 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -113,7 +113,3 @@ People _love_ thorough bug reports. I'm not even kidding.
 -   A quick idea summary
 -   What & why do you want to add the specific feature
 -   Additional context like images, links to resources to implement the feature, etc.
-
-## License
-
-By contributing, you agree that your contributions will be licensed under its [MIT License](./LICENSE).
diff --git a/jest.bench.config.js b/jest.bench.config.js
new file mode 100644
index 0000000..8a39b14
--- /dev/null
+++ b/jest.bench.config.js
@@ -0,0 +1,16 @@
+export default {
+  // Jest-bench need its own test environment to function
+  testEnvironment: "jest-bench/environment",
+  testEnvironmentOptions: {
+    // still Jest-bench environment will run your environment if you specify it here
+    testEnvironment: "jest-environment-node",
+    testEnvironmentOptions: {
+      // specify any option for your environment
+    },
+  },
+  // always include "default" reporter along with Jest-bench reporter
+  // for error reporting
+  reporters: ["default", "jest-bench/reporter"],
+  // will pick up "*.bench.js" file.
+  testRegex: "(\\.bench)\\.(ts|tsx|js)$",
+};
diff --git a/package-lock.json b/package-lock.json
index d7bb9c6..541bbd6 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -9,7 +9,7 @@
       "version": "1.0.0",
       "license": "MIT",
       "dependencies": {
-        "axios": "^1.5.0",
+        "axios": "^1.5.1",
         "dotenv": "^16.3.1",
         "emoji-name-map": "^1.2.8",
         "github-username-regex": "^1.0.0",
@@ -24,11 +24,12 @@
         "@uppercod/css-to-object": "^1.1.1",
         "axios-mock-adapter": "^1.22.0",
         "color-contrast-checker": "^2.1.0",
-        "eslint": "^8.49.0",
+        "eslint": "^8.51.0",
         "eslint-config-prettier": "^9.0.0",
         "hjson": "^3.2.2",
         "husky": "^8.0.3",
         "jest": "^29.7.0",
+        "jest-bench": "^29.4.1",
         "jest-environment-jsdom": "^29.7.0",
         "js-yaml": "^4.1.0",
         "lint-staged": "^14.0.1",
@@ -745,9 +746,9 @@
       }
     },
     "node_modules/@eslint/js": {
-      "version": "8.49.0",
-      "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.49.0.tgz",
-      "integrity": "sha512-1S8uAY/MTJqVx0SC4epBq+N2yhuwtNwLbJYNZyhL2pO1ZVKn5HFXav5T41Ryzy9K9V7ZId2JB2oy/W4aCd9/2w==",
+      "version": "8.51.0",
+      "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.51.0.tgz",
+      "integrity": "sha512-HxjQ8Qn+4SI3/AFv6sOrDB+g6PpUTDwSJiQqOrnneEk8L71161srI9gjzzZvYVbzHiVg/BvcH95+cK/zfIt4pg==",
       "dev": true,
       "engines": {
         "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
@@ -1800,9 +1801,9 @@
       }
     },
     "node_modules/axios": {
-      "version": "1.5.0",
-      "resolved": "https://registry.npmjs.org/axios/-/axios-1.5.0.tgz",
-      "integrity": "sha512-D4DdjDo5CY50Qms0qGQTTw6Q44jl7zRwY7bthds06pUGfChBCTcQs+N743eFWGEd6pRTMd6A+I87aWyFV5wiZQ==",
+      "version": "1.5.1",
+      "resolved": "https://registry.npmjs.org/axios/-/axios-1.5.1.tgz",
+      "integrity": "sha512-Q28iYCWzNHjAm+yEAot5QaAMxhMghWLFVf7rRdwhUI+c2jix2DUXjAHXVi+s1ibs3mjPO/cCgbA++3BjD0vP/A==",
       "dependencies": {
         "follow-redirects": "^1.15.0",
         "form-data": "^4.0.0",
@@ -1925,6 +1926,16 @@
       "integrity": "sha512-3pZEU3NT5BFUo/AD5ERPWOgQOCZITni6iavr5AUw5AUwQjMlI0kzu5btnyD39AF0gUEsDPwJT+oY1ORBJijPjQ==",
       "dev": true
     },
+    "node_modules/benchmark": {
+      "version": "2.1.4",
+      "resolved": "https://registry.npmjs.org/benchmark/-/benchmark-2.1.4.tgz",
+      "integrity": "sha512-l9MlfN4M1K/H2fbhfMy3B7vJd6AGKJVQn2h6Sg/Yx+KckoUA7ewS5Vv6TjSq18ooE1kS9hhAlQRH3AkXIh/aOQ==",
+      "dev": true,
+      "dependencies": {
+        "lodash": "^4.17.4",
+        "platform": "^1.3.3"
+      }
+    },
     "node_modules/brace-expansion": {
       "version": "1.1.11",
       "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
@@ -2640,15 +2651,15 @@
       }
     },
     "node_modules/eslint": {
-      "version": "8.49.0",
-      "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.49.0.tgz",
-      "integrity": "sha512-jw03ENfm6VJI0jA9U+8H5zfl5b+FvuU3YYvZRdZHOlU2ggJkxrlkJH4HcDrZpj6YwD8kuYqvQM8LyesoazrSOQ==",
+      "version": "8.51.0",
+      "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.51.0.tgz",
+      "integrity": "sha512-2WuxRZBrlwnXi+/vFSJyjMqrNjtJqiasMzehF0shoLaW7DzS3/9Yvrmq5JiT66+pNjiX4UBnLDiKHcWAr/OInA==",
       "dev": true,
       "dependencies": {
         "@eslint-community/eslint-utils": "^4.2.0",
         "@eslint-community/regexpp": "^4.6.1",
         "@eslint/eslintrc": "^2.1.2",
-        "@eslint/js": "8.49.0",
+        "@eslint/js": "8.51.0",
         "@humanwhocodes/config-array": "^0.11.11",
         "@humanwhocodes/module-importer": "^1.0.1",
         "@nodelib/fs.walk": "^1.2.8",
@@ -4006,6 +4017,23 @@
         }
       }
     },
+    "node_modules/jest-bench": {
+      "version": "29.4.1",
+      "resolved": "https://registry.npmjs.org/jest-bench/-/jest-bench-29.4.1.tgz",
+      "integrity": "sha512-CbhGPgHX+b4AQKnxz/iziVHpgLG+eoGKIvIkOH+VmuOLxme7klbgvOpNB0Ab+XNq/u/AmOlKK5cd1dGuaN4iEA==",
+      "dev": true,
+      "dependencies": {
+        "@jest/globals": "^29.4.1",
+        "@jest/reporters": "^29.4.1",
+        "benchmark": "^2.1.4",
+        "chalk": "^4.1.0",
+        "lodash": "^4.17.20",
+        "ndjson": "^2.0.0"
+      },
+      "peerDependencies": {
+        "jest": "^29.4.1"
+      }
+    },
     "node_modules/jest-changed-files": {
       "version": "29.7.0",
       "resolved": "https://registry.npmjs.org/jest-changed-files/-/jest-changed-files-29.7.0.tgz",
@@ -4976,6 +5004,12 @@
       "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==",
       "dev": true
     },
+    "node_modules/json-stringify-safe": {
+      "version": "5.0.1",
+      "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz",
+      "integrity": "sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA==",
+      "dev": true
+    },
     "node_modules/json5": {
       "version": "2.2.3",
       "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz",
@@ -5593,6 +5627,15 @@
         "node": "*"
       }
     },
+    "node_modules/minimist": {
+      "version": "1.2.8",
+      "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz",
+      "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==",
+      "dev": true,
+      "funding": {
+        "url": "https://github.com/sponsors/ljharb"
+      }
+    },
     "node_modules/ms": {
       "version": "2.1.2",
       "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
@@ -5605,6 +5648,25 @@
       "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==",
       "dev": true
     },
+    "node_modules/ndjson": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmjs.org/ndjson/-/ndjson-2.0.0.tgz",
+      "integrity": "sha512-nGl7LRGrzugTtaFcJMhLbpzJM6XdivmbkdlaGcrk/LXg2KL/YBC6z1g70xh0/al+oFuVFP8N8kiWRucmeEH/qQ==",
+      "dev": true,
+      "dependencies": {
+        "json-stringify-safe": "^5.0.1",
+        "minimist": "^1.2.5",
+        "readable-stream": "^3.6.0",
+        "split2": "^3.0.0",
+        "through2": "^4.0.0"
+      },
+      "bin": {
+        "ndjson": "cli.js"
+      },
+      "engines": {
+        "node": ">=10"
+      }
+    },
     "node_modules/node-fetch": {
       "version": "2.6.7",
       "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.7.tgz",
@@ -5962,6 +6024,12 @@
         "node": ">=8"
       }
     },
+    "node_modules/platform": {
+      "version": "1.3.6",
+      "resolved": "https://registry.npmjs.org/platform/-/platform-1.3.6.tgz",
+      "integrity": "sha512-fnWVljUchTro6RiCFvCXBbNhJc2NijN7oIQxbwsyL0buWJPG85v81ehlHI9fXrJsMNgTofEoWIQeClKpgxFLrg==",
+      "dev": true
+    },
     "node_modules/prelude-ls": {
       "version": "1.1.2",
       "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz",
@@ -6093,6 +6161,20 @@
       "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==",
       "dev": true
     },
+    "node_modules/readable-stream": {
+      "version": "3.6.2",
+      "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz",
+      "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==",
+      "dev": true,
+      "dependencies": {
+        "inherits": "^2.0.3",
+        "string_decoder": "^1.1.1",
+        "util-deprecate": "^1.0.1"
+      },
+      "engines": {
+        "node": ">= 6"
+      }
+    },
     "node_modules/redent": {
       "version": "3.0.0",
       "resolved": "https://registry.npmjs.org/redent/-/redent-3.0.0.tgz",
@@ -6261,6 +6343,26 @@
         "queue-microtask": "^1.2.2"
       }
     },
+    "node_modules/safe-buffer": {
+      "version": "5.2.1",
+      "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
+      "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==",
+      "dev": true,
+      "funding": [
+        {
+          "type": "github",
+          "url": "https://github.com/sponsors/feross"
+        },
+        {
+          "type": "patreon",
+          "url": "https://www.patreon.com/feross"
+        },
+        {
+          "type": "consulting",
+          "url": "https://feross.org/support"
+        }
+      ]
+    },
     "node_modules/safer-buffer": {
       "version": "2.1.2",
       "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
@@ -6403,6 +6505,15 @@
         "source-map": "^0.6.0"
       }
     },
+    "node_modules/split2": {
+      "version": "3.2.2",
+      "resolved": "https://registry.npmjs.org/split2/-/split2-3.2.2.tgz",
+      "integrity": "sha512-9NThjpgZnifTkJpzTZ7Eue85S49QwpNhZTq6GRJwObb6jnLFNGB7Qm73V5HewTROPyxD0C29xqmaI68bQtV+hg==",
+      "dev": true,
+      "dependencies": {
+        "readable-stream": "^3.0.0"
+      }
+    },
     "node_modules/sprintf-js": {
       "version": "1.0.3",
       "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz",
@@ -6433,6 +6544,15 @@
         "node": ">= 0.4"
       }
     },
+    "node_modules/string_decoder": {
+      "version": "1.3.0",
+      "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz",
+      "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==",
+      "dev": true,
+      "dependencies": {
+        "safe-buffer": "~5.2.0"
+      }
+    },
     "node_modules/string-argv": {
       "version": "0.3.2",
       "resolved": "https://registry.npmjs.org/string-argv/-/string-argv-0.3.2.tgz",
@@ -6573,6 +6693,15 @@
       "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==",
       "dev": true
     },
+    "node_modules/through2": {
+      "version": "4.0.2",
+      "resolved": "https://registry.npmjs.org/through2/-/through2-4.0.2.tgz",
+      "integrity": "sha512-iOqSav00cVxEEICeD7TjLB1sueEL+81Wpzp2bY17uZjZN0pWZPuo4suZ/61VujxmqSGFfgOcNuTZ85QJwNZQpw==",
+      "dev": true,
+      "dependencies": {
+        "readable-stream": "3"
+      }
+    },
     "node_modules/tmpl": {
       "version": "1.0.5",
       "resolved": "https://registry.npmjs.org/tmpl/-/tmpl-1.0.5.tgz",
@@ -6738,6 +6867,12 @@
         "requires-port": "^1.0.0"
       }
     },
+    "node_modules/util-deprecate": {
+      "version": "1.0.2",
+      "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
+      "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==",
+      "dev": true
+    },
     "node_modules/uuid": {
       "version": "8.3.2",
       "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz",
@@ -7571,9 +7706,9 @@
       }
     },
     "@eslint/js": {
-      "version": "8.49.0",
-      "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.49.0.tgz",
-      "integrity": "sha512-1S8uAY/MTJqVx0SC4epBq+N2yhuwtNwLbJYNZyhL2pO1ZVKn5HFXav5T41Ryzy9K9V7ZId2JB2oy/W4aCd9/2w==",
+      "version": "8.51.0",
+      "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.51.0.tgz",
+      "integrity": "sha512-HxjQ8Qn+4SI3/AFv6sOrDB+g6PpUTDwSJiQqOrnneEk8L71161srI9gjzzZvYVbzHiVg/BvcH95+cK/zfIt4pg==",
       "dev": true
     },
     "@humanwhocodes/config-array": {
@@ -8432,9 +8567,9 @@
       "dev": true
     },
     "axios": {
-      "version": "1.5.0",
-      "resolved": "https://registry.npmjs.org/axios/-/axios-1.5.0.tgz",
-      "integrity": "sha512-D4DdjDo5CY50Qms0qGQTTw6Q44jl7zRwY7bthds06pUGfChBCTcQs+N743eFWGEd6pRTMd6A+I87aWyFV5wiZQ==",
+      "version": "1.5.1",
+      "resolved": "https://registry.npmjs.org/axios/-/axios-1.5.1.tgz",
+      "integrity": "sha512-Q28iYCWzNHjAm+yEAot5QaAMxhMghWLFVf7rRdwhUI+c2jix2DUXjAHXVi+s1ibs3mjPO/cCgbA++3BjD0vP/A==",
       "requires": {
         "follow-redirects": "^1.15.0",
         "form-data": "^4.0.0",
@@ -8533,6 +8668,16 @@
       "integrity": "sha512-3pZEU3NT5BFUo/AD5ERPWOgQOCZITni6iavr5AUw5AUwQjMlI0kzu5btnyD39AF0gUEsDPwJT+oY1ORBJijPjQ==",
       "dev": true
     },
+    "benchmark": {
+      "version": "2.1.4",
+      "resolved": "https://registry.npmjs.org/benchmark/-/benchmark-2.1.4.tgz",
+      "integrity": "sha512-l9MlfN4M1K/H2fbhfMy3B7vJd6AGKJVQn2h6Sg/Yx+KckoUA7ewS5Vv6TjSq18ooE1kS9hhAlQRH3AkXIh/aOQ==",
+      "dev": true,
+      "requires": {
+        "lodash": "^4.17.4",
+        "platform": "^1.3.3"
+      }
+    },
     "brace-expansion": {
       "version": "1.1.11",
       "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
@@ -9049,15 +9194,15 @@
       }
     },
     "eslint": {
-      "version": "8.49.0",
-      "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.49.0.tgz",
-      "integrity": "sha512-jw03ENfm6VJI0jA9U+8H5zfl5b+FvuU3YYvZRdZHOlU2ggJkxrlkJH4HcDrZpj6YwD8kuYqvQM8LyesoazrSOQ==",
+      "version": "8.51.0",
+      "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.51.0.tgz",
+      "integrity": "sha512-2WuxRZBrlwnXi+/vFSJyjMqrNjtJqiasMzehF0shoLaW7DzS3/9Yvrmq5JiT66+pNjiX4UBnLDiKHcWAr/OInA==",
       "dev": true,
       "requires": {
         "@eslint-community/eslint-utils": "^4.2.0",
         "@eslint-community/regexpp": "^4.6.1",
         "@eslint/eslintrc": "^2.1.2",
-        "@eslint/js": "8.49.0",
+        "@eslint/js": "8.51.0",
         "@humanwhocodes/config-array": "^0.11.11",
         "@humanwhocodes/module-importer": "^1.0.1",
         "@nodelib/fs.walk": "^1.2.8",
@@ -9998,6 +10143,20 @@
         "jest-cli": "^29.7.0"
       }
     },
+    "jest-bench": {
+      "version": "29.4.1",
+      "resolved": "https://registry.npmjs.org/jest-bench/-/jest-bench-29.4.1.tgz",
+      "integrity": "sha512-CbhGPgHX+b4AQKnxz/iziVHpgLG+eoGKIvIkOH+VmuOLxme7klbgvOpNB0Ab+XNq/u/AmOlKK5cd1dGuaN4iEA==",
+      "dev": true,
+      "requires": {
+        "@jest/globals": "^29.4.1",
+        "@jest/reporters": "^29.4.1",
+        "benchmark": "^2.1.4",
+        "chalk": "^4.1.0",
+        "lodash": "^4.17.20",
+        "ndjson": "^2.0.0"
+      }
+    },
     "jest-changed-files": {
       "version": "29.7.0",
       "resolved": "https://registry.npmjs.org/jest-changed-files/-/jest-changed-files-29.7.0.tgz",
@@ -10748,6 +10907,12 @@
       "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==",
       "dev": true
     },
+    "json-stringify-safe": {
+      "version": "5.0.1",
+      "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz",
+      "integrity": "sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA==",
+      "dev": true
+    },
     "json5": {
       "version": "2.2.3",
       "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz",
@@ -11167,6 +11332,12 @@
         "brace-expansion": "^1.1.7"
       }
     },
+    "minimist": {
+      "version": "1.2.8",
+      "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz",
+      "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==",
+      "dev": true
+    },
     "ms": {
       "version": "2.1.2",
       "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
@@ -11179,6 +11350,19 @@
       "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==",
       "dev": true
     },
+    "ndjson": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmjs.org/ndjson/-/ndjson-2.0.0.tgz",
+      "integrity": "sha512-nGl7LRGrzugTtaFcJMhLbpzJM6XdivmbkdlaGcrk/LXg2KL/YBC6z1g70xh0/al+oFuVFP8N8kiWRucmeEH/qQ==",
+      "dev": true,
+      "requires": {
+        "json-stringify-safe": "^5.0.1",
+        "minimist": "^1.2.5",
+        "readable-stream": "^3.6.0",
+        "split2": "^3.0.0",
+        "through2": "^4.0.0"
+      }
+    },
     "node-fetch": {
       "version": "2.6.7",
       "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.7.tgz",
@@ -11439,6 +11623,12 @@
         "find-up": "^4.0.0"
       }
     },
+    "platform": {
+      "version": "1.3.6",
+      "resolved": "https://registry.npmjs.org/platform/-/platform-1.3.6.tgz",
+      "integrity": "sha512-fnWVljUchTro6RiCFvCXBbNhJc2NijN7oIQxbwsyL0buWJPG85v81ehlHI9fXrJsMNgTofEoWIQeClKpgxFLrg==",
+      "dev": true
+    },
     "prelude-ls": {
       "version": "1.1.2",
       "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz",
@@ -11521,6 +11711,17 @@
       "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==",
       "dev": true
     },
+    "readable-stream": {
+      "version": "3.6.2",
+      "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz",
+      "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==",
+      "dev": true,
+      "requires": {
+        "inherits": "^2.0.3",
+        "string_decoder": "^1.1.1",
+        "util-deprecate": "^1.0.1"
+      }
+    },
     "redent": {
       "version": "3.0.0",
       "resolved": "https://registry.npmjs.org/redent/-/redent-3.0.0.tgz",
@@ -11632,6 +11833,12 @@
         "queue-microtask": "^1.2.2"
       }
     },
+    "safe-buffer": {
+      "version": "5.2.1",
+      "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
+      "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==",
+      "dev": true
+    },
     "safer-buffer": {
       "version": "2.1.2",
       "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
@@ -11737,6 +11944,15 @@
         "source-map": "^0.6.0"
       }
     },
+    "split2": {
+      "version": "3.2.2",
+      "resolved": "https://registry.npmjs.org/split2/-/split2-3.2.2.tgz",
+      "integrity": "sha512-9NThjpgZnifTkJpzTZ7Eue85S49QwpNhZTq6GRJwObb6jnLFNGB7Qm73V5HewTROPyxD0C29xqmaI68bQtV+hg==",
+      "dev": true,
+      "requires": {
+        "readable-stream": "^3.0.0"
+      }
+    },
     "sprintf-js": {
       "version": "1.0.3",
       "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz",
@@ -11761,6 +11977,15 @@
         "internal-slot": "^1.0.4"
       }
     },
+    "string_decoder": {
+      "version": "1.3.0",
+      "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz",
+      "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==",
+      "dev": true,
+      "requires": {
+        "safe-buffer": "~5.2.0"
+      }
+    },
     "string-argv": {
       "version": "0.3.2",
       "resolved": "https://registry.npmjs.org/string-argv/-/string-argv-0.3.2.tgz",
@@ -11862,6 +12087,15 @@
       "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==",
       "dev": true
     },
+    "through2": {
+      "version": "4.0.2",
+      "resolved": "https://registry.npmjs.org/through2/-/through2-4.0.2.tgz",
+      "integrity": "sha512-iOqSav00cVxEEICeD7TjLB1sueEL+81Wpzp2bY17uZjZN0pWZPuo4suZ/61VujxmqSGFfgOcNuTZ85QJwNZQpw==",
+      "dev": true,
+      "requires": {
+        "readable-stream": "3"
+      }
+    },
     "tmpl": {
       "version": "1.0.5",
       "resolved": "https://registry.npmjs.org/tmpl/-/tmpl-1.0.5.tgz",
@@ -11977,6 +12211,12 @@
         "requires-port": "^1.0.0"
       }
     },
+    "util-deprecate": {
+      "version": "1.0.2",
+      "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
+      "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==",
+      "dev": true
+    },
     "uuid": {
       "version": "8.3.2",
       "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz",
diff --git a/package.json b/package.json
index 2dd0f9c..44e1b2c 100644
--- a/package.json
+++ b/package.json
@@ -30,7 +30,8 @@
     "format": "prettier --write .",
     "format:check": "prettier --check .",
     "prepare": "husky install",
-    "lint": "npx eslint --max-warnings 0 \"./src/**/*.js\" \"./scripts/**/*.js\" \"./tests/**/*.js\" \"./api/**/*.js\" \"./themes/**/*.js\""
+    "lint": "npx eslint --max-warnings 0 \"./src/**/*.js\" \"./scripts/**/*.js\" \"./tests/**/*.js\" \"./api/**/*.js\" \"./themes/**/*.js\"",
+    "bench": "node --experimental-vm-modules node_modules/jest/bin/jest.js --config jest.bench.config.js"
   },
   "author": "Anurag Hazra",
   "license": "MIT",
@@ -42,11 +43,12 @@
     "@uppercod/css-to-object": "^1.1.1",
     "axios-mock-adapter": "^1.22.0",
     "color-contrast-checker": "^2.1.0",
-    "eslint": "^8.49.0",
+    "eslint": "^8.51.0",
     "eslint-config-prettier": "^9.0.0",
     "hjson": "^3.2.2",
     "husky": "^8.0.3",
     "jest": "^29.7.0",
+    "jest-bench": "^29.4.1",
     "jest-environment-jsdom": "^29.7.0",
     "js-yaml": "^4.1.0",
     "lint-staged": "^14.0.1",
@@ -55,7 +57,7 @@
     "prettier": "^3.0.3"
   },
   "dependencies": {
-    "axios": "^1.5.0",
+    "axios": "^1.5.1",
     "dotenv": "^16.3.1",
     "emoji-name-map": "^1.2.8",
     "github-username-regex": "^1.0.0",
diff --git a/readme.md b/readme.md
index f42f79e..e0cd50b 100644
--- a/readme.md
+++ b/readme.md
@@ -24,12 +24,6 @@
     </a>
     <br />
     <br />
-    <a href="https://a.paddle.com/v2/click/16413/119403?link=1227">
-      <img src="https://img.shields.io/badge/Supported%20by-VSCode%20Power%20User%20%E2%86%92-gray.svg?colorA=655BE1&colorB=4F44D6&style=for-the-badge"/>
-    </a>
-    <a href="https://a.paddle.com/v2/click/16413/119403?link=2345">
-      <img src="https://img.shields.io/badge/Supported%20by-Node%20Cli.com%20%E2%86%92-gray.svg?colorA=61c265&colorB=4CAF50&style=for-the-badge"/>
-    </a>
   </p>
 
   <p align="center">
@@ -143,7 +137,7 @@ Change the `?username=` value to your GitHub username.
 > By default, the stats card only shows statistics like stars, commits and pull requests from public repositories. To show private statistics on the stats card, you should [deploy your own instance](#deploy-on-your-own) using your own GitHub API token.
 
 > [!NOTE]\
-> Available ranks are S (top 1%), A+ (12.5%), A (25%), A- (37.5%), B+ (50%), B (62.5%), B- (75%), C+ (87.5%) and C (everyone). This ranking scheme is based on the [Japanese academic grading](https://wikipedia.org/wiki/Academic_grading_in_Japan) system. The global percentile is calculated as a weighted sum of percentiles for each statistic (number of commits, pull requests, reviews, issues, stars and followers), based on the cumulative distribution function of the [exponential](https://wikipedia.org/wiki/exponential_distribution) and the [log-normal](https://wikipedia.org/wiki/Log-normal_distribution) distributions. The implementation can be investigated at [src/calculateRank.js](./src/calculateRank.js). The circle around the rank shows 100 minus the global percentile.
+> Available ranks are S (top 1%), A+ (12.5%), A (25%), A- (37.5%), B+ (50%), B (62.5%), B- (75%), C+ (87.5%) and C (everyone). This ranking scheme is based on the [Japanese academic grading](https://wikipedia.org/wiki/Academic_grading_in_Japan) system. The global percentile is calculated as a weighted sum of percentiles for each statistic (number of commits, pull requests, reviews, issues, stars and followers), based on the cumulative distribution function of the [exponential](https://wikipedia.org/wiki/exponential_distribution) and the [log-normal](https://wikipedia.org/wiki/Log-normal_distribution) distributions. The implementation can be investigated at [src/calculateRank.js](src/calculateRank.js). The circle around the rank shows 100 minus the global percentile.
 
 ### Hiding individual stats
 
@@ -189,7 +183,7 @@ GitHub Readme Stats comes with several built-in themes (e.g. `dark`, `radical`,
 
 <img src="https://res.cloudinary.com/anuraghazra/image/upload/v1595174536/grs-themes_l4ynja.png" alt="GitHub Readme Stats Themes" width="600px"/>
 
-You can look at a preview for [all available themes](./themes/README.md) or checkout the [theme config file](./themes/index.js). You can also contribute new themes if you like, contributing guidelines can be found [here](./CONTRIBUTING.md#themes-contribution).
+You can look at a preview for [all available themes](themes/README.md) or checkout the [theme config file](themes/index.js). You can also contribute new themes if you like, contributing guidelines can be found [here](CONTRIBUTING.md#themes-contribution).
 
 #### Responsive Card Theme
 
@@ -215,7 +209,7 @@ We have included a `transparent` theme that has a transparent background. This t
 
 ##### Add transparent alpha channel to a themes bg\_color
 
-You can use the `bg_color` parameter to make any of [the available themes](./themes/README.md) transparent. This is done by setting the `bg_color` to a color with a transparent alpha channel (i.e. `bg_color=00000000`):
+You can use the `bg_color` parameter to make any of [the available themes](themes/README.md) transparent. This is done by setting the `bg_color` to a color with a transparent alpha channel (i.e. `bg_color=00000000`):
 
 ```md
 ![Anurag's GitHub stats](https://github-readme-stats.vercel.app/api?username=anuraghazra&show_icons=true&bg_color=00000000)
@@ -292,9 +286,9 @@ You can customize the appearance of all your cards however you wish with URL par
 *   `border_color` - Card's border color *(hex color)*. Default: `e4e2e2` (Does not apply when `hide_border` is enabled).
 *   `bg_color` - Card's background color *(hex color)* **or** a gradient in the form of *angle,start,end*. Default: `fffefe`
 *   `hide_border` - Hides the card's border *(boolean)*. Default: `false`
-*   `theme` - Name of the theme, choose from [all available themes](./themes/README.md). Default: `default` theme.
+*   `theme` - Name of the theme, choose from [all available themes](themes/README.md). Default: `default` theme.
 *   `cache_seconds` - Sets the cache header manually *(min: 21600, max: 86400)*. Default: `21600 seconds (6 hours)`.
-*   `locale` - Sets the language in the card *(e.g. cn, de, es, etc.)*. Default: `en`.
+*   `locale` - Sets the language in the card, you can check full list of available locales [here](#available-locales). Default: `en`.
 *   `border_radius` - Corner rounding on the card. Default: `4.5`.
 
 > [!WARNING]\
@@ -306,6 +300,60 @@ You can provide multiple comma-separated values in the bg\_color option to rende
 
     &bg_color=DEG,COLOR1,COLOR2,COLOR3...COLOR10
 
+##### Available locales
+
+Here is a list of all available locales:
+
+<table>
+<tr><td>
+
+| Code | Locale |
+| --- | --- |
+| `cn` | Chinese |
+| `zh-tw` | Chinese (Taiwan) |
+| `ar` | Arabic |
+| `cs` | Czech |
+| `de` | German |
+| `en` | English |
+| `bn` | Bengali |
+| `es` | Spanish |
+| `fr` | French |
+| `hu` | Hungarian |
+
+</td><td>
+
+| Code | Locale |
+| --- | --- |
+| `it` | Italian |
+| `ja` | Japanese |
+| `kr` | Korean |
+| `nl` | Dutch |
+| `pt-pt` | Portuguese (Portugal) |
+| `pt-br` | Portuguese (Brazil) |
+| `np` | Nepali |
+| `el` | Greek |
+| `ru` | Russian |
+| `uk-ua` | Ukrainian |
+
+</td><td>
+
+| Code | Locale |
+| --- | --- |
+| `id` | Indonesian |
+| `ml` | Malayalam |
+| `my` | Burmese |
+| `sk` | Slovak |
+| `tr` | Turkish |
+| `pl` | Polish |
+| `uz` | Uzbek |
+| `vi` | Vietnamese |
+| `se` | Swedish |
+
+</td></tr>
+</table>
+
+If we don't support your language, please consider contributing!
+
 #### Stats Card Exclusive Options
 
 *   `hide` - Hides the [specified items](#hiding-individual-stats) from stats *(Comma-separated values)*. Default: `[] (blank array)`.
@@ -697,7 +745,7 @@ Since the GitHub API only allows 5k requests per hour, my `https://github-readme
 > Since [#58](https://github.com/anuraghazra/github-readme-stats/pull/58), we should be able to handle more than 5k requests and have fewer issues with downtime :grin:.
 
 > [!NOTE]\
-> If you are on the [Pro (i.e. paid)](https://vercel.com/pricing) Vercel plan, the [maxDuration](https://vercel.com/docs/concepts/projects/project-configuration#value-definition) value found in the [Vercel.json](https://github.com/anuraghazra/github-readme-stats/blob/master/vercel.json) can be increased when your Vercel instance frequently times out during the card request. You are advised to keep this value lower than `30` seconds to prevent high memory usage.
+> If you are on the [Pro (i.e. paid)](https://vercel.com/pricing) Vercel plan, the [maxDuration](https://vercel.com/docs/concepts/projects/project-configuration#value-definition) value found in the [Vercel.json](vercel.json) can be increased when your Vercel instance frequently times out during the card request. You are advised to keep this value lower than `30` seconds to prevent high memory usage.
 
 [![Deploy to Vercel](https://vercel.com/button)](https://vercel.com/import/project?template=https://github.com/anuraghazra/github-readme-stats)
 
@@ -767,7 +815,7 @@ Thanks! :heart:
 
 ***
 
-[![https://vercel.com?utm\_source=github\_readme\_stats\_team\&utm\_campaign=oss](./powered-by-vercel.svg)](https://vercel.com?utm_source=github_readme_stats_team\&utm_campaign=oss)
+[![https://vercel.com?utm\_source=github\_readme\_stats\_team\&utm\_campaign=oss](powered-by-vercel.svg)](https://vercel.com?utm_source=github_readme_stats_team\&utm_campaign=oss)
 
 Contributions are welcome! <3
 
diff --git a/src/common/utils.js b/src/common/utils.js
index 0906351..93d60cd 100644
--- a/src/common/utils.js
+++ b/src/common/utils.js
@@ -4,6 +4,41 @@ import toEmoji from "emoji-name-map";
 import wrap from "word-wrap";
 import { themes } from "../../themes/index.js";
 
+const TRY_AGAIN_LATER = "Please try again later";
+
+const SECONDARY_ERROR_MESSAGES = {
+  MAX_RETRY:
+    "You can deploy own instance or wait until public will be no longer limited",
+  NO_TOKENS:
+    "Please add an env variable called PAT_1 with your GitHub API token in vercel",
+  USER_NOT_FOUND: "Make sure the provided username is not an organization",
+  GRAPHQL_ERROR: TRY_AGAIN_LATER,
+  GITHUB_REST_API_ERROR: TRY_AGAIN_LATER,
+  WAKATIME_USER_NOT_FOUND: "Make sure you have a public WakaTime profile",
+};
+
+/**
+ * Custom error class to handle custom GRS errors.
+ */
+class CustomError extends Error {
+  /**
+   * @param {string} message Error message.
+   * @param {string} type Error type.
+   */
+  constructor(message, type) {
+    super(message);
+    this.type = type;
+    this.secondaryMessage = SECONDARY_ERROR_MESSAGES[type] || type;
+  }
+
+  static MAX_RETRY = "MAX_RETRY";
+  static NO_TOKENS = "NO_TOKENS";
+  static USER_NOT_FOUND = "USER_NOT_FOUND";
+  static GRAPHQL_ERROR = "GRAPHQL_ERROR";
+  static GITHUB_REST_API_ERROR = "GITHUB_REST_API_ERROR";
+  static WAKATIME_ERROR = "WAKATIME_ERROR";
+}
+
 // Script parameters.
 const ERROR_CARD_LENGTH = 576.5;
 
@@ -23,6 +58,11 @@ const encodeHTML = (str) => {
     .replace(/\u0008/gim, "");
 };
 
+const UPSTREAM_API_ERRORS = [
+  TRY_AGAIN_LATER,
+  SECONDARY_ERROR_MESSAGES.MAX_RETRY,
+];
+
 /**
  * Renders error message on the card.
  *
@@ -41,7 +81,11 @@ const renderError = (message, secondaryMessage = "") => {
     <rect x="0.5" y="0.5" width="${
       ERROR_CARD_LENGTH - 1
     }" height="99%" rx="4.5" fill="#FFFEFE" stroke="#E4E2E2"/>
-    <text x="25" y="45" class="text">Something went wrong! file an issue at https://tiny.one/readme-stats</text>
+    <text x="25" y="45" class="text">Something went wrong!${
+      UPSTREAM_API_ERRORS.includes(secondaryMessage)
+        ? ""
+        : " file an issue at https://tiny.one/readme-stats"
+    }</text>
     <text data-testid="message" x="25" y="55" class="text small">
       <tspan x="25" dy="18">${encodeHTML(message)}</tspan>
       <tspan x="25" dy="18" class="gray">${secondaryMessage}</tspan>
@@ -399,41 +443,6 @@ const CONSTANTS = {
   ERROR_CACHE_SECONDS: TEN_MINUTES,
 };
 
-const TRY_AGAING_LATER = "Please try again later";
-
-const SECONDARY_ERROR_MESSAGES = {
-  MAX_RETRY:
-    "You can deploy own instance or wait until public will be no longer limited",
-  NO_TOKENS:
-    "Please add an env variable called PAT_1 with your GitHub API token in vercel",
-  USER_NOT_FOUND: "Make sure the provided username is not an organization",
-  GRAPHQL_ERROR: TRY_AGAING_LATER,
-  GITHUB_REST_API_ERROR: TRY_AGAING_LATER,
-  WAKATIME_USER_NOT_FOUND: "Make sure you have a public WakaTime profile",
-};
-
-/**
- * Custom error class to handle custom GRS errors.
- */
-class CustomError extends Error {
-  /**
-   * @param {string} message Error message.
-   * @param {string} type Error type.
-   */
-  constructor(message, type) {
-    super(message);
-    this.type = type;
-    this.secondaryMessage = SECONDARY_ERROR_MESSAGES[type] || type;
-  }
-
-  static MAX_RETRY = "MAX_RETRY";
-  static NO_TOKENS = "NO_TOKENS";
-  static USER_NOT_FOUND = "USER_NOT_FOUND";
-  static GRAPHQL_ERROR = "GRAPHQL_ERROR";
-  static GITHUB_REST_API_ERROR = "GITHUB_REST_API_ERROR";
-  static WAKATIME_ERROR = "WAKATIME_ERROR";
-}
-
 /**
  * Missing query parameter class.
  */


Test patch:
diff --git a/.github/workflows/e2e-test.yml b/.github/workflows/e2e-test.yml
index e0ecbf4..c537f37 100644
--- a/.github/workflows/e2e-test.yml
+++ b/.github/workflows/e2e-test.yml
@@ -17,7 +17,7 @@ jobs:
         node-version: [18.x]
 
     steps:
-      - uses: actions/checkout@3df4ab11eba7bda6032a0b82a6bb43b11571feac # v4.0.0
+      - uses: actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608 # v4.1.0
 
       - name: Setup Node
         uses: actions/setup-node@5e21ff4d9bc1a8cf6de233a3057d20ec6b3fb69d # v3.8.1
diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml
index 598e39a..041ade3 100644
--- a/.github/workflows/test.yml
+++ b/.github/workflows/test.yml
@@ -18,7 +18,7 @@ jobs:
         node-version: [18.x]
 
     steps:
-      - uses: actions/checkout@3df4ab11eba7bda6032a0b82a6bb43b11571feac # v4.0.0
+      - uses: actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608 # v4.1.0
 
       - name: Setup Node
         uses: actions/setup-node@5e21ff4d9bc1a8cf6de233a3057d20ec6b3fb69d # v3.8.1
@@ -35,6 +35,10 @@ jobs:
         run: |
           npm run lint
 
+      - name: Run bench tests
+        run: |
+          npm run bench
+
       - name: Run Prettier
         run: |
           npm run format:check
diff --git a/tests/api.test.js b/tests/api.test.js
index 6af40f8..a6ed04b 100644
--- a/tests/api.test.js
+++ b/tests/api.test.js
@@ -321,5 +321,9 @@ describe("Test /api/", () => {
     expect(res.send).toBeCalledWith(
       renderError("Could not fetch total commits.", "Please try again later"),
     );
+    // Received SVG output should not contain string "https://tiny.one/readme-stats"
+    expect(res.send.mock.calls[0][0]).not.toContain(
+      "https://tiny.one/readme-stats",
+    );
   });
 });
diff --git a/tests/bench/api.bench.js b/tests/bench/api.bench.js
new file mode 100644
index 0000000..4796b64
--- /dev/null
+++ b/tests/bench/api.bench.js
@@ -0,0 +1,76 @@
+import { benchmarkSuite } from "jest-bench";
+import api from "../../api/index.js";
+import axios from "axios";
+import MockAdapter from "axios-mock-adapter";
+import { jest } from "@jest/globals";
+
+const stats = {
+  name: "Anurag Hazra",
+  totalStars: 100,
+  totalCommits: 200,
+  totalIssues: 300,
+  totalPRs: 400,
+  totalPRsMerged: 320,
+  mergedPRsPercentage: 80,
+  totalReviews: 50,
+  totalDiscussionsStarted: 10,
+  totalDiscussionsAnswered: 40,
+  contributedTo: 50,
+  rank: null,
+};
+
+const data_stats = {
+  data: {
+    user: {
+      name: stats.name,
+      repositoriesContributedTo: { totalCount: stats.contributedTo },
+      contributionsCollection: {
+        totalCommitContributions: stats.totalCommits,
+        totalPullRequestReviewContributions: stats.totalReviews,
+      },
+      pullRequests: { totalCount: stats.totalPRs },
+      mergedPullRequests: { totalCount: stats.totalPRsMerged },
+      openIssues: { totalCount: stats.totalIssues },
+      closedIssues: { totalCount: 0 },
+      followers: { totalCount: 0 },
+      repositoryDiscussions: { totalCount: stats.totalDiscussionsStarted },
+      repositoryDiscussionComments: {
+        totalCount: stats.totalDiscussionsAnswered,
+      },
+      repositories: {
+        totalCount: 1,
+        nodes: [{ stargazers: { totalCount: 100 } }],
+        pageInfo: {
+          hasNextPage: false,
+          endCursor: "cursor",
+        },
+      },
+    },
+  },
+};
+
+const mock = new MockAdapter(axios);
+
+const faker = (query, data) => {
+  const req = {
+    query: {
+      username: "anuraghazra",
+      ...query,
+    },
+  };
+  const res = {
+    setHeader: jest.fn(),
+    send: jest.fn(),
+  };
+  mock.onPost("https://api.github.com/graphql").replyOnce(200, data);
+
+  return { req, res };
+};
+
+benchmarkSuite("test /api", {
+  ["simple request"]: async () => {
+    const { req, res } = faker({}, data_stats);
+
+    await api(req, res);
+  },
+});
diff --git a/tests/bench/calculateRank.bench.js b/tests/bench/calculateRank.bench.js
new file mode 100644
index 0000000..1ce6b05
--- /dev/null
+++ b/tests/bench/calculateRank.bench.js
@@ -0,0 +1,17 @@
+import { benchmarkSuite } from "jest-bench";
+import { calculateRank } from "../../src/calculateRank.js";
+
+benchmarkSuite("calculateRank", {
+  ["calculateRank"]: () => {
+    calculateRank({
+      all_commits: false,
+      commits: 1300,
+      prs: 1500,
+      issues: 4500,
+      reviews: 1000,
+      repos: 0,
+      stars: 600000,
+      followers: 50000,
+    });
+  },
+});