Merge branch 'master' into release

This commit is contained in:
martin-mfg
2026-03-07 12:23:50 +01:00
321 changed files with 13931 additions and 30710 deletions
+13 -10
View File
@@ -2,12 +2,15 @@
## Local Development
To run and test GitHub-Stats-Extended, you need to follow a few simple steps:
To set up the project GitHub-Stats-Extended locally, run the following commands:
1. create [your own deployment](../docs/deploy.md)
2. optional: add an SQL database; by using e.g. the ["Nile" integration](https://vercel.com/marketplace/nile) or by manually setting the environment variable `POSTGRES_URL`
3. optional: [create your own OAuth App](https://github.com/settings/developers) and set environment variables `OAUTH_REDIRECT_URI`, `OAUTH_CLIENT_ID` and `OAUTH_CLIENT_SECRET` on Vercel accordingly
4. optional: in addition to the Vercel project based on the `backend` folder, create a second project based on the `frontend/frontend` folder. No environment variables needed.
```bash
./vercel-preparation.sh
pnpm install
pnpm --filter frontend run build
```
The easiest way to run and test the project is to deploy it to Vercel as described in the [deployment guide](../docs/deploy.md).
## Themes Contribution
@@ -37,9 +40,9 @@ We use GitHub issues to track public bugs. Report a bug by [opening a new issue]
> **Ans:** Please read all the related issues/comments before opening any issues regarding language card stats:
>
> - <https://github.com/anuraghazra/github-readme-stats/issues/136#issuecomment-665164174>
> - <https://github.com/anuraghazra/github-readme-stats/issues/136#issuecomment-665164174>
>
> - <https://github.com/anuraghazra/github-readme-stats/issues/136#issuecomment-665172181>
> - <https://github.com/anuraghazra/github-readme-stats/issues/136#issuecomment-665172181>
**Q:** How to count private stats?
@@ -49,6 +52,6 @@ We use GitHub issues to track public bugs. Report a bug by [opening a new issue]
**Great Feature Requests** tend to have:
- 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.
- 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.
@@ -0,0 +1,37 @@
name: Setup Node.js + PNPM and install Dependencies
description: |
This is a composite GitHub Action that:
- Sets up pnpm package manager
- Configures Node.js environment
- Installs project dependencies with caching
inputs:
node-version:
description: "Explicit node version. Otherwise fallback reading `.nvmrc`"
runs:
using: composite
steps:
- name: Install pnpm
uses: pnpm/action-setup@v4
- name: Setup Node.js (via input)
if: ${{ inputs.node-version }}
uses: actions/setup-node@v6
with:
node-version: ${{ inputs.node-version }}
cache: "pnpm"
registry-url: "https://registry.npmjs.org"
- name: Setup Node.js (via .nvmrc)
if: ${{ !inputs.node-version }}
uses: actions/setup-node@v6
with:
node-version-file: ".nvmrc"
cache: "pnpm"
registry-url: "https://registry.npmjs.org"
- name: Install Dependencies
shell: bash
run: pnpm install --frozen-lockfile
+43
View File
@@ -0,0 +1,43 @@
version: 2
updates:
# Maintain dependencies for npm
- package-ecosystem: npm
directory: "/"
schedule:
interval: cron
cronjob: every day at 5am
open-pull-requests-limit: 10
commit-message:
prefix: "build(deps)"
prefix-development: "build(deps-dev)"
cooldown:
default-days: 7
ignore:
- dependency-name: "@types/node"
update-types: ["version-update:semver-major"]
# Maintain dependencies for GitHub Actions
- package-ecosystem: github-actions
directory: "/"
schedule:
interval: cron
cronjob: every day at 5am
open-pull-requests-limit: 10
commit-message:
prefix: "ci(deps)"
prefix-development: "ci(deps-dev)"
cooldown:
default-days: 7
# Maintain dependencies for Devcontainers
- package-ecosystem: devcontainers
directory: "/"
schedule:
interval: cron
cronjob: every day at 5am
open-pull-requests-limit: 10
commit-message:
prefix: "build(deps)"
prefix-development: "build(deps-dev)"
cooldown:
default-days: 7
+118
View File
@@ -0,0 +1,118 @@
name: CI
on:
workflow_dispatch:
push:
branches:
- master
pull_request:
branches:
- master
concurrency:
group: "${{ github.workflow }}-${{ github.head_ref }}"
cancel-in-progress: ${{ github.event_name == 'pull_request' }}
permissions: {}
jobs:
build-and-test:
name: Build and test ${{ matrix.node }} on ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
node: [24]
os: [ubuntu-latest, macos-latest]
runs-on: ${{ matrix.os }}
permissions:
contents: read
steps:
- name: Checkout code
uses: actions/checkout@v6
- name: Run vercel-preparation.sh
run: |
chmod +x ./vercel-preparation.sh
./vercel-preparation.sh
- name: Install Dependencies
uses: ./.github/actions/install-dependencies
with:
node-version: ${{ matrix.node }}
- name: Build frontend
run: pnpm --filter frontend run build
- name: Run frontend tests
run: pnpm --filter frontend run test
- name: Run backend tests
run: pnpm --filter github-readme-stats run test
frontend-test-e2e:
name: Frontend E2E test
permissions:
contents: read
timeout-minutes: 60
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v6
- name: Install Dependencies
uses: ./.github/actions/install-dependencies
- name: Run vercel-preparation.sh
run: |
chmod +x ./vercel-preparation.sh
./vercel-preparation.sh
- name: Install Playwright Browsers
run: pnpm exec playwright install --with-deps
- name: Run Playwright tests
run: pnpm --filter frontend run test:e2e
code-checks:
name: Code checks
runs-on: ubuntu-latest
permissions:
contents: read
steps:
- name: Checkout code
uses: actions/checkout@v6
# Heads up!
#
# 1. Execution of this script is needed to resolve `.vercel` folder from `apps/frontend/src/components/Card/SVG.js`
# 2. This scripts removes `apps/backend/node_modules` breaking ESLints module resolution.
# Dependency installation must occur after running ./vercel-preparation.sh.
- name: Run vercel-preparation.sh
run: |
chmod +x ./vercel-preparation.sh
./vercel-preparation.sh
- name: Install Dependencies
uses: ./.github/actions/install-dependencies
- name: Format
run: pnpm run format:check
- name: Lint
run: pnpm run lint
- name: Lint (knip)
run: pnpm run lint:knip
- name: Typecheck
run: pnpm run typecheck
+53
View File
@@ -0,0 +1,53 @@
name: Generate Theme Readme
on:
push:
branches:
- master
paths:
- "apps/backend/themes/index.js"
workflow_dispatch:
permissions: {}
jobs:
generate-theme-doc:
runs-on: ubuntu-latest
name: Generate theme doc
permissions:
actions: read
checks: read
contents: write
deployments: read
issues: read
discussions: read
packages: read
pages: read
pull-requests: write
repository-projects: read
security-events: read
statuses: read
steps:
- name: Checkout code
uses: actions/checkout@v6
- name: Install Dependencies
uses: ./.github/actions/install-dependencies
- name: Generate readme
run: |
pnpm --filter github-readme-stats run theme-readme-gen
- name: Create Pull Request if themes README has changed
uses: peter-evans/create-pull-request@v8
with:
commit-message: "feat(backend): update themes README"
branch: "update_themes_readme/patch"
delete-branch: true
title: Update themes README
body: "The
[generate-theme-doc](https://github.com/stats-organization/github-stats-extended/actions/workflows/generate-theme-doc.yml)
action found new/updated languages in the [upstream languages JSON
file](https://raw.githubusercontent.com/github/linguist/master/lib/linguist/languages.yml)."
labels: "ci"
+1 -1
View File
@@ -20,7 +20,7 @@ jobs:
steps:
- name: Make Request
id: myRequest
uses: fjogeleit/http-request-action@v1
uses: fjogeleit/http-request-action@v2
with:
url: "https://github-stats-extended.vercel.app/api/repeat-recent"
method: "POST"
@@ -1,5 +1,6 @@
name: Update supported languages
on:
workflow_dispatch:
schedule:
# ┌───────────── minute (0 - 59)
# │ ┌───────────── hour (0 - 23)
@@ -27,41 +28,33 @@ permissions:
statuses: read
jobs:
updateLanguages:
if: github.repository == 'anuraghazra/github-readme-stats'
update-languages:
if: |
github.repository == 'anuraghazra/github-readme-stats' ||
github.repository == 'stats-organization/github-readme-stats' ||
github.repository == 'stats-organization/github-stats-extended'
name: Update supported languages
runs-on: ubuntu-latest
strategy:
matrix:
node-version: [22.x]
steps:
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
- name: Checkout code
uses: actions/checkout@v6
- name: Setup Node
uses: actions/setup-node@395ad3262231945c25e8478fd5baf05154b1d79f # v6.1.0
with:
node-version: ${{ matrix.node-version }}
cache: npm
- name: Install dependencies
run: npm ci
env:
CI: true
- name: Install Dependencies
uses: ./.github/actions/install-dependencies
- name: Run update-languages-json.js script
run: npm run generate-langs-json
run: pnpm --filter github-readme-stats run generate-langs-json
- name: Create Pull Request if upstream language file is changed
uses: peter-evans/create-pull-request@98357b18bf14b5342f975ff684046ec3b2a07725 # v8.0.0
uses: peter-evans/create-pull-request@v8
with:
commit-message: "refactor: update languages JSON"
commit-message: "feat(backend): 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)
body: "The
[update-langs](https://github.com/stats-organization/github-stats-extended/actions/workflows/update-langs.yml)
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"
labels: "ci"
+40 -9
View File
@@ -1,12 +1,36 @@
backend/.vercel
backend/.env
backend/node_modules
backend/*.lock
backend/coverage
backend/benchmarks
backend/vercel_token
frontend/frontend/.env
frontend/frontend/src/backend
node_modules
.env.local
.env.development.local
.env.test.local
.env.production.local
# OS
.DS_Store
# Logs
npm-debug.log*
yarn-debug.log*
yarn-error.log*
# Project
coverage
apps/backend/.vercel
apps/backend/.env
apps/backend/node_modules
apps/backend/*.lock
apps/backend/coverage
apps/backend/benchmarks
apps/backend/vercel_token
apps/backend-copy
apps/frontend/.env
apps/frontend/src/backend
apps/frontend/build
build-ts
tsconfig.tsbuildinfo
# IDE
.idea/
@@ -14,3 +38,10 @@ frontend/frontend/src/backend
!.vscode/extensions.json
!.vscode/settings.json
*.code-workspace
# Playwright
test-results/
playwright-report/
blob-report/
playwright/.cache/
playwright/.auth/
+4
View File
@@ -0,0 +1,4 @@
pnpm lint-staged
pnpm run lint
# TODO enable
# npm test
+1
View File
@@ -0,0 +1 @@
v24
+5
View File
@@ -0,0 +1,5 @@
pnpm-lock.yaml
# https://github.com/stats-organization/github-stats-extended/pull/26#discussion_r2709033732
# Avoid prettier format on md files to simplify merge on upstream repo
*.md
+17
View File
@@ -0,0 +1,17 @@
{
"$schema": "https://json.schemastore.org/prettierrc",
"useTabs": false,
"semi": true,
"trailingComma": "all",
"singleQuote": false,
"printWidth": 80,
"tabWidth": 2,
"overrides": [
{
"files": ["*.jsonc"],
"options": {
"trailingComma": "none"
}
}
]
}
+8
View File
@@ -0,0 +1,8 @@
{
"recommendations": [
"yzhang.markdown-all-in-one",
"prettier.prettier-vscode",
"dbaeumer.vscode-eslint",
"github.vscode-github-actions"
]
}
+10
View File
@@ -0,0 +1,10 @@
{
"markdown.extension.toc.levels": "1..3",
"editor.formatOnSave": true,
"editor.defaultFormatter": "esbenp.prettier-vscode",
"[javascript]": {
"editor.tabSize": 2,
"editor.defaultFormatter": "esbenp.prettier-vscode"
},
"cSpell.words": ["Wakatime"]
}
+1 -1
View File
@@ -38,7 +38,7 @@ As more comfortable alternative, use the [GitHub-Stats-Extended Wizard](https://
- ...and development time:
[![Harlok's WakaTime stats](https://github-stats-extended.vercel.app/api/wakatime?username=ffflabs&langs_count=6)](https://wakatime.com/@ffflabs)
[![Alan's WakaTime stats](https://github-stats-extended.vercel.app/api/wakatime?username=alan&langs_count=6)](https://wakatime.com/@alan)
- Pin more than 6 repos in your GitHub profile:
@@ -0,0 +1,33 @@
{
"name": "GitHub Readme Stats Dev",
"image": "mcr.microsoft.com/devcontainers/base:ubuntu",
"features": {
"ghcr.io/devcontainers/features/node:1": { "version": "22" }
},
"forwardPorts": [3000],
"portsAttributes": {
"3000": { "label": "HTTP" }
},
"appPort": [],
// Use 'postCreateCommand' to run commands after the container is created.
"postCreateCommand": "npm install -g vercel",
// Use 'postStartCommand' to run commands after the container is started.
"postStartCommand": "hostname dev && npm install",
// Configure tool-specific properties.
"customizations": {
"vscode": {
"extensions": [
"yzhang.markdown-all-in-one",
"esbenp.prettier-vscode",
"dbaeumer.vscode-eslint",
"github.vscode-github-actions"
]
}
},
"remoteUser": "root",
"privileged": true
}
@@ -31,7 +31,7 @@ jobs:
steps:
- name: Checkout repository
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
# Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL
@@ -1,7 +1,5 @@
.devcontainer
.github
.husky
.vscode
benchmarks
coverage
scripts
@@ -10,6 +8,4 @@ tests
**/*.md
**/*.svg
.eslintrc.json
.prettierignore
.pretterrc.json
codecov.yml
@@ -1,16 +1,17 @@
import { default as api } from "./api-renamed/index.js";
/* eslint-disable import-x/no-unresolved */
import { default as authenticate } from "./api-renamed/authenticate.js";
import { default as deleteUser } from "./api-renamed/delete-user.js";
import { default as downgrade } from "./api-renamed/downgrade.js";
import { default as gist } from "./api-renamed/gist.js";
import { default as api } from "./api-renamed/index.js";
import { default as pin } from "./api-renamed/pin.js";
import { default as topLangs } from "./api-renamed/top-langs.js";
import { default as wakatime } from "./api-renamed/wakatime.js";
import { default as wakatimeProxy } from "./api-renamed/wakatime-proxy.js";
import { default as repeatRecent } from "./api-renamed/repeat-recent.js";
import { default as patInfo } from "./api-renamed/status/pat-info.js";
import { default as statusUp } from "./api-renamed/status/up.js";
import { default as authenticate } from "./api-renamed/authenticate.js";
import { default as deleteUser } from "./api-renamed/delete-user.js";
import { default as topLangs } from "./api-renamed/top-langs.js";
import { default as userAccess } from "./api-renamed/user-access.js";
import { default as downgrade } from "./api-renamed/downgrade.js";
import { default as wakatimeProxy } from "./api-renamed/wakatime-proxy.js";
import { default as wakatime } from "./api-renamed/wakatime.js";
export default async (req, res) => {
// remaining code expects express.js-like request and response objects
@@ -2,4 +2,4 @@
"expiration": 39600,
"bypassToken": "r3fr3shT0k3n-r3fr3shT0k3n-r3fr3shT0k3n",
"passQuery": true
}
}
@@ -2,4 +2,4 @@
"expiration": 39600,
"bypassToken": "r3fr3shT0k3n-r3fr3shT0k3n-r3fr3shT0k3n",
"passQuery": true
}
}
@@ -2,4 +2,4 @@
"expiration": 39600,
"bypassToken": "r3fr3shT0k3n-r3fr3shT0k3n-r3fr3shT0k3n",
"passQuery": true
}
}
@@ -2,4 +2,4 @@
"expiration": 39600,
"bypassToken": "r3fr3shT0k3n-r3fr3shT0k3n-r3fr3shT0k3n",
"passQuery": true
}
}
@@ -2,4 +2,4 @@
"expiration": 39600,
"bypassToken": "r3fr3shT0k3n-r3fr3shT0k3n-r3fr3shT0k3n",
"passQuery": true
}
}
@@ -1,5 +1,5 @@
import { logger } from "../src/common/log.js";
import { deleteUser } from "../src/common/database.js";
import { logger } from "../src/common/log.js";
/**
* @param {any} req The request.
@@ -1,5 +1,6 @@
import { getUserAccessByKey, deleteUser } from "../src/common/database.js";
import axios from "axios";
import { deleteUser, getUserAccessByKey } from "../src/common/database.js";
import { logger } from "../src/common/log.js";
export default async (req, res) => {
@@ -53,10 +54,10 @@ export default async (req, res) => {
},
);
} catch (err) {
logger.error(err);
res.statusCode = 500;
res.send("Failed to delete GitHub authorization with private access");
return;
logger.error(err);
res.statusCode = 500;
res.send("Failed to delete GitHub authorization with private access");
return;
}
await deleteUser(user_key);
@@ -68,6 +69,9 @@ export default async (req, res) => {
}).toString();
res.statusCode = 302;
res.setHeader("Location", `https://github.com/login/oauth/authorize?${params}`);
res.setHeader(
"Location",
`https://github.com/login/oauth/authorize?${params}`,
);
res.end();
};
@@ -1,22 +1,22 @@
// @ts-check
import { renderError } from "../src/common/render.js";
import { isLocaleAvailable } from "../src/translations.js";
import { renderGistCard } from "../src/cards/gist.js";
import { fetchGist } from "../src/fetchers/gist.js";
import { storeRequest } from "../src/common/database.js";
import { guardAccess } from "../src/common/access.js";
import {
CACHE_TTL,
resolveCacheSeconds,
setCacheHeaders,
setErrorCacheHeaders,
} from "../src/common/cache.js";
import { guardAccess } from "../src/common/access.js";
import { storeRequest } from "../src/common/database.js";
import {
MissingParamError,
retrieveSecondaryMessage,
} from "../src/common/error.js";
import { parseBoolean } from "../src/common/ops.js";
import { renderError } from "../src/common/render.js";
import { fetchGist } from "../src/fetchers/gist.js";
import { isLocaleAvailable } from "../src/translations.js";
// @ts-ignore
export default async (req, res) => {
@@ -8,6 +8,7 @@ import {
setCacheHeaders,
setErrorCacheHeaders,
} from "../src/common/cache.js";
import { storeRequest } from "../src/common/database.js";
import {
MissingParamError,
retrieveSecondaryMessage,
@@ -16,7 +17,6 @@ import { parseArray, parseBoolean } from "../src/common/ops.js";
import { renderError } from "../src/common/render.js";
import { fetchStats } from "../src/fetchers/stats.js";
import { isLocaleAvailable } from "../src/translations.js";
import { storeRequest } from "../src/common/database.js";
// @ts-ignore
export default async (req, res) => {
@@ -87,26 +87,25 @@ export default async (req, res) => {
);
}
const safePattern = /^[-\w\/.,]+$/;
const safePattern = /^[-\w/.,]+$/;
if (
(username && !safePattern.test(username)) ||
(repo && !safePattern.test(repo)) ||
(owner && !safePattern.test(owner))
) {
return res.send(
renderError(
{
message: "Something went wrong",
secondaryMessage: "Username, repository or owner contains unsafe characters",
renderOptions: {
title_color,
text_color,
bg_color,
border_color,
theme,
},
renderError({
message: "Something went wrong",
secondaryMessage:
"Username, repository or owner contains unsafe characters",
renderOptions: {
title_color,
text_color,
bg_color,
border_color,
theme,
},
),
}),
);
}
@@ -124,7 +123,7 @@ export default async (req, res) => {
parseBoolean(include_all_commits),
parseArray(exclude_repo),
showStats.includes("prs_merged") ||
showStats.includes("prs_merged_percentage"),
showStats.includes("prs_merged_percentage"),
showStats.includes("discussions_started"),
showStats.includes("discussions_answered"),
parseInt(commits_year, 10),
@@ -147,7 +146,9 @@ export default async (req, res) => {
setCacheHeaders(res, cacheSeconds);
return res.send(
renderStatsCard(stats, {
renderStatsCard(
stats,
{
hide: parseArray(hide),
show_icons: parseBoolean(show_icons),
hide_title: parseBoolean(hide_title),
@@ -8,16 +8,15 @@ import {
setCacheHeaders,
setErrorCacheHeaders,
} from "../src/common/cache.js";
import { storeRequest } from "../src/common/database.js";
import {
MissingParamError,
retrieveSecondaryMessage,
} from "../src/common/error.js";
import { parseBoolean } from "../src/common/ops.js";
import { parseArray } from "../src/common/ops.js";
import { parseArray, parseBoolean } from "../src/common/ops.js";
import { renderError } from "../src/common/render.js";
import { fetchRepo } from "../src/fetchers/repo.js";
import { isLocaleAvailable } from "../src/translations.js";
import { storeRequest } from "../src/common/database.js";
// @ts-ignore
export default async (req, res) => {
@@ -78,7 +77,7 @@ export default async (req, res) => {
);
}
const safePattern = /^[-\w\/.,]+$/;
const safePattern = /^[-\w/.,]+$/;
if (
(username && !safePattern.test(username)) ||
(repo && !safePattern.test(repo))
@@ -8,8 +8,8 @@
*/
import { request } from "../../src/common/http.js";
import retryer from "../../src/common/retryer.js";
import { logger } from "../../src/common/log.js";
import { default as retryer } from "../../src/common/retryer.js";
export const RATE_LIMIT_SECONDS = 60 * 3; // 1 request per 3 minutes
@@ -8,6 +8,7 @@ import {
setCacheHeaders,
setErrorCacheHeaders,
} from "../src/common/cache.js";
import { storeRequest } from "../src/common/database.js";
import {
MissingParamError,
retrieveSecondaryMessage,
@@ -16,7 +17,6 @@ import { parseArray, parseBoolean } from "../src/common/ops.js";
import { renderError } from "../src/common/render.js";
import { fetchTopLanguages } from "../src/fetchers/top-languages.js";
import { isLocaleAvailable } from "../src/translations.js";
import { storeRequest } from "../src/common/database.js";
// @ts-ignore
export default async (req, res) => {
@@ -29,6 +29,7 @@ export default async (req, res) => {
title_color,
text_color,
bg_color,
prog_bar_bg_color,
theme,
cache_seconds,
layout,
@@ -147,6 +148,7 @@ export default async (req, res) => {
title_color,
text_color,
bg_color,
prog_bar_bg_color,
theme,
layout,
langs_count,
@@ -1,5 +1,5 @@
import { logger } from "../src/common/log.js";
import { getUserAccessByKey } from "../src/common/database.js";
import { logger } from "../src/common/log.js";
/**
* @param {any} req The request.
@@ -18,7 +18,7 @@ export default async (req, res) => {
res.send({
privateAccess: result.privateAccess,
token: result.token
token: result.token,
});
} catch (err) {
logger.error(err);
@@ -1,5 +1,5 @@
import { fetchWakatimeStats } from "../src/fetchers/wakatime.js";
import { logger } from "../src/common/log.js";
import { fetchWakatimeStats } from "../src/fetchers/wakatime.js";
/**
* @param {any} req The request.
@@ -1,22 +1,22 @@
// @ts-check
import { renderWakatimeCard } from "../src/cards/wakatime.js";
import { renderError } from "../src/common/render.js";
import { fetchWakatimeStats } from "../src/fetchers/wakatime.js";
import { isLocaleAvailable } from "../src/translations.js";
import { storeRequest } from "../src/common/database.js";
import { guardAccess } from "../src/common/access.js";
import {
CACHE_TTL,
resolveCacheSeconds,
setCacheHeaders,
setErrorCacheHeaders,
} from "../src/common/cache.js";
import { guardAccess } from "../src/common/access.js";
import { storeRequest } from "../src/common/database.js";
import {
MissingParamError,
retrieveSecondaryMessage,
} from "../src/common/error.js";
import { parseArray, parseBoolean } from "../src/common/ops.js";
import { renderError } from "../src/common/render.js";
import { fetchWakatimeStats } from "../src/fetchers/wakatime.js";
import { isLocaleAvailable } from "../src/translations.js";
// @ts-ignore
export default async (req, res) => {
@@ -1,11 +1,11 @@
import "dotenv/config";
import statsCard from "./api/index.js";
import repoCard from "./api/pin.js";
import langCard from "./api/top-langs.js";
import wakatimeCard from "./api/wakatime.js";
import gistCard from "./api/gist.js";
import express from "express";
import gistCard from "./api-renamed/gist.js";
import statsCard from "./api-renamed/index.js";
import repoCard from "./api-renamed/pin.js";
import langCard from "./api-renamed/top-langs.js";
import wakatimeCard from "./api-renamed/wakatime.js";
const app = express();
const router = express.Router();
+52
View File
@@ -0,0 +1,52 @@
{
"name": "github-readme-stats",
"version": "1.0.0",
"description": "Dynamically generate stats for your GitHub readme",
"keywords": [
"github-readme-stats",
"readme-stats",
"cards",
"card-generator"
],
"main": "src/index.js",
"type": "module",
"homepage": "https://github-stats-extended.vercel.app/frontend",
"bugs": {
"url": "https://github.com/stats-organization/github-stats-extended/issues"
},
"repository": {
"type": "git",
"url": "https://github.com/stats-organization/github-stats-extended.git"
},
"scripts": {
"test": "vitest",
"test:update:snapshot": "vitest -u",
"test:e2e": "vitest --config vitest.config.e2e.ts",
"theme-readme-gen": "node scripts/generate-theme-doc",
"generate-langs-json": "node scripts/generate-langs-json",
"bench": "vitest bench --run --config vitest.config.bench.ts"
},
"author": "Anurag Hazra",
"license": "MIT",
"devDependencies": {
"@testing-library/dom": "^10.4.1",
"@testing-library/jest-dom": "^6.9.1",
"@uppercod/css-to-object": "^1.1.1",
"@vitest/coverage-v8": "catalog:default",
"axios-mock-adapter": "^2.1.0",
"express": "^5.2.1",
"js-yaml": "^4.1.1",
"jsdom": "28.1.0",
"vitest": "catalog:default"
},
"dependencies": {
"axios": "^1.13.5",
"emoji-name-map": "^2.0.3",
"github-username-regex": "^1.0.0",
"pg": "^8.18.0",
"word-wrap": "^1.2.5"
},
"engines": {
"node": "24.x"
}
}

Before

Width:  |  Height:  |  Size: 5.2 KiB

After

Width:  |  Height:  |  Size: 5.2 KiB

@@ -0,0 +1,30 @@
import fs from "fs";
import axios from "axios";
import { default as jsYaml } from "js-yaml";
import * as prettier from "prettier";
const LANGS_FILEPATH = "./src/common/languageColors.json";
// Retrieve languages from github linguist repository yaml file
const response = await axios.get(
"https://raw.githubusercontent.com/github/linguist/master/lib/linguist/languages.yml",
);
// and convert them to a JS Object
const languages = jsYaml.load(response.data);
const languageColors = {};
// Filter only language colors from the whole file
Object.keys(languages).forEach((lang) => {
languageColors[lang] = languages[lang].color;
});
// Debug Print
// console.dir(languageColors);
const jsonString = JSON.stringify(languageColors);
fs.writeFileSync(
LANGS_FILEPATH,
await prettier.format(jsonString, { parser: "json" }),
);
@@ -1,4 +1,5 @@
import fs from "fs";
import { themes } from "../themes/index.js";
const TARGET_FILE = "./themes/README.md";
@@ -84,4 +84,3 @@ function calculateRank({
}
export { calculateRank };
export default calculateRank;
@@ -1,19 +1,18 @@
// @ts-check
import {
measureText,
flexLayout,
iconWithLabel,
createLanguageNode,
} from "../common/render.js";
import Card from "../common/Card.js";
import { default as Card } from "../common/Card.js";
import { getCardColors } from "../common/color.js";
import { kFormatter, wrapTextMultiline } from "../common/fmt.js";
import { encodeHTML } from "../common/html.js";
import { icons } from "../common/icons.js";
import languageColors from "../common/languageColors.json" with { type: "json" };
import { parseEmojis } from "../common/ops.js";
import {
createLanguageNode,
flexLayout,
iconWithLabel,
measureText,
} from "../common/render.js";
const ICON_SIZE = 16;
const CARD_DEFAULT_WIDTH = 400;
@@ -139,5 +138,4 @@ const renderGistCard = (gistData, options = {}) => {
`);
};
export { renderGistCard, HEADER_MAX_LENGTH };
export default renderGistCard;
export { renderGistCard };
@@ -1,20 +1,20 @@
// @ts-check
import { Card } from "../common/Card.js";
import { I18n } from "../common/I18n.js";
import { getCardColors } from "../common/color.js";
import { kFormatter, wrapTextMultiline } from "../common/fmt.js";
import { encodeHTML } from "../common/html.js";
import { I18n } from "../common/I18n.js";
import { icons } from "../common/icons.js";
import { clampValue, parseEmojis } from "../common/ops.js";
import { buildSearchFilter } from "../common/ops.js";
import { buildSearchFilter, clampValue, parseEmojis } from "../common/ops.js";
import {
flexLayout,
measureText,
iconWithLabel,
createLanguageNode,
flexLayout,
iconWithLabel,
measureText,
} from "../common/render.js";
import { repoCardLocales } from "../translations.js";
import { createTextNode } from "./stats.js";
const ICON_SIZE = 16;
@@ -322,4 +322,3 @@ const renderRepoCard = (repo, options = {}) => {
};
export { renderRepoCard };
export default renderRepoCard;
@@ -1,13 +1,12 @@
// @ts-check
import { Card } from "../common/Card.js";
import { I18n } from "../common/I18n.js";
import { getCardColors } from "../common/color.js";
import { CustomError } from "../common/error.js";
import { kFormatter } from "../common/fmt.js";
import { I18n } from "../common/I18n.js";
import { icons, rankIcon } from "../common/icons.js";
import { clampValue } from "../common/ops.js";
import { buildSearchFilter } from "../common/ops.js";
import { buildSearchFilter, clampValue } from "../common/ops.js";
import { flexLayout, measureText } from "../common/render.js";
import { statCardLocales, wakatimeCardLocales } from "../translations.js";
@@ -672,4 +671,3 @@ const renderStatsCard = (
};
export { renderStatsCard, createTextNode };
export default renderStatsCard;
@@ -1,9 +1,9 @@
// @ts-check
import { Card } from "../common/Card.js";
import { I18n } from "../common/I18n.js";
import { getCardColors } from "../common/color.js";
import { formatBytes } from "../common/fmt.js";
import { I18n } from "../common/I18n.js";
import { chunkArray, clampValue, lowercaseTrim } from "../common/ops.js";
import {
createProgressNode,
@@ -216,6 +216,7 @@ const getDisplayValue = (size, percentages, format) => {
* @param {object} props Function properties.
* @param {number} props.width The card width
* @param {string} props.color Color of the programming language.
* @param {string} props.progBarBgColor Color of the background of progress bar.
* @param {string} props.name Name of the programming language.
* @param {number} props.size Size of the programming language.
* @param {number} props.totalSize Total size of all languages.
@@ -226,6 +227,7 @@ const getDisplayValue = (size, percentages, format) => {
const createProgressTextNode = ({
width,
color,
progBarBgColor,
name,
size,
totalSize,
@@ -250,7 +252,9 @@ const createProgressTextNode = ({
color,
width: progressWidth,
progress,
progressBarBackgroundColor: "#ddd",
progressBarBackgroundColor: progBarBgColor
? `#${progBarBgColor}`
: "#ddd",
delay: staggerDelay + 300,
})}
</g>
@@ -367,10 +371,17 @@ const createDonutLanguagesNode = ({ langs, totalSize, statsFormat }) => {
* @param {Lang[]} langs Array of programming languages.
* @param {number} width Card width.
* @param {number} totalLanguageSize Total size of all languages.
* @param progBarBgColor Color of the background of progress bar.
* @param {string} statsFormat Stats format.
* @returns {string} Normal layout card SVG object.
*/
const renderNormalLayout = (langs, width, totalLanguageSize, statsFormat) => {
const renderNormalLayout = (
langs,
width,
totalLanguageSize,
progBarBgColor,
statsFormat,
) => {
return flexLayout({
items: langs.map((lang, index) => {
return createProgressTextNode({
@@ -381,6 +392,7 @@ const renderNormalLayout = (langs, width, totalLanguageSize, statsFormat) => {
totalSize: totalLanguageSize,
statsFormat,
index,
progBarBgColor,
});
}),
gap: 40,
@@ -787,6 +799,7 @@ const renderTopLanguages = (topLangs, options = {}) => {
title_color,
text_color,
bg_color,
prog_bar_bg_color,
hide,
hide_progress,
theme,
@@ -872,6 +885,7 @@ const renderTopLanguages = (topLangs, options = {}) => {
langs,
width,
totalLanguageSize,
prog_bar_bg_color,
stats_format,
);
}
@@ -1,7 +1,7 @@
type ThemeNames = keyof typeof import("../../themes/index.js");
type RankIcon = "default" | "github" | "percentile";
export type CommonOptions = {
type CommonOptions = {
title_color: string;
icon_color: string;
text_color: string;
@@ -53,6 +53,7 @@ export type TopLangOptions = CommonOptions & {
langs_count: number;
disable_animations: boolean;
hide_progress: boolean;
prog_bar_bg_color: string;
stats_format: "percentages" | "bytes";
};
@@ -1,12 +1,12 @@
// @ts-check
import { Card } from "../common/Card.js";
import { getCardColors } from "../common/color.js";
import { I18n } from "../common/I18n.js";
import { getCardColors } from "../common/color.js";
import languageColors from "../common/languageColors.json" with { type: "json" };
import { clampValue, lowercaseTrim } from "../common/ops.js";
import { createProgressNode, flexLayout } from "../common/render.js";
import { wakatimeCardLocales } from "../translations.js";
import languageColors from "../common/languageColors.json" with { type: "json" };
const DEFAULT_CARD_WIDTH = 495;
const MIN_CARD_WIDTH = 250;
@@ -378,7 +378,8 @@ const renderWakatimeCard = (stats = {}, options = { hide: [] }) => {
// @ts-ignore
progressBarColor: titleColor,
// @ts-ignore
progressBarBackgroundColor: textColor === titleColor ? bgColor : textColor,
progressBarBackgroundColor:
textColor === titleColor ? bgColor : textColor,
hideProgress: hide_progress,
progressBarWidth: normalizedWidth - TOTAL_TEXT_WIDTH,
});
@@ -468,4 +469,3 @@ const renderWakatimeCard = (stats = {}, options = { hide: [] }) => {
};
export { renderWakatimeCard };
export default renderWakatimeCard;
@@ -40,4 +40,3 @@ class I18n {
}
export { I18n };
export default I18n;
@@ -1,8 +1,8 @@
// @ts-check
import { renderError } from "./render.js";
import { blacklist } from "./blacklist.js";
import { whitelist, gistWhitelist } from "./envs.js";
import { gistWhitelist, whitelist } from "./envs.js";
import { renderError } from "./render.js";
const NOT_WHITELISTED_USERNAME_MESSAGE = "This username is not whitelisted";
const NOT_WHITELISTED_GIST_MESSAGE = "This gist ID is not whitelisted";
@@ -7,4 +7,3 @@ const blacklist = [
];
export { blacklist };
export default blacklist;
@@ -83,7 +83,8 @@ const getCardColors = ({
theme,
}) => {
const defaultTheme = themes["default"];
const isThemeProvided = theme !== null && theme !== undefined && theme in themes;
const isThemeProvided =
theme !== null && theme !== undefined && theme in themes;
// @ts-ignore
const selectedTheme = isThemeProvided ? themes[theme] : defaultTheme;
@@ -141,4 +142,4 @@ const getCardColors = ({
return { titleColor, iconColor, textColor, bgColor, borderColor, ringColor };
};
export { isValidHexColor, isValidGradient, getCardColors };
export { getCardColors };
+9
View File
@@ -0,0 +1,9 @@
// @ts-check
/**
* Valid owner affiliations for GitHub API queries.
* @type {Array<string>}
*/
const OWNER_AFFILIATIONS = ["OWNER", "COLLABORATOR", "ORGANIZATION_MEMBER"];
export { OWNER_AFFILIATIONS };
@@ -1,5 +1,8 @@
import pkg from "pg";
const { Pool } = pkg;
/**
* In the browser this has to be mocked to avoid runtime errors
* @see apps/frontend/vite.config.ts
*/
import { Pool } from "pg";
export const pool = process.env.POSTGRES_URL
? new Pool({
@@ -68,16 +71,16 @@ export async function storeRequest(req) {
}
/**
* Deletes all requests older than 8 days from the database.
* Deletes all requests older than the specified time from the database.
*/
export async function deleteOldRequests() {
export async function deleteOldRequests(interval) {
if (!pool) {
return;
}
const deleteQuery = `
DELETE FROM requests
WHERE user_requested_at < NOW() - INTERVAL '8 days'
WHERE user_requested_at < NOW() - INTERVAL '${interval}'
`;
try {
let result = await pool.query(deleteQuery);
@@ -92,11 +95,11 @@ export async function deleteOldRequests() {
}
/**
* Fetches all requests which are between 11 hours and 8 days old.
* Fetches all requests which are between minInterval and maxInterval old.
*
* @returns {Promise<string[]>} Array of all requests between 11 hours and 8 days old.
* @returns {Promise<string[]>} Array of all requests between minInterval and maxInterval old.
*/
export async function getRecentRequests() {
export async function getRecentRequests(minInterval, maxInterval) {
if (!pool) {
return [];
}
@@ -104,8 +107,8 @@ export async function getRecentRequests() {
const query = `
SELECT request
FROM requests
WHERE requested_at >= NOW() - INTERVAL '8 days'
AND requested_at < NOW() - INTERVAL '11 hours'
WHERE requested_at >= NOW() - INTERVAL '${maxInterval}'
AND requested_at < NOW() - INTERVAL '${minInterval}'
ORDER BY requested_at ASC
`;
let rows;
@@ -215,7 +218,7 @@ export async function getUserAccessByKey(userKey) {
}
return {
token: rows[0].access_token,
privateAccess: rows[0].private_access
privateAccess: rows[0].private_access,
};
} catch (err) {
if (err.code === "42P01") {
@@ -250,7 +253,7 @@ export async function getUserAccessByName(userName) {
}
return {
token: rows[0].access_token,
privateAccess: rows[0].private_access
privateAccess: rows[0].private_access,
};
} catch (err) {
if (err.code === "42P01") {
@@ -1,6 +1,6 @@
// @ts-check
import { OWNER_AFFILIATIONS } from "./ops.js";
import { OWNER_AFFILIATIONS } from "./constants.js";
/**
* @type {string} A general message to ask user to try again later.
@@ -1,6 +1,7 @@
// @ts-check
import wrap from "word-wrap";
import { encodeHTML } from "./html.js";
/**
@@ -9,11 +9,14 @@
* @returns {string} Encoded string.
*/
const encodeHTML = (str) => {
return str
.replace(/[\u00A0-\u9999<>&](?!#)/gim, (i) => {
return "&#" + i.charCodeAt(0) + ";";
})
.replace(/\u0008/gim, "");
return (
str
.replace(/[\u00A0-\u9999<>&](?!#)/gim, (i) => {
return "&#" + i.charCodeAt(0) + ";";
})
// eslint-disable-next-line no-control-regex
.replace(/\u0008/gim, "")
);
};
export { encodeHTML };
@@ -53,4 +53,3 @@ const rankIcon = (rankIcon, rankLevel, percentile) => {
};
export { icons, rankIcon };
export default icons;
+663
View File
@@ -0,0 +1,663 @@
{
"1C Enterprise": "#814CCC",
"2-Dimensional Array": "#38761D",
"4D": "#004289",
"ABAP": "#E8274B",
"ABAP CDS": "#555e25",
"AGS Script": "#B9D9FF",
"AIDL": "#34EB6B",
"AL": "#3AA2B5",
"ALGOL": "#D1E0DB",
"AMPL": "#E6EFBB",
"ANTLR": "#9DC3FF",
"API Blueprint": "#2ACCA8",
"APL": "#5A8164",
"ASP.NET": "#9400ff",
"ATS": "#1ac620",
"ActionScript": "#882B0F",
"Ada": "#02f88c",
"Adblock Filter List": "#800000",
"Adobe Font Metrics": "#fa0f00",
"Agda": "#315665",
"Aiken": "#640ff8",
"Alloy": "#64C800",
"Alpine Abuild": "#0D597F",
"Altium Designer": "#A89663",
"AngelScript": "#C7D7DC",
"Answer Set Programming": "#A9CC29",
"Ant Build System": "#A9157E",
"Antlers": "#ff269e",
"ApacheConf": "#d12127",
"Apex": "#1797c0",
"Apollo Guidance Computer": "#0B3D91",
"AppleScript": "#101F1F",
"Arc": "#aa2afe",
"AsciiDoc": "#73a0c5",
"AspectJ": "#a957b0",
"Assembly": "#6E4C13",
"Astro": "#ff5a03",
"Asymptote": "#ff0000",
"Augeas": "#9CC134",
"AutoHotkey": "#6594b9",
"AutoIt": "#1C3552",
"Avro IDL": "#0040FF",
"Awk": "#c30e9b",
"B (Formal Method)": "#8aa8c5",
"B4X": "#00e4ff",
"BASIC": "#ff0000",
"BQN": "#2b7067",
"Ballerina": "#FF5000",
"Batchfile": "#C1F12E",
"Beef": "#a52f4e",
"Berry": "#15A13C",
"BibTeX": "#778899",
"Bicep": "#519aba",
"Bikeshed": "#5562ac",
"Bison": "#6A463F",
"BitBake": "#00bce4",
"Blade": "#f7523f",
"BlitzBasic": "#00FFAE",
"BlitzMax": "#cd6400",
"Bluespec": "#12223c",
"Bluespec BH": "#12223c",
"Boo": "#d4bec1",
"Boogie": "#c80fa0",
"Brainfuck": "#2F2530",
"BrighterScript": "#66AABB",
"Brightscript": "#662D91",
"Browserslist": "#ffd539",
"Bru": "#F4AA41",
"BuildStream": "#006bff",
"C": "#555555",
"C#": "#178600",
"C++": "#f34b7d",
"C3": "#2563eb",
"CAP CDS": "#0092d1",
"CLIPS": "#00A300",
"CMake": "#DA3434",
"COLLADA": "#F1A42B",
"CQL": "#006091",
"CSON": "#244776",
"CSS": "#663399",
"CSV": "#237346",
"CUE": "#5886E1",
"CWeb": "#00007a",
"Cabal Config": "#483465",
"Caddyfile": "#22b638",
"Cadence": "#00ef8b",
"Cairo": "#ff4a48",
"Cairo Zero": "#ff4a48",
"CameLIGO": "#3be133",
"Cangjie": "#00868B",
"Cap'n Proto": "#c42727",
"Carbon": "#222222",
"Ceylon": "#dfa535",
"Chapel": "#8dc63f",
"ChucK": "#3f8000",
"Circom": "#707575",
"Cirru": "#ccccff",
"Clarion": "#db901e",
"Clarity": "#5546ff",
"Classic ASP": "#6a40fd",
"Clean": "#3F85AF",
"Click": "#E4E6F3",
"Clojure": "#db5855",
"Closure Templates": "#0d948f",
"Cloud Firestore Security Rules": "#FFA000",
"Clue": "#0009b5",
"CodeQL": "#140f46",
"CoffeeScript": "#244776",
"ColdFusion": "#ed2cd6",
"ColdFusion CFC": "#ed2cd6",
"Common Lisp": "#3fb68b",
"Common Workflow Language": "#B5314C",
"Component Pascal": "#B0CE4E",
"Cooklang": "#E15A29",
"Crystal": "#000100",
"Csound": "#1a1a1a",
"Csound Document": "#1a1a1a",
"Csound Score": "#1a1a1a",
"Cuda": "#3A4E3A",
"Curry": "#531242",
"Cylc": "#00b3fd",
"Cypher": "#34c0eb",
"Cython": "#fedf5b",
"D": "#ba595e",
"D2": "#526ee8",
"DM": "#447265",
"Dafny": "#FFEC25",
"Darcs Patch": "#8eff23",
"Dart": "#00B4AB",
"Daslang": "#d3d3d3",
"DataWeave": "#003a52",
"Debian Package Control File": "#D70751",
"DenizenScript": "#FBEE96",
"Dhall": "#dfafff",
"DirectX 3D File": "#aace60",
"Dockerfile": "#384d54",
"Dogescript": "#cca760",
"Dotenv": "#e5d559",
"Dune": "#89421e",
"Dylan": "#6c616e",
"E": "#ccce35",
"ECL": "#8a1267",
"ECLiPSe": "#001d9d",
"EJS": "#a91e50",
"EQ": "#a78649",
"Earthly": "#2af0ff",
"Easybuild": "#069406",
"Ecere Projects": "#913960",
"Ecmarkup": "#eb8131",
"Edge": "#0dffe0",
"EdgeQL": "#31A7FF",
"EditorConfig": "#fff1f2",
"Eiffel": "#4d6977",
"Elixir": "#6e4a7e",
"Elm": "#60B5CC",
"Elvish": "#55BB55",
"Elvish Transcript": "#55BB55",
"Emacs Lisp": "#c065db",
"EmberScript": "#FFF4F3",
"Erlang": "#B83998",
"Euphoria": "#FF790B",
"F#": "#b845fc",
"F*": "#572e30",
"FIGlet Font": "#FFDDBB",
"FIRRTL": "#2f632f",
"FLUX": "#88ccff",
"Factor": "#636746",
"Fancy": "#7b9db4",
"Fantom": "#14253c",
"Faust": "#c37240",
"Fennel": "#fff3d7",
"Filebench WML": "#F6B900",
"Flix": "#d44a45",
"Fluent": "#ffcc33",
"Forth": "#341708",
"Fortran": "#4d41b1",
"Fortran Free Form": "#4d41b1",
"FreeBASIC": "#141AC9",
"FreeMarker": "#0050b2",
"Frege": "#00cafe",
"Futhark": "#5f021f",
"G-code": "#D08CF2",
"GAML": "#FFC766",
"GAMS": "#f49a22",
"GAP": "#0000cc",
"GCC Machine Description": "#FFCFAB",
"GDScript": "#355570",
"GDShader": "#478CBF",
"GEDCOM": "#003058",
"GLSL": "#5686a5",
"GSC": "#FF6800",
"Game Maker Language": "#71b417",
"Gemfile.lock": "#701516",
"Gemini": "#ff6900",
"Genero 4gl": "#63408e",
"Genero per": "#d8df39",
"Genie": "#fb855d",
"Genshi": "#951531",
"Gentoo Ebuild": "#9400ff",
"Gentoo Eclass": "#9400ff",
"Gerber Image": "#d20b00",
"Gherkin": "#5B2063",
"Git Attributes": "#F44D27",
"Git Commit": "#F44D27",
"Git Config": "#F44D27",
"Git Revision List": "#F44D27",
"Gleam": "#ffaff3",
"Glimmer JS": "#F5835F",
"Glimmer TS": "#3178c6",
"Glyph": "#c1ac7f",
"Gnuplot": "#f0a9f0",
"Go": "#00ADD8",
"Go Checksums": "#00ADD8",
"Go Module": "#00ADD8",
"Go Template": "#00ADD8",
"Go Workspace": "#00ADD8",
"Godot Resource": "#355570",
"Golo": "#88562A",
"Gosu": "#82937f",
"Grace": "#615f8b",
"Gradle": "#02303a",
"Gradle Kotlin DSL": "#02303a",
"Grammatical Framework": "#ff0000",
"GraphQL": "#e10098",
"Graphviz (DOT)": "#2596be",
"Groovy": "#4298b8",
"Groovy Server Pages": "#4298b8",
"HAProxy": "#106da9",
"HCL": "#844FBA",
"HIP": "#4F3A4F",
"HLSL": "#aace60",
"HOCON": "#9ff8ee",
"HTML": "#e34c26",
"HTML+ECR": "#2e1052",
"HTML+EEX": "#6e4a7e",
"HTML+ERB": "#701516",
"HTML+PHP": "#4f5d95",
"HTML+Razor": "#512be4",
"HTTP": "#005C9C",
"HXML": "#f68712",
"Hack": "#878787",
"Haml": "#ece2a9",
"Handlebars": "#f7931e",
"Harbour": "#0e60e3",
"Hare": "#9d7424",
"Haskell": "#5e5086",
"Haxe": "#df7900",
"HiveQL": "#dce200",
"HolyC": "#ffefaf",
"Hosts File": "#308888",
"Hurl": "#FF0288",
"Hy": "#7790B2",
"IDL": "#a3522f",
"IGOR Pro": "#0000cc",
"INI": "#d1dbe0",
"ISPC": "#2D68B1",
"Idris": "#b30000",
"Ignore List": "#000000",
"ImageJ Macro": "#99AAFF",
"Imba": "#16cec6",
"Inno Setup": "#264b99",
"Io": "#a9188d",
"Ioke": "#078193",
"Isabelle": "#FEFE00",
"Isabelle ROOT": "#FEFE00",
"J": "#9EEDFF",
"JAR Manifest": "#b07219",
"JCL": "#d90e09",
"JFlex": "#DBCA00",
"JSON": "#292929",
"JSON with Comments": "#292929",
"JSON5": "#267CB9",
"JSONLD": "#0c479c",
"JSONiq": "#40d47e",
"Jac": "#FC792D",
"Jai": "#ab8b4b",
"Janet": "#0886a5",
"Jasmin": "#d03600",
"Java": "#b07219",
"Java Properties": "#2A6277",
"Java Server Pages": "#2A6277",
"Java Template Engine": "#2A6277",
"JavaScript": "#f1e05a",
"JavaScript+ERB": "#f1e05a",
"Jest Snapshot": "#15c213",
"JetBrains MPS": "#21D789",
"Jinja": "#a52a22",
"Jison": "#56b3cb",
"Jison Lex": "#56b3cb",
"Jolie": "#843179",
"Jsonnet": "#0064bd",
"Julia": "#a270ba",
"Julia REPL": "#a270ba",
"Jupyter Notebook": "#DA5B0B",
"Just": "#384d54",
"KCL": "#7ABABF",
"KDL": "#ffb3b3",
"KFramework": "#4195c5",
"KRL": "#28430A",
"Kaitai Struct": "#773b37",
"KakouneScript": "#6f8042",
"KerboScript": "#41adf0",
"KiCad Layout": "#2f4aab",
"KiCad Legacy Layout": "#2f4aab",
"KiCad Schematic": "#2f4aab",
"KoLmafia ASH": "#B9D9B9",
"Koka": "#215166",
"Kotlin": "#A97BFF",
"LFE": "#4C3023",
"LLVM": "#185619",
"LOLCODE": "#cc9900",
"LSL": "#3d9970",
"LabVIEW": "#fede06",
"Lambdapi": "#8027a3",
"Langium": "#2c8c87",
"Lark": "#2980B9",
"Lasso": "#999999",
"Latte": "#f2a542",
"Leo": "#C4FFC2",
"Less": "#1d365d",
"Lex": "#DBCA00",
"LigoLANG": "#0e74ff",
"LilyPond": "#9ccc7c",
"Liquid": "#67b8de",
"Literate Agda": "#315665",
"Literate CoffeeScript": "#244776",
"Literate Haskell": "#5e5086",
"LiveCode Script": "#0c5ba5",
"LiveScript": "#499886",
"Logtalk": "#295b9a",
"LookML": "#652B81",
"Lua": "#000080",
"Luau": "#00A2FF",
"M3U": "#179C7D",
"MATLAB": "#e16737",
"MAXScript": "#00a6a6",
"MDX": "#fcb32c",
"MLIR": "#5EC8DB",
"MQL4": "#62A8D6",
"MQL5": "#4A76B8",
"MTML": "#b7e1f4",
"Macaulay2": "#d8ffff",
"Makefile": "#427819",
"Mako": "#7e858d",
"Markdown": "#083fa1",
"Marko": "#42bff2",
"Mask": "#f97732",
"Mathematical Programming System": "#0530ad",
"Max": "#c4a79c",
"Mercury": "#ff2b2b",
"Mermaid": "#ff3670",
"Meson": "#007800",
"Metal": "#8f14e9",
"MiniYAML": "#ff1111",
"MiniZinc": "#06a9e6",
"Mint": "#02b046",
"Mirah": "#c7a938",
"Modelica": "#de1d31",
"Modula-2": "#10253f",
"Modula-3": "#223388",
"Mojo": "#ff4c1f",
"Monkey C": "#8D6747",
"MoonBit": "#b92381",
"MoonScript": "#ff4585",
"Motoko": "#fbb03b",
"Motorola 68K Assembly": "#005daa",
"Move": "#4a137a",
"Mustache": "#724b3b",
"NCL": "#28431f",
"NMODL": "#00356B",
"NPM Config": "#cb3837",
"NWScript": "#111522",
"Nasal": "#1d2c4e",
"Nearley": "#990000",
"Nemerle": "#3d3c6e",
"NetLinx": "#0aa0ff",
"NetLinx+ERB": "#747faa",
"NetLogo": "#ff6375",
"NewLisp": "#87AED7",
"Nextflow": "#3ac486",
"Nginx": "#009639",
"Nickel": "#E0C3FC",
"Nim": "#ffc200",
"Nit": "#009917",
"Nix": "#7e7eff",
"Noir": "#2f1f49",
"Nu": "#c9df40",
"NumPy": "#9C8AF9",
"Nunjucks": "#3d8137",
"Nushell": "#4E9906",
"OASv2-json": "#85ea2d",
"OASv2-yaml": "#85ea2d",
"OASv3-json": "#85ea2d",
"OASv3-yaml": "#85ea2d",
"OCaml": "#ef7a08",
"OMNeT++ MSG": "#a0e0a0",
"OMNeT++ NED": "#08607c",
"ObjectScript": "#424893",
"Objective-C": "#438eff",
"Objective-C++": "#6866fb",
"Objective-J": "#ff0c5a",
"Odin": "#60AFFE",
"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",
"OverpassQL": "#cce2aa",
"Oxygene": "#cdd0e3",
"Oz": "#fab738",
"P4": "#7055b5",
"PDDL": "#0d00ff",
"PEG.js": "#234d6b",
"PHP": "#4F5D95",
"PLSQL": "#dad8d8",
"PLpgSQL": "#336790",
"POV-Ray SDL": "#6bac65",
"Pact": "#F7A8B8",
"Pan": "#cc0000",
"Papyrus": "#6600cc",
"Parrot": "#f3ca0a",
"Pascal": "#E3F171",
"Pawn": "#dbb284",
"Pep8": "#C76F5B",
"Perl": "#0298c3",
"PicoLisp": "#6067af",
"PigLatin": "#fcd7de",
"Pike": "#005390",
"Pip Requirements": "#FFD343",
"Pkl": "#6b9543",
"PlantUML": "#fbbd16",
"PogoScript": "#d80074",
"Polar": "#ae81ff",
"Portugol": "#f8bd00",
"PostCSS": "#dc3a0c",
"PostScript": "#da291c",
"PowerBuilder": "#8f0f8d",
"PowerShell": "#012456",
"Praat": "#c8506d",
"Prisma": "#0c344b",
"Processing": "#0096D8",
"Procfile": "#3B2F63",
"Prolog": "#74283c",
"Promela": "#de0000",
"Propeller Spin": "#7fa2a7",
"Pug": "#a86454",
"Puppet": "#302B6D",
"PureBasic": "#5a6986",
"PureScript": "#1D222D",
"Pyret": "#ee1e10",
"Python": "#3572A5",
"Python console": "#3572A5",
"Python traceback": "#3572A5",
"Q#": "#fed659",
"QML": "#44a51c",
"Qt Script": "#00b841",
"Quake": "#882233",
"QuakeC": "#975777",
"QuickBASIC": "#008080",
"R": "#198CE7",
"RAML": "#77d9fb",
"RAScript": "#2C97FA",
"RBS": "#701516",
"RDoc": "#701516",
"REXX": "#d90e09",
"RMarkdown": "#198ce7",
"RON": "#a62c00",
"ROS Interface": "#22314e",
"RPGLE": "#2BDE21",
"RUNOFF": "#665a4e",
"Racket": "#3c5caa",
"Ragel": "#9d5200",
"Raku": "#0000fb",
"Rascal": "#fffaa0",
"ReScript": "#ed5051",
"Reason": "#ff5847",
"ReasonLIGO": "#ff5847",
"Rebol": "#358a5b",
"Record Jar": "#0673ba",
"Red": "#f50000",
"Regular Expression": "#009a00",
"Ren'Py": "#ff7f7f",
"Rez": "#FFDAB3",
"Ring": "#2D54CB",
"Riot": "#A71E49",
"RobotFramework": "#00c0b5",
"Roc": "#7c38f5",
"Rocq Prover": "#d0b68c",
"Roff": "#ecdebe",
"Roff Manpage": "#ecdebe",
"Rouge": "#cc0088",
"RouterOS Script": "#DE3941",
"Ruby": "#701516",
"Rust": "#dea584",
"SAS": "#B34936",
"SCSS": "#c6538c",
"SPARQL": "#0C4597",
"SQF": "#3F3F3F",
"SQL": "#e38c00",
"SQLPL": "#e38c00",
"SRecode Template": "#348a34",
"STL": "#373b5e",
"SVG": "#ff9900",
"Sail": "#259dd5",
"SaltStack": "#646464",
"Sass": "#a53b70",
"Scala": "#c22d40",
"Scaml": "#bd181a",
"Scenic": "#fdc700",
"Scheme": "#1e4aec",
"Scilab": "#ca0f21",
"Self": "#0579aa",
"ShaderLab": "#222c37",
"Shell": "#89e051",
"ShellCheck Config": "#cecfcb",
"Shen": "#120F14",
"Simple File Verification": "#C9BFED",
"Singularity": "#64E6AD",
"Slang": "#1fbec9",
"Slash": "#007eff",
"Slice": "#003fa2",
"Slim": "#2b2b2b",
"Slint": "#2379F4",
"SmPL": "#c94949",
"Smalltalk": "#596706",
"Smarty": "#f0c040",
"Smithy": "#c44536",
"Snakemake": "#419179",
"Solidity": "#AA6746",
"SourcePawn": "#f69e1d",
"Squirrel": "#800000",
"Stan": "#b2011d",
"Standard ML": "#dc566d",
"Starlark": "#76d275",
"Stata": "#1a5f91",
"StringTemplate": "#3fb34f",
"Stylus": "#ff6347",
"SubRip Text": "#9e0101",
"SugarSS": "#2fcc9f",
"SuperCollider": "#46390b",
"SurrealQL": "#ff00a0",
"Survex data": "#ffcc99",
"Svelte": "#ff3e00",
"Sway": "#00F58C",
"Sweave": "#198ce7",
"Swift": "#F05138",
"SystemVerilog": "#DAE1C2",
"TI Program": "#A0AA87",
"TL-Verilog": "#C40023",
"TLA": "#4b0079",
"TMDL": "#f0c913",
"TOML": "#9c4221",
"TSQL": "#e38c00",
"TSV": "#237346",
"TSX": "#3178c6",
"TXL": "#0178b8",
"Tact": "#48b5ff",
"Talon": "#333333",
"Tcl": "#e4cc98",
"TeX": "#3D6117",
"Teal": "#00B1BC",
"Terra": "#00004c",
"Terraform Template": "#7b42bb",
"TextGrid": "#c8506d",
"TextMate Properties": "#df66e4",
"Textile": "#ffe7ac",
"Thrift": "#D12127",
"Toit": "#c2c9fb",
"Tor Config": "#59316b",
"Tree-sitter Query": "#8ea64c",
"Turing": "#cf142b",
"Twig": "#c1d026",
"TypeScript": "#3178c6",
"TypeSpec": "#4A3665",
"Typst": "#239dad",
"Unified Parallel C": "#4e3617",
"Unity3D Asset": "#222c37",
"Uno": "#9933cc",
"UnrealScript": "#a54c4d",
"Untyped Plutus Core": "#36adbd",
"UrWeb": "#ccccee",
"V": "#4f87c4",
"VBA": "#867db1",
"VBScript": "#15dcdc",
"VCL": "#148AA8",
"VHDL": "#adb2cb",
"Vala": "#a56de2",
"Valve Data Format": "#f26025",
"Velocity Template Language": "#507cff",
"Vento": "#ff0080",
"Verilog": "#b2b7f8",
"Vim Help File": "#199f4b",
"Vim Script": "#199f4b",
"Vim Snippet": "#199f4b",
"Visual Basic .NET": "#945db7",
"Visual Basic 6.0": "#2c6353",
"Volt": "#1F1F1F",
"Vue": "#41b883",
"Vyper": "#9F4CF2",
"WDL": "#42f1f4",
"WGSL": "#1a5e9a",
"Web Ontology Language": "#5b70bd",
"WebAssembly": "#04133b",
"WebAssembly Interface Type": "#6250e7",
"Whiley": "#d5c397",
"Wikitext": "#fc5757",
"Windows Registry Entries": "#52d5ff",
"Witcher Script": "#ff0000",
"Wolfram Language": "#dd1100",
"Wollok": "#a23738",
"World of Warcraft Addon Data": "#f7e43f",
"Wren": "#383838",
"X10": "#4B6BEF",
"XC": "#99DA07",
"XML": "#0060ac",
"XML Property List": "#0060ac",
"XQuery": "#5232e7",
"XSLT": "#EB8CEB",
"Xmake": "#22a079",
"Xojo": "#81bd41",
"Xonsh": "#285EEF",
"Xtend": "#24255d",
"YAML": "#cb171e",
"YARA": "#220000",
"YASnippet": "#32AB90",
"Yacc": "#4B6C4B",
"Yul": "#794932",
"ZAP": "#0d665e",
"ZIL": "#dc75e5",
"ZenScript": "#00BCD1",
"Zephir": "#118f9e",
"Zig": "#ec915c",
"Zimpl": "#d67711",
"Zmodel": "#ff7100",
"crontab": "#ead7ac",
"eC": "#913960",
"fish": "#4aae47",
"hoon": "#00b171",
"iCalendar": "#ec564c",
"jq": "#c7254e",
"kvlang": "#1da6e0",
"mIRC Script": "#3d57c3",
"mcfunction": "#E22837",
"mdsvex": "#5f9ea0",
"mupad": "#244963",
"nanorc": "#2d004d",
"nesC": "#94B0C7",
"ooc": "#b0b77e",
"q": "#0040cd",
"reStructuredText": "#141414",
"sed": "#64b970",
"templ": "#66D0DD",
"vCard": "#ee2647",
"wisp": "#7582D1",
"xBase": "#403a40"
}
@@ -11,4 +11,3 @@ const logger =
process.env.NODE_ENV === "test" ? { log: noop, error: noop } : console;
export { logger };
export default logger;
@@ -2,7 +2,8 @@
import toEmoji from "emoji-name-map";
const OWNER_AFFILIATIONS = ["OWNER", "COLLABORATOR", "ORGANIZATION_MEMBER"];
import { OWNER_AFFILIATIONS } from "./constants.js";
import { CustomError } from "./error.js";
/**
* Returns boolean if value is either "true" or "false" else the value as it is.
@@ -159,7 +160,6 @@ const buildSearchFilter = (repos = [], owners = []) => {
};
export {
OWNER_AFFILIATIONS,
parseBoolean,
parseArray,
clampValue,
@@ -1,7 +1,7 @@
// @ts-check
import { SECONDARY_ERROR_MESSAGES, TRY_AGAIN_LATER } from "./error.js";
import { getCardColors } from "./color.js";
import { SECONDARY_ERROR_MESSAGES, TRY_AGAIN_LATER } from "./error.js";
import { encodeHTML } from "./html.js";
import { clampValue } from "./ops.js";
@@ -72,7 +72,7 @@ const createProgressNode = ({
return `
<svg width="${width}" x="${x}" y="${y}">
<rect rx="5" ry="5" x="0" y="0" width="${width}" height="8" fill="${progressBarBackgroundColor}"></rect>
<rect data-testid="progress-background" rx="5" ry="5" x="0" y="0" width="${width}" height="8" fill="${progressBarBackgroundColor}"></rect>
<svg data-testid="lang-progress" width="${progressPercentage}%">
<rect
height="8"
@@ -1,9 +1,19 @@
// @ts-check
import { getUserAccessByName } from "./database.js";
import { CustomError } from "./error.js";
import { logger } from "./log.js";
import { getUserAccessByKey, getUserAccessByName } from "./database.js";
/**
* Returns a random integer from 0 (inclusive) to `max` (exclusive).
*
* The value is generated using `Math.random()` and uniformly distributed
* across the range.
*
* @param {number} max The upper bound (exclusive). Must be a positive number.
*
* @returns {number} A random integer `n` such that `0 <= n < max`.
*/
function getRandomInt(max) {
return Math.floor(Math.random() * max);
}
@@ -17,7 +27,7 @@ function getRandomInt(max) {
* Try to execute the fetcher function until it succeeds or the max number of retries is reached.
*
* @param {FetcherFunction} fetcher The fetcher function.
* @param username GitHub username of the user whose PAT to use, if available
* @param {string?} username GitHub username of the user whose PAT to use, if available
* @param {any} variables Object with arguments to pass to the fetcher function.
* @returns {Promise<any>} The response from the fetcher function.
*/
@@ -36,14 +46,13 @@ const retryer = async (fetcher, username, variables) => {
);
PATs = patNames.map((name) => ({ name, value: process.env[name] }));
}
const RETRIES = process.env.NODE_ENV === "test" ? 7 : PATs.length;
if (!RETRIES) {
if (!PATs.length) {
throw new CustomError("No GitHub API tokens found", CustomError.NO_TOKENS);
}
const startPAT = getRandomInt(PATs.length);
for (let retries = 0; retries < RETRIES; retries++) {
for (let retries = 0; retries < PATs.length; retries++) {
const currentPAT = PATs[(startPAT + retries) % PATs.length];
try {
@@ -1,8 +1,8 @@
// @ts-check
import { retryer } from "../common/retryer.js";
import { MissingParamError } from "../common/error.js";
import { request } from "../common/http.js";
import { retryer } from "../common/retryer.js";
const QUERY = `
query gistInfo($gistName: String!) {
@@ -111,4 +111,3 @@ const fetchGist = async (id) => {
};
export { fetchGist };
export default fetchGist;
@@ -3,6 +3,7 @@
import { MissingParamError } from "../common/error.js";
import { request } from "../common/http.js";
import { retryer } from "../common/retryer.js";
import { fetchRepoUserStats } from "./stats.js";
/**
@@ -159,4 +160,3 @@ const fetchRepo = async (
};
export { fetchRepo };
export default fetchRepo;
@@ -1,21 +1,16 @@
// @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";
import {
buildSearchFilter,
parseOwnerAffiliations,
} from "../common/ops.js";
import { logger } from "../common/log.js";
import { excludeRepositories } from "../common/envs.js";
import { CustomError, MissingParamError } from "../common/error.js";
import { wrapTextMultiline } from "../common/fmt.js";
import { request } from "../common/http.js";
dotenv.config();
import { logger } from "../common/log.js";
import { buildSearchFilter, parseOwnerAffiliations } from "../common/ops.js";
import { retryer } from "../common/retryer.js";
// GraphQL queries.
const GRAPHQL_REPOS_FIELD = `
@@ -164,11 +159,13 @@ const statsFetcher = async ({
const repoNodesWithStars = repoNodes.filter(
(node) => node.stargazers.totalCount !== 0,
);
hasNextPage =
(process.env.FETCH_MULTI_PAGE_STARS === "true" ||
process.env.FETCH_MULTI_PAGE_STARS > fetchedPages) &&
repoNodes.length === repoNodesWithStars.length &&
res.data.data.user.repositories.pageInfo.hasNextPage;
endCursor = res.data.data.user.repositories.pageInfo.endCursor;
}
@@ -191,10 +188,7 @@ const fetchTotalItems = (variables, token) => {
`https://api.github.com/search/` +
variables.type +
`?per_page=1&q=` +
buildSearchFilter(variables.repo, variables.owner).replaceAll(
" ",
"+",
) +
buildSearchFilter(variables.repo, variables.owner).replaceAll(" ", "+") +
variables.filter,
headers: {
"Content-Type": "application/json",
@@ -461,4 +455,3 @@ const fetchStats = async (
};
export { fetchStats, fetchRepoUserStats };
export default fetchStats;
@@ -1,12 +1,12 @@
// @ts-check
import { retryer } from "../common/retryer.js";
import { parseOwnerAffiliations } from "../common/ops.js";
import { logger } from "../common/log.js";
import { excludeRepositories } from "../common/envs.js";
import { CustomError, MissingParamError } from "../common/error.js";
import { wrapTextMultiline } from "../common/fmt.js";
import { request } from "../common/http.js";
import { logger } from "../common/log.js";
import { parseOwnerAffiliations } from "../common/ops.js";
import { retryer } from "../common/retryer.js";
/**
* Top languages fetcher object.
@@ -73,7 +73,10 @@ const fetchTopLanguages = async (
}
ownerAffiliations = parseOwnerAffiliations(ownerAffiliations);
const res = await retryer(fetcher, username, { login: username, ownerAffiliations });
const res = await retryer(fetcher, username, {
login: username,
ownerAffiliations,
});
if (res.data.errors) {
logger.error(res.data.errors);
@@ -163,4 +166,3 @@ const fetchTopLanguages = async (
};
export { fetchTopLanguages };
export default fetchTopLanguages;
@@ -1,6 +1,7 @@
// @ts-check
import axios from "axios";
import { CustomError, MissingParamError } from "../common/error.js";
/**
@@ -34,4 +35,3 @@ const fetchWakatimeStats = async ({ username, api_domain }) => {
};
export { fetchWakatimeStats };
export default fetchWakatimeStats;
@@ -1,8 +1,9 @@
import axios from "axios";
import {
pool,
deleteOldRequests,
getRecentRequests,
pool,
} from "./common/database.js";
/**
@@ -47,16 +48,21 @@ async function makeRequests(urls, poolSize) {
}
/**
* Repeats requests made in the last 8 days, excluding those made in the last 11 hours.
* Repeats requests made in the last 8 days (or configured interval), excluding those made in the last 11 hours (or configured interval).
*/
export async function repeatRecentRequests() {
if (!pool) {
console.error("Postgres pool is not initialized.");
return;
}
await deleteOldRequests();
const urls = await getRecentRequests();
let maxInterval = process.env.DELETE_AFTER_HOURS
? `${process.env.DELETE_AFTER_HOURS} hours`
: "8 days";
let minInterval = process.env.UPDATE_AFTER_HOURS
? `${process.env.UPDATE_AFTER_HOURS} hours`
: "11 hours";
await deleteOldRequests(maxInterval);
const urls = await getRecentRequests(minInterval, maxInterval);
if (urls.length === 0) {
console.log("No recent requests found.");
} else {
@@ -62,6 +62,7 @@ const statCardLocales = ({ name, apostrophe }) => {
sr: `GitHub статистика корисника ${encodedName}`,
"sr-latn": `GitHub statistika korisnika ${encodedName}`,
no: `GitHub-statistikk for ${encodedName}`,
be: `Статыстыка GitHub карыстальніка ${encodedName}`,
},
"statcard.ranktitle": {
en: `${encodedName}'${apostrophe} GitHub Rank`,
@@ -110,6 +111,7 @@ const statCardLocales = ({ name, apostrophe }) => {
sr: `Ранк корисника ${encodedName}`,
"sr-latn": `Rank korisnika ${encodedName}`,
no: `GitHub-statistikk for ${encodedName}`,
be: `Рэйтынг GitHub карыстальніка ${encodedName}`,
},
"statcard.totalstars": {
en: "Total Stars Earned",
@@ -158,6 +160,7 @@ const statCardLocales = ({ name, apostrophe }) => {
sr: "Број освојених звездица",
"sr-latn": "Broj osvojenih zvezdica",
no: "Totalt antall stjerner",
be: "Зароблена зорак агулам",
},
"statcard.commits": {
en: "Total Commits",
@@ -206,6 +209,7 @@ const statCardLocales = ({ name, apostrophe }) => {
sr: "Укупно commit-ова",
"sr-latn": "Ukupno commit-ova",
no: "Totalt antall commits",
be: "Агульная колькасць камітаў",
},
"statcard.prs": {
en: "Total PRs",
@@ -254,6 +258,7 @@ const statCardLocales = ({ name, apostrophe }) => {
sr: "Укупно PR-ова",
"sr-latn": "Ukupno PR-ova",
no: "Totalt antall PR",
be: "Агулам запытаў на змены, PRаў",
},
"statcard.issues": {
en: "Total Issues",
@@ -302,6 +307,7 @@ const statCardLocales = ({ name, apostrophe }) => {
sr: "Укупно пријављених проблема",
"sr-latn": "Ukupno prijavljenih problema",
no: "Totalt antall issues",
be: "Усяго пытанняў",
},
"statcard.contribs": {
en: "Contributed to (last year)",
@@ -350,6 +356,7 @@ const statCardLocales = ({ name, apostrophe }) => {
sr: "Доприноси (прошла година)",
"sr-latn": "Doprinosi (prošla godina)",
no: "Bidro til (i fjor)",
be: "Уклад (за мінулы год)",
},
"statcard.reviews": {
en: "Total PRs Reviewed",
@@ -398,6 +405,7 @@ const statCardLocales = ({ name, apostrophe }) => {
sr: "Укупно прегледаних PR-ова",
"sr-latn": "Ukupno pregledanih PR-ova",
no: "Totalt antall vurderte PR",
be: "Агулам разгледжана PR",
},
"statcard.discussions-started": {
en: "Total Discussions Started",
@@ -446,6 +454,7 @@ const statCardLocales = ({ name, apostrophe }) => {
sr: "Укупно покренутих дискусија",
"sr-latn": "Ukupno pokrenutih diskusija",
no: "Totalt antall startede diskusjoner",
be: "Агульная колькасць пачатых абмеркаванняў",
},
"statcard.discussions-answered": {
en: "Total Discussions Answered",
@@ -494,6 +503,7 @@ const statCardLocales = ({ name, apostrophe }) => {
sr: "Укупно одговорених дискусија",
"sr-latn": "Ukupno odgovorenih diskusija",
no: "Totalt antall besvarte diskusjoner",
be: "Усяго адказаў у абмеркаваннях",
},
"statcard.prs-authored": {
en: "PRs Created",
@@ -556,6 +566,7 @@ const statCardLocales = ({ name, apostrophe }) => {
sr: "Укупно спојених PR-ова",
"sr-latn": "Ukupno spojenih PR-ova",
no: "Totalt antall sammenslåtte PR",
be: "Усяго PR прынята",
},
"statcard.prs-merged-percentage": {
en: "Merged PRs Percentage",
@@ -603,6 +614,7 @@ const statCardLocales = ({ name, apostrophe }) => {
sr: "Проценат спојених PR-ова",
"sr-latn": "Procenat spojenih PR-ova",
no: "Prosentandel sammenslåtte PR",
be: "Адсотак аб'яднаных PR",
},
};
};
@@ -655,6 +667,7 @@ const repoCardLocales = {
sr: "Шаблон",
"sr-latn": "Šablon",
no: "Mal",
be: "Шаблон",
},
"repocard.archived": {
en: "Archived",
@@ -703,6 +716,7 @@ const repoCardLocales = {
sr: "Архивирано",
"sr-latn": "Arhivirano",
no: "Arkivert",
be: "Архіваваны",
},
"repocard.prs-authored": {
en: "my created PRs",
@@ -769,6 +783,7 @@ const langCardLocales = {
sr: "Најкоришћенији језици",
"sr-latn": "Najkorišćeniji jezici",
no: "Mest brukte språk",
be: "Найбольш ужываныя мовы",
},
"langcard.nodata": {
en: "No languages data.",
@@ -817,6 +832,7 @@ const langCardLocales = {
sr: "Нема података о језицима.",
"sr-latn": "Nema podataka o jezicima.",
no: "Ingen språkdata.",
be: "Няма дадзеных пра мовы.",
},
};
@@ -868,6 +884,7 @@ const wakatimeCardLocales = {
sr: "WakaTime статистика",
"sr-latn": "WakaTime statistika",
no: "WakaTime-statistikk",
be: "Статыстыка WakaTime",
},
"wakatimecard.lastyear": {
en: "last year",
@@ -916,6 +933,7 @@ const wakatimeCardLocales = {
sr: "Прошла год.",
"sr-latn": "Prošla god.",
no: "I fjor",
be: "мінулы год",
},
"wakatimecard.last7days": {
en: "last 7 days",
@@ -964,6 +982,7 @@ const wakatimeCardLocales = {
sr: "Претходних 7 дана",
"sr-latn": "Prethodnih 7 dana",
no: "Siste 7 dager",
be: "апошнія 7 дзён",
},
"wakatimecard.notpublic": {
en: "WakaTime user profile not public",
@@ -1012,6 +1031,7 @@ const wakatimeCardLocales = {
sr: "WakaTime профил корисника није јаван",
"sr-latn": "WakaTime profil korisnika nije javan",
no: "WakaTime brukerprofil ikke offentlig",
be: "Профіль карыстальніка WakaTime не публічны",
},
"wakatimecard.nocodedetails": {
en: "User doesn't publicly share detailed code statistics",
@@ -1062,6 +1082,7 @@ const wakatimeCardLocales = {
sr: "Корисник не дели јавно детаљну статистику кода",
"sr-latn": "Korisnik ne deli javno detaljnu statistiku koda",
no: "Brukeren deler ikke detaljert kodestatistikk offentlig",
be: "Карыстальнік не дзеліцца падрабязнай статыстыкай кода публічна",
},
"wakatimecard.nocodingactivity": {
en: "No coding activity this week",
@@ -1110,6 +1131,7 @@ const wakatimeCardLocales = {
sr: "Током ове недеље није било никаквих активности",
"sr-latn": "Tokom ove nedelje nije bilo nikakvih aktivnosti",
no: "Ingen kodeaktivitet denne uken",
be: "На гэтым тыдні не было актыўнасці",
},
};
@@ -1126,7 +1148,6 @@ const isLocaleAvailable = (locale) => {
};
export {
availableLocales,
isLocaleAvailable,
langCardLocales,
repoCardLocales,
@@ -1,4 +1,5 @@
import axios from "axios";
import { storeUser } from "./common/database.js";
/**
@@ -60,7 +61,10 @@ async function githubAuthenticate(code, privateAccess) {
const accessToken = body && body.access_token ? body.access_token : null;
if (!accessToken) {
throw new Error("OAuth Error: access_token missing from response");
throw new Error(
"OAuth Error: access_token missing from response: " +
JSON.stringify(body),
);
}
const userId = await getUserFromToken(accessToken);
@@ -1,6 +1,6 @@
// Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
exports[`Test Render WakaTime Card should render correctly 1`] = `
exports[`Test Render WakaTime Card > should render correctly 1`] = `
"
<svg
width="495"
@@ -114,7 +114,7 @@ exports[`Test Render WakaTime Card should render correctly 1`] = `
>19 mins</text>
<svg width="220" x="110" y="4">
<rect rx="5" ry="5" x="0" y="0" width="220" height="8" fill="#434d58"></rect>
<rect data-testid="progress-background" rx="5" ry="5" x="0" y="0" width="220" height="8" fill="#434d58"></rect>
<svg data-testid="lang-progress" width="89.38%">
<rect
height="8"
@@ -137,7 +137,7 @@ exports[`Test Render WakaTime Card should render correctly 1`] = `
>1 min</text>
<svg width="220" x="110" y="4">
<rect rx="5" ry="5" x="0" y="0" width="220" height="8" fill="#434d58"></rect>
<rect data-testid="progress-background" rx="5" ry="5" x="0" y="0" width="220" height="8" fill="#434d58"></rect>
<svg data-testid="lang-progress" width="6.25%">
<rect
height="8"
@@ -158,7 +158,7 @@ exports[`Test Render WakaTime Card should render correctly 1`] = `
"
`;
exports[`Test Render WakaTime Card should render correctly with compact layout 1`] = `
exports[`Test Render WakaTime Card > should render correctly with compact layout 1`] = `
"
<svg
width="495"
@@ -310,7 +310,7 @@ exports[`Test Render WakaTime Card should render correctly with compact layout 1
"
`;
exports[`Test Render WakaTime Card should render correctly with compact layout when langs_count is set 1`] = `
exports[`Test Render WakaTime Card > should render correctly with compact layout when langs_count is set 1`] = `
"
<svg
width="495"
@@ -462,7 +462,7 @@ exports[`Test Render WakaTime Card should render correctly with compact layout w
"
`;
exports[`Test Render WakaTime Card should render correctly with percent display format 1`] = `
exports[`Test Render WakaTime Card > should render correctly with percent display format 1`] = `
"
<svg
width="495"
@@ -576,7 +576,7 @@ exports[`Test Render WakaTime Card should render correctly with percent display
>47.39 %</text>
<svg width="220" x="110" y="4">
<rect rx="5" ry="5" x="0" y="0" width="220" height="8" fill="#434d58"></rect>
<rect data-testid="progress-background" rx="5" ry="5" x="0" y="0" width="220" height="8" fill="#434d58"></rect>
<svg data-testid="lang-progress" width="47.39%">
<rect
height="8"
@@ -599,7 +599,7 @@ exports[`Test Render WakaTime Card should render correctly with percent display
>50.48 %</text>
<svg width="220" x="110" y="4">
<rect rx="5" ry="5" x="0" y="0" width="220" height="8" fill="#434d58"></rect>
<rect data-testid="progress-background" rx="5" ry="5" x="0" y="0" width="220" height="8" fill="#434d58"></rect>
<svg data-testid="lang-progress" width="50.48%">
<rect
height="8"
@@ -0,0 +1,115 @@
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
exports[`Test /api/wakatime > should render error if user data is not accessible 1`] = `
"
<svg
width="495"
height="150"
viewBox="0 0 495 150"
fill="none"
xmlns="http://www.w3.org/2000/svg"
role="img"
aria-labelledby="descId"
>
<title id="titleId"></title>
<desc id="descId"></desc>
<style>
.header {
font: 600 18px 'Segoe UI', Ubuntu, Sans-Serif;
fill: #2f80ed;
animation: fadeInAnimation 0.8s ease-in-out forwards;
}
@supports(-moz-appearance: auto) {
/* Selector detects Firefox */
.header { font-size: 15.5px; }
}
.stat {
font: 600 14px 'Segoe UI', Ubuntu, "Helvetica Neue", Sans-Serif; fill: #434d58;
}
@supports(-moz-appearance: auto) {
/* Selector detects Firefox */
.stat { font-size:12px; }
}
.stagger {
opacity: 0;
animation: fadeInAnimation 0.3s ease-in-out forwards;
}
.not_bold { font-weight: 400 }
.bold { font-weight: 700 }
@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;
}
</style>
<rect
data-testid="card-bg"
x="0.5"
y="0.5"
rx="4.5"
height="99%"
stroke="#e4e2e2"
width="494"
fill="#fffefe"
stroke-opacity="1"
/>
<g
data-testid="card-title"
transform="translate(25, 35)"
>
<g transform="translate(0, 0)">
<text
x="0"
y="0"
class="header"
data-testid="header"
>WakaTime Stats</text>
</g>
</g>
<g
data-testid="main-card-body"
transform="translate(0, 55)"
>
<svg x="0" y="0" width="100%">
<g transform="translate(0, 0)">
<text x="25" y="11" class="stat bold" fill="#434d58">WakaTime user profile not public</text>
</g>
</svg>
</g>
</svg>
"
`;
+7
View File
@@ -0,0 +1,7 @@
import { TextDecoder, TextEncoder } from "util";
//https://stackoverflow.com/a/68468204/1643179
Object.assign(global, { TextDecoder, TextEncoder });
process.env.PAT_1 = "dummyPAT1";
process.env.PAT_2 = "dummyPAT2";
@@ -0,0 +1,2 @@
process.env.GIST_WHITELIST = "bbfce31e0217a3689c8d961a356cb10d";
process.env.WHITELIST = "anuraghazra";

Some files were not shown because too many files have changed in this diff Show More