diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md
index a304b2f9380b6..002e4576355ec 100644
--- a/.github/CONTRIBUTING.md
+++ b/.github/CONTRIBUTING.md
@@ -7,7 +7,8 @@ To set up the project GitHub-Stats-Extended locally, run the following commands:
```bash
./vercel-preparation.sh
pnpm install
-pnpm --filter frontend run build
+pnpm run build:packages
+pnpm run dev:frontend
```
The easiest way to run and test the project is to deploy it to Vercel as described in the [deployment guide](../docs/deploy.md).
diff --git a/.github/actions/install-dependencies/action.yml b/.github/actions/install-dependencies/action.yml
index 901d2f63ab4b5..ba1ccbae2ee40 100644
--- a/.github/actions/install-dependencies/action.yml
+++ b/.github/actions/install-dependencies/action.yml
@@ -14,11 +14,11 @@ runs:
steps:
- name: Install pnpm
- uses: pnpm/action-setup@v4
+ uses: pnpm/action-setup@fc06bc1257f339d1d5d8b3a19a8cae5388b55320 # v5
- name: Setup Node.js (via input)
if: ${{ inputs.node-version }}
- uses: actions/setup-node@v6
+ uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0
with:
node-version: ${{ inputs.node-version }}
cache: "pnpm"
@@ -26,7 +26,7 @@ runs:
- name: Setup Node.js (via .nvmrc)
if: ${{ !inputs.node-version }}
- uses: actions/setup-node@v6
+ uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0
with:
node-version-file: ".nvmrc"
cache: "pnpm"
diff --git a/.github/dependabot.yml b/.github/dependabot.yml
index e0eeed90e2ee4..6bb490648ca0d 100644
--- a/.github/dependabot.yml
+++ b/.github/dependabot.yml
@@ -4,9 +4,8 @@ updates:
- package-ecosystem: npm
directory: "/"
schedule:
- interval: cron
- cronjob: every day at 5am
- open-pull-requests-limit: 10
+ interval: daily
+ open-pull-requests-limit: 20
commit-message:
prefix: "build(deps)"
prefix-development: "build(deps-dev)"
@@ -20,9 +19,8 @@ updates:
- package-ecosystem: github-actions
directory: "/"
schedule:
- interval: cron
- cronjob: every day at 5am
- open-pull-requests-limit: 10
+ interval: daily
+ open-pull-requests-limit: 20
commit-message:
prefix: "ci(deps)"
prefix-development: "ci(deps-dev)"
@@ -33,9 +31,8 @@ updates:
- package-ecosystem: devcontainers
directory: "/"
schedule:
- interval: cron
- cronjob: every day at 5am
- open-pull-requests-limit: 10
+ interval: daily
+ open-pull-requests-limit: 20
commit-message:
prefix: "build(deps)"
prefix-development: "build(deps-dev)"
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 67d0bad27e235..9f27b1fda07b5 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -31,7 +31,7 @@ jobs:
steps:
- name: Checkout code
- uses: actions/checkout@v6
+ uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: Run vercel-preparation.sh
run: |
@@ -43,14 +43,14 @@ jobs:
with:
node-version: ${{ matrix.node }}
- - name: Build frontend
- run: pnpm --filter frontend run build
+ - name: Build packages
+ run: pnpm run build:packages
- - name: Run frontend tests
- run: pnpm --filter frontend run test
+ - name: Build frontend
+ run: pnpm run build:frontend
- - name: Run backend tests
- run: pnpm --filter github-readme-stats run test
+ - name: Run tests
+ run: pnpm run test --silent
frontend-test-e2e:
name: Frontend E2E test
@@ -64,21 +64,42 @@ jobs:
steps:
- name: Checkout code
- uses: actions/checkout@v6
+ uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: Install Dependencies
uses: ./.github/actions/install-dependencies
- - name: Run vercel-preparation.sh
- run: |
- chmod +x ./vercel-preparation.sh
- ./vercel-preparation.sh
+ - name: Build packages
+ run: pnpm run build:packages
- name: Install Playwright Browsers
run: pnpm exec playwright install --with-deps
- name: Run Playwright tests
- run: pnpm --filter frontend run test:e2e
+ run: pnpm --filter ./apps/frontend/ run test:e2e
+
+ backend-test-e2e:
+ name: Backend E2E test
+
+ runs-on: ubuntu-latest
+
+ permissions:
+ contents: read
+
+ continue-on-error: true
+
+ steps:
+ - name: Checkout code
+ uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
+
+ - name: Install Dependencies
+ uses: ./.github/actions/install-dependencies
+
+ - name: Build packages
+ run: pnpm run build:packages
+
+ - name: Run backend end-to-end tests
+ run: pnpm --filter ./apps/backend/ run test:e2e
code-checks:
name: Code checks
@@ -90,17 +111,7 @@ jobs:
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 ESLint’s 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
+ uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: Install Dependencies
uses: ./.github/actions/install-dependencies
diff --git a/.github/workflows/generate-theme-doc.yml b/.github/workflows/generate-theme-doc.yml
index c985fdedb8b19..92cce540a5ffb 100644
--- a/.github/workflows/generate-theme-doc.yml
+++ b/.github/workflows/generate-theme-doc.yml
@@ -4,7 +4,7 @@ on:
branches:
- master
paths:
- - "apps/backend/themes/index.js"
+ - "packages/core/src/themes/index.js"
workflow_dispatch:
permissions: {}
@@ -30,17 +30,17 @@ jobs:
steps:
- name: Checkout code
- uses: actions/checkout@v6
+ uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: Install Dependencies
uses: ./.github/actions/install-dependencies
- name: Generate readme
run: |
- pnpm --filter github-readme-stats run theme-readme-gen
+ pnpm --filter ./packages/core/ run theme-readme-gen
- name: Create Pull Request if themes README has changed
- uses: peter-evans/create-pull-request@v8
+ uses: peter-evans/create-pull-request@c0f553fe549906ede9cf27b5156039d195d2ece0 # v8
with:
commit-message: "feat(backend): update themes README"
branch: "update_themes_readme/patch"
diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
new file mode 100644
index 0000000000000..f8ba92bcc211f
--- /dev/null
+++ b/.github/workflows/release.yml
@@ -0,0 +1,18 @@
+name: Update version tags
+on:
+ push:
+ branches-ignore:
+ - "**"
+ tags:
+ - "v*.*.*"
+
+permissions: {}
+
+jobs:
+ update-semver:
+ runs-on: ubuntu-latest
+ permissions:
+ contents: write
+ steps:
+ - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
+ - uses: haya14busa/action-update-semver@7d2c558640ea49e798d46539536190aff8c18715 # v1.5.1
diff --git a/.github/workflows/repeat-recent-requests.yml b/.github/workflows/repeat-recent-requests.yml
index ebf23adf3e89b..6e4d680175a2c 100644
--- a/.github/workflows/repeat-recent-requests.yml
+++ b/.github/workflows/repeat-recent-requests.yml
@@ -20,7 +20,7 @@ jobs:
steps:
- name: Make Request
id: myRequest
- uses: fjogeleit/http-request-action@v2
+ uses: fjogeleit/http-request-action@551353b829c3646756b2ec2b3694f819d7957495 # v2
with:
url: "https://github-stats-extended.vercel.app/api/repeat-recent"
method: "POST"
diff --git a/.github/workflows/update-langs.yml b/.github/workflows/update-langs.yml
index 8ead85e2c17cf..b720eecb96b06 100644
--- a/.github/workflows/update-langs.yml
+++ b/.github/workflows/update-langs.yml
@@ -38,16 +38,16 @@ jobs:
steps:
- name: Checkout code
- uses: actions/checkout@v6
+ uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: Install Dependencies
uses: ./.github/actions/install-dependencies
- name: Run update-languages-json.js script
- run: pnpm --filter github-readme-stats run generate-langs-json
+ run: pnpm --filter ./packages/core/ run generate-langs-json
- name: Create Pull Request if upstream language file is changed
- uses: peter-evans/create-pull-request@v8
+ uses: peter-evans/create-pull-request@c0f553fe549906ede9cf27b5156039d195d2ece0 # v8
with:
commit-message: "feat(backend): update languages JSON"
branch: "update_langs/patch"
diff --git a/.gitignore b/.gitignore
index bd17695b5cf3f..2890613ac5301 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,18 +1,8 @@
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
@@ -26,11 +16,12 @@ apps/backend/vercel_token
apps/backend-copy
apps/frontend/.env
-apps/frontend/src/backend
-apps/frontend/build
+
+.turbo
+build
build-ts
-tsconfig.tsbuildinfo
+*.tsbuildinfo
# IDE
.idea/
diff --git a/.husky/pre-commit b/.husky/pre-commit
index b4c15f46acd0f..e742101ea1abb 100644
--- a/.husky/pre-commit
+++ b/.husky/pre-commit
@@ -1,4 +1,3 @@
pnpm lint-staged
pnpm run lint
-# TODO enable
-# npm test
+pnpm test
diff --git a/apps/backend/_dot_vercel_copy/output/functions/api.func/router.js b/apps/backend/_dot_vercel_copy/output/functions/api.func/router.js
deleted file mode 100644
index b9a98e1e5f744..0000000000000
--- a/apps/backend/_dot_vercel_copy/output/functions/api.func/router.js
+++ /dev/null
@@ -1,76 +0,0 @@
-/* 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 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 topLangs } from "./api-renamed/top-langs.js";
-import { default as userAccess } from "./api-renamed/user-access.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
- res.send = function (data) {
- if (typeof data === "object") {
- res.setHeader("Content-Type", "application/json");
- res.end(JSON.stringify(data));
- } else if (typeof data === "string") {
- res.end(data);
- } else {
- res.end(String(data));
- }
- };
- const url = new URL(req.url, "https://localhost");
- req.query = Object.fromEntries(url.searchParams.entries());
-
- switch (url.pathname) {
- case "/api":
- await api(req, res);
- break;
- case "/api/gist":
- await gist(req, res);
- break;
- case "/api/pin":
- await pin(req, res);
- break;
- case "/api/top-langs":
- await topLangs(req, res);
- break;
- case "/api/wakatime":
- await wakatime(req, res);
- break;
- case "/api/wakatime-proxy":
- await wakatimeProxy(req, res);
- break;
- case "/api/repeat-recent":
- await repeatRecent(req, res);
- break;
- case "/api/status/pat-info":
- await patInfo(req, res);
- break;
- case "/api/status/up":
- await statusUp(req, res);
- break;
- case "/api/authenticate":
- await authenticate(req, res);
- break;
- case "/api/delete-user":
- await deleteUser(req, res);
- break;
- case "/api/user-access":
- await userAccess(req, res);
- break;
- case "/api/downgrade":
- await downgrade(req, res);
- break;
- default:
- res.statusCode = 404;
- res.end("Not Found");
- break;
- }
-};
diff --git a/apps/backend/api-renamed/authenticate.js b/apps/backend/api-renamed/authenticate.js
index 18511a224721b..bcfaa3ec92027 100644
--- a/apps/backend/api-renamed/authenticate.js
+++ b/apps/backend/api-renamed/authenticate.js
@@ -1,4 +1,5 @@
-import { logger } from "../src/common/log.js";
+import { logger } from "@stats-organization/github-readme-stats-core";
+
import { authenticate } from "../src/users.js";
/**
diff --git a/apps/backend/api-renamed/delete-user.js b/apps/backend/api-renamed/delete-user.js
index 83c073e910b39..f19c3672d1c53 100644
--- a/apps/backend/api-renamed/delete-user.js
+++ b/apps/backend/api-renamed/delete-user.js
@@ -1,5 +1,6 @@
+import { logger } from "@stats-organization/github-readme-stats-core";
+
import { deleteUser } from "../src/common/database.js";
-import { logger } from "../src/common/log.js";
/**
* @param {any} req The request.
diff --git a/apps/backend/api-renamed/downgrade.js b/apps/backend/api-renamed/downgrade.js
index 519d3f9f553c7..e52600d777dde 100644
--- a/apps/backend/api-renamed/downgrade.js
+++ b/apps/backend/api-renamed/downgrade.js
@@ -1,7 +1,7 @@
+import { logger } from "@stats-organization/github-readme-stats-core";
import axios from "axios";
import { deleteUser, getUserAccessByKey } from "../src/common/database.js";
-import { logger } from "../src/common/log.js";
export default async (req, res) => {
// We could optimize this method by doing both database operations in one statement, using "DELETE ... RETURNING ..."
diff --git a/apps/backend/api-renamed/gist.js b/apps/backend/api-renamed/gist.js
deleted file mode 100644
index 26e973eed2224..0000000000000
--- a/apps/backend/api-renamed/gist.js
+++ /dev/null
@@ -1,129 +0,0 @@
-// @ts-check
-
-import { renderGistCard } from "../src/cards/gist.js";
-import { guardAccess } from "../src/common/access.js";
-import {
- CACHE_TTL,
- resolveCacheSeconds,
- 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 { 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) => {
- const {
- id,
- title_color,
- icon_color,
- text_color,
- bg_color,
- theme,
- cache_seconds,
- locale,
- border_radius,
- border_color,
- show_owner,
- hide_border,
- } = req.query;
-
- res.setHeader("Content-Type", "image/svg+xml");
-
- const access = guardAccess({
- res,
- id,
- type: "gist",
- colors: {
- title_color,
- text_color,
- bg_color,
- border_color,
- theme,
- },
- });
- if (!access.isPassed) {
- return access.result;
- }
-
- if (locale && !isLocaleAvailable(locale)) {
- return res.send(
- renderError({
- message: "Something went wrong",
- secondaryMessage: "Language not found",
- renderOptions: {
- title_color,
- text_color,
- bg_color,
- border_color,
- theme,
- },
- }),
- );
- }
-
- try {
- await storeRequest(req);
- const gistData = await fetchGist(id);
- const cacheSeconds = resolveCacheSeconds({
- requested: parseInt(cache_seconds, 10),
- def: CACHE_TTL.GIST_CARD.DEFAULT,
- min: CACHE_TTL.GIST_CARD.MIN,
- max: CACHE_TTL.GIST_CARD.MAX,
- });
-
- setCacheHeaders(res, cacheSeconds);
-
- return res.send(
- renderGistCard(gistData, {
- title_color,
- icon_color,
- text_color,
- bg_color,
- theme,
- border_radius,
- border_color,
- locale: locale ? locale.toLowerCase() : null,
- show_owner: parseBoolean(show_owner),
- hide_border: parseBoolean(hide_border),
- }),
- );
- } catch (err) {
- setErrorCacheHeaders(res);
- if (err instanceof Error) {
- return res.send(
- renderError({
- message: err.message,
- secondaryMessage: retrieveSecondaryMessage(err),
- renderOptions: {
- title_color,
- text_color,
- bg_color,
- border_color,
- theme,
- show_repo_link: !(err instanceof MissingParamError),
- },
- }),
- );
- }
- return res.send(
- renderError({
- message: "An unknown error occurred",
- renderOptions: {
- title_color,
- text_color,
- bg_color,
- border_color,
- theme,
- },
- }),
- );
- }
-};
diff --git a/apps/backend/api-renamed/status/pat-info.js b/apps/backend/api-renamed/status/pat-info.js
index 998d7cbc46b30..5725fc128838c 100644
--- a/apps/backend/api-renamed/status/pat-info.js
+++ b/apps/backend/api-renamed/status/pat-info.js
@@ -6,10 +6,12 @@
*
* @description This function is currently rate limited to 1 request per 3 minutes.
*/
-
-import { request } from "../../src/common/http.js";
-import { logger } from "../../src/common/log.js";
-import { dateDiff } from "../../src/common/ops.js";
+import {
+ dateDiff,
+ getConfig,
+ logger,
+ request,
+} from "@stats-organization/github-readme-stats-core";
export const RATE_LIMIT_SECONDS = 60 * 3; // 1 request per 3 minutes
@@ -39,7 +41,7 @@ const uptimeFetcher = (variables, token) => {
};
const getAllPATs = () => {
- return Object.keys(process.env).filter((key) => /PAT_\d*$/.exec(key));
+ return getConfig().pats;
};
/**
@@ -61,7 +63,7 @@ const getPATInfo = async (fetcher, variables) => {
for (const pat of PATs) {
try {
- const response = await fetcher(variables, process.env[pat]);
+ const response = await fetcher(variables, pat.value);
const errors = response.data.errors;
const hasErrors = Boolean(errors);
const errorType = errors?.[0]?.type;
@@ -71,7 +73,7 @@ const getPATInfo = async (fetcher, variables) => {
// Store PATs with errors.
if (hasErrors && errorType !== "RATE_LIMITED") {
- details[pat] = {
+ details[pat.name] = {
status: "error",
error: {
type: errors[0].type,
@@ -82,13 +84,13 @@ const getPATInfo = async (fetcher, variables) => {
} else if (isRateLimited) {
const date1 = new Date();
const date2 = new Date(response.data?.data?.rateLimit?.resetAt);
- details[pat] = {
+ details[pat.name] = {
status: "exhausted",
remaining: 0,
resetIn: dateDiff(date2, date1) + " minutes",
};
} else {
- details[pat] = {
+ details[pat.name] = {
status: "valid",
remaining: response.data.data.rateLimit.remaining,
};
@@ -97,11 +99,11 @@ const getPATInfo = async (fetcher, variables) => {
// Store the PAT if it is expired.
const errorMessage = err.response?.data?.message?.toLowerCase();
if (errorMessage === "bad credentials") {
- details[pat] = {
+ details[pat.name] = {
status: "expired",
};
} else if (errorMessage === "sorry. your account was suspended.") {
- details[pat] = {
+ details[pat.name] = {
status: "suspended",
};
} else {
diff --git a/apps/backend/api-renamed/status/up.js b/apps/backend/api-renamed/status/up.js
index 3afe5853051ab..66162b6797f90 100644
--- a/apps/backend/api-renamed/status/up.js
+++ b/apps/backend/api-renamed/status/up.js
@@ -7,9 +7,11 @@
* @description This function is currently rate limited to 1 request per 3 minutes.
*/
-import { request } from "../../src/common/http.js";
-import { logger } from "../../src/common/log.js";
-import { default as retryer } from "../../src/common/retryer.js";
+import {
+ logger,
+ request,
+ retryer,
+} from "@stats-organization/github-readme-stats-core";
export const RATE_LIMIT_SECONDS = 60 * 3; // 1 request per 3 minutes
@@ -87,7 +89,7 @@ export default async (req, res) => {
try {
let PATsValid = true;
try {
- await retryer(uptimeFetcher, null, {});
+ await retryer(uptimeFetcher, {});
} catch (err) {
// Resolve eslint no-unused-vars
err;
diff --git a/apps/backend/api-renamed/user-access.js b/apps/backend/api-renamed/user-access.js
index 218ceac07faee..0e86821699b5e 100644
--- a/apps/backend/api-renamed/user-access.js
+++ b/apps/backend/api-renamed/user-access.js
@@ -1,5 +1,6 @@
+import { logger } from "@stats-organization/github-readme-stats-core";
+
import { getUserAccessByKey } from "../src/common/database.js";
-import { logger } from "../src/common/log.js";
/**
* @param {any} req The request.
diff --git a/apps/backend/api-renamed/wakatime-proxy.js b/apps/backend/api-renamed/wakatime-proxy.js
index 34dc4611c3b19..d370ed0e4e601 100644
--- a/apps/backend/api-renamed/wakatime-proxy.js
+++ b/apps/backend/api-renamed/wakatime-proxy.js
@@ -1,5 +1,7 @@
-import { logger } from "../src/common/log.js";
-import { fetchWakatimeStats } from "../src/fetchers/wakatime.js";
+import {
+ fetchWakatimeStats,
+ logger,
+} from "@stats-organization/github-readme-stats-core";
/**
* @param {any} req The request.
diff --git a/apps/backend/api-renamed/wakatime.js b/apps/backend/api-renamed/wakatime.js
deleted file mode 100644
index dd1c3eadd903b..0000000000000
--- a/apps/backend/api-renamed/wakatime.js
+++ /dev/null
@@ -1,148 +0,0 @@
-// @ts-check
-
-import { renderWakatimeCard } from "../src/cards/wakatime.js";
-import { guardAccess } from "../src/common/access.js";
-import {
- CACHE_TTL,
- resolveCacheSeconds,
- setCacheHeaders,
- setErrorCacheHeaders,
-} from "../src/common/cache.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) => {
- const {
- username,
- title_color,
- icon_color,
- hide_border,
- card_width,
- line_height,
- text_color,
- bg_color,
- theme,
- cache_seconds,
- hide_title,
- hide_progress,
- custom_title,
- locale,
- layout,
- langs_count,
- hide,
- api_domain,
- border_radius,
- border_color,
- display_format,
- disable_animations,
- } = req.query;
-
- res.setHeader("Content-Type", "image/svg+xml");
-
- const access = guardAccess({
- res,
- id: username,
- type: "wakatime",
- colors: {
- title_color,
- text_color,
- bg_color,
- border_color,
- theme,
- },
- });
- if (!access.isPassed) {
- return access.result;
- }
-
- if (locale && !isLocaleAvailable(locale)) {
- return res.send(
- renderError({
- message: "Something went wrong",
- secondaryMessage: "Language not found",
- renderOptions: {
- title_color,
- text_color,
- bg_color,
- border_color,
- theme,
- },
- }),
- );
- }
-
- try {
- await storeRequest(req);
- const stats = await fetchWakatimeStats({ username, api_domain });
- const cacheSeconds = resolveCacheSeconds({
- requested: parseInt(cache_seconds, 10),
- def: CACHE_TTL.WAKATIME_CARD.DEFAULT,
- min: CACHE_TTL.WAKATIME_CARD.MIN,
- max: CACHE_TTL.WAKATIME_CARD.MAX,
- });
-
- setCacheHeaders(res, cacheSeconds);
-
- return res.send(
- renderWakatimeCard(stats, {
- custom_title,
- hide_title: parseBoolean(hide_title),
- hide_border: parseBoolean(hide_border),
- card_width: parseInt(card_width, 10),
- hide: parseArray(hide),
- line_height,
- title_color,
- icon_color,
- text_color,
- bg_color,
- theme,
- hide_progress,
- border_radius,
- border_color,
- locale: locale ? locale.toLowerCase() : null,
- layout,
- langs_count,
- display_format,
- disable_animations: parseBoolean(disable_animations),
- }),
- );
- } catch (err) {
- setErrorCacheHeaders(res);
- if (err instanceof Error) {
- return res.send(
- renderError({
- message: err.message,
- secondaryMessage: retrieveSecondaryMessage(err),
- renderOptions: {
- title_color,
- text_color,
- bg_color,
- border_color,
- theme,
- show_repo_link: !(err instanceof MissingParamError),
- },
- }),
- );
- }
- return res.send(
- renderError({
- message: "An unknown error occurred",
- renderOptions: {
- title_color,
- text_color,
- bg_color,
- border_color,
- theme,
- },
- }),
- );
- }
-};
diff --git a/apps/backend/express.js b/apps/backend/express.js
index 35a24a36849aa..099c72541b17d 100644
--- a/apps/backend/express.js
+++ b/apps/backend/express.js
@@ -1,21 +1,10 @@
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";
+import router from "./router.js";
const app = express();
-const router = express.Router();
-router.get("/", statsCard);
-router.get("/pin", repoCard);
-router.get("/top-langs", langCard);
-router.get("/wakatime", wakatimeCard);
-router.get("/gist", gistCard);
-
-app.use("/api", router);
+app.use(router);
const port = process.env.PORT || process.env.port || 9000;
app.listen(port, "0.0.0.0", () => {
diff --git a/apps/backend/index.js b/apps/backend/index.js
new file mode 100644
index 0000000000000..5dce81e4512de
--- /dev/null
+++ b/apps/backend/index.js
@@ -0,0 +1 @@
+export { default as router } from "./router.js";
diff --git a/apps/backend/package.json b/apps/backend/package.json
index e6ef6ed0a49ff..8d5a8adf99122 100644
--- a/apps/backend/package.json
+++ b/apps/backend/package.json
@@ -1,52 +1,29 @@
{
- "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",
+ "name": "@stats-organization/github-readme-stats-backend",
"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"
+ "license": "MIT",
+ "engines": {
+ "node": "24.x"
},
+ "private": true,
+ "main": "index.js",
"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"
+ "bench": "vitest bench --run --config vitest.config.bench.ts",
+ "lint": "eslint",
+ "typecheck": "tsc -p tsconfig.typecheck.json"
},
- "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",
+ "axios-mock-adapter": "2.1.0",
+ "express": "5.2.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"
+ "@stats-organization/github-readme-stats-core": "workspace:^",
+ "pg": "^8.18.0"
}
}
diff --git a/apps/backend/powered-by-vercel.svg b/apps/backend/powered-by-vercel.svg
deleted file mode 100644
index 8778286845d29..0000000000000
--- a/apps/backend/powered-by-vercel.svg
+++ /dev/null
@@ -1,6 +0,0 @@
-
diff --git a/apps/backend/router.js b/apps/backend/router.js
new file mode 100644
index 0000000000000..3da27ad514661
--- /dev/null
+++ b/apps/backend/router.js
@@ -0,0 +1,225 @@
+import {
+ api,
+ gist,
+ pin,
+ topLangs,
+ wakatime,
+} from "@stats-organization/github-readme-stats-core";
+
+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 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 userAccess } from "./api-renamed/user-access.js";
+import { default as wakatimeProxy } from "./api-renamed/wakatime-proxy.js";
+import { guardAccess } from "./src/common/access.js";
+import {
+ CACHE_TTL,
+ resolveCacheSeconds,
+ setCacheHeaders,
+ setErrorCacheHeaders,
+} from "./src/common/cache.js";
+import { getUserAccessByName, storeRequest } from "./src/common/database.js";
+
+const getGuardResult = (query, type, id) => {
+ const access = guardAccess({
+ id,
+ type,
+ colors: {
+ title_color: query.title_color,
+ text_color: query.text_color,
+ bg_color: query.bg_color,
+ border_color: query.border_color,
+ theme: query.theme,
+ },
+ });
+
+ if (access.isPassed) {
+ return null;
+ }
+
+ return {
+ status: "error - permanent",
+ content: access.result,
+ };
+};
+
+const getUserPat = async (username) => {
+ if (!username) {
+ return null;
+ }
+
+ const userAccess = await getUserAccessByName(username);
+ if (!userAccess?.token) {
+ return null;
+ }
+
+ return userAccess.token;
+};
+
+export default async (req, res) => {
+ const url = new URL(req.url, "https://localhost");
+ if (res.send === undefined) {
+ // remaining code expects express.js-like request and response objects
+ res.send = function (data) {
+ if (typeof data === "object") {
+ res.setHeader("Content-Type", "application/json");
+ res.end(JSON.stringify(data));
+ } else if (typeof data === "string") {
+ res.end(data);
+ } else {
+ res.end(String(data));
+ }
+ };
+ req.query = Object.fromEntries(url.searchParams.entries());
+ }
+
+ let result;
+
+ switch (url.pathname) {
+ case "/api": {
+ result = getGuardResult(req.query, "username", req.query.username);
+ if (!result) {
+ const userPat = await getUserPat(req.query.username);
+ result = await api(req.query, userPat);
+ }
+ if (result.status === "error - temporary") {
+ setErrorCacheHeaders(res);
+ } else {
+ const cacheSeconds = resolveCacheSeconds({
+ requested: parseInt(req.query.cache_seconds, 10),
+ def: CACHE_TTL.STATS_CARD.DEFAULT,
+ min: CACHE_TTL.STATS_CARD.MIN,
+ max: CACHE_TTL.STATS_CARD.MAX,
+ });
+ setCacheHeaders(res, cacheSeconds);
+ }
+ res.setHeader("Content-Type", "image/svg+xml");
+ res.end(result.content);
+ if (result.status !== "error - permanent") {
+ await storeRequest(req);
+ }
+ break;
+ }
+ case "/api/gist":
+ result =
+ getGuardResult(req.query, "gist", req.query.id) ??
+ (await gist(req.query));
+ if (result.status === "error - temporary") {
+ setErrorCacheHeaders(res);
+ } else {
+ const cacheSeconds = resolveCacheSeconds({
+ requested: parseInt(req.query.cache_seconds, 10),
+ def: CACHE_TTL.GIST_CARD.DEFAULT,
+ min: CACHE_TTL.GIST_CARD.MIN,
+ max: CACHE_TTL.GIST_CARD.MAX,
+ });
+ setCacheHeaders(res, cacheSeconds);
+ }
+ res.setHeader("Content-Type", "image/svg+xml");
+ res.end(result.content);
+ if (result.status !== "error - permanent") {
+ await storeRequest(req);
+ }
+ break;
+ case "/api/pin": {
+ result = getGuardResult(req.query, "username", req.query.username);
+ if (!result) {
+ const userPat = await getUserPat(req.query.username);
+ result = await pin(req.query, userPat);
+ }
+ if (result.status === "error - temporary") {
+ setErrorCacheHeaders(res);
+ } else {
+ const cacheSeconds = resolveCacheSeconds({
+ requested: parseInt(req.query.cache_seconds, 10),
+ def: CACHE_TTL.PIN_CARD.DEFAULT,
+ min: CACHE_TTL.PIN_CARD.MIN,
+ max: CACHE_TTL.PIN_CARD.MAX,
+ });
+ setCacheHeaders(res, cacheSeconds);
+ }
+ res.setHeader("Content-Type", "image/svg+xml");
+ res.end(result.content);
+ if (result.status !== "error - permanent") {
+ await storeRequest(req);
+ }
+ break;
+ }
+ case "/api/top-langs": {
+ result = getGuardResult(req.query, "username", req.query.username);
+ if (!result) {
+ const userPat = await getUserPat(req.query.username);
+ result = await topLangs(req.query, userPat);
+ }
+ if (result.status === "error - temporary") {
+ setErrorCacheHeaders(res);
+ } else {
+ const cacheSeconds = resolveCacheSeconds({
+ requested: parseInt(req.query.cache_seconds, 10),
+ def: CACHE_TTL.TOP_LANGS_CARD.DEFAULT,
+ min: CACHE_TTL.TOP_LANGS_CARD.MIN,
+ max: CACHE_TTL.TOP_LANGS_CARD.MAX,
+ });
+ setCacheHeaders(res, cacheSeconds);
+ }
+ res.setHeader("Content-Type", "image/svg+xml");
+ res.end(result.content);
+ if (result.status !== "error - permanent") {
+ await storeRequest(req);
+ }
+ break;
+ }
+ case "/api/wakatime":
+ result =
+ getGuardResult(req.query, "wakatime", req.query.username) ??
+ (await wakatime(req.query));
+ if (result.status === "error - temporary") {
+ setErrorCacheHeaders(res);
+ } else {
+ const cacheSeconds = resolveCacheSeconds({
+ requested: parseInt(req.query.cache_seconds, 10),
+ def: CACHE_TTL.WAKATIME_CARD.DEFAULT,
+ min: CACHE_TTL.WAKATIME_CARD.MIN,
+ max: CACHE_TTL.WAKATIME_CARD.MAX,
+ });
+ setCacheHeaders(res, cacheSeconds);
+ }
+ res.setHeader("Content-Type", "image/svg+xml");
+ res.end(result.content);
+ if (result.status !== "error - permanent") {
+ await storeRequest(req);
+ }
+ break;
+ case "/api/wakatime-proxy":
+ await wakatimeProxy(req, res);
+ break;
+ case "/api/repeat-recent":
+ await repeatRecent(req, res);
+ break;
+ case "/api/status/pat-info":
+ await patInfo(req, res);
+ break;
+ case "/api/status/up":
+ await statusUp(req, res);
+ break;
+ case "/api/authenticate":
+ await authenticate(req, res);
+ break;
+ case "/api/delete-user":
+ await deleteUser(req, res);
+ break;
+ case "/api/user-access":
+ await userAccess(req, res);
+ break;
+ case "/api/downgrade":
+ await downgrade(req, res);
+ break;
+ default:
+ res.statusCode = 404;
+ res.end("Not Found");
+ break;
+ }
+};
diff --git a/apps/backend/src/cards/index.js b/apps/backend/src/cards/index.js
deleted file mode 100644
index 5ca3a97adff02..0000000000000
--- a/apps/backend/src/cards/index.js
+++ /dev/null
@@ -1,4 +0,0 @@
-export { renderRepoCard } from "./repo.js";
-export { renderStatsCard } from "./stats.js";
-export { renderTopLanguages } from "./top-languages.js";
-export { renderWakatimeCard } from "./wakatime.js";
diff --git a/apps/backend/src/common/access.js b/apps/backend/src/common/access.js
index 05a5dbe5f1c6e..09cf5b30c2632 100644
--- a/apps/backend/src/common/access.js
+++ b/apps/backend/src/common/access.js
@@ -1,8 +1,11 @@
// @ts-check
+import {
+ getConfig,
+ renderError,
+} from "@stats-organization/github-readme-stats-core";
+
import { blacklist } from "./blacklist.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";
@@ -12,36 +15,35 @@ const BLACKLISTED_MESSAGE = "This username is blacklisted";
* Guards access using whitelist/blacklist.
*
* @param {Object} args The parameters object.
- * @param {any} args.res The response object.
* @param {string} args.id Resource identifier (username or gist id).
* @param {"username"|"gist"|"wakatime"} args.type The type of identifier.
* @param {{ title_color?: string, text_color?: string, bg_color?: string, border_color?: string, theme?: string }} args.colors Color options for the error card.
* @returns {{ isPassed: boolean, result?: any }} The result object indicating success or failure.
*/
-const guardAccess = ({ res, id, type, colors }) => {
+const guardAccess = ({ id, type, colors }) => {
if (!["username", "gist", "wakatime"].includes(type)) {
throw new Error(
'Invalid type. Expected "username", "gist", or "wakatime".',
);
}
- const currentWhitelist = type === "gist" ? gistWhitelist : whitelist;
+ const config = getConfig();
+ const currentWhitelist =
+ type === "gist" ? config.gistWhitelist : config.whitelist;
const notWhitelistedMsg =
type === "gist"
? NOT_WHITELISTED_GIST_MESSAGE
: NOT_WHITELISTED_USERNAME_MESSAGE;
if (Array.isArray(currentWhitelist) && !currentWhitelist.includes(id)) {
- const result = res.send(
- renderError({
- message: notWhitelistedMsg,
- secondaryMessage: "Please deploy your own instance",
- renderOptions: {
- ...colors,
- show_repo_link: false,
- },
- }),
- );
+ const result = renderError({
+ message: notWhitelistedMsg,
+ secondaryMessage: "Please deploy your own instance",
+ renderOptions: {
+ ...colors,
+ show_repo_link: false,
+ },
+ });
return { isPassed: false, result };
}
@@ -50,16 +52,14 @@ const guardAccess = ({ res, id, type, colors }) => {
currentWhitelist === undefined &&
blacklist.includes(id)
) {
- const result = res.send(
- renderError({
- message: BLACKLISTED_MESSAGE,
- secondaryMessage: "Please deploy your own instance",
- renderOptions: {
- ...colors,
- show_repo_link: false,
- },
- }),
- );
+ const result = renderError({
+ message: BLACKLISTED_MESSAGE,
+ secondaryMessage: "Please deploy your own instance",
+ renderOptions: {
+ ...colors,
+ show_repo_link: false,
+ },
+ });
return { isPassed: false, result };
}
diff --git a/apps/backend/src/common/cache.js b/apps/backend/src/common/cache.js
index 6fe2ea599d709..f71f300b9c92e 100644
--- a/apps/backend/src/common/cache.js
+++ b/apps/backend/src/common/cache.js
@@ -1,6 +1,6 @@
// @ts-check
-import { clampValue } from "./ops.js";
+import { clampValue } from "@stats-organization/github-readme-stats-core";
const MIN = 60;
const HOUR = 60 * MIN;
@@ -106,7 +106,7 @@ const disableCaching = (res) => {
* @param {number} cacheSeconds The cache seconds to set in the headers.
*/
const setCacheHeaders = (res, cacheSeconds) => {
- if (cacheSeconds < 1 || process.env.NODE_ENV === "development") {
+ if (cacheSeconds < 1) {
disableCaching(res);
return;
}
@@ -128,10 +128,7 @@ const setErrorCacheHeaders = (res) => {
const envCacheSeconds = process.env.CACHE_SECONDS
? parseInt(process.env.CACHE_SECONDS, 10)
: NaN;
- if (
- (!isNaN(envCacheSeconds) && envCacheSeconds < 1) ||
- process.env.NODE_ENV === "development"
- ) {
+ if (!isNaN(envCacheSeconds) && envCacheSeconds < 1) {
disableCaching(res);
return;
}
diff --git a/apps/backend/src/common/database.js b/apps/backend/src/common/database.js
index e76c3109247c2..8bf2e83f21aa4 100644
--- a/apps/backend/src/common/database.js
+++ b/apps/backend/src/common/database.js
@@ -1,14 +1,10 @@
-/**
- * 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({
- connectionString: process.env.POSTGRES_URL,
- })
- : null;
+export let pool = null;
+if (process.env.POSTGRES_URL) {
+ const { Pool } = await import("pg");
+ pool = new Pool({
+ connectionString: process.env.POSTGRES_URL,
+ });
+}
/**
* Creates all required tables if they do not exist.
diff --git a/apps/backend/src/common/envs.js b/apps/backend/src/common/envs.js
deleted file mode 100644
index 5f1319662b94d..0000000000000
--- a/apps/backend/src/common/envs.js
+++ /dev/null
@@ -1,15 +0,0 @@
-// @ts-check
-
-const whitelist = process.env.WHITELIST
- ? process.env.WHITELIST.split(",")
- : undefined;
-
-const gistWhitelist = process.env.GIST_WHITELIST
- ? process.env.GIST_WHITELIST.split(",")
- : undefined;
-
-const excludeRepositories = process.env.EXCLUDE_REPO
- ? process.env.EXCLUDE_REPO.split(",")
- : [];
-
-export { whitelist, gistWhitelist, excludeRepositories };
diff --git a/apps/backend/src/common/index.js b/apps/backend/src/common/index.js
deleted file mode 100644
index db914b86b1ebe..0000000000000
--- a/apps/backend/src/common/index.js
+++ /dev/null
@@ -1,13 +0,0 @@
-// @ts-check
-
-export { blacklist } from "./blacklist.js";
-export { Card } from "./Card.js";
-export { I18n } from "./I18n.js";
-export { icons } from "./icons.js";
-export { retryer } from "./retryer.js";
-export {
- ERROR_CARD_LENGTH,
- renderError,
- flexLayout,
- measureText,
-} from "./render.js";
diff --git a/apps/backend/src/index.js b/apps/backend/src/index.js
deleted file mode 100644
index ca8d586db136b..0000000000000
--- a/apps/backend/src/index.js
+++ /dev/null
@@ -1,2 +0,0 @@
-export * from "./common/index.js";
-export * from "./cards/index.js";
diff --git a/apps/backend/src/users.js b/apps/backend/src/users.js
index 12e87457fd740..3f40c8f84367d 100644
--- a/apps/backend/src/users.js
+++ b/apps/backend/src/users.js
@@ -80,6 +80,7 @@ async function githubAuthenticate(code, privateAccess) {
return { userId, accessToken, needDowngrade };
} catch (err) {
if (err.response) {
+ // eslint-disable-next-line preserve-caught-error
throw new Error(`OAuth Error: ${err.response.status}`);
}
throw err;
diff --git a/apps/backend/tests/__snapshots__/wakatime.test.js.snap b/apps/backend/tests/__snapshots__/wakatime.test.js.snap
deleted file mode 100644
index 34f060a5dde5a..0000000000000
--- a/apps/backend/tests/__snapshots__/wakatime.test.js.snap
+++ /dev/null
@@ -1,115 +0,0 @@
-// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
-
-exports[`Test /api/wakatime > should render error if user data is not accessible 1`] = `
-"
-
- "
-`;
diff --git a/apps/backend/tests/_setup.private-instance.js b/apps/backend/tests/_setup.private-instance.js
deleted file mode 100644
index f4f094e5f828b..0000000000000
--- a/apps/backend/tests/_setup.private-instance.js
+++ /dev/null
@@ -1,2 +0,0 @@
-process.env.GIST_WHITELIST = "bbfce31e0217a3689c8d961a356cb10d";
-process.env.WHITELIST = "anuraghazra";
diff --git a/apps/backend/tests/api.test.js b/apps/backend/tests/api.test.js
index 7aeef4ca01a95..e086abb524339 100644
--- a/apps/backend/tests/api.test.js
+++ b/apps/backend/tests/api.test.js
@@ -1,345 +1,182 @@
// @ts-check
-import axios from "axios";
-import MockAdapter from "axios-mock-adapter";
-import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
-
-import api from "../api-renamed/index.js";
-import { calculateRank } from "../src/calculateRank.js";
-import { renderStatsCard } from "../src/cards/stats.js";
-import { CACHE_TTL, DURATIONS } from "../src/common/cache.js";
-import { renderError } from "../src/common/render.js";
-
-import { data_stats, stats } from "./test-data/api-data.js";
-
-stats.rank = calculateRank({
- all_commits: false,
- commits: stats.totalCommits,
- prs: stats.totalPRs,
- reviews: stats.totalReviews,
- issues: stats.totalIssues,
- repos: 1,
- stars: stats.totalStars,
- followers: 0,
+import { beforeEach, describe, expect, it, vi } from "vitest";
+
+const mocks = vi.hoisted(() => ({
+ api: vi.fn(),
+ storeRequest: vi.fn(),
+ getUserAccessByName: vi.fn(),
+ config: {},
+}));
+
+vi.mock("@stats-organization/github-readme-stats-core", async () => {
+ const { mockCore } = await import("./utils.js");
+ return mockCore({ api: mocks.api, getConfig: () => mocks.config });
});
-const error = {
- errors: [
- {
- type: "NOT_FOUND",
- path: ["user"],
- locations: [],
- message: "Could not fetch user",
- },
- ],
-};
-
-const mock = new MockAdapter(axios);
+vi.mock("../src/common/database.js", () => ({
+ storeRequest: mocks.storeRequest,
+ getUserAccessByName: mocks.getUserAccessByName,
+}));
-// @ts-ignore
-const faker = (query, data) => {
- const req = {
- query: {
- username: "anuraghazra",
- ...query,
- },
- };
- const res = {
- setHeader: vi.fn(),
- send: vi.fn(),
- };
- mock.onPost("https://api.github.com/graphql").replyOnce(200, data);
-
- return { req, res };
-};
+import router from "../router.js";
+import { CACHE_TTL, DURATIONS } from "../src/common/cache.js";
-beforeEach(() => {
- process.env.CACHE_SECONDS = undefined;
+const createRequest = (search = "") => ({
+ headers: {},
+ url: `/api?${search}`,
});
-afterEach(() => {
- mock.reset();
+const createResponse = () => ({
+ end: vi.fn(),
+ setHeader: vi.fn(),
});
-describe("Test /api/", () => {
- it("should test the request", async () => {
- const { req, res } = faker({}, data_stats);
+const defaultCacheHeader =
+ `max-age=${CACHE_TTL.STATS_CARD.DEFAULT}, ` +
+ `s-maxage=${CACHE_TTL.STATS_CARD.DEFAULT}, ` +
+ `stale-while-revalidate=${DURATIONS.ONE_DAY}`;
- await api(req, res);
+const errorCacheHeader =
+ `max-age=${CACHE_TTL.ERROR}, ` +
+ `s-maxage=${CACHE_TTL.ERROR}, ` +
+ `stale-while-revalidate=${DURATIONS.ONE_DAY}`;
- expect(res.setHeader).toHaveBeenCalledWith("Content-Type", "image/svg+xml");
- expect(res.send).toHaveBeenCalledWith(
- renderStatsCard(stats, { ...req.query }),
- );
- });
-
- it("should render error card on error", async () => {
- const { req, res } = faker({}, error);
+beforeEach(() => {
+ mocks.api.mockReset();
+ mocks.storeRequest.mockReset().mockResolvedValue(undefined);
+ mocks.getUserAccessByName.mockReset().mockResolvedValue(null);
+ mocks.config = {};
+ // CACHE_SECONDS is not set here, this is just to safeguard against CACHE_SECONDS being set externally
+ delete process.env.CACHE_SECONDS;
+});
- await api(req, res);
+describe("Test /api backend routing", () => {
+ it("happy path should pass query params and user PAT, respond with stats content and persist request", async () => {
+ mocks.getUserAccessByName.mockResolvedValue({ token: "user-pat" });
+ mocks.api.mockResolvedValue({
+ status: "success",
+ content: "mock-stats-svg",
+ });
- expect(res.setHeader).toHaveBeenCalledWith("Content-Type", "image/svg+xml");
- expect(res.send).toHaveBeenCalledWith(
- renderError({
- message: error.errors[0].message,
- secondaryMessage:
- "Make sure the provided username is not an organization",
- }),
+ const req = createRequest(
+ "username=anuraghazra&theme=dark&hide=issues,prs,contribs",
);
- });
+ const res = createResponse();
- it("should render error card in same theme as requested card", async () => {
- const { req, res } = faker({ theme: "merko" }, error);
+ await router(req, res);
- await api(req, res);
-
- expect(res.setHeader).toHaveBeenCalledWith("Content-Type", "image/svg+xml");
- expect(res.send).toHaveBeenCalledWith(
- renderError({
- message: error.errors[0].message,
- secondaryMessage:
- "Make sure the provided username is not an organization",
- renderOptions: { theme: "merko" },
- }),
- );
- });
-
- it("should get the query options", async () => {
- const { req, res } = faker(
+ expect(mocks.getUserAccessByName).toHaveBeenCalledWith("anuraghazra");
+ expect(mocks.api).toHaveBeenCalledWith(
{
username: "anuraghazra",
+ theme: "dark",
hide: "issues,prs,contribs",
- show_icons: true,
- hide_border: true,
- line_height: 100,
- title_color: "fff",
- icon_color: "fff",
- text_color: "fff",
- bg_color: "fff",
},
- data_stats,
- );
-
- await api(req, res);
-
- expect(res.setHeader).toHaveBeenCalledWith("Content-Type", "image/svg+xml");
- expect(res.send).toHaveBeenCalledWith(
- renderStatsCard(stats, {
- hide: ["issues", "prs", "contribs"],
- show_icons: true,
- hide_border: true,
- line_height: 100,
- title_color: "fff",
- icon_color: "fff",
- text_color: "fff",
- bg_color: "fff",
- }),
+ "user-pat",
);
- });
-
- it("should have proper cache", async () => {
- const { req, res } = faker({}, data_stats);
-
- await api(req, res);
-
+ expect(req.query).toEqual({
+ username: "anuraghazra",
+ theme: "dark",
+ hide: "issues,prs,contribs",
+ });
expect(res.setHeader.mock.calls).toEqual([
+ ["Cache-Control", defaultCacheHeader],
["Content-Type", "image/svg+xml"],
- [
- "Cache-Control",
- `max-age=${CACHE_TTL.STATS_CARD.DEFAULT}, ` +
- `s-maxage=${CACHE_TTL.STATS_CARD.DEFAULT}, ` +
- `stale-while-revalidate=${DURATIONS.ONE_DAY}`,
- ],
]);
+ expect(res.end).toHaveBeenCalledExactlyOnceWith("mock-stats-svg");
+ expect(mocks.storeRequest).toHaveBeenCalledExactlyOnceWith(req);
});
- it("should set proper cache", async () => {
- const cache_seconds = DURATIONS.TWELVE_HOURS;
- const { req, res } = faker({ cache_seconds }, data_stats);
- await api(req, res);
+ it("should use the shorter error cache for temporary stats errors", async () => {
+ mocks.api.mockResolvedValue({
+ status: "error - temporary",
+ content: "temporary-error-svg",
+ });
- expect(res.setHeader.mock.calls).toEqual([
- ["Content-Type", "image/svg+xml"],
- [
- "Cache-Control",
- `max-age=${cache_seconds}, ` +
- `s-maxage=${cache_seconds}, ` +
- `stale-while-revalidate=${DURATIONS.ONE_DAY}`,
- ],
- ]);
- });
+ const req = createRequest("username=anuraghazra");
+ const res = createResponse();
- it("should set shorter cache when error", async () => {
- const { req, res } = faker({}, error);
- await api(req, res);
+ await router(req, res);
+ expect(mocks.getUserAccessByName).toHaveBeenCalledWith("anuraghazra");
+ expect(mocks.api).toHaveBeenCalledWith(
+ {
+ username: "anuraghazra",
+ },
+ null,
+ );
expect(res.setHeader.mock.calls).toEqual([
+ ["Cache-Control", errorCacheHeader],
["Content-Type", "image/svg+xml"],
- [
- "Cache-Control",
- `max-age=${CACHE_TTL.ERROR}, ` +
- `s-maxage=${CACHE_TTL.ERROR}, ` +
- `stale-while-revalidate=${DURATIONS.ONE_DAY}`,
- ],
]);
+ expect(res.end).toHaveBeenCalledExactlyOnceWith("temporary-error-svg");
+ expect(mocks.storeRequest).toHaveBeenCalledExactlyOnceWith(req);
});
- it("should properly set cache using CACHE_SECONDS env variable", async () => {
- const cacheSeconds = "10000";
- process.env.CACHE_SECONDS = cacheSeconds;
+ it("should not persist permanent stats errors returned by core", async () => {
+ mocks.api.mockResolvedValue({
+ status: "error - permanent",
+ content: "permanent-error-svg",
+ });
+
+ const req = createRequest("username=anuraghazra");
+ const res = createResponse();
- const { req, res } = faker({}, data_stats);
- await api(req, res);
+ await router(req, res);
+ expect(mocks.getUserAccessByName).toHaveBeenCalledWith("anuraghazra");
+ expect(mocks.api).toHaveBeenCalledWith(
+ {
+ username: "anuraghazra",
+ },
+ null,
+ );
expect(res.setHeader.mock.calls).toEqual([
+ ["Cache-Control", defaultCacheHeader],
["Content-Type", "image/svg+xml"],
- [
- "Cache-Control",
- `max-age=${cacheSeconds}, ` +
- `s-maxage=${cacheSeconds}, ` +
- `stale-while-revalidate=${DURATIONS.ONE_DAY}`,
- ],
]);
+ expect(res.end).toHaveBeenCalledExactlyOnceWith("permanent-error-svg");
+ expect(mocks.storeRequest).not.toHaveBeenCalled();
});
- it("should disable cache when CACHE_SECONDS is set to 0", async () => {
- process.env.CACHE_SECONDS = "0";
+ it("should reject blacklisted usernames before calling core logic", async () => {
+ const req = createRequest("username=renovate-bot");
+ const res = createResponse();
- const { req, res } = faker({}, data_stats);
- await api(req, res);
+ await router(req, res);
+ expect(mocks.api).not.toHaveBeenCalled();
+ expect(mocks.getUserAccessByName).not.toHaveBeenCalled();
expect(res.setHeader.mock.calls).toEqual([
+ ["Cache-Control", defaultCacheHeader],
["Content-Type", "image/svg+xml"],
- [
- "Cache-Control",
- "no-cache, no-store, must-revalidate, max-age=0, s-maxage=0",
- ],
- ["Pragma", "no-cache"],
- ["Expires", "0"],
]);
- });
-
- it("should set proper cache with clamped values", async () => {
- {
- let { req, res } = faker({ cache_seconds: 200_000 }, data_stats);
- await api(req, res);
-
- expect(res.setHeader.mock.calls).toEqual([
- ["Content-Type", "image/svg+xml"],
- [
- "Cache-Control",
- `max-age=${CACHE_TTL.STATS_CARD.MAX}, ` +
- `s-maxage=${CACHE_TTL.STATS_CARD.MAX}, ` +
- `stale-while-revalidate=${DURATIONS.ONE_DAY}`,
- ],
- ]);
- }
-
- // note i'm using block scoped vars
- {
- let { req, res } = faker({ cache_seconds: 0 }, data_stats);
- await api(req, res);
-
- expect(res.setHeader.mock.calls).toEqual([
- ["Content-Type", "image/svg+xml"],
- [
- "Cache-Control",
- `max-age=${CACHE_TTL.STATS_CARD.MIN}, ` +
- `s-maxage=${CACHE_TTL.STATS_CARD.MIN}, ` +
- `stale-while-revalidate=${DURATIONS.ONE_DAY}`,
- ],
- ]);
- }
-
- {
- let { req, res } = faker({ cache_seconds: -10_000 }, data_stats);
- await api(req, res);
-
- expect(res.setHeader.mock.calls).toEqual([
- ["Content-Type", "image/svg+xml"],
- [
- "Cache-Control",
- `max-age=${CACHE_TTL.STATS_CARD.MIN}, ` +
- `s-maxage=${CACHE_TTL.STATS_CARD.MIN}, ` +
- `stale-while-revalidate=${DURATIONS.ONE_DAY}`,
- ],
- ]);
- }
- });
-
- it("should allow changing ring_color", async () => {
- const { req, res } = faker(
- {
- username: "anuraghazra",
- hide: "issues,prs,contribs",
- show_icons: true,
- hide_border: true,
- line_height: 100,
- title_color: "fff",
- ring_color: "0000ff",
- icon_color: "fff",
- text_color: "fff",
- bg_color: "fff",
- },
- data_stats,
- );
-
- await api(req, res);
-
- expect(res.setHeader).toHaveBeenCalledWith("Content-Type", "image/svg+xml");
- expect(res.send).toHaveBeenCalledWith(
- renderStatsCard(stats, {
- hide: ["issues", "prs", "contribs"],
- show_icons: true,
- hide_border: true,
- line_height: 100,
- title_color: "fff",
- ring_color: "0000ff",
- icon_color: "fff",
- text_color: "fff",
- bg_color: "fff",
- }),
- );
- });
-
- it("should render error card when wrong locale is provided", async () => {
- const { req, res } = faker({ locale: "asdf" }, data_stats);
-
- await api(req, res);
-
- expect(res.setHeader).toHaveBeenCalledWith("Content-Type", "image/svg+xml");
- expect(res.send).toHaveBeenCalledWith(
- renderError({
- message: "Something went wrong",
- secondaryMessage: "Language not found",
- }),
+ expect(res.end).toHaveBeenCalledExactlyOnceWith(
+ "render-error:This username is blacklisted",
);
+ expect(mocks.storeRequest).not.toHaveBeenCalled();
});
- it("should render error card when include_all_commits true and upstream API fails", async () => {
- mock
- .onGet(
- "https://api.github.com/search/commits?per_page=1&q=author:anuraghazra",
- )
- .reply(200, { error: "Some test error message" });
+ it("should reject non-whitelisted usernames before calling core logic", async () => {
+ mocks.config = {
+ whitelist: ["allowed-user"],
+ };
- const { req, res } = faker(
- { username: "anuraghazra", include_all_commits: true },
- data_stats,
- );
+ const req = createRequest("username=blocked-user");
+ const res = createResponse();
- await api(req, res);
+ await router(req, res);
- expect(res.setHeader).toHaveBeenCalledWith("Content-Type", "image/svg+xml");
- expect(res.send).toHaveBeenCalledWith(
- renderError({
- message: "Could not fetch data from GitHub REST API.",
- secondaryMessage: "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",
+ expect(mocks.api).not.toHaveBeenCalled();
+ expect(mocks.getUserAccessByName).not.toHaveBeenCalled();
+ expect(res.setHeader.mock.calls).toEqual([
+ ["Cache-Control", defaultCacheHeader],
+ ["Content-Type", "image/svg+xml"],
+ ]);
+ expect(res.end).toHaveBeenCalledExactlyOnceWith(
+ "render-error:This username is not whitelisted",
);
+ expect(mocks.storeRequest).not.toHaveBeenCalled();
});
});
diff --git a/apps/backend/tests/bench/api.bench.js b/apps/backend/tests/bench/api.bench.js
index a7326840ff0b8..6b4a47aef68e5 100644
--- a/apps/backend/tests/bench/api.bench.js
+++ b/apps/backend/tests/bench/api.bench.js
@@ -1,81 +1,40 @@
import axios from "axios";
import MockAdapter from "axios-mock-adapter";
-import { bench, describe, vi } from "vitest";
+import { beforeAll, bench, describe, vi } from "vitest";
-import api from "../../api-renamed/index.js";
+import { data_stats } from "../utils.js";
-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 mock = new MockAdapter(axios);
-const data_stats = {
- data: {
- user: {
- name: stats.name,
- repositoriesContributedTo: { totalCount: stats.contributedTo },
- commits: {
- totalCommitContributions: stats.totalCommits,
- },
- reviews: {
- 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 createResponse = () => ({
+ end: vi.fn(),
+ setHeader: vi.fn(),
+});
-const mock = new MockAdapter(axios);
+let router;
-const faker = (query, data) => {
- const req = {
- query: {
- username: "anuraghazra",
- ...query,
- },
- };
- const res = {
- setHeader: vi.fn(),
- send: vi.fn(),
- };
- mock.onPost("https://api.github.com/graphql").replyOnce(200, data);
+beforeAll(async () => {
+ vi.stubEnv("CACHE_SECONDS", "");
+ vi.stubEnv("GIST_WHITELIST", "");
+ vi.stubEnv("POSTGRES_URL", "");
+ vi.stubEnv("WHITELIST", "");
+
+ ({ default: router } = await import("../../router.js"));
- return { req, res };
-};
+ mock.onPost("https://api.github.com/graphql").reply(200, data_stats);
+});
-describe("/api", () => {
+describe("bench /api", () => {
bench(
"base",
async () => {
- const { req, res } = faker({}, data_stats);
+ const req = {
+ headers: {},
+ url: "/api?username=anuraghazra",
+ };
+ const res = createResponse();
- await api(req, res);
+ await router(req, res);
},
{ warmupIterations: 50 },
);
diff --git a/apps/backend/tests/bench/calculateRank.bench.js b/apps/backend/tests/bench/calculateRank.bench.js
deleted file mode 100644
index a9e52d09bb367..0000000000000
--- a/apps/backend/tests/bench/calculateRank.bench.js
+++ /dev/null
@@ -1,22 +0,0 @@
-import { bench, describe } from "vitest";
-
-import { calculateRank } from "../../src/calculateRank.js";
-
-describe("calculateRank", () => {
- bench(
- "base",
- async () => {
- calculateRank({
- all_commits: false,
- commits: 1300,
- prs: 1500,
- issues: 4500,
- reviews: 1000,
- repos: 0,
- stars: 600000,
- followers: 50000,
- });
- },
- { warmupIterations: 50 },
- );
-});
diff --git a/apps/backend/tests/bench/gist.bench.js b/apps/backend/tests/bench/gist.bench.js
index 9b15c47c2890d..997a28f9ca68e 100644
--- a/apps/backend/tests/bench/gist.bench.js
+++ b/apps/backend/tests/bench/gist.bench.js
@@ -1,54 +1,42 @@
import axios from "axios";
import MockAdapter from "axios-mock-adapter";
-import { bench, describe, vi } from "vitest";
-
-import gist from "../../api-renamed/gist.js";
-
-const gist_data = {
- data: {
- viewer: {
- gist: {
- description:
- "List of countries and territories in English and Spanish: name, continent, capital, dial code, country codes, TLD, and area in sq km. Lista de países y territorios en Inglés y Español: nombre, continente, capital, código de teléfono, códigos de país, dominio y área en km cuadrados. Updated 2023",
- owner: {
- login: "Yizack",
- },
- stargazerCount: 33,
- forks: {
- totalCount: 11,
- },
- files: [
- {
- name: "countries.json",
- language: {
- name: "JSON",
- },
- size: 85858,
- },
- ],
- },
- },
- },
-};
+import { beforeAll, bench, describe, vi } from "vitest";
+
+import { happy_path_gist_data } from "../utils.js";
const mock = new MockAdapter(axios);
-mock.onPost("https://api.github.com/graphql").reply(200, gist_data);
-describe("test /api/gist", () => {
+const createResponse = () => ({
+ end: vi.fn(),
+ setHeader: vi.fn(),
+});
+
+let router;
+
+beforeAll(async () => {
+ vi.stubEnv("CACHE_SECONDS", "");
+ vi.stubEnv("GIST_WHITELIST", "");
+ vi.stubEnv("POSTGRES_URL", "");
+ vi.stubEnv("WHITELIST", "");
+
+ ({ default: router } = await import("../../router.js"));
+
+ mock
+ .onPost("https://api.github.com/graphql")
+ .reply(200, happy_path_gist_data);
+});
+
+describe("bench /api/gist", () => {
bench(
"base",
async () => {
const req = {
- query: {
- id: "bbfce31e0217a3689c8d961a356cb10d",
- },
- };
- const res = {
- setHeader: vi.fn(),
- send: vi.fn(),
+ headers: {},
+ url: "/api/gist?id=happy-gist-id",
};
+ const res = createResponse();
- await gist(req, res);
+ await router(req, res);
},
{ warmupIterations: 50 },
);
diff --git a/apps/backend/tests/bench/pin.bench.js b/apps/backend/tests/bench/pin.bench.js
index 790c3a1bfd0ef..f57acdda59391 100644
--- a/apps/backend/tests/bench/pin.bench.js
+++ b/apps/backend/tests/bench/pin.bench.js
@@ -1,53 +1,40 @@
import axios from "axios";
import MockAdapter from "axios-mock-adapter";
-import { bench, describe, vi } from "vitest";
+import { beforeAll, bench, describe, vi } from "vitest";
-import pin from "../../api-renamed/pin.js";
-
-const data_repo = {
- repository: {
- username: "anuraghazra",
- name: "convoychat",
- stargazers: {
- totalCount: 38000,
- },
- description: "Help us take over the world! React + TS + GraphQL Chat App",
- primaryLanguage: {
- color: "#2b7489",
- id: "MDg6TGFuZ3VhZ2UyODc=",
- name: "TypeScript",
- },
- forkCount: 100,
- isTemplate: false,
- },
-};
-
-const data_user = {
- data: {
- user: { repository: data_repo.repository },
- organization: null,
- },
-};
+import { data_user } from "../utils.js";
const mock = new MockAdapter(axios);
-mock.onPost("https://api.github.com/graphql").reply(200, data_user);
-describe("/api/pin", () => {
+const createResponse = () => ({
+ end: vi.fn(),
+ setHeader: vi.fn(),
+});
+
+let router;
+
+beforeAll(async () => {
+ vi.stubEnv("CACHE_SECONDS", "");
+ vi.stubEnv("GIST_WHITELIST", "");
+ vi.stubEnv("POSTGRES_URL", "");
+ vi.stubEnv("WHITELIST", "");
+
+ ({ default: router } = await import("../../router.js"));
+
+ mock.onPost("https://api.github.com/graphql").reply(200, data_user);
+});
+
+describe("bench /api/pin", () => {
bench(
"base",
async () => {
const req = {
- query: {
- username: "anuraghazra",
- repo: "convoychat",
- },
- };
- const res = {
- setHeader: vi.fn(),
- send: vi.fn(),
+ headers: {},
+ url: "/api/pin?username=anuraghazra&repo=convoychat",
};
+ const res = createResponse();
- await pin(req, res);
+ await router(req, res);
},
{ warmupIterations: 50 },
);
diff --git a/apps/backend/tests/e2e/e2e.test.js b/apps/backend/tests/e2e/e2e.test.js
index a923922aa4f81..3b13143f776f7 100644
--- a/apps/backend/tests/e2e/e2e.test.js
+++ b/apps/backend/tests/e2e/e2e.test.js
@@ -3,221 +3,343 @@
*/
import axios from "axios";
-import { beforeAll, describe, expect, test } from "vitest";
-
-import { renderGistCard } from "../../src/cards/gist.js";
-import { renderRepoCard } from "../../src/cards/repo.js";
-import { renderStatsCard } from "../../src/cards/stats.js";
-import { renderTopLanguages } from "../../src/cards/top-languages.js";
-import { renderWakatimeCard } from "../../src/cards/wakatime.js";
+import MockAdapter from "axios-mock-adapter";
+import { afterAll, beforeAll, describe, expect, test, vi } from "vitest";
const REPO = "curly-fiesta";
const USER = "catelinemnemosyne";
const STATS_CARD_USER = "e2eninja";
const GIST_ID = "372cef55fd897b31909fdeb3a7262758";
-const STATS_DATA = {
- name: "CodeNinja",
- totalPRs: 1,
- totalReviews: 0,
- totalCommits: 3,
- totalIssues: 1,
- totalStars: 1,
- contributedTo: 0,
- rank: {
- level: "C",
- percentile: 98.73972605284538,
+const STATS_MOCK_RESPONSE = {
+ data: {
+ user: {
+ name: "CodeNinja",
+ login: STATS_CARD_USER,
+ repositoriesContributedTo: { totalCount: 0 },
+ commits: {
+ totalCommitContributions: 3,
+ },
+ reviews: {
+ totalPullRequestReviewContributions: 0,
+ },
+ pullRequests: { totalCount: 1 },
+ openIssues: { totalCount: 1 },
+ closedIssues: { totalCount: 0 },
+ followers: { totalCount: 0 },
+ repositories: {
+ totalCount: 1,
+ nodes: [{ name: REPO, stargazers: { totalCount: 1 } }],
+ pageInfo: {
+ hasNextPage: false,
+ endCursor: "cursor",
+ },
+ },
+ },
},
};
-const LANGS_DATA = {
- HTML: {
- color: "#e34c26",
- name: "HTML",
- size: 1721,
- },
- CSS: {
- color: "#663399",
- name: "CSS",
- size: 930,
- },
- JavaScript: {
- color: "#f1e05a",
- name: "JavaScript",
- size: 1912,
+const COMMITS_SEARCH_MOCK_RESPONSE = {
+ total_count: 3,
+};
+
+const TOP_LANGS_MOCK_RESPONSE = {
+ data: {
+ user: {
+ repositories: {
+ nodes: [
+ {
+ name: REPO,
+ languages: {
+ edges: [
+ {
+ size: 1721,
+ node: {
+ color: "#e34c26",
+ name: "HTML",
+ },
+ },
+ {
+ size: 930,
+ node: {
+ color: "#663399",
+ name: "CSS",
+ },
+ },
+ {
+ size: 1912,
+ node: {
+ color: "#f1e05a",
+ name: "JavaScript",
+ },
+ },
+ ],
+ },
+ },
+ ],
+ },
+ },
},
};
-const WAKATIME_DATA = {
- human_readable_range: "last week",
- is_already_updating: false,
- is_coding_activity_visible: true,
- is_including_today: false,
- is_other_usage_visible: false,
- is_stuck: false,
- is_up_to_date: false,
- is_up_to_date_pending_future: false,
- percent_calculated: 0,
- range: "all_time",
- status: "pending_update",
- timeout: 15,
- username: USER,
- writes_only: false,
+const WAKATIME_MOCK_RESPONSE = {
+ data: {
+ human_readable_range: "last week",
+ is_already_updating: false,
+ is_coding_activity_visible: true,
+ is_including_today: false,
+ is_other_usage_visible: false,
+ is_stuck: false,
+ is_up_to_date: false,
+ is_up_to_date_pending_future: false,
+ percent_calculated: 0,
+ range: "all_time",
+ status: "pending_update",
+ timeout: 15,
+ username: USER,
+ writes_only: false,
+ },
};
-const REPOSITORY_DATA = {
- name: REPO,
- nameWithOwner: `${USER}/cra-test`,
- isPrivate: false,
- isArchived: false,
- isTemplate: false,
- stargazers: {
- totalCount: 1,
+const REPO_MOCK_RESPONSE = {
+ data: {
+ user: {
+ repository: {
+ name: REPO,
+ nameWithOwner: `${USER}/cra-test`,
+ isPrivate: false,
+ isArchived: false,
+ isTemplate: false,
+ stargazers: {
+ totalCount: 1,
+ },
+ description: "Simple cra test repo.",
+ primaryLanguage: {
+ color: "#f1e05a",
+ id: "MDg6TGFuZ3VhZ2UxNDA=",
+ name: "JavaScript",
+ },
+ forkCount: 0,
+ },
+ },
+ organization: null,
},
- description: "Simple cra test repo.",
- primaryLanguage: {
- color: "#f1e05a",
- id: "MDg6TGFuZ3VhZ2UxNDA=",
- name: "JavaScript",
+};
+
+const GIST_MOCK_RESPONSE = {
+ data: {
+ viewer: {
+ gist: {
+ description:
+ "Trying to access this path on Windows 10 ver. 1803+ will breaks NTFS",
+ owner: {
+ login: "qwerty541",
+ },
+ stargazerCount: 1,
+ forks: {
+ totalCount: 0,
+ },
+ files: [
+ {
+ name: "link.txt",
+ language: {
+ name: "Text",
+ },
+ size: 1,
+ },
+ ],
+ },
+ },
},
- forkCount: 0,
- starCount: 1,
};
-/**
- * @typedef {import("../../src/fetchers/types").GistData} GistData Gist data type.
- */
+const CACHE_BURST_STRING = `v=${new Date().getTime()}`;
+
+const mock = new MockAdapter(axios, { onNoMatch: "passthrough" });
+
+const createResponse = () => ({
+ end: vi.fn(),
+ setHeader: vi.fn(),
+});
+
+let router;
/**
- * @type {GistData}
+ * Renders a card locally through the backend router.
+ * @param {string} url Card URL to render through the router.
+ * @returns {Promise} Rendered SVG markup.
*/
-const GIST_DATA = {
- name: "link.txt",
- nameWithOwner: "qwerty541/link.txt",
- description:
- "Trying to access this path on Windows 10 ver. 1803+ will breaks NTFS",
- language: "Text",
- starsCount: 1,
- forksCount: 0,
-};
+async function getLocalSvg(url) {
+ const req = {
+ headers: {},
+ url,
+ };
+ const res = createResponse();
-const CACHE_BURST_STRING = `v=${new Date().getTime()}`;
+ await router(req, res);
-describe("Fetch Cards", () => {
- let VERCEL_PREVIEW_URL = "https://github-stats-extended.vercel.app";
+ expect(res.end).toHaveBeenCalledOnce();
+ return res.end.mock.calls[0][0];
+}
+
+beforeAll(async () => {
+ vi.stubEnv("CACHE_SECONDS", "");
+ vi.stubEnv("GIST_WHITELIST", "");
+ vi.stubEnv("POSTGRES_URL", "");
+ vi.stubEnv("WHITELIST", "");
+
+ vi.stubEnv("PAT_1", "dummyPAT1");
+ vi.stubEnv("PAT_2", "dummyPAT2");
+
+ ({ default: router } = await import("../../router.js"));
- beforeAll(() => {
- process.env.NODE_ENV = "development";
+ mock.onPost("https://api.github.com/graphql").reply((config) => {
+ const { query, variables } = JSON.parse(config.data);
+
+ if (
+ query.includes("query userInfo") &&
+ variables?.login === STATS_CARD_USER
+ ) {
+ return [200, STATS_MOCK_RESPONSE];
+ }
+
+ if (query.includes("query userInfo") && variables?.login === USER) {
+ return [200, TOP_LANGS_MOCK_RESPONSE];
+ }
+
+ if (query.includes("query getRepo")) {
+ return [200, REPO_MOCK_RESPONSE];
+ }
+
+ if (query.includes("query gistInfo")) {
+ return [200, GIST_MOCK_RESPONSE];
+ }
+
+ return [500, { error: "Unhandled GraphQL request in e2e test" }];
});
+ mock
+ .onGet(
+ `https://api.github.com/search/commits?per_page=1&q=author:${STATS_CARD_USER}`,
+ )
+ .reply(200, COMMITS_SEARCH_MOCK_RESPONSE);
+
+ mock
+ .onGet(
+ `https://wakatime.com/api/v1/users/${USER}/stats?is_including_today=true`,
+ )
+ .reply(200, WAKATIME_MOCK_RESPONSE);
+});
+
+afterAll(() => {
+ mock.restore();
+ vi.unstubAllEnvs();
+});
+
+describe("Fetch Cards", () => {
+ const VERCEL_PREVIEW_URL = "https://github-stats-extended-preview.vercel.app";
+
test("retrieve stats card", async () => {
expect(VERCEL_PREVIEW_URL).toBeDefined();
+ const cardPath = `/api?username=${STATS_CARD_USER}&include_all_commits=true&${CACHE_BURST_STRING}`;
+
// Check if the Vercel preview instance stats card function is up and running.
await expect(
- axios.get(`${VERCEL_PREVIEW_URL}/api?username=${STATS_CARD_USER}`),
+ axios.get(`${VERCEL_PREVIEW_URL}${cardPath}`),
).resolves.not.toThrow();
// Get local stats card.
- const localStatsCardSVG = renderStatsCard(STATS_DATA, {
- include_all_commits: true,
- });
+ const localStatsCardSVG = await getLocalSvg(cardPath);
// Get the Vercel preview stats card response.
- const serverStatsSvg = await axios.get(
- `${VERCEL_PREVIEW_URL}/api?username=${STATS_CARD_USER}&include_all_commits=true&${CACHE_BURST_STRING}`,
- );
+ const serverStatsSvg = await axios.get(`${VERCEL_PREVIEW_URL}${cardPath}`);
// Check if stats card from deployment matches the stats card from local.
expect(serverStatsSvg.data).toEqual(localStatsCardSVG);
- }, 15000);
+ }, 20000);
test("retrieve language card", async () => {
expect(VERCEL_PREVIEW_URL).toBeDefined();
+ const cardPath = `/api/top-langs?username=${USER}&${CACHE_BURST_STRING}`;
+
// Check if the Vercel preview instance language card function is up and running.
- console.log(
- `${VERCEL_PREVIEW_URL}/api/top-langs/?username=${USER}&${CACHE_BURST_STRING}`,
- );
await expect(
- axios.get(
- `${VERCEL_PREVIEW_URL}/api/top-langs/?username=${USER}&${CACHE_BURST_STRING}`,
- ),
+ axios.get(`${VERCEL_PREVIEW_URL}${cardPath}`),
).resolves.not.toThrow();
// Get local language card.
- const localLanguageCardSVG = renderTopLanguages(LANGS_DATA);
+ const localLanguageCardSVG = await getLocalSvg(cardPath);
// Get the Vercel preview language card response.
- const severLanguageSVG = await axios.get(
- `${VERCEL_PREVIEW_URL}/api/top-langs/?username=${USER}&${CACHE_BURST_STRING}`,
+ const serverLanguageSVG = await axios.get(
+ `${VERCEL_PREVIEW_URL}${cardPath}`,
);
// Check if language card from deployment matches the local language card.
- expect(severLanguageSVG.data).toEqual(localLanguageCardSVG);
- }, 15000);
+ expect(serverLanguageSVG.data).toEqual(localLanguageCardSVG);
+ }, 20000);
test("retrieve WakaTime card", async () => {
expect(VERCEL_PREVIEW_URL).toBeDefined();
+ const cardPath = `/api/wakatime?username=${USER}&${CACHE_BURST_STRING}`;
+
// Check if the Vercel preview instance WakaTime function is up and running.
await expect(
- axios.get(`${VERCEL_PREVIEW_URL}/api/wakatime?username=${USER}`),
+ axios.get(`${VERCEL_PREVIEW_URL}${cardPath}`),
).resolves.not.toThrow();
// Get local WakaTime card.
- const localWakaCardSVG = renderWakatimeCard(WAKATIME_DATA);
+ const localWakaCardSVG = await getLocalSvg(cardPath);
// Get the Vercel preview WakaTime card response.
const serverWakaTimeSvg = await axios.get(
- `${VERCEL_PREVIEW_URL}/api/wakatime?username=${USER}&${CACHE_BURST_STRING}`,
+ `${VERCEL_PREVIEW_URL}${cardPath}`,
);
// Check if WakaTime card from deployment matches the local WakaTime card.
expect(serverWakaTimeSvg.data).toEqual(localWakaCardSVG);
- }, 15000);
+ }, 20000);
test("retrieve repo card", async () => {
expect(VERCEL_PREVIEW_URL).toBeDefined();
+ const cardPath = `/api/pin?username=${USER}&repo=${REPO}&${CACHE_BURST_STRING}`;
+
// Check if the Vercel preview instance Repo function is up and running.
await expect(
- axios.get(
- `${VERCEL_PREVIEW_URL}/api/pin/?username=${USER}&repo=${REPO}&${CACHE_BURST_STRING}`,
- ),
+ axios.get(`${VERCEL_PREVIEW_URL}${cardPath}`),
).resolves.not.toThrow();
// Get local repo card.
- const localRepoCardSVG = renderRepoCard(REPOSITORY_DATA);
+ const localRepoCardSVG = await getLocalSvg(cardPath);
// Get the Vercel preview repo card response.
- const serverRepoSvg = await axios.get(
- `${VERCEL_PREVIEW_URL}/api/pin/?username=${USER}&repo=${REPO}&${CACHE_BURST_STRING}`,
- );
+ const serverRepoSvg = await axios.get(`${VERCEL_PREVIEW_URL}${cardPath}`);
// Check if Repo card from deployment matches the local Repo card.
expect(serverRepoSvg.data).toEqual(localRepoCardSVG);
- }, 15000);
+ }, 20000);
test("retrieve gist card", async () => {
expect(VERCEL_PREVIEW_URL).toBeDefined();
+ const cardPath = `/api/gist?id=${GIST_ID}&${CACHE_BURST_STRING}`;
+
// Check if the Vercel preview instance Gist function is up and running.
await expect(
- axios.get(
- `${VERCEL_PREVIEW_URL}/api/gist?id=${GIST_ID}&${CACHE_BURST_STRING}`,
- ),
+ axios.get(`${VERCEL_PREVIEW_URL}${cardPath}`),
).resolves.not.toThrow();
// Get local gist card.
- const localGistCardSVG = renderGistCard(GIST_DATA);
+ const localGistCardSVG = await getLocalSvg(cardPath);
// Get the Vercel preview gist card response.
- const serverGistSvg = await axios.get(
- `${VERCEL_PREVIEW_URL}/api/gist?id=${GIST_ID}&${CACHE_BURST_STRING}`,
- );
+ const serverGistSvg = await axios.get(`${VERCEL_PREVIEW_URL}${cardPath}`);
// Check if Gist card from deployment matches the local Gist card.
expect(serverGistSvg.data).toEqual(localGistCardSVG);
- }, 15000);
+ }, 20000);
});
diff --git a/apps/backend/tests/gist.test.js b/apps/backend/tests/gist.test.js
index 2cb6d4c3b53b3..8ee563791e66f 100644
--- a/apps/backend/tests/gist.test.js
+++ b/apps/backend/tests/gist.test.js
@@ -1,161 +1,150 @@
// @ts-check
-import axios from "axios";
-import MockAdapter from "axios-mock-adapter";
-import { afterEach, describe, expect, it, vi } from "vitest";
-
-import gist from "../api-renamed/gist.js";
-import { renderGistCard } from "../src/cards/gist.js";
-import { CACHE_TTL, DURATIONS } from "../src/common/cache.js";
-import { renderError } from "../src/common/render.js";
+import { beforeEach, describe, expect, it, vi } from "vitest";
+
+const mocks = vi.hoisted(() => ({
+ gist: vi.fn(),
+ storeRequest: vi.fn(),
+ getUserAccessByName: vi.fn(),
+ config: {},
+}));
+
+vi.mock("@stats-organization/github-readme-stats-core", async () => {
+ const { mockCore } = await import("./utils.js");
+ return mockCore({ gist: mocks.gist, getConfig: () => mocks.config });
+});
-import { gist_data } from "./test-data/gist-data.js";
+vi.mock("../src/common/database.js", () => ({
+ storeRequest: mocks.storeRequest,
+ getUserAccessByName: mocks.getUserAccessByName,
+}));
-import "@testing-library/jest-dom/vitest";
+import router from "../router.js";
+import { CACHE_TTL, DURATIONS } from "../src/common/cache.js";
-const gist_not_found_data = {
- data: {
- viewer: {
- gist: null,
- },
- },
-};
+const createRequest = (search) => ({
+ headers: {},
+ url: `/api/gist?${search}`,
+});
-const mock = new MockAdapter(axios);
+const createResponse = () => ({
+ end: vi.fn(),
+ setHeader: vi.fn(),
+});
-afterEach(() => {
- mock.reset();
+const defaultCacheHeader =
+ `max-age=${CACHE_TTL.GIST_CARD.DEFAULT}, ` +
+ `s-maxage=${CACHE_TTL.GIST_CARD.DEFAULT}, ` +
+ `stale-while-revalidate=${DURATIONS.ONE_DAY}`;
+
+const errorCacheHeader =
+ `max-age=${CACHE_TTL.ERROR}, ` +
+ `s-maxage=${CACHE_TTL.ERROR}, ` +
+ `stale-while-revalidate=${DURATIONS.ONE_DAY}`;
+
+beforeEach(() => {
+ mocks.gist.mockReset();
+ mocks.storeRequest.mockReset().mockResolvedValue(undefined);
+ mocks.getUserAccessByName.mockReset().mockResolvedValue(null);
+ mocks.config = {};
+ // CACHE_SECONDS is not set here, this is just to safeguard against CACHE_SECONDS being set externally
+ delete process.env.CACHE_SECONDS;
});
-describe("Test /api/gist", () => {
- it("should test the request", async () => {
- const req = {
- query: {
- id: "bbfce31e0217a3689c8d961a356cb10d",
- },
- };
- const res = {
- setHeader: vi.fn(),
- send: vi.fn(),
- };
- mock.onPost("https://api.github.com/graphql").reply(200, gist_data);
-
- await gist(req, res);
-
- expect(res.setHeader).toHaveBeenCalledWith("Content-Type", "image/svg+xml");
- expect(res.send).toHaveBeenCalledWith(
- renderGistCard({
- name: gist_data.data.viewer.gist.files[0].name,
- nameWithOwner: `${gist_data.data.viewer.gist.owner.login}/${gist_data.data.viewer.gist.files[0].name}`,
- description: gist_data.data.viewer.gist.description,
- language: gist_data.data.viewer.gist.files[0].language.name,
- starsCount: gist_data.data.viewer.gist.stargazerCount,
- forksCount: gist_data.data.viewer.gist.forks.totalCount,
- }),
- );
+describe("Test /api/gist backend routing", () => {
+ it("happy path should pass query params, respond with gist content and persist request", async () => {
+ mocks.gist.mockResolvedValue({
+ status: "success",
+ content: "mock-gist-svg",
+ });
+
+ const req = createRequest("id=bbfce31e0217a3689c8d961a356cb10d&theme=dark");
+ const res = createResponse();
+
+ await router(req, res);
+
+ expect(mocks.gist).toHaveBeenCalledWith({
+ id: "bbfce31e0217a3689c8d961a356cb10d",
+ theme: "dark",
+ });
+ expect(mocks.getUserAccessByName).not.toHaveBeenCalled();
+ expect(req.query).toEqual({
+ id: "bbfce31e0217a3689c8d961a356cb10d",
+ theme: "dark",
+ });
+ expect(res.setHeader.mock.calls).toEqual([
+ ["Cache-Control", defaultCacheHeader],
+ ["Content-Type", "image/svg+xml"],
+ ]);
+ expect(res.end).toHaveBeenCalledExactlyOnceWith("mock-gist-svg");
+ expect(mocks.storeRequest).toHaveBeenCalledExactlyOnceWith(req);
});
- it("should get the query options", async () => {
- const req = {
- query: {
- id: "bbfce31e0217a3689c8d961a356cb10d",
- title_color: "fff",
- icon_color: "fff",
- text_color: "fff",
- bg_color: "fff",
- show_owner: true,
- },
- };
- const res = {
- setHeader: vi.fn(),
- send: vi.fn(),
- };
- mock.onPost("https://api.github.com/graphql").reply(200, gist_data);
-
- await gist(req, res);
-
- expect(res.setHeader).toHaveBeenCalledWith("Content-Type", "image/svg+xml");
- expect(res.send).toHaveBeenCalledWith(
- renderGistCard(
- {
- name: gist_data.data.viewer.gist.files[0].name,
- nameWithOwner: `${gist_data.data.viewer.gist.owner.login}/${gist_data.data.viewer.gist.files[0].name}`,
- description: gist_data.data.viewer.gist.description,
- language: gist_data.data.viewer.gist.files[0].language.name,
- starsCount: gist_data.data.viewer.gist.stargazerCount,
- forksCount: gist_data.data.viewer.gist.forks.totalCount,
- },
- { ...req.query },
- ),
- );
+ it("should use the shorter error cache for temporary gist errors", async () => {
+ mocks.gist.mockResolvedValue({
+ status: "error - temporary",
+ content: "temporary-error-svg",
+ });
+
+ const req = createRequest("id=bbfce31e0217a3689c8d961a356cb10d");
+ const res = createResponse();
+
+ await router(req, res);
+
+ expect(mocks.gist).toHaveBeenCalledWith({
+ id: "bbfce31e0217a3689c8d961a356cb10d",
+ });
+ expect(mocks.getUserAccessByName).not.toHaveBeenCalled();
+ expect(res.setHeader.mock.calls).toEqual([
+ ["Cache-Control", errorCacheHeader],
+ ["Content-Type", "image/svg+xml"],
+ ]);
+ expect(res.end).toHaveBeenCalledExactlyOnceWith("temporary-error-svg");
+ expect(mocks.storeRequest).toHaveBeenCalledExactlyOnceWith(req);
});
- it("should render error if gist is not found", async () => {
- const req = {
- query: {
- id: "bbfce31e0217a3689c8d961a356cb10d",
- },
- };
- const res = {
- setHeader: vi.fn(),
- send: vi.fn(),
- };
- mock
- .onPost("https://api.github.com/graphql")
- .reply(200, gist_not_found_data);
-
- await gist(req, res);
-
- expect(res.setHeader).toHaveBeenCalledWith("Content-Type", "image/svg+xml");
- expect(res.send).toHaveBeenCalledWith(
- renderError({ message: "Gist not found" }),
- );
+ it("should not persist permanent gist errors returned by core", async () => {
+ mocks.gist.mockResolvedValue({
+ status: "error - permanent",
+ content: "permanent-error-svg",
+ });
+
+ const req = createRequest("id=bbfce31e0217a3689c8d961a356cb10d");
+ const res = createResponse();
+
+ await router(req, res);
+
+ expect(mocks.gist).toHaveBeenCalledWith({
+ id: "bbfce31e0217a3689c8d961a356cb10d",
+ });
+ expect(mocks.getUserAccessByName).not.toHaveBeenCalled();
+ expect(res.setHeader.mock.calls).toEqual([
+ ["Cache-Control", defaultCacheHeader],
+ ["Content-Type", "image/svg+xml"],
+ ]);
+ expect(res.end).toHaveBeenCalledExactlyOnceWith("permanent-error-svg");
+ expect(mocks.storeRequest).not.toHaveBeenCalled();
});
- it("should render error if wrong locale is provided", async () => {
- const req = {
- query: {
- id: "bbfce31e0217a3689c8d961a356cb10d",
- locale: "asdf",
- },
- };
- const res = {
- setHeader: vi.fn(),
- send: vi.fn(),
+ it("should reject non-whitelisted gist ids before calling core logic", async () => {
+ mocks.config = {
+ gistWhitelist: ["allowed-gist-id"],
};
- mock.onPost("https://api.github.com/graphql").reply(200, gist_data);
- await gist(req, res);
-
- expect(res.setHeader).toHaveBeenCalledWith("Content-Type", "image/svg+xml");
- expect(res.send).toHaveBeenCalledWith(
- renderError({
- message: "Something went wrong",
- secondaryMessage: "Language not found",
- }),
- );
- });
-
- it("should have proper cache", async () => {
- const req = {
- query: {
- id: "bbfce31e0217a3689c8d961a356cb10d",
- },
- };
- const res = {
- setHeader: vi.fn(),
- send: vi.fn(),
- };
- mock.onPost("https://api.github.com/graphql").reply(200, gist_data);
+ const req = createRequest("id=blocked-gist-id");
+ const res = createResponse();
- await gist(req, res);
+ await router(req, res);
- expect(res.setHeader).toHaveBeenCalledWith("Content-Type", "image/svg+xml");
- expect(res.setHeader).toHaveBeenCalledWith(
- "Cache-Control",
- `max-age=${CACHE_TTL.GIST_CARD.DEFAULT}, ` +
- `s-maxage=${CACHE_TTL.GIST_CARD.DEFAULT}, ` +
- `stale-while-revalidate=${DURATIONS.ONE_DAY}`,
+ expect(mocks.gist).not.toHaveBeenCalled();
+ expect(mocks.getUserAccessByName).not.toHaveBeenCalled();
+ expect(res.setHeader.mock.calls).toEqual([
+ ["Cache-Control", defaultCacheHeader],
+ ["Content-Type", "image/svg+xml"],
+ ]);
+ expect(res.end).toHaveBeenCalledExactlyOnceWith(
+ "render-error:This gist ID is not whitelisted",
);
+ expect(mocks.storeRequest).not.toHaveBeenCalled();
});
});
diff --git a/apps/backend/tests/pat-info.test.js b/apps/backend/tests/pat-info.test.js
index 3e98fa8b808ca..ef01b9bae4233 100644
--- a/apps/backend/tests/pat-info.test.js
+++ b/apps/backend/tests/pat-info.test.js
@@ -6,8 +6,6 @@ import axios from "axios";
import MockAdapter from "axios-mock-adapter";
import { afterEach, beforeAll, describe, expect, it, vi } from "vitest";
-import patInfo, { RATE_LIMIT_SECONDS } from "../api-renamed/status/pat-info.js";
-
const mock = new MockAdapter(axios);
const successData = {
@@ -57,20 +55,26 @@ const bad_credentials_error = {
message: "Bad credentials",
};
+let RATE_LIMIT_SECONDS, patInfo;
+
+beforeAll(async () => {
+ vi.stubEnv("PAT_1", "testPAT1");
+ vi.stubEnv("PAT_2", "testPAT2");
+ vi.stubEnv("PAT_3", "testPAT3");
+ vi.stubEnv("PAT_4", "testPAT4");
+
+ ({ RATE_LIMIT_SECONDS, default: patInfo } =
+ await import("../api-renamed/status/pat-info.js"));
+});
+
afterEach(() => {
mock.reset();
+ vi.unstubAllEnvs();
+ // modules may cache environment variables, so we need to reset them
+ vi.resetModules();
});
describe("Test /api/status/pat-info", () => {
- beforeAll(() => {
- // reset patenv first so that they are not populated with local envs
- process.env = {};
- process.env.PAT_1 = "testPAT1";
- process.env.PAT_2 = "testPAT2";
- process.env.PAT_3 = "testPAT3";
- process.env.PAT_4 = "testPAT4";
- });
-
it("should return only 'validPATs' if all PATs are valid", async () => {
mock
.onPost("https://api.github.com/graphql")
@@ -243,7 +247,6 @@ describe("Test /api/status/pat-info", () => {
});
it("should have proper cache when error is thrown", async () => {
- mock.reset();
mock.onPost("https://api.github.com/graphql").networkError();
const { req, res } = faker({}, {});
diff --git a/apps/backend/tests/pin.test.js b/apps/backend/tests/pin.test.js
index b9fa03566dcbc..628e68fa07fd9 100644
--- a/apps/backend/tests/pin.test.js
+++ b/apps/backend/tests/pin.test.js
@@ -1,175 +1,83 @@
// @ts-check
-import axios from "axios";
-import MockAdapter from "axios-mock-adapter";
-import { afterEach, describe, expect, it, vi } from "vitest";
+import { beforeEach, describe, expect, it, vi } from "vitest";
-import pin from "../api-renamed/pin.js";
-import { renderRepoCard } from "../src/cards/repo.js";
-import { CACHE_TTL, DURATIONS } from "../src/common/cache.js";
-import { renderError } from "../src/common/render.js";
-
-import { data_repo, data_user } from "./test-data/pin-data.js";
-
-import "@testing-library/jest-dom/vitest";
+const mocks = vi.hoisted(() => ({
+ pin: vi.fn(),
+ storeRequest: vi.fn(),
+ getUserAccessByName: vi.fn(),
+}));
-const mock = new MockAdapter(axios);
-
-afterEach(() => {
- mock.reset();
+vi.mock("@stats-organization/github-readme-stats-core", async () => {
+ const { mockCore } = await import("./utils.js");
+ return mockCore({ pin: mocks.pin });
});
-describe("Test /api/pin", () => {
- it("should test the request", async () => {
- const req = {
- query: {
- username: "anuraghazra",
- repo: "convoychat",
- },
- };
- const res = {
- setHeader: vi.fn(),
- send: vi.fn(),
- };
- mock.onPost("https://api.github.com/graphql").reply(200, data_user);
-
- await pin(req, res);
-
- expect(res.setHeader).toHaveBeenCalledWith("Content-Type", "image/svg+xml");
- expect(res.send).toHaveBeenCalledWith(
- // @ts-ignore
- renderRepoCard({
- ...data_repo.repository,
- starCount: data_repo.repository.stargazers.totalCount,
- }),
- );
- });
+vi.mock("../src/common/database.js", () => ({
+ storeRequest: mocks.storeRequest,
+ getUserAccessByName: mocks.getUserAccessByName,
+}));
- it("should get the query options", async () => {
- const req = {
- query: {
- username: "anuraghazra",
- repo: "convoychat",
- title_color: "fff",
- icon_color: "fff",
- text_color: "fff",
- bg_color: "fff",
- full_name: "1",
- },
- };
- const res = {
- setHeader: vi.fn(),
- send: vi.fn(),
- };
- mock.onPost("https://api.github.com/graphql").reply(200, data_user);
-
- await pin(req, res);
-
- expect(res.setHeader).toHaveBeenCalledWith("Content-Type", "image/svg+xml");
- expect(res.send).toHaveBeenCalledWith(
- renderRepoCard(
- // @ts-ignore
- {
- ...data_repo.repository,
- starCount: data_repo.repository.stargazers.totalCount,
- },
- { ...req.query },
- ),
- );
- });
-
- it("should render error card if user repo not found", async () => {
- const req = {
- query: {
- username: "anuraghazra",
- repo: "convoychat",
- },
- };
- const res = {
- setHeader: vi.fn(),
- send: vi.fn(),
- };
- mock
- .onPost("https://api.github.com/graphql")
- .reply(200, { data: { user: { repository: null }, organization: null } });
+import router from "../router.js";
+import { CACHE_TTL, DURATIONS } from "../src/common/cache.js";
- await pin(req, res);
+const createRequest = (search = "") => ({
+ headers: {},
+ url: `/api/pin?${search}`,
+});
- expect(res.setHeader).toHaveBeenCalledWith("Content-Type", "image/svg+xml");
- expect(res.send).toHaveBeenCalledWith(
- renderError({ message: "User Repository Not found" }),
- );
- });
+const createResponse = () => ({
+ end: vi.fn(),
+ setHeader: vi.fn(),
+});
- it("should render error card if org repo not found", async () => {
- const req = {
- query: {
- username: "anuraghazra",
- repo: "convoychat",
- },
- };
- const res = {
- setHeader: vi.fn(),
- send: vi.fn(),
- };
- mock
- .onPost("https://api.github.com/graphql")
- .reply(200, { data: { user: null, organization: { repository: null } } });
+const defaultCacheHeader =
+ `max-age=${CACHE_TTL.PIN_CARD.DEFAULT}, ` +
+ `s-maxage=${CACHE_TTL.PIN_CARD.DEFAULT}, ` +
+ `stale-while-revalidate=${DURATIONS.ONE_DAY}`;
+
+beforeEach(() => {
+ mocks.pin.mockReset();
+ mocks.storeRequest.mockReset().mockResolvedValue(undefined);
+ mocks.getUserAccessByName.mockReset().mockResolvedValue(null);
+ // CACHE_SECONDS is not set here, this is just to safeguard against CACHE_SECONDS being set externally
+ delete process.env.CACHE_SECONDS;
+});
- await pin(req, res);
+describe("Test /api/pin backend routing", () => {
+ it("happy path should pass query params and user PAT, respond with pin content and persist request", async () => {
+ mocks.getUserAccessByName.mockResolvedValue({ token: "user-pat" });
+ mocks.pin.mockResolvedValue({
+ status: "success",
+ content: "mock-pin-svg",
+ });
- expect(res.setHeader).toHaveBeenCalledWith("Content-Type", "image/svg+xml");
- expect(res.send).toHaveBeenCalledWith(
- renderError({ message: "Organization Repository Not found" }),
+ const req = createRequest(
+ "username=anuraghazra&repo=convoychat&theme=dark",
);
- });
-
- it("should render error card if wrong locale provided", async () => {
- const req = {
- query: {
- username: "anuraghazra",
- repo: "convoychat",
- locale: "asdf",
- },
- };
- const res = {
- setHeader: vi.fn(),
- send: vi.fn(),
- };
- mock.onPost("https://api.github.com/graphql").reply(200, data_user);
+ const res = createResponse();
- await pin(req, res);
+ await router(req, res);
- expect(res.setHeader).toHaveBeenCalledWith("Content-Type", "image/svg+xml");
- expect(res.send).toHaveBeenCalledWith(
- renderError({
- message: "Something went wrong",
- secondaryMessage: "Language not found",
- }),
- );
- });
-
- it("should have proper cache", async () => {
- const req = {
- query: {
+ expect(mocks.getUserAccessByName).toHaveBeenCalledWith("anuraghazra");
+ expect(mocks.pin).toHaveBeenCalledWith(
+ {
username: "anuraghazra",
repo: "convoychat",
+ theme: "dark",
},
- };
- const res = {
- setHeader: vi.fn(),
- send: vi.fn(),
- };
- mock.onPost("https://api.github.com/graphql").reply(200, data_user);
-
- await pin(req, res);
-
- expect(res.setHeader).toHaveBeenCalledWith("Content-Type", "image/svg+xml");
- expect(res.setHeader).toHaveBeenCalledWith(
- "Cache-Control",
- `max-age=${CACHE_TTL.PIN_CARD.DEFAULT}, ` +
- `s-maxage=${CACHE_TTL.PIN_CARD.DEFAULT}, ` +
- `stale-while-revalidate=${DURATIONS.ONE_DAY}`,
+ "user-pat",
);
+ expect(req.query).toEqual({
+ username: "anuraghazra",
+ repo: "convoychat",
+ theme: "dark",
+ });
+ expect(res.setHeader.mock.calls).toEqual([
+ ["Cache-Control", defaultCacheHeader],
+ ["Content-Type", "image/svg+xml"],
+ ]);
+ expect(res.end).toHaveBeenCalledExactlyOnceWith("mock-pin-svg");
+ expect(mocks.storeRequest).toHaveBeenCalledExactlyOnceWith(req);
});
});
diff --git a/apps/backend/tests/private-instance/api.test.js b/apps/backend/tests/private-instance/api.test.js
deleted file mode 100644
index b422a3b13ef14..0000000000000
--- a/apps/backend/tests/private-instance/api.test.js
+++ /dev/null
@@ -1,41 +0,0 @@
-// @ts-check
-
-import axios from "axios";
-import MockAdapter from "axios-mock-adapter";
-import { afterEach, describe, expect, it, vi } from "vitest";
-
-import api from "../../api-renamed/index.js";
-import { renderError } from "../../src/common/render.js";
-import { data_stats } from "../test-data/api-data.js";
-
-const mock = new MockAdapter(axios);
-
-afterEach(() => {
- mock.reset();
-});
-
-describe("Test /api/", () => {
- it("should render error card if username not in whitelist", async () => {
- const req = {
- query: {
- username: "renovate-bot",
- },
- };
- const res = {
- setHeader: vi.fn(),
- send: vi.fn(),
- };
- mock.onPost("https://api.github.com/graphql").replyOnce(200, data_stats);
-
- await api(req, res);
-
- expect(res.setHeader).toHaveBeenCalledWith("Content-Type", "image/svg+xml");
- expect(res.send).toHaveBeenCalledWith(
- renderError({
- message: "This username is not whitelisted",
- secondaryMessage: "Please deploy your own instance",
- renderOptions: { show_repo_link: false },
- }),
- );
- });
-});
diff --git a/apps/backend/tests/private-instance/gist.test.js b/apps/backend/tests/private-instance/gist.test.js
deleted file mode 100644
index 9a23afea84e1d..0000000000000
--- a/apps/backend/tests/private-instance/gist.test.js
+++ /dev/null
@@ -1,41 +0,0 @@
-// @ts-check
-
-import axios from "axios";
-import MockAdapter from "axios-mock-adapter";
-import { afterEach, describe, expect, it, vi } from "vitest";
-
-import gist from "../../api-renamed/gist.js";
-import { renderError } from "../../src/common/render.js";
-import { gist_data } from "../test-data/gist-data.js";
-
-const mock = new MockAdapter(axios);
-
-afterEach(() => {
- mock.reset();
-});
-
-describe("Test /api/gist with gist whitelist", () => {
- it("should render error card if id not in whitelist", async () => {
- const req = {
- query: {
- id: "9bae0392ee3a26bac5cc388a6c8b1469",
- },
- };
- const res = {
- setHeader: vi.fn(),
- send: vi.fn(),
- };
- mock.onPost("https://api.github.com/graphql").reply(200, gist_data);
-
- await gist(req, res);
-
- expect(res.setHeader).toHaveBeenCalledWith("Content-Type", "image/svg+xml");
- expect(res.send).toHaveBeenCalledWith(
- renderError({
- message: "This gist ID is not whitelisted",
- secondaryMessage: "Please deploy your own instance",
- renderOptions: { show_repo_link: false },
- }),
- );
- });
-});
diff --git a/apps/backend/tests/private-instance/pin.test.js b/apps/backend/tests/private-instance/pin.test.js
deleted file mode 100644
index fb81dc5577a89..0000000000000
--- a/apps/backend/tests/private-instance/pin.test.js
+++ /dev/null
@@ -1,64 +0,0 @@
-// @ts-check
-
-import axios from "axios";
-import MockAdapter from "axios-mock-adapter";
-import { afterEach, describe, expect, it, vi } from "vitest";
-
-import pin from "../../api-renamed/pin.js";
-import { renderError } from "../../src/common/render.js";
-import { data_user } from "../test-data/pin-data.js";
-
-const mock = new MockAdapter(axios);
-
-afterEach(() => {
- mock.reset();
-});
-
-describe("Test /api/pin", () => {
- it("should render error card if username not in whitelist", async () => {
- const req = {
- query: {
- username: "renovate-bot",
- repo: "convoychat",
- },
- };
- const res = {
- setHeader: vi.fn(),
- send: vi.fn(),
- };
- mock.onPost("https://api.github.com/graphql").reply(200, data_user);
-
- await pin(req, res);
-
- expect(res.setHeader).toHaveBeenCalledWith("Content-Type", "image/svg+xml");
- expect(res.send).toHaveBeenCalledWith(
- renderError({
- message: "This username is not whitelisted",
- secondaryMessage: "Please deploy your own instance",
- renderOptions: { show_repo_link: false },
- }),
- );
- });
-
- it("should render error card if missing required parameters", async () => {
- const req = {
- query: {},
- };
- const res = {
- setHeader: vi.fn(),
- send: vi.fn(),
- };
- mock.onPost("https://api.github.com/graphql").reply(200, data_user);
-
- await pin(req, res);
-
- expect(res.setHeader).toHaveBeenCalledWith("Content-Type", "image/svg+xml");
- expect(res.send).toHaveBeenCalledWith(
- renderError({
- message: "This username is not whitelisted",
- secondaryMessage: "Please deploy your own instance",
- renderOptions: { show_repo_link: false },
- }),
- );
- });
-});
diff --git a/apps/backend/tests/private-instance/top-langs.test.js b/apps/backend/tests/private-instance/top-langs.test.js
deleted file mode 100644
index c72f4d6fcbd88..0000000000000
--- a/apps/backend/tests/private-instance/top-langs.test.js
+++ /dev/null
@@ -1,41 +0,0 @@
-// @ts-check
-
-import axios from "axios";
-import MockAdapter from "axios-mock-adapter";
-import { afterEach, describe, expect, it, vi } from "vitest";
-
-import topLangs from "../../api-renamed/top-langs.js";
-import { renderError } from "../../src/common/render.js";
-import { data_langs } from "../test-data/langs-data.js";
-
-const mock = new MockAdapter(axios);
-
-afterEach(() => {
- mock.reset();
-});
-
-describe("Test /api/top-langs", () => {
- it("should render error card if username not in whitelist", async () => {
- const req = {
- query: {
- username: "renovate-bot",
- },
- };
- const res = {
- setHeader: vi.fn(),
- send: vi.fn(),
- };
- mock.onPost("https://api.github.com/graphql").reply(200, data_langs);
-
- await topLangs(req, res);
-
- expect(res.setHeader).toHaveBeenCalledWith("Content-Type", "image/svg+xml");
- expect(res.send).toHaveBeenCalledWith(
- renderError({
- message: "This username is not whitelisted",
- secondaryMessage: "Please deploy your own instance",
- renderOptions: { show_repo_link: false },
- }),
- );
- });
-});
diff --git a/apps/backend/tests/public-instance/__snapshots__/api.test.js.snap b/apps/backend/tests/public-instance/__snapshots__/api.test.js.snap
new file mode 100644
index 0000000000000..1edbcd227b6ed
--- /dev/null
+++ b/apps/backend/tests/public-instance/__snapshots__/api.test.js.snap
@@ -0,0 +1,514 @@
+// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
+
+exports[`Test /api contract > should match the private missing-username response snapshot 1`] = `
+{
+ "content": "
+
+
+ Something went wrong!
+
+ This username is not whitelisted
+ Please deploy your own instance
+
+ ",
+ "headers": [
+ [
+ "Cache-Control",
+ "max-age=36000, s-maxage=36000, stale-while-revalidate=86400",
+ ],
+ [
+ "Content-Type",
+ "image/svg+xml",
+ ],
+ ],
+}
+`;
+
+exports[`Test /api contract > should match the public blacklisted-username response snapshot 1`] = `
+{
+ "content": "
+
+
+ Something went wrong!
+
+ This username is blacklisted
+ Please deploy your own instance
+
+ ",
+ "headers": [
+ [
+ "Cache-Control",
+ "max-age=36000, s-maxage=36000, stale-while-revalidate=86400",
+ ],
+ [
+ "Content-Type",
+ "image/svg+xml",
+ ],
+ ],
+}
+`;
+
+exports[`Test /api contract > should match the public happy-path response snapshot 1`] = `
+{
+ "content": "
+ Anurag Hazra's GitHub Stats, Rank: A+
+ Total Stars Earned: 14099, Total Commits (last year) : 200, Total PRs: 4000, Total Issues: 340, Contributed to (last year): 51
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ A+
+
+
+
+
+
+
+
+
+ Total Stars Earned:
+ 14.1k
+
+
+
+
+ Total Commits (last year):
+ 200
+
+
+
+
+ Total PRs:
+ 4k
+
+
+
+
+ Total Issues:
+ 340
+
+
+
+
+ Contributed to (last year):
+ 51
+
+
+
+
+
+ ",
+ "headers": [
+ [
+ "Cache-Control",
+ "max-age=36000, s-maxage=36000, stale-while-revalidate=86400",
+ ],
+ [
+ "Content-Type",
+ "image/svg+xml",
+ ],
+ ],
+}
+`;
+
+exports[`Test /api contract > should match the public many-params response snapshot 1`] = `
+{
+ "content": "
+ a custom title, Rank: A+
+ कुल अर्जित सितारे: 4100, कुल commits (2024) : 200, कुल PR: 4000, कुल PR का विलय: 3200, मर्ज किए गए PRs प्रतिशत: 80.0, कुल PRs की समीक्षा की गई: 1234, कुल चर्चाएँ शुरू हुईं: 222, कुल चर्चाओं के उत्तर: 111, (पिछले वर्ष) में योगदान दिया: 51
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ कुल अर्जित सितारे:
+ 4100
+
+
+
+
+
+
+
+
+ कुल commits (2024):
+ 200
+
+
+
+
+
+
+
+
+ कुल PR:
+ 4000
+
+
+
+
+
+
+
+
+ कुल PR का विलय:
+ 3200
+
+
+
+
+
+
+
+
+ मर्ज किए गए PRs प्रतिशत:
+ 80.0 %
+
+
+
+
+
+
+
+
+ कुल PRs की समीक्षा की गई:
+ 1234
+
+
+
+
+
+
+
+
+ कुल चर्चाएँ शुरू हुईं:
+ 222
+
+
+
+
+
+
+
+
+ कुल चर्चाओं के उत्तर:
+ 111
+
+
+
+
+
+
+
+
+ (पिछले वर्ष) में योगदान दिया:
+ 51
+
+
+
+
+
+ ",
+ "graphqlRequest": "{"query":"\\n query userInfo($login: String!, $after: String, $includeMergedPullRequests: Boolean!, $includeDiscussions: Boolean!, $includeDiscussionsAnswers: Boolean!, $startTime: DateTime = null, $ownerAffiliations: [RepositoryAffiliation]) {\\n user(login: $login) {\\n name\\n login\\n commits: contributionsCollection (from: $startTime) {\\n totalCommitContributions,\\n }\\n reviews: contributionsCollection {\\n totalPullRequestReviewContributions\\n }\\n repositoriesContributedTo(first: 1, contributionTypes: [COMMIT, ISSUE, PULL_REQUEST, REPOSITORY]) {\\n totalCount\\n }\\n pullRequests(first: 1) {\\n totalCount\\n }\\n mergedPullRequests: pullRequests(states: MERGED) @include(if: $includeMergedPullRequests) {\\n totalCount\\n }\\n openIssues: issues(states: OPEN) {\\n totalCount\\n }\\n closedIssues: issues(states: CLOSED) {\\n totalCount\\n }\\n followers {\\n totalCount\\n }\\n repositoryDiscussions @include(if: $includeDiscussions) {\\n totalCount\\n }\\n repositoryDiscussionComments(onlyAnswers: true) @include(if: $includeDiscussionsAnswers) {\\n totalCount\\n }\\n \\n repositories(first: 100, after: $after, ownerAffiliations: $ownerAffiliations, orderBy: {direction: DESC, field: STARGAZERS}) {\\n totalCount\\n nodes {\\n name\\n stargazers {\\n totalCount\\n }\\n }\\n pageInfo {\\n hasNextPage\\n endCursor\\n }\\n }\\n\\n }\\n }\\n","variables":{"login":"anuraghazra","first":100,"after":null,"includeMergedPullRequests":true,"includeDiscussions":true,"includeDiscussionsAnswers":true,"startTime":"2024-01-01T00:00:00Z","ownerAffiliations":["OWNER","COLLABORATOR"]}}",
+ "headers": [
+ [
+ "Cache-Control",
+ "max-age=36000, s-maxage=36000, stale-while-revalidate=86400",
+ ],
+ [
+ "Content-Type",
+ "image/svg+xml",
+ ],
+ ],
+}
+`;
+
+exports[`Test /api contract > should match the public missing-username response snapshot 1`] = `
+{
+ "content": "
+
+
+ Something went wrong!
+
+ Missing params "username" make sure you pass the parameters in URL
+
+
+ ",
+ "headers": [
+ [
+ "Cache-Control",
+ "max-age=600, s-maxage=600, stale-while-revalidate=86400",
+ ],
+ [
+ "Content-Type",
+ "image/svg+xml",
+ ],
+ ],
+}
+`;
+
+exports[`Test /api contract > should render error card in same theme as requested card 1`] = `
+{
+ "content": "
+
+
+ Something went wrong!
+
+ Missing params "username" make sure you pass the parameters in URL
+
+
+ ",
+ "headers": [
+ [
+ "Cache-Control",
+ "max-age=600, s-maxage=600, stale-while-revalidate=86400",
+ ],
+ [
+ "Content-Type",
+ "image/svg+xml",
+ ],
+ ],
+}
+`;
diff --git a/apps/backend/tests/public-instance/__snapshots__/gist.test.js.snap b/apps/backend/tests/public-instance/__snapshots__/gist.test.js.snap
new file mode 100644
index 0000000000000..6e8e2779e7762
--- /dev/null
+++ b/apps/backend/tests/public-instance/__snapshots__/gist.test.js.snap
@@ -0,0 +1,252 @@
+// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
+
+exports[`Test /api/gist contract > should match the private missing-id response snapshot 1`] = `
+{
+ "content": "
+
+
+ Something went wrong!
+
+ This gist ID is not whitelisted
+ Please deploy your own instance
+
+ ",
+ "headers": [
+ [
+ "Cache-Control",
+ "max-age=36000, s-maxage=36000, stale-while-revalidate=86400",
+ ],
+ [
+ "Content-Type",
+ "image/svg+xml",
+ ],
+ ],
+}
+`;
+
+exports[`Test /api/gist contract > should match the public happy-path response snapshot 1`] = `
+{
+ "content": "
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ List of countries and territories in English and Spanish:name, continent, capital, dial code, country codes, TLD,and area in sq km. Lista de países y territorios enInglés y Español: nombre, continente, capital,código de teléfono, códigos de país,dominio y área en km cuadrados. Updated 2023
+
+
+
+
+
+
+ JSON
+
+
+
+
+
+ 33
+
+
+
+ 11
+
+
+
+ ",
+ "headers": [
+ [
+ "Cache-Control",
+ "max-age=36000, s-maxage=36000, stale-while-revalidate=86400",
+ ],
+ [
+ "Content-Type",
+ "image/svg+xml",
+ ],
+ ],
+}
+`;
+
+exports[`Test /api/gist contract > should match the public many-params response snapshot 1`] = `
+{
+ "content": "
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ List of countries and territories in English and Spanish:name, continent, capital, dial code, country codes, TLD,and area in sq km. Lista de países y territorios enInglés y Español: nombre, continente, capital,código de teléfono, códigos de país,dominio y área en km cuadrados. Updated 2023
+
+
+
+
+
+
+ JSON
+
+
+
+
+
+ 33
+
+
+
+ 11
+
+
+
+ ",
+ "graphqlRequest": "{"query":"\\nquery gistInfo($gistName: String!) {\\n viewer {\\n gist(name: $gistName) {\\n description\\n owner {\\n login\\n }\\n stargazerCount\\n forks {\\n totalCount\\n }\\n files {\\n name\\n language {\\n name\\n }\\n size\\n }\\n }\\n }\\n}\\n","variables":{"gistName":"happy-gist-id"}}",
+ "headers": [
+ [
+ "Cache-Control",
+ "max-age=36000, s-maxage=36000, stale-while-revalidate=86400",
+ ],
+ [
+ "Content-Type",
+ "image/svg+xml",
+ ],
+ ],
+}
+`;
+
+exports[`Test /api/gist contract > should match the public missing-id response snapshot 1`] = `
+{
+ "content": "
+
+
+ Something went wrong!
+
+ Missing params "id" make sure you pass the parameters in URL
+ /api/gist?id=GIST_ID
+
+ ",
+ "headers": [
+ [
+ "Cache-Control",
+ "max-age=600, s-maxage=600, stale-while-revalidate=86400",
+ ],
+ [
+ "Content-Type",
+ "image/svg+xml",
+ ],
+ ],
+}
+`;
diff --git a/apps/backend/tests/public-instance/__snapshots__/pin.test.js.snap b/apps/backend/tests/public-instance/__snapshots__/pin.test.js.snap
new file mode 100644
index 0000000000000..959c008a0c8eb
--- /dev/null
+++ b/apps/backend/tests/public-instance/__snapshots__/pin.test.js.snap
@@ -0,0 +1,406 @@
+// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
+
+exports[`Test /api/pin contract > should match the private missing-username response snapshot 1`] = `
+{
+ "content": "
+
+
+ Something went wrong!
+
+ This username is not whitelisted
+ Please deploy your own instance
+
+ ",
+ "headers": [
+ [
+ "Cache-Control",
+ "max-age=36000, s-maxage=36000, stale-while-revalidate=86400",
+ ],
+ [
+ "Content-Type",
+ "image/svg+xml",
+ ],
+ ],
+}
+`;
+
+exports[`Test /api/pin contract > should match the private non-whitelisted username response snapshot 1`] = `
+{
+ "content": "
+
+
+ Something went wrong!
+
+ This username is not whitelisted
+ Please deploy your own instance
+
+ ",
+ "headers": [
+ [
+ "Cache-Control",
+ "max-age=36000, s-maxage=36000, stale-while-revalidate=86400",
+ ],
+ [
+ "Content-Type",
+ "image/svg+xml",
+ ],
+ ],
+}
+`;
+
+exports[`Test /api/pin contract > should match the public blacklisted-username response snapshot 1`] = `
+{
+ "content": "
+
+
+ Something went wrong!
+
+ This username is blacklisted
+ Please deploy your own instance
+
+ ",
+ "headers": [
+ [
+ "Cache-Control",
+ "max-age=36000, s-maxage=36000, stale-while-revalidate=86400",
+ ],
+ [
+ "Content-Type",
+ "image/svg+xml",
+ ],
+ ],
+}
+`;
+
+exports[`Test /api/pin contract > should match the public happy-path response snapshot 1`] = `
+{
+ "content": "
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Help us take over the world with a deeply customizableReact, TypeScript and GraphQL chat app that has enough textto wrap across multiple lines in the repository card.
+
+
+
+
+
+
+ TypeScript
+
+
+
+
+
+ 38k
+
+
+
+ 100
+
+
+
+
+
+
+
+
+ ",
+ "headers": [
+ [
+ "Cache-Control",
+ "max-age=36000, s-maxage=36000, stale-while-revalidate=86400",
+ ],
+ [
+ "Content-Type",
+ "image/svg+xml",
+ ],
+ ],
+}
+`;
+
+exports[`Test /api/pin contract > should match the public many-params response snapshot 1`] = `
+{
+ "content": "
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Help us take over the world with a deeply customizable React, TypeScript and GraphQL...
+
+
+
+
+
+
+ TypeScript
+
+
+
+
+
+ 38k
+
+
+
+ 100
+
+
+
+
+
+
+ my created PRs:
+ 1234
+
+
+
+
+ my commented PRs:
+ 2345
+
+
+
+
+ my reviewed PRs:
+ 3456
+
+
+
+
+ my created issues:
+ 4567
+
+
+
+
+ my commented issues:
+ 5678
+
+
+
+
+
+
+ ",
+ "graphqlRequest": "{"query":"\\n fragment RepoInfo on Repository {\\n name\\n nameWithOwner\\n isPrivate\\n isArchived\\n isTemplate\\n stargazers {\\n totalCount\\n }\\n description\\n primaryLanguage {\\n color\\n id\\n name\\n }\\n forkCount\\n }\\n query getRepo($login: String!, $repo: String!) {\\n user(login: $login) {\\n repository(name: $repo) {\\n ...RepoInfo\\n }\\n }\\n organization(login: $login) {\\n repository(name: $repo) {\\n ...RepoInfo\\n }\\n }\\n }\\n ","variables":{"login":"anuraghazra","repo":"convoychat"}}",
+ "headers": [
+ [
+ "Cache-Control",
+ "max-age=36000, s-maxage=36000, stale-while-revalidate=86400",
+ ],
+ [
+ "Content-Type",
+ "image/svg+xml",
+ ],
+ ],
+}
+`;
+
+exports[`Test /api/pin contract > should match the public missing-params response snapshot 1`] = `
+{
+ "content": "
+
+
+ Something went wrong!
+
+ Missing params "username", "repo" make sure you pass the parameters in URL
+ /api/pin?username=USERNAME&repo=REPO_NAME
+
+ ",
+ "headers": [
+ [
+ "Cache-Control",
+ "max-age=600, s-maxage=600, stale-while-revalidate=86400",
+ ],
+ [
+ "Content-Type",
+ "image/svg+xml",
+ ],
+ ],
+}
+`;
+
+exports[`Test /api/pin contract > should render error card in same theme as requested card 1`] = `
+{
+ "content": "
+
+
+ Something went wrong!
+
+ Missing params "username", "repo" make sure you pass the parameters in URL
+ /api/pin?username=USERNAME&repo=REPO_NAME
+
+ ",
+ "headers": [
+ [
+ "Cache-Control",
+ "max-age=600, s-maxage=600, stale-while-revalidate=86400",
+ ],
+ [
+ "Content-Type",
+ "image/svg+xml",
+ ],
+ ],
+}
+`;
diff --git a/apps/backend/tests/public-instance/__snapshots__/top-langs.test.js.snap b/apps/backend/tests/public-instance/__snapshots__/top-langs.test.js.snap
new file mode 100644
index 0000000000000..589f315e4dd53
--- /dev/null
+++ b/apps/backend/tests/public-instance/__snapshots__/top-langs.test.js.snap
@@ -0,0 +1,460 @@
+// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
+
+exports[`Test /api/top-langs contract > should match the private missing-username response snapshot 1`] = `
+{
+ "content": "
+
+
+ Something went wrong!
+
+ This username is not whitelisted
+ Please deploy your own instance
+
+ ",
+ "headers": [
+ [
+ "Cache-Control",
+ "max-age=36000, s-maxage=36000, stale-while-revalidate=86400",
+ ],
+ [
+ "Content-Type",
+ "image/svg+xml",
+ ],
+ ],
+}
+`;
+
+exports[`Test /api/top-langs contract > should match the private non-whitelisted username response snapshot 1`] = `
+{
+ "content": "
+
+
+ Something went wrong!
+
+ This username is not whitelisted
+ Please deploy your own instance
+
+ ",
+ "headers": [
+ [
+ "Cache-Control",
+ "max-age=36000, s-maxage=36000, stale-while-revalidate=86400",
+ ],
+ [
+ "Content-Type",
+ "image/svg+xml",
+ ],
+ ],
+}
+`;
+
+exports[`Test /api/top-langs contract > should match the public blacklisted-username response snapshot 1`] = `
+{
+ "content": "
+
+
+ Something went wrong!
+
+ This username is blacklisted
+ Please deploy your own instance
+
+ ",
+ "headers": [
+ [
+ "Cache-Control",
+ "max-age=36000, s-maxage=36000, stale-while-revalidate=86400",
+ ],
+ [
+ "Content-Type",
+ "image/svg+xml",
+ ],
+ ],
+}
+`;
+
+exports[`Test /api/top-langs contract > should match the public happy-path response snapshot 1`] = `
+{
+ "content": "
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Rust
+ 70.92%
+
+
+
+
+
+
+
+
+
+
+
+ HTML
+ 12.77%
+
+
+
+
+
+
+
+
+
+
+
+ JavaScript
+ 8.51%
+
+
+
+
+
+
+
+
+
+
+
+ TypeScript
+ 6.38%
+
+
+
+
+
+
+
+
+
+
+
+ Java
+ 1.42%
+
+
+
+
+
+
+
+
+
+
+
+
+
+ ",
+ "headers": [
+ [
+ "Cache-Control",
+ "max-age=36000, s-maxage=36000, stale-while-revalidate=86400",
+ ],
+ [
+ "Content-Type",
+ "image/svg+xml",
+ ],
+ ],
+}
+`;
+
+exports[`Test /api/top-langs contract > should match the public many-params response snapshot 1`] = `
+{
+ "content": "
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ TypeScript 19.0 B
+
+
+
+
+
+
+ Java 4.5 B
+
+
+
+
+
+
+ Python 3.9 B
+
+
+
+
+
+
+
+
+ ",
+ "graphqlRequest": "{"query":"\\n query userInfo($login: String!, $ownerAffiliations: [RepositoryAffiliation]) {\\n user(login: $login) {\\n # do not fetch forks\\n repositories(ownerAffiliations: $ownerAffiliations, isFork: false, first: 100) {\\n nodes {\\n name\\n languages(first: 10, orderBy: {field: SIZE, direction: DESC}) {\\n edges {\\n size\\n node {\\n color\\n name\\n }\\n }\\n }\\n }\\n }\\n }\\n }\\n ","variables":{"login":"anuraghazra","ownerAffiliations":["OWNER","COLLABORATOR"]}}",
+ "headers": [
+ [
+ "Cache-Control",
+ "max-age=36000, s-maxage=36000, stale-while-revalidate=86400",
+ ],
+ [
+ "Content-Type",
+ "image/svg+xml",
+ ],
+ ],
+}
+`;
+
+exports[`Test /api/top-langs contract > should match the public missing-username response snapshot 1`] = `
+{
+ "content": "
+
+
+ Something went wrong!
+
+ Missing params "username" make sure you pass the parameters in URL
+
+
+ ",
+ "headers": [
+ [
+ "Cache-Control",
+ "max-age=600, s-maxage=600, stale-while-revalidate=86400",
+ ],
+ [
+ "Content-Type",
+ "image/svg+xml",
+ ],
+ ],
+}
+`;
+
+exports[`Test /api/top-langs contract > should render error card in same theme as requested card 1`] = `
+{
+ "content": "
+
+
+ Something went wrong!
+
+ Missing params "username" make sure you pass the parameters in URL
+
+
+ ",
+ "headers": [
+ [
+ "Cache-Control",
+ "max-age=600, s-maxage=600, stale-while-revalidate=86400",
+ ],
+ [
+ "Content-Type",
+ "image/svg+xml",
+ ],
+ ],
+}
+`;
diff --git a/apps/backend/tests/public-instance/__snapshots__/wakatime.test.js.snap b/apps/backend/tests/public-instance/__snapshots__/wakatime.test.js.snap
new file mode 100644
index 0000000000000..a1a3075a93e55
--- /dev/null
+++ b/apps/backend/tests/public-instance/__snapshots__/wakatime.test.js.snap
@@ -0,0 +1,546 @@
+// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
+
+exports[`Test /api/wakatime contract > should match the private missing-username response snapshot 1`] = `
+{
+ "content": "
+
+
+ Something went wrong!
+
+ This username is not whitelisted
+ Please deploy your own instance
+
+ ",
+ "headers": [
+ [
+ "Cache-Control",
+ "max-age=36000, s-maxage=36000, stale-while-revalidate=86400",
+ ],
+ [
+ "Content-Type",
+ "image/svg+xml",
+ ],
+ ],
+}
+`;
+
+exports[`Test /api/wakatime contract > should match the private non-whitelisted username response snapshot 1`] = `
+{
+ "content": "
+
+
+ Something went wrong!
+
+ This username is not whitelisted
+ Please deploy your own instance
+
+ ",
+ "headers": [
+ [
+ "Cache-Control",
+ "max-age=36000, s-maxage=36000, stale-while-revalidate=86400",
+ ],
+ [
+ "Content-Type",
+ "image/svg+xml",
+ ],
+ ],
+}
+`;
+
+exports[`Test /api/wakatime contract > should match the public happy-path response snapshot 1`] = `
+{
+ "content": "
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ TypeScript:
+ 12 hrs
+
+
+
+
+
+
+
+
+
+
+
+ JavaScript:
+ 6 hrs 30 mins
+
+
+
+
+
+
+
+
+
+
+
+ Other:
+ 3 hrs
+
+
+
+
+
+
+
+
+
+
+
+ YAML:
+ 1 hr
+
+
+
+
+
+
+
+
+
+
+
+ JSON:
+ 30 mins
+
+
+
+
+
+
+
+
+
+
+
+
+
+ ",
+ "headers": [
+ [
+ "Cache-Control",
+ "max-age=36000, s-maxage=36000, stale-while-revalidate=86400",
+ ],
+ [
+ "Content-Type",
+ "image/svg+xml",
+ ],
+ ],
+}
+`;
+
+exports[`Test /api/wakatime contract > should match the public inaccessible-profile response snapshot 1`] = `
+{
+ "content": "
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ WakaTime user profile not public
+
+
+
+
+ ",
+ "headers": [
+ [
+ "Cache-Control",
+ "max-age=36000, s-maxage=36000, stale-while-revalidate=86400",
+ ],
+ [
+ "Content-Type",
+ "image/svg+xml",
+ ],
+ ],
+}
+`;
+
+exports[`Test /api/wakatime contract > should match the public many-params response snapshot 1`] = `
+{
+ "content": "
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ TypeScript - 61.88 %
+
+
+
+
+
+
+ JavaScript - 33.32 %
+
+
+
+
+
+
+ YAML - 4.76 %
+
+
+
+
+
+
+
+ ",
+ "headers": [
+ [
+ "Cache-Control",
+ "max-age=36000, s-maxage=36000, stale-while-revalidate=86400",
+ ],
+ [
+ "Content-Type",
+ "image/svg+xml",
+ ],
+ ],
+}
+`;
+
+exports[`Test /api/wakatime contract > should match the public missing-username response snapshot 1`] = `
+{
+ "content": "
+
+
+ Something went wrong!
+
+ Missing params "username" make sure you pass the parameters in URL
+
+
+ ",
+ "headers": [
+ [
+ "Cache-Control",
+ "max-age=600, s-maxage=600, stale-while-revalidate=86400",
+ ],
+ [
+ "Content-Type",
+ "image/svg+xml",
+ ],
+ ],
+}
+`;
+
+exports[`Test /api/wakatime contract > should render error card in same theme as requested card 1`] = `
+{
+ "content": "
+
+
+ Something went wrong!
+
+ Missing params "username" make sure you pass the parameters in URL
+
+
+ ",
+ "headers": [
+ [
+ "Cache-Control",
+ "max-age=600, s-maxage=600, stale-while-revalidate=86400",
+ ],
+ [
+ "Content-Type",
+ "image/svg+xml",
+ ],
+ ],
+}
+`;
diff --git a/apps/backend/tests/public-instance/api.test.js b/apps/backend/tests/public-instance/api.test.js
index 52080c8997eb3..506d9f05f09f8 100644
--- a/apps/backend/tests/public-instance/api.test.js
+++ b/apps/backend/tests/public-instance/api.test.js
@@ -2,40 +2,176 @@
import axios from "axios";
import MockAdapter from "axios-mock-adapter";
-import { afterEach, describe, expect, it, vi } from "vitest";
+import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
-import api from "../../api-renamed/index.js";
-import { renderError } from "../../src/common/render.js";
-import { data_stats } from "../test-data/api-data.js";
+import { data_stats, normalizeSvg } from "../utils.js";
const mock = new MockAdapter(axios);
+const createResponse = () => ({
+ end: vi.fn(),
+ setHeader: vi.fn(),
+});
+
+beforeEach(() => {
+ vi.stubEnv("CACHE_SECONDS", "");
+ vi.stubEnv("GIST_WHITELIST", "");
+ vi.stubEnv("POSTGRES_URL", "");
+ vi.stubEnv("WHITELIST", "");
+
+ mock.onPost("https://api.github.com/graphql").reply(200, data_stats);
+});
+
afterEach(() => {
mock.reset();
+ vi.unstubAllEnvs();
+ // modules may cache environment variables, so we need to reset them
+ vi.resetModules();
});
-describe("Test /api/", () => {
- it("should render error card if username in blacklist", async () => {
+describe("Test /api contract", () => {
+ it("should match the public happy-path response snapshot", async () => {
+ const { default: router } = await import("../../router.js");
+
+ const req = {
+ headers: {},
+ url: "/api?username=anuraghazra",
+ };
+ const res = createResponse();
+
+ await router(req, res);
+
+ expect(res.end).toHaveBeenCalledOnce();
+
+ expect({
+ headers: res.setHeader.mock.calls,
+ content: normalizeSvg(res.end.mock.calls[0][0]),
+ }).toMatchSnapshot();
+ });
+
+ it("should match the public many-params response snapshot", async () => {
+ mock.onPost("https://api.github.com/graphql").reply(200, data_stats);
+
+ const { default: router } = await import("../../router.js");
+
+ const params = new URLSearchParams({
+ username: "anuraghazra",
+ show_icons: "true",
+ card_width: "540",
+ line_height: "32",
+ title_color: "123456",
+ ring_color: "654321",
+ icon_color: "ff00aa",
+ text_color: "abcdef",
+ text_bold: "false",
+ bg_color: "0f172a",
+ exclude_repo: "repo-exclude-me",
+ custom_title: "a custom title",
+ locale: "hi",
+ disable_animations: "true",
+ border_radius: "12",
+ number_format: "long",
+ number_precision: "1",
+ border_color: "fedcba",
+ rank_icon: "github",
+ commits_year: "2024",
+ hide: "issues",
+ role: "OWNER,COLLABORATOR",
+ show: "reviews,prs_merged,prs_merged_percentage,discussions_started,discussions_answered",
+ });
+
const req = {
- query: {
- username: "renovate-bot",
- },
+ headers: {},
+ url: `/api?${params.toString()}`,
};
- const res = {
- setHeader: vi.fn(),
- send: vi.fn(),
+ const res = createResponse();
+
+ await router(req, res);
+
+ expect(res.end).toHaveBeenCalledOnce();
+
+ expect({
+ graphqlRequest: mock.history.post[0].data,
+ headers: res.setHeader.mock.calls,
+ content: normalizeSvg(res.end.mock.calls[0][0]),
+ }).toMatchSnapshot();
+ });
+
+ it("should match the public missing-username response snapshot", async () => {
+ const { default: router } = await import("../../router.js");
+
+ const req = {
+ headers: {},
+ url: "/api",
};
- mock.onPost("https://api.github.com/graphql").replyOnce(200, data_stats);
-
- await api(req, res);
-
- expect(res.setHeader).toHaveBeenCalledWith("Content-Type", "image/svg+xml");
- expect(res.send).toHaveBeenCalledWith(
- renderError({
- message: "This username is blacklisted",
- secondaryMessage: "Please deploy your own instance",
- renderOptions: { show_repo_link: false },
- }),
- );
+ const res = createResponse();
+
+ await router(req, res);
+
+ expect(res.end).toHaveBeenCalledOnce();
+
+ expect({
+ headers: res.setHeader.mock.calls,
+ content: normalizeSvg(res.end.mock.calls[0][0]),
+ }).toMatchSnapshot();
+ });
+
+ it("should render error card in same theme as requested card", async () => {
+ const { default: router } = await import("../../router.js");
+
+ const req = {
+ headers: {},
+ url: "/api?theme=merko",
+ };
+ const res = createResponse();
+
+ await router(req, res);
+
+ expect(res.end).toHaveBeenCalledOnce();
+
+ expect({
+ headers: res.setHeader.mock.calls,
+ content: normalizeSvg(res.end.mock.calls[0][0]),
+ }).toMatchSnapshot();
+ });
+
+ it("should match the public blacklisted-username response snapshot", async () => {
+ const { default: router } = await import("../../router.js");
+
+ const req = {
+ headers: {},
+ url: "/api?username=renovate-bot",
+ };
+ const res = createResponse();
+
+ await router(req, res);
+
+ expect(res.end).toHaveBeenCalledOnce();
+
+ expect({
+ headers: res.setHeader.mock.calls,
+ content: normalizeSvg(res.end.mock.calls[0][0]),
+ }).toMatchSnapshot();
+ });
+
+ it("should match the private missing-username response snapshot", async () => {
+ vi.stubEnv("WHITELIST", "anuraghazra");
+
+ const { default: router } = await import("../../router.js");
+
+ const req = {
+ headers: {},
+ url: "/api",
+ };
+ const res = createResponse();
+
+ await router(req, res);
+
+ expect(res.end).toHaveBeenCalledOnce();
+
+ expect({
+ headers: res.setHeader.mock.calls,
+ content: normalizeSvg(res.end.mock.calls[0][0]),
+ }).toMatchSnapshot();
});
});
diff --git a/apps/backend/tests/public-instance/gist.test.js b/apps/backend/tests/public-instance/gist.test.js
index 421547360171f..0742ab3b00246 100644
--- a/apps/backend/tests/public-instance/gist.test.js
+++ b/apps/backend/tests/public-instance/gist.test.js
@@ -2,38 +2,123 @@
import axios from "axios";
import MockAdapter from "axios-mock-adapter";
-import { afterEach, describe, expect, it, vi } from "vitest";
+import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
-import gist from "../../api-renamed/gist.js";
-import { renderError } from "../../src/common/render.js";
-import { gist_data } from "../test-data/gist-data.js";
+import { happy_path_gist_data, normalizeSvg } from "../utils.js";
const mock = new MockAdapter(axios);
+const createResponse = () => ({
+ end: vi.fn(),
+ setHeader: vi.fn(),
+});
+
+beforeEach(() => {
+ vi.stubEnv("CACHE_SECONDS", "");
+ vi.stubEnv("GIST_WHITELIST", "");
+ vi.stubEnv("POSTGRES_URL", "");
+ vi.stubEnv("WHITELIST", "");
+
+ mock
+ .onPost("https://api.github.com/graphql")
+ .reply(200, happy_path_gist_data);
+});
+
afterEach(() => {
mock.reset();
+ vi.unstubAllEnvs();
+ // modules may cache environment variables, so we need to reset them
+ vi.resetModules();
});
-describe("Test /api/gist", () => {
- it("should render error if id is not provided", async () => {
+describe("Test /api/gist contract", () => {
+ it("should match the public happy-path response snapshot", async () => {
+ const { default: router } = await import("../../router.js");
+
+ const req = {
+ headers: {},
+ url: "/api/gist?id=happy-gist-id",
+ };
+ const res = createResponse();
+
+ await router(req, res);
+
+ expect(res.end).toHaveBeenCalledOnce();
+
+ expect({
+ headers: res.setHeader.mock.calls,
+ content: normalizeSvg(res.end.mock.calls[0][0]),
+ }).toMatchSnapshot();
+ });
+
+ it("should match the public many-params response snapshot", async () => {
+ const { default: router } = await import("../../router.js");
+
+ const params = new URLSearchParams({
+ id: "happy-gist-id",
+ title_color: "123456",
+ icon_color: "ff00aa",
+ text_color: "abcdef",
+ bg_color: "0f172a",
+ border_radius: "12",
+ border_color: "fedcba",
+ show_owner: "true",
+ });
+
+ const req = {
+ headers: {},
+ url: `/api/gist?${params.toString()}`,
+ };
+ const res = createResponse();
+
+ await router(req, res);
+
+ expect(res.end).toHaveBeenCalledOnce();
+
+ expect({
+ graphqlRequest: mock.history.post[0].data,
+ headers: res.setHeader.mock.calls,
+ content: normalizeSvg(res.end.mock.calls[0][0]),
+ }).toMatchSnapshot();
+ });
+
+ it("should match the public missing-id response snapshot", async () => {
+ const { default: router } = await import("../../router.js");
+
const req = {
- query: {},
+ headers: {},
+ url: "/api/gist",
};
- const res = {
- setHeader: vi.fn(),
- send: vi.fn(),
+ const res = createResponse();
+
+ await router(req, res);
+
+ expect(res.end).toHaveBeenCalledOnce();
+
+ expect({
+ headers: res.setHeader.mock.calls,
+ content: normalizeSvg(res.end.mock.calls[0][0]),
+ }).toMatchSnapshot();
+ });
+
+ it("should match the private missing-id response snapshot", async () => {
+ vi.stubEnv("GIST_WHITELIST", "allowed-gist-id");
+
+ const { default: router } = await import("../../router.js");
+
+ const req = {
+ headers: {},
+ url: "/api/gist",
};
- mock.onPost("https://api.github.com/graphql").reply(200, gist_data);
-
- await gist(req, res);
-
- expect(res.setHeader).toHaveBeenCalledWith("Content-Type", "image/svg+xml");
- expect(res.send).toHaveBeenCalledWith(
- renderError({
- message: 'Missing params "id" make sure you pass the parameters in URL',
- secondaryMessage: "/api/gist?id=GIST_ID",
- renderOptions: { show_repo_link: false },
- }),
- );
+ const res = createResponse();
+
+ await router(req, res);
+
+ expect(res.end).toHaveBeenCalledOnce();
+
+ expect({
+ headers: res.setHeader.mock.calls,
+ content: normalizeSvg(res.end.mock.calls[0][0]),
+ }).toMatchSnapshot();
});
});
diff --git a/apps/backend/tests/public-instance/pin.test.js b/apps/backend/tests/public-instance/pin.test.js
index fdbdfe98aae65..12da77f939354 100644
--- a/apps/backend/tests/public-instance/pin.test.js
+++ b/apps/backend/tests/public-instance/pin.test.js
@@ -2,64 +2,215 @@
import axios from "axios";
import MockAdapter from "axios-mock-adapter";
-import { afterEach, describe, expect, it, vi } from "vitest";
+import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
-import pin from "../../api-renamed/pin.js";
-import { renderError } from "../../src/common/render.js";
-import { data_user } from "../test-data/pin-data.js";
+import { data_user, normalizeSvg } from "../utils.js";
const mock = new MockAdapter(axios);
+const createResponse = () => ({
+ end: vi.fn(),
+ setHeader: vi.fn(),
+});
+
+beforeEach(() => {
+ vi.stubEnv("CACHE_SECONDS", "");
+ vi.stubEnv("GIST_WHITELIST", "");
+ vi.stubEnv("POSTGRES_URL", "");
+ vi.stubEnv("WHITELIST", "");
+
+ mock.onPost("https://api.github.com/graphql").reply(200, data_user);
+});
+
afterEach(() => {
mock.reset();
+ vi.unstubAllEnvs();
+ // modules may cache environment variables, so we need to reset them
+ vi.resetModules();
});
-describe("Test /api/pin", () => {
- it("should render error card if username in blacklist", async () => {
+describe("Test /api/pin contract", () => {
+ it("should match the public happy-path response snapshot", async () => {
+ const { default: router } = await import("../../router.js");
+
const req = {
- query: {
- username: "renovate-bot",
- repo: "convoychat",
- },
- };
- const res = {
- setHeader: vi.fn(),
- send: vi.fn(),
+ headers: {},
+ url: "/api/pin?username=anuraghazra&repo=convoychat",
};
+ const res = createResponse();
+
+ await router(req, res);
+
+ expect(res.end).toHaveBeenCalledOnce();
+
+ expect({
+ headers: res.setHeader.mock.calls,
+ content: normalizeSvg(res.end.mock.calls[0][0]),
+ }).toMatchSnapshot();
+ });
+
+ it("should match the public many-params response snapshot", async () => {
mock.onPost("https://api.github.com/graphql").reply(200, data_user);
+ mock
+ .onGet(
+ "https://api.github.com/search/issues?per_page=1&q=repo:anuraghazra/convoychat+author:anuraghazra+type:pr",
+ )
+ .reply(200, { total_count: 1234 });
+ mock
+ .onGet(
+ "https://api.github.com/search/issues?per_page=1&q=repo:anuraghazra/convoychat+commenter:anuraghazra+-author:anuraghazra+type:pr",
+ )
+ .reply(200, { total_count: 2345 });
+ mock
+ .onGet(
+ "https://api.github.com/search/issues?per_page=1&q=repo:anuraghazra/convoychat+reviewed-by:anuraghazra+-author:anuraghazra+type:pr",
+ )
+ .reply(200, { total_count: 3456 });
+ mock
+ .onGet(
+ "https://api.github.com/search/issues?per_page=1&q=repo:anuraghazra/convoychat+author:anuraghazra+type:issue",
+ )
+ .reply(200, { total_count: 4567 });
+ mock
+ .onGet(
+ "https://api.github.com/search/issues?per_page=1&q=repo:anuraghazra/convoychat+commenter:anuraghazra+-author:anuraghazra+type:issue",
+ )
+ .reply(200, { total_count: 5678 });
+
+ const { default: router } = await import("../../router.js");
+
+ const params = new URLSearchParams({
+ username: "anuraghazra",
+ repo: "convoychat",
+ title_color: "123456",
+ icon_color: "ff00aa",
+ text_color: "abcdef",
+ bg_color: "0f172a",
+ card_width: "560",
+ show_owner: "true",
+ show: "prs_authored,prs_commented,prs_reviewed,issues_authored,issues_commented",
+ show_icons: "false",
+ number_format: "long",
+ text_bold: "true",
+ line_height: "30",
+ border_radius: "12",
+ border_color: "fedcba",
+ description_lines_count: "1",
+ });
- await pin(req, res);
+ const req = {
+ headers: {},
+ url: `/api/pin?${params.toString()}`,
+ };
+ const res = createResponse();
+
+ await router(req, res);
+
+ expect(res.end).toHaveBeenCalledOnce();
- expect(res.setHeader).toHaveBeenCalledWith("Content-Type", "image/svg+xml");
- expect(res.send).toHaveBeenCalledWith(
- renderError({
- message: "This username is blacklisted",
- secondaryMessage: "Please deploy your own instance",
- renderOptions: { show_repo_link: false },
- }),
- );
+ expect({
+ graphqlRequest: mock.history.post[0].data,
+ headers: res.setHeader.mock.calls,
+ content: normalizeSvg(res.end.mock.calls[0][0]),
+ }).toMatchSnapshot();
});
- it("should render error card if missing required parameters", async () => {
+ it("should match the public missing-params response snapshot", async () => {
+ const { default: router } = await import("../../router.js");
+
const req = {
- query: {},
+ headers: {},
+ url: "/api/pin",
};
- const res = {
- setHeader: vi.fn(),
- send: vi.fn(),
+ const res = createResponse();
+
+ await router(req, res);
+
+ expect(res.end).toHaveBeenCalledOnce();
+
+ expect({
+ headers: res.setHeader.mock.calls,
+ content: normalizeSvg(res.end.mock.calls[0][0]),
+ }).toMatchSnapshot();
+ });
+
+ it("should render error card in same theme as requested card", async () => {
+ const { default: router } = await import("../../router.js");
+
+ const req = {
+ headers: {},
+ url: "/api/pin?theme=merko",
};
- mock.onPost("https://api.github.com/graphql").reply(200, data_user);
+ const res = createResponse();
+
+ await router(req, res);
+
+ expect(res.end).toHaveBeenCalledOnce();
+
+ expect({
+ headers: res.setHeader.mock.calls,
+ content: normalizeSvg(res.end.mock.calls[0][0]),
+ }).toMatchSnapshot();
+ });
+
+ it("should match the public blacklisted-username response snapshot", async () => {
+ const { default: router } = await import("../../router.js");
+
+ const req = {
+ headers: {},
+ url: "/api/pin?username=renovate-bot&repo=convoychat",
+ };
+ const res = createResponse();
+
+ await router(req, res);
+
+ expect(res.end).toHaveBeenCalledOnce();
+
+ expect({
+ headers: res.setHeader.mock.calls,
+ content: normalizeSvg(res.end.mock.calls[0][0]),
+ }).toMatchSnapshot();
+ });
+
+ it("should match the private non-whitelisted username response snapshot", async () => {
+ vi.stubEnv("WHITELIST", "anuraghazra");
+
+ const { default: router } = await import("../../router.js");
+
+ const req = {
+ headers: {},
+ url: "/api/pin?username=martin-mfg",
+ };
+ const res = createResponse();
+
+ await router(req, res);
+
+ expect(res.end).toHaveBeenCalledOnce();
+
+ expect({
+ headers: res.setHeader.mock.calls,
+ content: normalizeSvg(res.end.mock.calls[0][0]),
+ }).toMatchSnapshot();
+ });
+
+ it("should match the private missing-username response snapshot", async () => {
+ vi.stubEnv("WHITELIST", "anuraghazra");
+
+ const { default: router } = await import("../../router.js");
+
+ const req = {
+ headers: {},
+ url: "/api/pin",
+ };
+ const res = createResponse();
+
+ await router(req, res);
+
+ expect(res.end).toHaveBeenCalledOnce();
- await pin(req, res);
-
- expect(res.setHeader).toHaveBeenCalledWith("Content-Type", "image/svg+xml");
- expect(res.send).toHaveBeenCalledWith(
- renderError({
- message:
- 'Missing params "username", "repo" make sure you pass the parameters in URL',
- secondaryMessage: "/api/pin?username=USERNAME&repo=REPO_NAME",
- renderOptions: { show_repo_link: false },
- }),
- );
+ expect({
+ headers: res.setHeader.mock.calls,
+ content: normalizeSvg(res.end.mock.calls[0][0]),
+ }).toMatchSnapshot();
});
});
diff --git a/apps/backend/tests/public-instance/top-langs.test.js b/apps/backend/tests/public-instance/top-langs.test.js
index 2e180e340f4cd..9eaba86cc4064 100644
--- a/apps/backend/tests/public-instance/top-langs.test.js
+++ b/apps/backend/tests/public-instance/top-langs.test.js
@@ -2,40 +2,190 @@
import axios from "axios";
import MockAdapter from "axios-mock-adapter";
-import { afterEach, describe, expect, it, vi } from "vitest";
+import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
-import topLangs from "../../api-renamed/top-langs.js";
-import { renderError } from "../../src/common/render.js";
-import { data_langs } from "../test-data/langs-data.js";
+import { data_langs, normalizeSvg } from "../utils.js";
const mock = new MockAdapter(axios);
+const createResponse = () => ({
+ end: vi.fn(),
+ setHeader: vi.fn(),
+});
+
+beforeEach(() => {
+ vi.stubEnv("CACHE_SECONDS", "");
+ vi.stubEnv("GIST_WHITELIST", "");
+ vi.stubEnv("POSTGRES_URL", "");
+ vi.stubEnv("WHITELIST", "");
+
+ mock.onPost("https://api.github.com/graphql").reply(200, data_langs);
+});
+
afterEach(() => {
mock.reset();
+ vi.unstubAllEnvs();
+ // modules may cache environment variables, so we need to reset them
+ vi.resetModules();
});
-describe("Test /api/top-langs", () => {
- it("should render error card if username in blacklist", async () => {
+describe("Test /api/top-langs contract", () => {
+ it("should match the public happy-path response snapshot", async () => {
+ const { default: router } = await import("../../router.js");
+
const req = {
- query: {
- username: "renovate-bot",
- },
- };
- const res = {
- setHeader: vi.fn(),
- send: vi.fn(),
+ headers: {},
+ url: "/api/top-langs?username=anuraghazra",
};
+ const res = createResponse();
+
+ await router(req, res);
+
+ expect(res.end).toHaveBeenCalledOnce();
+
+ expect({
+ headers: res.setHeader.mock.calls,
+ content: normalizeSvg(res.end.mock.calls[0][0]),
+ }).toMatchSnapshot();
+ });
+
+ it("should match the public many-params response snapshot", async () => {
mock.onPost("https://api.github.com/graphql").reply(200, data_langs);
- await topLangs(req, res);
+ const { default: router } = await import("../../router.js");
+
+ const params = new URLSearchParams({
+ username: "anuraghazra",
+ hide: "javascript,HTML",
+ hide_title: "true",
+ hide_border: "true",
+ card_width: "420",
+ title_color: "123456",
+ text_color: "abcdef",
+ bg_color: "0f172a",
+ layout: "compact",
+ langs_count: "3",
+ exclude_repo: "repo-hidden",
+ size_weight: "0.5",
+ count_weight: "1",
+ role: "OWNER,COLLABORATOR",
+ disable_animations: "true",
+ stats_format: "bytes",
+ });
+
+ const req = {
+ headers: {},
+ url: `/api/top-langs?${params.toString()}`,
+ };
+ const res = createResponse();
+
+ await router(req, res);
+
+ expect(res.end).toHaveBeenCalledOnce();
+
+ expect({
+ graphqlRequest: mock.history.post[0].data,
+ headers: res.setHeader.mock.calls,
+ content: normalizeSvg(res.end.mock.calls[0][0]),
+ }).toMatchSnapshot();
+ });
+
+ it("should match the public missing-username response snapshot", async () => {
+ const { default: router } = await import("../../router.js");
+
+ const req = {
+ headers: {},
+ url: "/api/top-langs",
+ };
+ const res = createResponse();
+
+ await router(req, res);
+
+ expect(res.end).toHaveBeenCalledOnce();
+
+ expect({
+ headers: res.setHeader.mock.calls,
+ content: normalizeSvg(res.end.mock.calls[0][0]),
+ }).toMatchSnapshot();
+ });
+
+ it("should render error card in same theme as requested card", async () => {
+ const { default: router } = await import("../../router.js");
+
+ const req = {
+ headers: {},
+ url: "/api/top-langs?theme=merko",
+ };
+ const res = createResponse();
+
+ await router(req, res);
+
+ expect(res.end).toHaveBeenCalledOnce();
+
+ expect({
+ headers: res.setHeader.mock.calls,
+ content: normalizeSvg(res.end.mock.calls[0][0]),
+ }).toMatchSnapshot();
+ });
+
+ it("should match the public blacklisted-username response snapshot", async () => {
+ const { default: router } = await import("../../router.js");
+
+ const req = {
+ headers: {},
+ url: "/api/top-langs?username=renovate-bot",
+ };
+ const res = createResponse();
+
+ await router(req, res);
+
+ expect(res.end).toHaveBeenCalledOnce();
+
+ expect({
+ headers: res.setHeader.mock.calls,
+ content: normalizeSvg(res.end.mock.calls[0][0]),
+ }).toMatchSnapshot();
+ });
+
+ it("should match the private non-whitelisted username response snapshot", async () => {
+ vi.stubEnv("WHITELIST", "anuraghazra");
+
+ const { default: router } = await import("../../router.js");
+
+ const req = {
+ headers: {},
+ url: "/api/top-langs?username=martin-mfg",
+ };
+ const res = createResponse();
+
+ await router(req, res);
+
+ expect(res.end).toHaveBeenCalledOnce();
+
+ expect({
+ headers: res.setHeader.mock.calls,
+ content: normalizeSvg(res.end.mock.calls[0][0]),
+ }).toMatchSnapshot();
+ });
+
+ it("should match the private missing-username response snapshot", async () => {
+ vi.stubEnv("WHITELIST", "anuraghazra");
+
+ const { default: router } = await import("../../router.js");
+
+ const req = {
+ headers: {},
+ url: "/api/top-langs",
+ };
+ const res = createResponse();
+
+ await router(req, res);
+
+ expect(res.end).toHaveBeenCalledOnce();
- expect(res.setHeader).toHaveBeenCalledWith("Content-Type", "image/svg+xml");
- expect(res.send).toHaveBeenCalledWith(
- renderError({
- message: "This username is blacklisted",
- secondaryMessage: "Please deploy your own instance",
- renderOptions: { show_repo_link: false },
- }),
- );
+ expect({
+ headers: res.setHeader.mock.calls,
+ content: normalizeSvg(res.end.mock.calls[0][0]),
+ }).toMatchSnapshot();
});
});
diff --git a/apps/backend/tests/public-instance/wakatime.test.js b/apps/backend/tests/public-instance/wakatime.test.js
new file mode 100644
index 0000000000000..48a23bf83a9fd
--- /dev/null
+++ b/apps/backend/tests/public-instance/wakatime.test.js
@@ -0,0 +1,212 @@
+// @ts-check
+
+import axios from "axios";
+import MockAdapter from "axios-mock-adapter";
+import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
+
+import { normalizeSvg, wakaTimeData } from "../utils.js";
+
+const mock = new MockAdapter(axios);
+
+const wakaTimeProfileNotPublicData = {
+ data: {
+ viewer: {
+ gist: null,
+ },
+ },
+};
+
+const createResponse = () => ({
+ end: vi.fn(),
+ setHeader: vi.fn(),
+});
+
+beforeEach(() => {
+ vi.stubEnv("CACHE_SECONDS", "");
+ vi.stubEnv("GIST_WHITELIST", "");
+ vi.stubEnv("POSTGRES_URL", "");
+ vi.stubEnv("WHITELIST", "");
+
+ mock
+ .onGet(
+ "https://wakatime.com/api/v1/users/anuraghazra/stats?is_including_today=true",
+ )
+ .reply(200, wakaTimeData);
+});
+
+afterEach(() => {
+ mock.reset();
+ vi.unstubAllEnvs();
+ // modules may cache environment variables, so we need to reset them
+ vi.resetModules();
+});
+
+describe("Test /api/wakatime contract", () => {
+ it("should match the public happy-path response snapshot", async () => {
+ const { default: router } = await import("../../router.js");
+
+ const req = {
+ headers: {},
+ url: "/api/wakatime?username=anuraghazra",
+ };
+ const res = createResponse();
+
+ await router(req, res);
+
+ expect(res.end).toHaveBeenCalledOnce();
+
+ expect({
+ headers: res.setHeader.mock.calls,
+ content: normalizeSvg(res.end.mock.calls[0][0]),
+ }).toMatchSnapshot();
+ });
+
+ it("should match the public many-params response snapshot", async () => {
+ mock.reset(); // to verify api_domain param is working
+ mock
+ .onGet(
+ "https://wakatime.local/api/v1/users/anuraghazra/stats?is_including_today=true",
+ )
+ .reply(200, wakaTimeData);
+
+ const { default: router } = await import("../../router.js");
+
+ const params = new URLSearchParams({
+ username: "anuraghazra",
+ title_color: "123456",
+ text_color: "abcdef",
+ bg_color: "0f172a",
+ card_width: "620",
+ custom_title: "a custom title",
+ layout: "compact",
+ langs_count: "3",
+ hide: "other",
+ api_domain: "wakatime.local",
+ border_radius: "12",
+ border_color: "fedcba",
+ display_format: "percent",
+ disable_animations: "true",
+ });
+
+ const req = {
+ headers: {},
+ url: `/api/wakatime?${params.toString()}`,
+ };
+ const res = createResponse();
+
+ await router(req, res);
+
+ expect(res.end).toHaveBeenCalledOnce();
+
+ expect({
+ headers: res.setHeader.mock.calls,
+ content: normalizeSvg(res.end.mock.calls[0][0]),
+ }).toMatchSnapshot();
+ });
+
+ it("should match the public missing-username response snapshot", async () => {
+ const { default: router } = await import("../../router.js");
+
+ const req = {
+ headers: {},
+ url: "/api/wakatime",
+ };
+ const res = createResponse();
+
+ await router(req, res);
+
+ expect(res.end).toHaveBeenCalledOnce();
+
+ expect({
+ headers: res.setHeader.mock.calls,
+ content: normalizeSvg(res.end.mock.calls[0][0]),
+ }).toMatchSnapshot();
+ });
+
+ it("should render error card in same theme as requested card", async () => {
+ const { default: router } = await import("../../router.js");
+
+ const req = {
+ headers: {},
+ url: "/api/wakatime?theme=merko",
+ };
+ const res = createResponse();
+
+ await router(req, res);
+
+ expect(res.end).toHaveBeenCalledOnce();
+
+ expect({
+ headers: res.setHeader.mock.calls,
+ content: normalizeSvg(res.end.mock.calls[0][0]),
+ }).toMatchSnapshot();
+ });
+
+ it("should match the public inaccessible-profile response snapshot", async () => {
+ mock.reset();
+ mock
+ .onGet(
+ "https://wakatime.com/api/v1/users/anuraghazra/stats?is_including_today=true",
+ )
+ .reply(200, wakaTimeProfileNotPublicData);
+
+ const { default: router } = await import("../../router.js");
+
+ const req = {
+ headers: {},
+ url: "/api/wakatime?username=anuraghazra",
+ };
+ const res = createResponse();
+
+ await router(req, res);
+
+ expect(res.end).toHaveBeenCalledOnce();
+
+ expect({
+ headers: res.setHeader.mock.calls,
+ content: normalizeSvg(res.end.mock.calls[0][0]),
+ }).toMatchSnapshot();
+ });
+
+ it("should match the private non-whitelisted username response snapshot", async () => {
+ vi.stubEnv("WHITELIST", "anuraghazra");
+
+ const { default: router } = await import("../../router.js");
+
+ const req = {
+ headers: {},
+ url: "/api/wakatime?username=martin-mfg",
+ };
+ const res = createResponse();
+
+ await router(req, res);
+
+ expect(res.end).toHaveBeenCalledOnce();
+
+ expect({
+ headers: res.setHeader.mock.calls,
+ content: normalizeSvg(res.end.mock.calls[0][0]),
+ }).toMatchSnapshot();
+ });
+
+ it("should match the private missing-username response snapshot", async () => {
+ vi.stubEnv("WHITELIST", "anuraghazra");
+
+ const { default: router } = await import("../../router.js");
+
+ const req = {
+ headers: {},
+ url: "/api/wakatime",
+ };
+ const res = createResponse();
+
+ await router(req, res);
+
+ expect(res.end).toHaveBeenCalledOnce();
+
+ expect({
+ headers: res.setHeader.mock.calls,
+ content: normalizeSvg(res.end.mock.calls[0][0]),
+ }).toMatchSnapshot();
+ });
+});
diff --git a/apps/backend/tests/test-data/api-data.js b/apps/backend/tests/test-data/api-data.js
deleted file mode 100644
index cd0c1e64b05b7..0000000000000
--- a/apps/backend/tests/test-data/api-data.js
+++ /dev/null
@@ -1,51 +0,0 @@
-// @ts-check
-
-/**
- * @type {import("../../src/fetchers/stats").StatsData}
- */
-export 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: { level: "DEV", percentile: 0 },
-};
-
-export const data_stats = {
- data: {
- user: {
- name: stats.name,
- repositoriesContributedTo: { totalCount: stats.contributedTo },
- commits: {
- totalCommitContributions: stats.totalCommits,
- },
- reviews: {
- 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",
- },
- },
- },
- },
-};
diff --git a/apps/backend/tests/test-data/gist-data.js b/apps/backend/tests/test-data/gist-data.js
deleted file mode 100644
index 8bab8d2a2add9..0000000000000
--- a/apps/backend/tests/test-data/gist-data.js
+++ /dev/null
@@ -1,28 +0,0 @@
-// @ts-check
-
-export const gist_data = {
- data: {
- viewer: {
- gist: {
- description:
- "List of countries and territories in English and Spanish: name, continent, capital, dial code, country codes, TLD, and area in sq km. Lista de países y territorios en Inglés y Español: nombre, continente, capital, código de teléfono, códigos de país, dominio y área en km cuadrados. Updated 2023",
- owner: {
- login: "Yizack",
- },
- stargazerCount: 33,
- forks: {
- totalCount: 11,
- },
- files: [
- {
- name: "countries.json",
- language: {
- name: "JSON",
- },
- size: 85858,
- },
- ],
- },
- },
- },
-};
diff --git a/apps/backend/tests/test-data/langs-data.js b/apps/backend/tests/test-data/langs-data.js
deleted file mode 100644
index febec1c7eb955..0000000000000
--- a/apps/backend/tests/test-data/langs-data.js
+++ /dev/null
@@ -1,36 +0,0 @@
-// @ts-check
-
-export const data_langs = {
- data: {
- user: {
- repositories: {
- nodes: [
- {
- languages: {
- edges: [{ size: 150, node: { color: "#0f0", name: "HTML" } }],
- },
- },
- {
- languages: {
- edges: [{ size: 100, node: { color: "#0f0", name: "HTML" } }],
- },
- },
- {
- languages: {
- edges: [
- { size: 100, node: { color: "#0ff", name: "javascript" } },
- ],
- },
- },
- {
- languages: {
- edges: [
- { size: 100, node: { color: "#0ff", name: "javascript" } },
- ],
- },
- },
- ],
- },
- },
- },
-};
diff --git a/apps/backend/tests/test-data/pin-data.js b/apps/backend/tests/test-data/pin-data.js
deleted file mode 100644
index 549f2c6187323..0000000000000
--- a/apps/backend/tests/test-data/pin-data.js
+++ /dev/null
@@ -1,26 +0,0 @@
-// @ts-check
-
-export const data_repo = {
- repository: {
- username: "anuraghazra",
- name: "convoychat",
- stargazers: {
- totalCount: 38000,
- },
- description: "Help us take over the world! React + TS + GraphQL Chat App",
- primaryLanguage: {
- color: "#2b7489",
- id: "MDg6TGFuZ3VhZ2UyODc=",
- name: "TypeScript",
- },
- forkCount: 100,
- isTemplate: false,
- },
-};
-
-export const data_user = {
- data: {
- user: { repository: data_repo.repository },
- organization: null,
- },
-};
diff --git a/apps/backend/tests/top-langs.test.js b/apps/backend/tests/top-langs.test.js
index f0519629aab90..96d424e773561 100644
--- a/apps/backend/tests/top-langs.test.js
+++ b/apps/backend/tests/top-langs.test.js
@@ -1,192 +1,83 @@
// @ts-check
-import axios from "axios";
-import MockAdapter from "axios-mock-adapter";
-import { afterEach, describe, expect, it, vi } from "vitest";
+import { beforeEach, describe, expect, it, vi } from "vitest";
-import topLangs from "../api-renamed/top-langs.js";
-import { renderTopLanguages } from "../src/cards/top-languages.js";
-import { CACHE_TTL, DURATIONS } from "../src/common/cache.js";
-import { renderError } from "../src/common/render.js";
-
-import { data_langs } from "./test-data/langs-data.js";
-
-import "@testing-library/jest-dom/vitest";
-
-const error = {
- errors: [
- {
- type: "NOT_FOUND",
- path: ["user"],
- locations: [],
- message: "Could not fetch user",
- },
- ],
-};
-
-const langs = {
- HTML: {
- color: "#0f0",
- name: "HTML",
- size: 250,
- },
- javascript: {
- color: "#0ff",
- name: "javascript",
- size: 200,
- },
-};
+const mocks = vi.hoisted(() => ({
+ topLangs: vi.fn(),
+ storeRequest: vi.fn(),
+ getUserAccessByName: vi.fn(),
+}));
-const mock = new MockAdapter(axios);
-
-afterEach(() => {
- mock.reset();
+vi.mock("@stats-organization/github-readme-stats-core", async () => {
+ const { mockCore } = await import("./utils.js");
+ return mockCore({ topLangs: mocks.topLangs });
});
-describe("Test /api/top-langs", () => {
- it("should test the request", async () => {
- const req = {
- query: {
- username: "anuraghazra",
- },
- };
- const res = {
- setHeader: vi.fn(),
- send: vi.fn(),
- };
- mock.onPost("https://api.github.com/graphql").reply(200, data_langs);
+vi.mock("../src/common/database.js", () => ({
+ storeRequest: mocks.storeRequest,
+ getUserAccessByName: mocks.getUserAccessByName,
+}));
- await topLangs(req, res);
-
- expect(res.setHeader).toHaveBeenCalledWith("Content-Type", "image/svg+xml");
- expect(res.send).toHaveBeenCalledWith(renderTopLanguages(langs));
- });
-
- it("should work with the query options", async () => {
- const req = {
- query: {
- username: "anuraghazra",
- hide_title: true,
- card_width: 100,
- title_color: "fff",
- icon_color: "fff",
- text_color: "fff",
- bg_color: "fff",
- },
- };
- const res = {
- setHeader: vi.fn(),
- send: vi.fn(),
- };
- mock.onPost("https://api.github.com/graphql").reply(200, data_langs);
-
- await topLangs(req, res);
-
- expect(res.setHeader).toHaveBeenCalledWith("Content-Type", "image/svg+xml");
- expect(res.send).toHaveBeenCalledWith(
- renderTopLanguages(langs, {
- hide_title: true,
- card_width: 100,
- title_color: "fff",
- icon_color: "fff",
- text_color: "fff",
- bg_color: "fff",
- }),
- );
- });
-
- it("should render error card on user data fetch error", async () => {
- const req = {
- query: {
- username: "anuraghazra",
- },
- };
- const res = {
- setHeader: vi.fn(),
- send: vi.fn(),
- };
- mock.onPost("https://api.github.com/graphql").reply(200, error);
+import router from "../router.js";
+import { CACHE_TTL, DURATIONS } from "../src/common/cache.js";
- await topLangs(req, res);
+const createRequest = (search = "") => ({
+ headers: {},
+ url: `/api/top-langs?${search}`,
+});
- expect(res.setHeader).toHaveBeenCalledWith("Content-Type", "image/svg+xml");
- expect(res.send).toHaveBeenCalledWith(
- renderError({
- message: error.errors[0].message,
- secondaryMessage:
- "Make sure the provided username is not an organization",
- }),
- );
- });
+const createResponse = () => ({
+ end: vi.fn(),
+ setHeader: vi.fn(),
+});
- it("should render error card on incorrect layout input", async () => {
- const req = {
- query: {
- username: "anuraghazra",
- layout: ["pie"],
- },
- };
- const res = {
- setHeader: vi.fn(),
- send: vi.fn(),
- };
- mock.onPost("https://api.github.com/graphql").reply(200, data_langs);
+const defaultCacheHeader =
+ `max-age=${CACHE_TTL.TOP_LANGS_CARD.DEFAULT}, ` +
+ `s-maxage=${CACHE_TTL.TOP_LANGS_CARD.DEFAULT}, ` +
+ `stale-while-revalidate=${DURATIONS.ONE_DAY}`;
+
+beforeEach(() => {
+ mocks.topLangs.mockReset();
+ mocks.storeRequest.mockReset().mockResolvedValue(undefined);
+ mocks.getUserAccessByName.mockReset().mockResolvedValue(null);
+ // CACHE_SECONDS is not set here, this is just to safeguard against CACHE_SECONDS being set externally
+ delete process.env.CACHE_SECONDS;
+});
- await topLangs(req, res);
+describe("Test /api/top-langs backend routing", () => {
+ it("happy path should pass query params and user PAT, respond with top languages content and persist request", async () => {
+ mocks.getUserAccessByName.mockResolvedValue({ token: "user-pat" });
+ mocks.topLangs.mockResolvedValue({
+ status: "success",
+ content: "mock-top-langs-svg",
+ });
- expect(res.setHeader).toHaveBeenCalledWith("Content-Type", "image/svg+xml");
- expect(res.send).toHaveBeenCalledWith(
- renderError({
- message: "Something went wrong",
- secondaryMessage: "Incorrect layout input",
- }),
+ const req = createRequest(
+ "username=anuraghazra&layout=compact&langs_count=5",
);
- });
+ const res = createResponse();
- it("should render error card if wrong locale provided", async () => {
- const req = {
- query: {
- username: "anuraghazra",
- locale: "asdf",
- },
- };
- const res = {
- setHeader: vi.fn(),
- send: vi.fn(),
- };
- mock.onPost("https://api.github.com/graphql").reply(200, data_langs);
+ await router(req, res);
- await topLangs(req, res);
-
- expect(res.setHeader).toHaveBeenCalledWith("Content-Type", "image/svg+xml");
- expect(res.send).toHaveBeenCalledWith(
- renderError({
- message: "Something went wrong",
- secondaryMessage: "Locale not found",
- }),
- );
- });
-
- it("should have proper cache", async () => {
- const req = {
- query: {
+ expect(mocks.getUserAccessByName).toHaveBeenCalledWith("anuraghazra");
+ expect(mocks.topLangs).toHaveBeenCalledWith(
+ {
username: "anuraghazra",
+ layout: "compact",
+ langs_count: "5",
},
- };
- const res = {
- setHeader: vi.fn(),
- send: vi.fn(),
- };
- mock.onPost("https://api.github.com/graphql").reply(200, data_langs);
-
- await topLangs(req, res);
-
- expect(res.setHeader).toHaveBeenCalledWith("Content-Type", "image/svg+xml");
- expect(res.setHeader).toHaveBeenCalledWith(
- "Cache-Control",
- `max-age=${CACHE_TTL.TOP_LANGS_CARD.DEFAULT}, ` +
- `s-maxage=${CACHE_TTL.TOP_LANGS_CARD.DEFAULT}, ` +
- `stale-while-revalidate=${DURATIONS.ONE_DAY}`,
+ "user-pat",
);
+ expect(req.query).toEqual({
+ username: "anuraghazra",
+ layout: "compact",
+ langs_count: "5",
+ });
+ expect(res.setHeader.mock.calls).toEqual([
+ ["Cache-Control", defaultCacheHeader],
+ ["Content-Type", "image/svg+xml"],
+ ]);
+ expect(res.end).toHaveBeenCalledExactlyOnceWith("mock-top-langs-svg");
+ expect(mocks.storeRequest).toHaveBeenCalledExactlyOnceWith(req);
});
});
diff --git a/apps/backend/tests/utils.js b/apps/backend/tests/utils.js
index 21b9f1f887391..9108685ace6cc 100644
--- a/apps/backend/tests/utils.js
+++ b/apps/backend/tests/utils.js
@@ -1,41 +1,288 @@
// @ts-check
+export const data_stats = {
+ data: {
+ user: {
+ name: "Anurag Hazra",
+ login: "anuraghazra",
+ repositoriesContributedTo: { totalCount: 51 },
+ commits: {
+ totalCommitContributions: 200,
+ },
+ reviews: {
+ totalPullRequestReviewContributions: 1234,
+ },
+ pullRequests: { totalCount: 4000 },
+ mergedPullRequests: { totalCount: 3200 },
+ openIssues: { totalCount: 300 },
+ closedIssues: { totalCount: 40 },
+ followers: { totalCount: 150 },
+ repositoryDiscussions: { totalCount: 222 },
+ repositoryDiscussionComments: {
+ totalCount: 111,
+ },
+ repositories: {
+ totalCount: 3,
+ nodes: [
+ { name: "repo-keep-1", stargazers: { totalCount: 1500 } },
+ { name: "repo-exclude-me", stargazers: { totalCount: 9999 } },
+ { name: "repo-keep-2", stargazers: { totalCount: 2600 } },
+ ],
+ pageInfo: {
+ hasNextPage: false,
+ endCursor: "cursor",
+ },
+ },
+ },
+ },
+};
+
+export const happy_path_gist_data = {
+ data: {
+ viewer: {
+ gist: {
+ description:
+ "List of countries and territories in English and Spanish: name, continent, capital, dial code, country codes, TLD, and area in sq km. Lista de países y territorios en Inglés y Español: nombre, continente, capital, código de teléfono, códigos de país, dominio y área en km cuadrados. Updated 2023",
+ owner: {
+ login: "Yizack",
+ },
+ stargazerCount: 33,
+ forks: {
+ totalCount: 11,
+ },
+ files: [
+ {
+ name: "countries.json",
+ language: {
+ name: "JSON",
+ },
+ size: 85858,
+ },
+ ],
+ },
+ },
+ },
+};
+
+export const data_user = {
+ data: {
+ user: {
+ repository: {
+ username: "anuraghazra",
+ name: "convoychat",
+ nameWithOwner: "anuraghazra/convoychat",
+ stargazers: {
+ totalCount: 38000,
+ },
+ description:
+ "Help us take over the world with a deeply customizable React, TypeScript and GraphQL chat app that has enough text to wrap across multiple lines in the repository card.",
+ primaryLanguage: {
+ color: "#2b7489",
+ id: "MDg6TGFuZ3VhZ2UyODc=",
+ name: "TypeScript",
+ },
+ forkCount: 100,
+ isTemplate: false,
+ isArchived: false,
+ },
+ },
+ organization: null,
+ },
+};
+
+export const data_langs = {
+ data: {
+ user: {
+ repositories: {
+ nodes: [
+ {
+ name: "repo-html",
+ languages: {
+ edges: [{ size: 180, node: { color: "#0f0", name: "HTML" } }],
+ },
+ },
+ {
+ name: "repo-javascript",
+ languages: {
+ edges: [
+ { size: 120, node: { color: "#0ff", name: "JavaScript" } },
+ ],
+ },
+ },
+ {
+ name: "repo-ts-1",
+ languages: {
+ edges: [
+ { size: 45, node: { color: "#3178c6", name: "TypeScript" } },
+ ],
+ },
+ },
+ {
+ name: "repo-ts-2",
+ languages: {
+ edges: [
+ { size: 45, node: { color: "#3178c6", name: "TypeScript" } },
+ ],
+ },
+ },
+ {
+ name: "repo-other-langs",
+ languages: {
+ edges: [
+ { size: 20, node: { color: "#f00", name: "Java" } },
+ { size: 10, node: { color: "#0f0", name: "Cobol" } },
+ { size: 15, node: { color: "#00f", name: "Python" } },
+ ],
+ },
+ },
+ {
+ name: "repo-hidden",
+ languages: {
+ edges: [{ size: 1000, node: { color: "#dea584", name: "Rust" } }],
+ },
+ },
+ ],
+ },
+ },
+ },
+};
+
+export const wakaTimeData = {
+ data: {
+ categories: [
+ {
+ digital: "22:40",
+ hours: 22,
+ minutes: 40,
+ name: "Coding",
+ percent: 100,
+ text: "22 hrs 40 mins",
+ total_seconds: 81643.570077,
+ },
+ ],
+ daily_average: 16095,
+ daily_average_including_other_language: 16329,
+ days_including_holidays: 7,
+ days_minus_holidays: 5,
+ editors: [
+ {
+ digital: "22:40",
+ hours: 22,
+ minutes: 40,
+ name: "VS Code",
+ percent: 100,
+ text: "22 hrs 40 mins",
+ total_seconds: 81643.570077,
+ },
+ ],
+ holidays: 2,
+ human_readable_daily_average: "4 hrs 28 mins",
+ human_readable_daily_average_including_other_language: "4 hrs 32 mins",
+ human_readable_total: "22 hrs 21 mins",
+ human_readable_total_including_other_language: "22 hrs 40 mins",
+ id: "random hash",
+ is_already_updating: false,
+ is_coding_activity_visible: true,
+ is_including_today: false,
+ is_other_usage_visible: true,
+ is_stuck: false,
+ is_up_to_date: true,
+ languages: [
+ {
+ digital: "12:00",
+ hours: 12,
+ minutes: 0,
+ name: "TypeScript",
+ percent: 52,
+ text: "12 hrs",
+ total_seconds: 43200,
+ },
+ {
+ digital: "6:30",
+ hours: 6,
+ minutes: 30,
+ name: "JavaScript",
+ percent: 28,
+ text: "6 hrs 30 mins",
+ total_seconds: 23400,
+ },
+ {
+ digital: "3:00",
+ hours: 3,
+ minutes: 0,
+ name: "Other",
+ percent: 13,
+ text: "3 hrs",
+ total_seconds: 10800,
+ },
+ {
+ digital: "1:00",
+ hours: 1,
+ minutes: 0,
+ name: "YAML",
+ percent: 4,
+ text: "1 hr",
+ total_seconds: 3600,
+ },
+ {
+ digital: "0:30",
+ hours: 0,
+ minutes: 30,
+ name: "JSON",
+ percent: 3,
+ text: "30 mins",
+ total_seconds: 1800,
+ },
+ ],
+ operating_systems: [
+ {
+ digital: "22:40",
+ hours: 22,
+ minutes: 40,
+ name: "Mac",
+ percent: 100,
+ text: "22 hrs 40 mins",
+ total_seconds: 81643.570077,
+ },
+ ],
+ percent_calculated: 100,
+ range: "last_7_days",
+ status: "ok",
+ timeout: 15,
+ total_seconds: 80473.135716,
+ total_seconds_including_other_language: 81643.570077,
+ user_id: "random hash",
+ username: "anuraghazra",
+ writes_only: false,
+ },
+};
+
/**
- * Creates an asymmetric matcher for approximate numeric equality.
- *
- * This helper is intended for use in test frameworks (e.g., Jest) where
- * values need to be compared within a configurable decimal precision
- * instead of strict equality.
- *
- * The comparison succeeds when:
- *
- * |actual - expected| < 10^(-precision)
- *
- * For example, with `precision = 3`, values must be within `0.001`.
- *
- * @param {number} expected The expected numeric value to compare against.
- *
- * @param {number} [precision=10]
- * The number of decimal places of tolerance. Higher values mean stricter
- * comparison. Internally converted to epsilon = 10^-precision.
- *
- * @returns {{
- * asymmetricMatch(actual: unknown): boolean,
- * toAsymmetricMatcher(): string
- * }} An object implementing Jest-style asymmetric matcher methods.
- *
+ * Creates a mock module for @stats-organization/github-readme-stats-core.
+ * @param {any} mocks Mocked functions of the core module.
+ * @returns {any} Mocked core module.
*/
-export function approxNumber(expected, precision = 10) {
+export function mockCore(mocks) {
+ const noop = () => undefined;
+
return {
- asymmetricMatch(actual) {
- if (typeof actual !== "number" || typeof expected !== "number") {
- return false;
- }
- const epsilon = Math.pow(10, -precision);
- return Math.abs(actual - expected) < epsilon;
- },
- toAsymmetricMatcher() {
- return `≈ ${expected} (precision ${precision})`;
- },
+ api: mocks.api ?? noop,
+ gist: mocks.gist ?? noop,
+ pin: mocks.pin ?? noop,
+ topLangs: mocks.topLangs ?? noop,
+ wakatime: mocks.wakatime ?? noop,
+ getConfig: mocks.getConfig ?? (() => mocks.config ?? {}),
+ renderError: ({ message }) => `render-error:${message}`,
+ clampValue: (value, min, max) => Math.min(Math.max(value, min), max),
};
}
+
+/**
+ * Normalizes SVG code for stable test comparisons.
+ * @param {string} svg SVG code to normalize.
+ * @returns {string} Normalized SVG code.
+ */
+export function normalizeSvg(svg) {
+ const document = new DOMParser().parseFromString(svg, "image/svg+xml");
+ return new XMLSerializer().serializeToString(document);
+}
diff --git a/apps/backend/tests/wakatime.test.js b/apps/backend/tests/wakatime.test.js
index f27eec0bfb05e..e1561724fa57d 100644
--- a/apps/backend/tests/wakatime.test.js
+++ b/apps/backend/tests/wakatime.test.js
@@ -1,195 +1,77 @@
-import axios from "axios";
-import MockAdapter from "axios-mock-adapter";
-import { afterEach, describe, expect, it, vi } from "vitest";
+// @ts-check
-import wakatime from "../api-renamed/wakatime.js";
-import { renderWakatimeCard } from "../src/cards/wakatime.js";
-import { CACHE_TTL, DURATIONS } from "../src/common/cache.js";
-import { renderError } from "../src/index.js";
-
-import "@testing-library/jest-dom/vitest";
-
-const wakaTimeData = {
- data: {
- categories: [
- {
- digital: "22:40",
- hours: 22,
- minutes: 40,
- name: "Coding",
- percent: 100,
- text: "22 hrs 40 mins",
- total_seconds: 81643.570077,
- },
- ],
- daily_average: 16095,
- daily_average_including_other_language: 16329,
- days_including_holidays: 7,
- days_minus_holidays: 5,
- editors: [
- {
- digital: "22:40",
- hours: 22,
- minutes: 40,
- name: "VS Code",
- percent: 100,
- text: "22 hrs 40 mins",
- total_seconds: 81643.570077,
- },
- ],
- holidays: 2,
- human_readable_daily_average: "4 hrs 28 mins",
- human_readable_daily_average_including_other_language: "4 hrs 32 mins",
- human_readable_total: "22 hrs 21 mins",
- human_readable_total_including_other_language: "22 hrs 40 mins",
- id: "random hash",
- is_already_updating: false,
- is_coding_activity_visible: true,
- is_including_today: false,
- is_other_usage_visible: true,
- is_stuck: false,
- is_up_to_date: true,
- languages: [
- {
- digital: "0:19",
- hours: 0,
- minutes: 19,
- name: "Other",
- percent: 1.43,
- text: "19 mins",
- total_seconds: 1170.434361,
- },
- {
- digital: "0:01",
- hours: 0,
- minutes: 1,
- name: "TypeScript",
- percent: 0.1,
- text: "1 min",
- total_seconds: 83.293809,
- },
- {
- digital: "0:00",
- hours: 0,
- minutes: 0,
- name: "YAML",
- percent: 0.07,
- text: "0 secs",
- total_seconds: 54.975151,
- },
- ],
- operating_systems: [
- {
- digital: "22:40",
- hours: 22,
- minutes: 40,
- name: "Mac",
- percent: 100,
- text: "22 hrs 40 mins",
- total_seconds: 81643.570077,
- },
- ],
- percent_calculated: 100,
- range: "last_7_days",
- status: "ok",
- timeout: 15,
- total_seconds: 80473.135716,
- total_seconds_including_other_language: 81643.570077,
- user_id: "random hash",
- username: "anuraghazra",
- writes_only: false,
- },
-};
+import { beforeEach, describe, expect, it, vi } from "vitest";
-const wakaTimeNotFoundData = {
- data: {
- viewer: {
- gist: null,
- },
- },
-};
+const mocks = vi.hoisted(() => ({
+ wakatime: vi.fn(),
+ storeRequest: vi.fn(),
+ getUserAccessByName: vi.fn(),
+}));
-const mock = new MockAdapter(axios);
-
-afterEach(() => {
- mock.reset();
+vi.mock("@stats-organization/github-readme-stats-core", async () => {
+ const { mockCore } = await import("./utils.js");
+ return mockCore({ wakatime: mocks.wakatime });
});
-describe("Test /api/wakatime", () => {
- it("should test the request", async () => {
- const username = "anuraghazra";
- const req = { query: { username } };
- const res = { setHeader: vi.fn(), send: vi.fn() };
- mock
- .onGet(
- `https://wakatime.com/api/v1/users/${username}/stats?is_including_today=true`,
- )
- .reply(200, wakaTimeData);
-
- await wakatime(req, res);
-
- expect(res.setHeader).toHaveBeenCalledWith("Content-Type", "image/svg+xml");
- expect(res.send).toHaveBeenCalledWith(
- renderWakatimeCard(wakaTimeData.data, {}),
- );
- });
-
- it("should render error if wrong locale is provided", async () => {
- const username = "anuraghazra";
- const req = { query: { username, locale: "asdf" } };
- const res = { setHeader: vi.fn(), send: vi.fn() };
- mock
- .onGet(
- `https://wakatime.com/api/v1/users/${username}/stats?is_including_today=true`,
- )
- .reply(200, wakaTimeData);
-
- await wakatime(req, res);
+vi.mock("../src/common/database.js", () => ({
+ storeRequest: mocks.storeRequest,
+ getUserAccessByName: mocks.getUserAccessByName,
+}));
- expect(res.setHeader).toHaveBeenCalledWith("Content-Type", "image/svg+xml");
- expect(res.send).toHaveBeenCalledWith(
- renderError({
- message: "Something went wrong",
- secondaryMessage: "Language not found",
- }),
- );
- });
-
- it("should render error if user data is not accessible", async () => {
- const username = "anuraghazra";
- const req = { query: { username } };
- const res = { setHeader: vi.fn(), send: vi.fn() };
- mock
- .onGet(
- `https://wakatime.com/api/v1/users/${username}/stats?is_including_today=true`,
- )
- .reply(200, wakaTimeNotFoundData);
-
- await wakatime(req, res);
+import router from "../router.js";
+import { CACHE_TTL, DURATIONS } from "../src/common/cache.js";
- expect(res.setHeader).toHaveBeenCalledWith("Content-Type", "image/svg+xml");
- expect(res.send).toHaveBeenCalledTimes(1);
- expect(res.send.mock.calls[0][0]).toMatchSnapshot();
- });
+const createRequest = (search = "") => ({
+ headers: {},
+ url: `/api/wakatime?${search}`,
+});
- it("should have proper cache", async () => {
- const username = "anuraghazra";
- const req = { query: { username } };
- const res = { setHeader: vi.fn(), send: vi.fn() };
- mock
- .onGet(
- `https://wakatime.com/api/v1/users/${username}/stats?is_including_today=true`,
- )
- .reply(200, wakaTimeData);
+const createResponse = () => ({
+ end: vi.fn(),
+ setHeader: vi.fn(),
+});
- await wakatime(req, res);
+const defaultCacheHeader =
+ `max-age=${CACHE_TTL.WAKATIME_CARD.DEFAULT}, ` +
+ `s-maxage=${CACHE_TTL.WAKATIME_CARD.DEFAULT}, ` +
+ `stale-while-revalidate=${DURATIONS.ONE_DAY}`;
+
+beforeEach(() => {
+ mocks.wakatime.mockReset();
+ mocks.storeRequest.mockReset().mockResolvedValue(undefined);
+ mocks.getUserAccessByName.mockReset().mockResolvedValue(null);
+ // CACHE_SECONDS is not set here, this is just to safeguard against CACHE_SECONDS being set externally
+ delete process.env.CACHE_SECONDS;
+});
- expect(res.setHeader).toHaveBeenCalledWith("Content-Type", "image/svg+xml");
- expect(res.setHeader).toHaveBeenCalledWith(
- "Cache-Control",
- `max-age=${CACHE_TTL.WAKATIME_CARD.DEFAULT}, ` +
- `s-maxage=${CACHE_TTL.WAKATIME_CARD.DEFAULT}, ` +
- `stale-while-revalidate=${DURATIONS.ONE_DAY}`,
- );
+describe("Test /api/wakatime backend routing", () => {
+ it("happy path should pass query params, respond with wakatime content and persist request", async () => {
+ mocks.wakatime.mockResolvedValue({
+ status: "success",
+ content: "mock-wakatime-svg",
+ });
+
+ const req = createRequest("username=anuraghazra&theme=dark&layout=compact");
+ const res = createResponse();
+
+ await router(req, res);
+
+ expect(mocks.wakatime).toHaveBeenCalledWith({
+ username: "anuraghazra",
+ theme: "dark",
+ layout: "compact",
+ });
+ expect(mocks.getUserAccessByName).not.toHaveBeenCalled();
+ expect(req.query).toEqual({
+ username: "anuraghazra",
+ theme: "dark",
+ layout: "compact",
+ });
+ expect(res.setHeader.mock.calls).toEqual([
+ ["Cache-Control", defaultCacheHeader],
+ ["Content-Type", "image/svg+xml"],
+ ]);
+ expect(res.end).toHaveBeenCalledExactlyOnceWith("mock-wakatime-svg");
+ expect(mocks.storeRequest).toHaveBeenCalledExactlyOnceWith(req);
});
});
diff --git a/apps/backend/tsconfig.typecheck.json b/apps/backend/tsconfig.typecheck.json
new file mode 100644
index 0000000000000..4c13d7b105526
--- /dev/null
+++ b/apps/backend/tsconfig.typecheck.json
@@ -0,0 +1,7 @@
+{
+ "extends": ["./tsconfig.json"],
+ "compilerOptions": {
+ "noEmit": true,
+ "customConditions": []
+ }
+}
diff --git a/apps/backend/vercel.json b/apps/backend/vercel.json
index 62ccb490d7852..addf7bddfb6f7 100644
--- a/apps/backend/vercel.json
+++ b/apps/backend/vercel.json
@@ -1,6 +1,6 @@
{
"$schema": "https://openapi.vercel.sh/vercel.json",
- "buildCommand": "cd ../../ && git clean ./apps -fx && pnpm --filter ./apps/backend/ --legacy deploy ./apps/deployment/ && mv ./apps/deployment/node_modules/ ./apps/backend/node_modules/ && ./vercel-preparation.sh",
+ "buildCommand": "cd ../../ && git clean ./apps -fx && pnpm run build:packages && pnpm --filter ./apps/backend/ --legacy deploy ./apps/deployment/ && mv ./apps/deployment/node_modules/ ./apps/backend/node_modules/ && ./vercel-preparation.sh",
"redirects": [
{
"source": "/",
diff --git a/apps/backend/vitest.config.bench.ts b/apps/backend/vitest.config.bench.ts
index 9fe2e73d58016..1f3eef9d8ca26 100644
--- a/apps/backend/vitest.config.bench.ts
+++ b/apps/backend/vitest.config.bench.ts
@@ -1,6 +1,9 @@
import { defineConfig } from "vitest/config";
export default defineConfig({
+ resolve: {
+ conditions: ["@stats/source"],
+ },
test: {
dir: "./tests/bench",
environment: "jsdom",
diff --git a/apps/backend/vitest.config.e2e.ts b/apps/backend/vitest.config.e2e.ts
index 0e6c6312d70a9..f7433de3c9f57 100644
--- a/apps/backend/vitest.config.e2e.ts
+++ b/apps/backend/vitest.config.e2e.ts
@@ -1,6 +1,9 @@
import { defineConfig } from "vitest/config";
export default defineConfig({
+ resolve: {
+ conditions: ["@stats/source"],
+ },
test: {
environment: "node",
dir: "tests/e2e",
diff --git a/apps/backend/vitest.config.ts b/apps/backend/vitest.config.ts
index 5da6c76b2ff07..54dcb61324b61 100644
--- a/apps/backend/vitest.config.ts
+++ b/apps/backend/vitest.config.ts
@@ -1,32 +1,15 @@
-import { defineConfig } from "vitest/config";
+import { defineProject } from "vitest/config";
-export default defineConfig({
+export default defineProject({
+ resolve: {
+ conditions: ["@stats/source"],
+ },
test: {
- coverage: {
- enabled: true,
- },
- projects: [
- {
- test: {
- name: "backend/public-instance",
- environment: "jsdom",
- dir: "tests",
- include: ["./*.test.{ts,js}", "./public-instance/*.test.{ts,js}"],
- setupFiles: ["./tests/_setup.js"],
- },
- },
- {
- test: {
- name: "backend/private-instance",
- environment: "jsdom",
- dir: "tests",
- include: ["./*.test.{ts,js}", "./private-instance/*.test.{ts,js}"],
- setupFiles: [
- "./tests/_setup.js",
- "./tests/_setup.private-instance.js",
- ],
- },
- },
+ environment: "jsdom",
+ include: [
+ "./tests/*.test.{ts,js}",
+ "./tests/public-instance/*.test.{ts,js}",
],
+ setupFiles: ["./tests/_setup.js"],
},
});
diff --git a/apps/frontend/package.json b/apps/frontend/package.json
index aa6d33c59508a..7e3031f48301f 100644
--- a/apps/frontend/package.json
+++ b/apps/frontend/package.json
@@ -1,27 +1,35 @@
{
- "name": "frontend",
- "version": "0.1.0",
- "private": true,
+ "name": "@stats-organization/github-readme-stats-frontend",
"type": "module",
+ "license": "MIT",
+ "private": true,
+ "scripts": {
+ "dev": "vite",
+ "build": "vite build",
+ "preview": "vite preview",
+ "test": "vitest",
+ "test:e2e": "playwright test",
+ "lint": "eslint",
+ "typecheck": "tsc -p tsconfig.typecheck.json"
+ },
"dependencies": {
- "@reduxjs/toolkit": "2.11.2",
- "@tailwindcss/vite": "4.2.1",
+ "@reduxjs/toolkit": "^2.11.2",
+ "@tailwindcss/vite": "^4.2.1",
"axios": "^1",
"axios-cache-interceptor": "^1",
- "daisyui": "5.5.19",
- "emoji-name-map": "^2.0.3",
- "github-username-regex": "^1.0.0",
- "react": "18.3.1",
- "react-dom": "18.3.1",
+ "daisyui": "^5.5.19",
+ "@stats-organization/github-readme-stats-backend": "workspace:^",
+ "@stats-organization/github-readme-stats-core": "workspace:^",
+ "react": "^18.3.1",
+ "react-dom": "^18.3.1",
"react-icons": "^5.5.0",
"react-loading-skeleton": "^3.3.1",
- "react-redux": "9.2.0",
+ "react-redux": "^9.2.0",
"react-spinners": "^0.17.0",
- "react-toastify": "11.0.5",
- "redux": "5.0.1",
+ "react-toastify": "^11.0.5",
+ "redux": "^5.0.1",
"save-svg-as-png": "^1.4.17",
- "uuid": "13.0.0",
- "word-wrap": "^1.2.5"
+ "uuid": "^13.0.0"
},
"devDependencies": {
"@types/react": "18.3.27",
@@ -30,19 +38,8 @@
"clsx": "2.1.1",
"tailwindcss": "4.2.1",
"vite": "catalog:default",
- "vite-plugin-node-polyfills": "0.25.0",
- "vite-plugin-string-replace": "1.1.5",
"vitest": "catalog:default"
},
- "scripts": {
- "dev": "vite",
- "build": "vite build",
- "preview": "vite preview",
- "test": "vitest",
- "test:e2e": "playwright test",
- "typecheck": "tsc --noEmit"
- },
- "homepage": "/frontend",
"browserslist": {
"production": [
">0.2%",
diff --git a/apps/frontend/src/components/Card/SvgInline.tsx b/apps/frontend/src/components/Card/SvgInline.tsx
index 7c85a010d5217..2834864ea47bd 100644
--- a/apps/frontend/src/components/Card/SvgInline.tsx
+++ b/apps/frontend/src/components/Card/SvgInline.tsx
@@ -1,3 +1,5 @@
+// @ts-expect-error type info should be added later
+import { router } from "@stats-organization/github-readme-stats-backend";
import axios from "axios";
import { useEffect, useRef, useState } from "react";
import type { JSX } from "react";
@@ -5,8 +7,6 @@ import Skeleton from "react-loading-skeleton";
import "react-loading-skeleton/dist/skeleton.css";
import { setShouldMock } from "../../axios-override.js";
-// @ts-expect-error will be solved by npm package
-import { default as router } from "../../backend/.vercel/output/functions/api.func/router.js";
import { createMockRequest, createMockResponse } from "../../mock-http.js";
import {
useIsAuthenticated,
diff --git a/apps/frontend/src/constants.ts b/apps/frontend/src/constants.ts
index 79c02da965e01..e3328ed27ec5d 100644
--- a/apps/frontend/src/constants.ts
+++ b/apps/frontend/src/constants.ts
@@ -13,10 +13,10 @@ const REDIRECT_URI = `https://${HOST}/frontend`;
export const GITHUB_PRIVATE_AUTH_URL = `https://github.com/login/oauth/authorize?scope=user,repo&client_id=${CLIENT_ID}&redirect_uri=${REDIRECT_URI}?mode=private`;
export const GITHUB_PUBLIC_AUTH_URL = `https://github.com/login/oauth/authorize?client_id=${CLIENT_ID}&redirect_uri=${REDIRECT_URI}?mode=public`;
-export const DEMO_USER = "anuraghazra";
-export const DEMO_REPO = "anuraghazra/github-readme-stats";
-export const DEMO_GIST = "bbfce31e0217a3689c8d961a356cb10d";
-export const DEMO_WAKATIME_USER = "alan";
+export const DEMO_USER = "anuraghazra" as string;
+export const DEMO_REPO = "anuraghazra/github-readme-stats" as string;
+export const DEMO_GIST = "bbfce31e0217a3689c8d961a356cb10d" as string;
+export const DEMO_WAKATIME_USER = "alan" as string;
window.process = {
env: {
diff --git a/apps/frontend/src/pages/Home/Home.tsx b/apps/frontend/src/pages/Home/Home.tsx
index e73cb397847d1..a7b0b6656e6a1 100644
--- a/apps/frontend/src/pages/Home/Home.tsx
+++ b/apps/frontend/src/pages/Home/Home.tsx
@@ -48,7 +48,7 @@ export function HomeScreen({ stage, setStage }: HomeScreenProps): JSX.Element {
const dispatch = useDispatch();
// for stage two
- const [selectedUserId, setSelectedUserId] = useState(userId);
+ const [selectedUserId, setSelectedUserId] = useState(userId);
const [repo, setRepo] = useState(DEMO_REPO);
const [gist, setGist] = useState(DEMO_GIST);
const [wakatimeUser, setWakatimeUser] = useState(DEMO_WAKATIME_USER);
diff --git a/apps/frontend/src/pages/Home/stages/Theme.tsx b/apps/frontend/src/pages/Home/stages/Theme.tsx
index 247d8caf89243..23797cf3f1cfb 100644
--- a/apps/frontend/src/pages/Home/stages/Theme.tsx
+++ b/apps/frontend/src/pages/Home/stages/Theme.tsx
@@ -1,21 +1,8 @@
+import { themes } from "@stats-organization/github-readme-stats-core";
import type { JSX } from "react";
-// @ts-expect-error this will be provided by the npm package
-import { themes } from "../../../backend/themes/index";
import { Card } from "../../../components/Card/Card";
-// to be removed once npm package has been created
-type ThemeData = Record<
- string,
- {
- title_color: string;
- icon_color: string;
- text_color: string;
- bg_color: string;
- border_color: string;
- }
->;
-
const excludedThemes = [
"merko",
"blue-green",
@@ -25,10 +12,9 @@ const excludedThemes = [
"holi",
];
-const themeList = Object.keys(
- /* Needed until themes is typed correctly and retrieved from npm package */
- themes as ThemeData,
-).filter((myTheme) => !excludedThemes.includes(myTheme));
+const themeList = Object.keys(themes).filter(
+ (myTheme) => !excludedThemes.includes(myTheme),
+);
interface ThemeStageProps {
fullSuffix: string;
diff --git a/apps/frontend/tsconfig.typecheck.json b/apps/frontend/tsconfig.typecheck.json
new file mode 100644
index 0000000000000..4c13d7b105526
--- /dev/null
+++ b/apps/frontend/tsconfig.typecheck.json
@@ -0,0 +1,7 @@
+{
+ "extends": ["./tsconfig.json"],
+ "compilerOptions": {
+ "noEmit": true,
+ "customConditions": []
+ }
+}
diff --git a/apps/frontend/vercel.json b/apps/frontend/vercel.json
index 138025f9af2ca..6ed946480cbf9 100644
--- a/apps/frontend/vercel.json
+++ b/apps/frontend/vercel.json
@@ -1,6 +1,6 @@
{
"$schema": "https://openapi.vercel.sh/vercel.json",
- "installCommand": "cd ../../ && git clean ./apps -fx && pnpm install && rm -rf ./apps/frontend/node_modules && ./vercel-preparation.sh && pnpm --filter ./apps/frontend/ --legacy deploy ./apps/deployment/ && mv ./apps/deployment/node_modules/ ./apps/frontend/node_modules/",
+ "installCommand": "cd ../../ && git clean ./apps -fx && pnpm install && pnpm run build:packages && rm -rf ./apps/frontend/node_modules && pnpm --filter ./apps/frontend/ --legacy deploy ./apps/deployment/ && mv ./apps/deployment/node_modules/ ./apps/frontend/node_modules/",
"buildCommand": "pnpm run build",
"outputDirectory": "build"
}
diff --git a/apps/frontend/vite.config.ts b/apps/frontend/vite.config.ts
index b6c6e15275e2c..da01ed72e8367 100644
--- a/apps/frontend/vite.config.ts
+++ b/apps/frontend/vite.config.ts
@@ -2,57 +2,12 @@ import path from "node:path";
import tailwindcss from "@tailwindcss/vite";
import react from "@vitejs/plugin-react-swc";
-import { nodePolyfills } from "vite-plugin-node-polyfills";
-import StringReplace from "vite-plugin-string-replace";
-import { defineConfig } from "vitest/config";
+import { defineProject } from "vitest/config";
// https://vitejs.dev/config/
-export default defineConfig({
+export default defineProject({
base: "/frontend/",
- plugins: [
- StringReplace([
- {
- search: "process.env",
- replace: "window.process.env",
- fileName: ".*backend.*",
- },
- ]),
- nodePolyfills({
- include: [
- "path",
- "querystring",
- "url",
- "http",
- "util",
- "stream",
- "buffer",
- ],
- exclude: ["fs", "net"],
- }),
-
- react(),
- tailwindcss(),
-
- /**
- * mock pg (postgres) package in the browser to avoid runtime errors
- * @see apps/backend/src/common/database.js
- */
- {
- name: "empty-pg-package",
- resolveId(id) {
- if (id === "pg") {
- return id;
- }
- return undefined;
- },
- load(id) {
- if (id === "pg") {
- return "export class Pool { constructor(config) {} }";
- }
- return undefined;
- },
- },
- ],
+ plugins: [react(), tailwindcss()],
build: {
outDir: "build",
sourcemap: true,
@@ -61,6 +16,7 @@ export default defineConfig({
chunkSizeWarningLimit: 800,
},
resolve: {
+ conditions: ["@stats/source"],
alias: [
{
find: "../src/fetchers/wakatime.js",
@@ -72,7 +28,7 @@ export default defineConfig({
],
},
test: {
- dir: "./src",
+ dir: path.join(import.meta.dirname, "./src"),
exclude: ["**/backend/**"],
},
});
diff --git a/docs/advanced_documentation.md b/docs/advanced_documentation.md
index 10965e45f0653..bd2e7877c95a9 100644
--- a/docs/advanced_documentation.md
+++ b/docs/advanced_documentation.md
@@ -76,7 +76,7 @@ GitHub Stats Extended comes with several built-in themes (e.g. `dark`, `radical`
-You can look at a preview for [all available themes](../backend/themes/README.md) or checkout the [theme config file](../backend/themes/index.js). Please note that we paused the addition of new themes to decrease maintenance efforts; all pull requests related to new themes will be closed.
+You can look at a preview for [all available themes](../packages/core/src/themes/README.md) or checkout the [theme config file](../packages/core/src/themes/index.js). Please note that we paused the addition of new themes to decrease maintenance efforts; all pull requests related to new themes will be closed.
#### Responsive Card Theme
@@ -102,7 +102,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](../backend/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](../packages/core/src/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

@@ -181,7 +181,7 @@ You can customize the appearance of all your cards however you wish with URL par
| `border_color` | Card's border color. Does not apply when `hide_border` is enabled. | string (hex color) | `e4e2e2` |
| `bg_color` | Card's background color. | string (hex color or a gradient in the form of *angle,start,end*) | `fffefe` |
| `hide_border` | Hides the card's border. | boolean | `false` |
-| `theme` | Name of the theme, choose from [all available themes](../backend/themes/README.md). | enum | `default` |
+| `theme` | Name of the theme, choose from [all available themes](../packages/core/src/themes/README.md). | enum | `default` |
| `cache_seconds` | Sets the cache header manually (min: 21600, max: 86400). | integer | `21600` |
| `locale` | Sets the language in the card, you can check full list of available locales [here](#available-locales). | enum | `en` |
| `border_radius` | Corner rounding on the card. | number | `4.5` |
diff --git a/docs/deploy.md b/docs/deploy.md
index a837d1ec731c6..637cb46a25e12 100644
--- a/docs/deploy.md
+++ b/docs/deploy.md
@@ -123,6 +123,7 @@ Click on the deploy button to get started!
13. 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`
14. 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
15. optional: in addition to the Vercel project based on the `apps/backend` folder, create a second project based on the `apps/frontend` folder. No environment variables needed.
+16. optional: set the environment variable `TURBO_PLATFORM_ENV_DISABLED` to `true` to disable the build-time warning from [turbo](https://turborepo.dev/) about environment variables missing from "turbo.json" - This warning is not relevant in our project.
diff --git a/eslint.config.js b/eslint.config.js
index b1b9de209fea7..270d6cbb80d67 100644
--- a/eslint.config.js
+++ b/eslint.config.js
@@ -3,6 +3,7 @@ import { fileURLToPath } from "node:url";
import { includeIgnoreFile } from "@eslint/compat";
import js from "@eslint/js";
import { defineConfig } from "eslint/config";
+import { createTypeScriptImportResolver } from "eslint-import-resolver-typescript";
import { importX } from "eslint-plugin-import-x";
import { default as jsdoc } from "eslint-plugin-jsdoc";
import react from "eslint-plugin-react";
@@ -18,6 +19,25 @@ export default defineConfig(
{
extends: [importX.flatConfigs.recommended, importX.flatConfigs.typescript],
+ settings: {
+ "import-x/resolver-next": [
+ createTypeScriptImportResolver({
+ conditionNames: [
+ /** Keep in sync with `tsconfig.base.json#customConditions` */
+ "@stats/source",
+
+ "types",
+ "import",
+
+ "require",
+ "node",
+ "node-addons",
+ "browser",
+ "default",
+ ],
+ }),
+ ],
+ },
rules: {
"import-x/consistent-type-specifier-style": ["error", "prefer-top-level"],
"import-x/order": [
diff --git a/knip.jsonc b/knip.jsonc
index 5de96712633ff..1980b45b9c20a 100644
--- a/knip.jsonc
+++ b/knip.jsonc
@@ -2,21 +2,10 @@
"$schema": "./node_modules/knip/schema.json",
"workspaces": {
"apps/backend": {
- "entry": ["api-renamed/*.js", "express.js", "tests/bench/*.bench.js"],
- "ignoreFiles": [
- "_dot_vercel_copy/**/*" // Hopefully by using a npm package this file will be removed
- ]
+ "entry": ["api-renamed/*.js", "express.js", "tests/bench/*.bench.js"]
},
"apps/frontend": {
- "entry": ["src/wakatime-override.ts"],
-
- "ignoreDependencies": [
- // below dependencies are added because backend folder is copied inside frontend folder,
- // so some of his dependencies must be present here
- "github-username-regex",
- "emoji-name-map",
- "word-wrap"
- ]
+ "entry": ["src/wakatime-override.ts"]
}
}
}
diff --git a/package.json b/package.json
index b0ed2b1c7960f..53e0d77002694 100644
--- a/package.json
+++ b/package.json
@@ -1,35 +1,44 @@
{
- "name": "root",
+ "name": "@stats-organization/root",
"private": true,
"type": "module",
- "packageManager": "pnpm@10.30.3+sha512.c961d1e0a2d8e354ecaa5166b822516668b7f44cb5bd95122d590dd81922f606f5473b6d23ec4a5be05e7fcd18e8488d47d978bbe981872f1145d06e9a740017",
+ "packageManager": "pnpm@10.33.0+sha512.10568bb4a6afb58c9eb3630da90cc9516417abebd3fabbe6739f0ae795728da1491e9db5a544c76ad8eb7570f5c4bb3d6c637b2cb41bfdcdb47fa823c8649319",
"devDependencies": {
- "@eslint/compat": "2.0.2",
- "@eslint/js": "9.39.3",
- "@playwright/test": "1.58.2",
- "@types/node": "24.10.13",
- "eslint": "9.39.2",
+ "@eslint/compat": "2.0.3",
+ "@eslint/js": "10.0.1",
+ "@playwright/test": "1.59.1",
+ "@types/node": "24.12.0",
+ "@vitest/coverage-v8": "catalog:default",
+ "eslint": "10.1.0",
"eslint-import-resolver-typescript": "4.4.4",
- "eslint-plugin-import-x": "4.16.1",
- "eslint-plugin-jsdoc": "62.7.1",
+ "eslint-plugin-import-x": "4.16.2",
+ "eslint-plugin-jsdoc": "62.9.0",
"eslint-plugin-react": "7.37.5",
"eslint-plugin-react-hooks": "7.0.1",
- "globals": "17.3.0",
+ "globals": "17.4.0",
"husky": "9.1.7",
- "knip": "5.85.0",
- "lint-staged": "16.2.7",
+ "knip": "6.2.0",
+ "lint-staged": "16.4.0",
"prettier": "3.8.1",
+ "turbo": "2.9.3",
"typescript": "5.9.3",
- "typescript-eslint": "8.56.1"
+ "typescript-eslint": "8.58.0",
+ "vitest": "catalog:default"
},
"scripts": {
"prepare": "husky",
+ "build:packages": "turbo run build --filter=./packages/*",
+ "build:frontend": "turbo run build --filter=./apps/frontend",
+ "dev:frontend": "pnpm run --filter=./apps/frontend dev",
+ "test": "vitest",
+ "test:coverage": "vitest --config vitest.config.coverage.ts",
"format": "prettier --write .",
"format:check": "prettier --check .",
- "lint": "eslint",
- "lint:fix": "eslint --fix",
+ "lint": "turbo run lint",
+ "lint:eslint": "eslint",
+ "lint:eslint:fix": "eslint --fix",
"lint:knip": "knip",
- "typecheck": "tsc --build --noEmit"
+ "typecheck": "turbo run typecheck"
},
"lint-staged": {
"*.{js,jsx,ts,tsx,css,json,jsonc,yaml,yml}": "prettier --write"
diff --git a/apps/backend/codecov.yml b/packages/core/codecov.yml
similarity index 100%
rename from apps/backend/codecov.yml
rename to packages/core/codecov.yml
diff --git a/packages/core/package.json b/packages/core/package.json
new file mode 100644
index 0000000000000..e9e10e70a0032
--- /dev/null
+++ b/packages/core/package.json
@@ -0,0 +1,94 @@
+{
+ "name": "@stats-organization/github-readme-stats-core",
+ "version": "2.0.0",
+ "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",
+ "directory": "packages/core"
+ },
+ "author": {
+ "name": "Anurag Hazra",
+ "url": "https://github.com/anuraghazra/"
+ },
+ "contributors": [
+ {
+ "name": "Rick Staa",
+ "url": "https://github.com/rickstaa"
+ },
+ {
+ "name": "Alexandr Garbuzov",
+ "url": "https://github.com/qwerty541"
+ },
+ {
+ "name": "Abhijit Gupta",
+ "url": "https://github.com/avgupta456"
+ },
+ {
+ "name": "Marco Pasqualetti",
+ "url": "https://github.com/marcalexiei"
+ },
+ {
+ "name": "martin-mfg",
+ "url": "https://github.com/martin-mfg"
+ }
+ ],
+ "license": "MIT",
+ "engines": {
+ "node": "24.x"
+ },
+ "description": "Dynamically generate stats for your GitHub readme",
+ "keywords": [
+ "github-readme-stats",
+ "readme-stats",
+ "cards",
+ "card-generator",
+ "github-stats",
+ "github-stats-extended",
+ "github-readme-stats-extended"
+ ],
+ "main": "./build/index.js",
+ "exports": {
+ ".": {
+ "@stats/source": "./src/index.ts",
+ "default": "./build/index.js"
+ }
+ },
+ "files": [
+ "build",
+ "src"
+ ],
+ "publishConfig": {
+ "access": "public"
+ },
+ "scripts": {
+ "build": "tsc -p tsconfig.build.json",
+ "test": "vitest",
+ "test:update:snapshot": "vitest -u",
+ "test:e2e": "vitest --config vitest.config.e2e.ts",
+ "bench": "vitest bench --run --config vitest.config.bench.ts",
+ "lint": "eslint",
+ "typecheck": "tsc -p tsconfig.typecheck.json",
+ "theme-readme-gen": "node scripts/generate-theme-doc",
+ "generate-langs-json": "node scripts/generate-langs-json"
+ },
+ "devDependencies": {
+ "@testing-library/dom": "10.4.1",
+ "@testing-library/jest-dom": "6.9.1",
+ "@uppercod/css-to-object": "1.1.1",
+ "axios-mock-adapter": "2.1.0",
+ "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",
+ "word-wrap": "^1.2.5"
+ }
+}
diff --git a/apps/backend/scripts/generate-langs-json.js b/packages/core/scripts/generate-langs-json.js
similarity index 100%
rename from apps/backend/scripts/generate-langs-json.js
rename to packages/core/scripts/generate-langs-json.js
diff --git a/apps/backend/scripts/generate-theme-doc.js b/packages/core/scripts/generate-theme-doc.js
similarity index 96%
rename from apps/backend/scripts/generate-theme-doc.js
rename to packages/core/scripts/generate-theme-doc.js
index 93c8e6a76c26e..afd4b1b3ad5ec 100644
--- a/apps/backend/scripts/generate-theme-doc.js
+++ b/packages/core/scripts/generate-theme-doc.js
@@ -1,8 +1,8 @@
import fs from "fs";
-import { themes } from "../themes/index.js";
+import { themes } from "../src/themes/index.js";
-const TARGET_FILE = "./themes/README.md";
+const TARGET_FILE = "./src/themes/README.md";
const REPO_CARD_LINKS_FLAG = "";
const STAT_CARD_LINKS_FLAG = "";
diff --git a/packages/core/src/api/gist.js b/packages/core/src/api/gist.js
new file mode 100644
index 0000000000000..ebce051e0e6f3
--- /dev/null
+++ b/packages/core/src/api/gist.js
@@ -0,0 +1,97 @@
+// @ts-check
+
+import { renderGistCard } from "../cards/gist.js";
+import {
+ MissingParamError,
+ retrieveSecondaryMessage,
+} from "../common/error.js";
+import { parseBoolean } from "../common/ops.js";
+import { renderError } from "../common/render.js";
+import { fetchGist } from "../fetchers/gist.js";
+import { isLocaleAvailable } from "../translations.js";
+
+// @ts-ignore
+export default async (
+ {
+ id,
+ title_color,
+ icon_color,
+ text_color,
+ bg_color,
+ theme,
+ locale,
+ border_radius,
+ border_color,
+ show_owner,
+ hide_border,
+ },
+ pat = null,
+) => {
+ if (locale && !isLocaleAvailable(locale)) {
+ return {
+ status: "error - permanent",
+ content: renderError({
+ message: "Something went wrong",
+ secondaryMessage: "Language not found",
+ renderOptions: {
+ title_color,
+ text_color,
+ bg_color,
+ border_color,
+ theme,
+ },
+ }),
+ };
+ }
+
+ try {
+ const gistData = await fetchGist(id, pat);
+
+ return {
+ status: "success",
+ content: renderGistCard(gistData, {
+ title_color,
+ icon_color,
+ text_color,
+ bg_color,
+ theme,
+ border_radius,
+ border_color,
+ locale: locale ? locale.toLowerCase() : null,
+ show_owner: parseBoolean(show_owner),
+ hide_border: parseBoolean(hide_border),
+ }),
+ };
+ } catch (err) {
+ if (err instanceof Error) {
+ return {
+ status: "error - temporary",
+ content: renderError({
+ message: err.message,
+ secondaryMessage: retrieveSecondaryMessage(err),
+ renderOptions: {
+ title_color,
+ text_color,
+ bg_color,
+ border_color,
+ theme,
+ show_repo_link: !(err instanceof MissingParamError),
+ },
+ }),
+ };
+ }
+ return {
+ status: "error - temporary",
+ content: renderError({
+ message: "An unknown error occurred",
+ renderOptions: {
+ title_color,
+ text_color,
+ bg_color,
+ border_color,
+ theme,
+ },
+ }),
+ };
+ }
+};
diff --git a/apps/backend/api-renamed/index.js b/packages/core/src/api/index.js
similarity index 71%
rename from apps/backend/api-renamed/index.js
rename to packages/core/src/api/index.js
index 55ac2e722d79f..5709e30a08930 100644
--- a/apps/backend/api-renamed/index.js
+++ b/packages/core/src/api/index.js
@@ -1,26 +1,18 @@
// @ts-check
-import { renderStatsCard } from "../src/cards/stats.js";
-import { guardAccess } from "../src/common/access.js";
-import {
- CACHE_TTL,
- resolveCacheSeconds,
- setCacheHeaders,
- setErrorCacheHeaders,
-} from "../src/common/cache.js";
-import { storeRequest } from "../src/common/database.js";
+import { renderStatsCard } from "../cards/stats.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 { fetchStats } from "../src/fetchers/stats.js";
-import { isLocaleAvailable } from "../src/translations.js";
+} from "../common/error.js";
+import { parseArray, parseBoolean } from "../common/ops.js";
+import { renderError } from "../common/render.js";
+import { fetchStats } from "../fetchers/stats.js";
+import { isLocaleAvailable } from "../translations.js";
// @ts-ignore
-export default async (req, res) => {
- const {
+export default async (
+ {
username,
repo,
owner,
@@ -40,7 +32,6 @@ export default async (req, res) => {
text_bold,
bg_color,
theme,
- cache_seconds,
exclude_repo,
custom_title,
locale,
@@ -52,28 +43,13 @@ export default async (req, res) => {
border_color,
rank_icon,
show,
- } = req.query;
- res.setHeader("Content-Type", "image/svg+xml");
-
- const access = guardAccess({
- res,
- id: username,
- type: "username",
- colors: {
- title_color,
- text_color,
- bg_color,
- border_color,
- theme,
- },
- });
- if (!access.isPassed) {
- return access.result;
- }
-
+ },
+ pat = null,
+) => {
if (locale && !isLocaleAvailable(locale)) {
- return res.send(
- renderError({
+ return {
+ status: "error - permanent",
+ content: renderError({
message: "Something went wrong",
secondaryMessage: "Language not found",
renderOptions: {
@@ -84,7 +60,7 @@ export default async (req, res) => {
theme,
},
}),
- );
+ };
}
const safePattern = /^[-\w/.,]+$/;
@@ -93,8 +69,9 @@ export default async (req, res) => {
(repo && !safePattern.test(repo)) ||
(owner && !safePattern.test(owner))
) {
- return res.send(
- renderError({
+ return {
+ status: "error - permanent",
+ content: renderError({
message: "Something went wrong",
secondaryMessage:
"Username, repository or owner contains unsafe characters",
@@ -106,11 +83,10 @@ export default async (req, res) => {
theme,
},
}),
- );
+ };
}
try {
- await storeRequest(req);
const showStats = parseArray(show);
const repoOwner = parseArray(owner);
let repository = parseArray(repo);
@@ -135,18 +111,12 @@ export default async (req, res) => {
showStats.includes("issues_authored"),
showStats.includes("issues_commented"),
parseArray(role),
+ pat,
);
- const cacheSeconds = resolveCacheSeconds({
- requested: parseInt(cache_seconds, 10),
- def: CACHE_TTL.STATS_CARD.DEFAULT,
- min: CACHE_TTL.STATS_CARD.MIN,
- max: CACHE_TTL.STATS_CARD.MAX,
- });
-
- setCacheHeaders(res, cacheSeconds);
- return res.send(
- renderStatsCard(
+ return {
+ status: "success",
+ content: renderStatsCard(
stats,
{
hide: parseArray(hide),
@@ -179,12 +149,12 @@ export default async (req, res) => {
repository,
repoOwner,
),
- );
+ };
} catch (err) {
- setErrorCacheHeaders(res);
if (err instanceof Error) {
- return res.send(
- renderError({
+ return {
+ status: "error - temporary",
+ content: renderError({
message: err.message,
secondaryMessage: retrieveSecondaryMessage(err),
renderOptions: {
@@ -196,10 +166,11 @@ export default async (req, res) => {
show_repo_link: !(err instanceof MissingParamError),
},
}),
- );
+ };
}
- return res.send(
- renderError({
+ return {
+ status: "error - temporary",
+ content: renderError({
message: "An unknown error occurred",
renderOptions: {
title_color,
@@ -209,6 +180,6 @@ export default async (req, res) => {
theme,
},
}),
- );
+ };
}
};
diff --git a/apps/backend/api-renamed/pin.js b/packages/core/src/api/pin.js
similarity index 63%
rename from apps/backend/api-renamed/pin.js
rename to packages/core/src/api/pin.js
index b79a74f6820ec..77107373b71f3 100644
--- a/apps/backend/api-renamed/pin.js
+++ b/packages/core/src/api/pin.js
@@ -1,26 +1,18 @@
// @ts-check
-import { renderRepoCard } from "../src/cards/repo.js";
-import { guardAccess } from "../src/common/access.js";
-import {
- CACHE_TTL,
- resolveCacheSeconds,
- setCacheHeaders,
- setErrorCacheHeaders,
-} from "../src/common/cache.js";
-import { storeRequest } from "../src/common/database.js";
+import { renderRepoCard } from "../cards/repo.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 { fetchRepo } from "../src/fetchers/repo.js";
-import { isLocaleAvailable } from "../src/translations.js";
+} from "../common/error.js";
+import { parseArray, parseBoolean } from "../common/ops.js";
+import { renderError } from "../common/render.js";
+import { fetchRepo } from "../fetchers/repo.js";
+import { isLocaleAvailable } from "../translations.js";
// @ts-ignore
-export default async (req, res) => {
- const {
+export default async (
+ {
username,
repo,
hide_border,
@@ -36,34 +28,17 @@ export default async (req, res) => {
number_format,
text_bold,
line_height,
- cache_seconds,
locale,
border_radius,
border_color,
description_lines_count,
- } = req.query;
-
- res.setHeader("Content-Type", "image/svg+xml");
-
- const access = guardAccess({
- res,
- id: username,
- type: "username",
- colors: {
- title_color,
- text_color,
- bg_color,
- border_color,
- theme,
- },
- });
- if (!access.isPassed) {
- return access.result;
- }
-
+ },
+ pat = null,
+) => {
if (locale && !isLocaleAvailable(locale)) {
- return res.send(
- renderError({
+ return {
+ status: "error - permanent",
+ content: renderError({
message: "Something went wrong",
secondaryMessage: "Language not found",
renderOptions: {
@@ -74,7 +49,7 @@ export default async (req, res) => {
theme,
},
}),
- );
+ };
}
const safePattern = /^[-\w/.,]+$/;
@@ -82,8 +57,9 @@ export default async (req, res) => {
(username && !safePattern.test(username)) ||
(repo && !safePattern.test(repo))
) {
- return res.send(
- renderError({
+ return {
+ status: "error - permanent",
+ content: renderError({
message: "Something went wrong",
secondaryMessage: "Username or repository contains unsafe characters",
renderOptions: {
@@ -94,11 +70,10 @@ export default async (req, res) => {
theme,
},
}),
- );
+ };
}
try {
- await storeRequest(req);
const showStats = parseArray(show);
const repoData = await fetchRepo(
username,
@@ -108,19 +83,12 @@ export default async (req, res) => {
showStats.includes("prs_reviewed"),
showStats.includes("issues_authored"),
showStats.includes("issues_commented"),
+ pat,
);
- const cacheSeconds = resolveCacheSeconds({
- requested: parseInt(cache_seconds, 10),
- def: CACHE_TTL.PIN_CARD.DEFAULT,
- min: CACHE_TTL.PIN_CARD.MIN,
- max: CACHE_TTL.PIN_CARD.MAX,
- });
-
- setCacheHeaders(res, cacheSeconds);
-
- return res.send(
- renderRepoCard(repoData, {
+ return {
+ status: "success",
+ content: renderRepoCard(repoData, {
hide_border: parseBoolean(hide_border),
title_color,
icon_color,
@@ -140,12 +108,12 @@ export default async (req, res) => {
locale: locale ? locale.toLowerCase() : null,
description_lines_count,
}),
- );
+ };
} catch (err) {
- setErrorCacheHeaders(res);
if (err instanceof Error) {
- return res.send(
- renderError({
+ return {
+ status: "error - temporary",
+ content: renderError({
message: err.message,
secondaryMessage: retrieveSecondaryMessage(err),
renderOptions: {
@@ -157,10 +125,11 @@ export default async (req, res) => {
show_repo_link: !(err instanceof MissingParamError),
},
}),
- );
+ };
}
- return res.send(
- renderError({
+ return {
+ status: "error - temporary",
+ content: renderError({
message: "An unknown error occurred",
renderOptions: {
title_color,
@@ -170,6 +139,6 @@ export default async (req, res) => {
theme,
},
}),
- );
+ };
}
};
diff --git a/apps/backend/api-renamed/top-langs.js b/packages/core/src/api/top-langs.js
similarity index 64%
rename from apps/backend/api-renamed/top-langs.js
rename to packages/core/src/api/top-langs.js
index 056193c81ca44..4972b6fd4a750 100644
--- a/apps/backend/api-renamed/top-langs.js
+++ b/packages/core/src/api/top-langs.js
@@ -1,26 +1,18 @@
// @ts-check
-import { renderTopLanguages } from "../src/cards/top-languages.js";
-import { guardAccess } from "../src/common/access.js";
-import {
- CACHE_TTL,
- resolveCacheSeconds,
- setCacheHeaders,
- setErrorCacheHeaders,
-} from "../src/common/cache.js";
-import { storeRequest } from "../src/common/database.js";
+import { renderTopLanguages } from "../cards/top-languages.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 { fetchTopLanguages } from "../src/fetchers/top-languages.js";
-import { isLocaleAvailable } from "../src/translations.js";
+} from "../common/error.js";
+import { parseArray, parseBoolean } from "../common/ops.js";
+import { renderError } from "../common/render.js";
+import { fetchTopLanguages } from "../fetchers/top-languages.js";
+import { isLocaleAvailable } from "../translations.js";
// @ts-ignore
-export default async (req, res) => {
- const {
+export default async (
+ {
username,
hide,
hide_title,
@@ -31,7 +23,6 @@ export default async (req, res) => {
bg_color,
prog_bar_bg_color,
theme,
- cache_seconds,
layout,
langs_count,
exclude_repo,
@@ -45,28 +36,13 @@ export default async (req, res) => {
disable_animations,
hide_progress,
stats_format,
- } = req.query;
- res.setHeader("Content-Type", "image/svg+xml");
-
- const access = guardAccess({
- res,
- id: username,
- type: "username",
- colors: {
- title_color,
- text_color,
- bg_color,
- border_color,
- theme,
- },
- });
- if (!access.isPassed) {
- return access.result;
- }
-
+ },
+ pat = null,
+) => {
if (locale && !isLocaleAvailable(locale)) {
- return res.send(
- renderError({
+ return {
+ status: "error - permanent",
+ content: renderError({
message: "Something went wrong",
secondaryMessage: "Locale not found",
renderOptions: {
@@ -77,7 +53,7 @@ export default async (req, res) => {
theme,
},
}),
- );
+ };
}
if (
@@ -85,8 +61,9 @@ export default async (req, res) => {
(typeof layout !== "string" ||
!["compact", "normal", "donut", "donut-vertical", "pie"].includes(layout))
) {
- return res.send(
- renderError({
+ return {
+ status: "error - permanent",
+ content: renderError({
message: "Something went wrong",
secondaryMessage: "Incorrect layout input",
renderOptions: {
@@ -97,7 +74,7 @@ export default async (req, res) => {
theme,
},
}),
- );
+ };
}
if (
@@ -105,8 +82,9 @@ export default async (req, res) => {
(typeof stats_format !== "string" ||
!["bytes", "percentages"].includes(stats_format))
) {
- return res.send(
- renderError({
+ return {
+ status: "error - permanent",
+ content: renderError({
message: "Something went wrong",
secondaryMessage: "Incorrect stats_format input",
renderOptions: {
@@ -117,29 +95,22 @@ export default async (req, res) => {
theme,
},
}),
- );
+ };
}
try {
- await storeRequest(req);
const topLangs = await fetchTopLanguages(
username,
parseArray(exclude_repo),
size_weight,
count_weight,
parseArray(role),
+ pat,
);
- const cacheSeconds = resolveCacheSeconds({
- requested: parseInt(cache_seconds, 10),
- def: CACHE_TTL.TOP_LANGS_CARD.DEFAULT,
- min: CACHE_TTL.TOP_LANGS_CARD.MIN,
- max: CACHE_TTL.TOP_LANGS_CARD.MAX,
- });
-
- setCacheHeaders(res, cacheSeconds);
- return res.send(
- renderTopLanguages(topLangs, {
+ return {
+ status: "success",
+ content: renderTopLanguages(topLangs, {
custom_title,
hide_title: parseBoolean(hide_title),
hide_border: parseBoolean(hide_border),
@@ -159,12 +130,12 @@ export default async (req, res) => {
hide_progress: parseBoolean(hide_progress),
stats_format,
}),
- );
+ };
} catch (err) {
- setErrorCacheHeaders(res);
if (err instanceof Error) {
- return res.send(
- renderError({
+ return {
+ status: "error - temporary",
+ content: renderError({
message: err.message,
secondaryMessage: retrieveSecondaryMessage(err),
renderOptions: {
@@ -176,10 +147,11 @@ export default async (req, res) => {
show_repo_link: !(err instanceof MissingParamError),
},
}),
- );
+ };
}
- return res.send(
- renderError({
+ return {
+ status: "error - temporary",
+ content: renderError({
message: "An unknown error occurred",
renderOptions: {
title_color,
@@ -189,6 +161,6 @@ export default async (req, res) => {
theme,
},
}),
- );
+ };
}
};
diff --git a/packages/core/src/api/wakatime.js b/packages/core/src/api/wakatime.js
new file mode 100644
index 0000000000000..eaf02daf5c38a
--- /dev/null
+++ b/packages/core/src/api/wakatime.js
@@ -0,0 +1,113 @@
+// @ts-check
+
+import { renderWakatimeCard } from "../cards/wakatime.js";
+import {
+ MissingParamError,
+ retrieveSecondaryMessage,
+} from "../common/error.js";
+import { parseArray, parseBoolean } from "../common/ops.js";
+import { renderError } from "../common/render.js";
+import { fetchWakatimeStats } from "../fetchers/wakatime.js";
+import { isLocaleAvailable } from "../translations.js";
+
+// @ts-ignore
+export default async ({
+ username,
+ title_color,
+ icon_color,
+ hide_border,
+ card_width,
+ line_height,
+ text_color,
+ bg_color,
+ theme,
+ hide_title,
+ hide_progress,
+ custom_title,
+ locale,
+ layout,
+ langs_count,
+ hide,
+ api_domain,
+ border_radius,
+ border_color,
+ display_format,
+ disable_animations,
+}) => {
+ if (locale && !isLocaleAvailable(locale)) {
+ return {
+ status: "error - permanent",
+ content: renderError({
+ message: "Something went wrong",
+ secondaryMessage: "Language not found",
+ renderOptions: {
+ title_color,
+ text_color,
+ bg_color,
+ border_color,
+ theme,
+ },
+ }),
+ };
+ }
+
+ try {
+ const stats = await fetchWakatimeStats({ username, api_domain });
+
+ return {
+ status: "success",
+ content: renderWakatimeCard(stats, {
+ custom_title,
+ hide_title: parseBoolean(hide_title),
+ hide_border: parseBoolean(hide_border),
+ card_width: parseInt(card_width, 10),
+ hide: parseArray(hide),
+ line_height,
+ title_color,
+ icon_color,
+ text_color,
+ bg_color,
+ theme,
+ hide_progress,
+ border_radius,
+ border_color,
+ locale: locale ? locale.toLowerCase() : null,
+ layout,
+ langs_count,
+ display_format,
+ disable_animations: parseBoolean(disable_animations),
+ }),
+ };
+ } catch (err) {
+ if (err instanceof Error) {
+ return {
+ status: "error - temporary",
+ content: renderError({
+ message: err.message,
+ secondaryMessage: retrieveSecondaryMessage(err),
+ renderOptions: {
+ title_color,
+ text_color,
+ bg_color,
+ border_color,
+ theme,
+ show_repo_link: !(err instanceof MissingParamError),
+ },
+ }),
+ };
+ }
+ return {
+ status: "error - temporary",
+ content: renderError({
+ message: "An unknown error occurred",
+ renderOptions: {
+ title_color,
+ text_color,
+ bg_color,
+ border_color,
+ theme,
+ },
+ }),
+ };
+ }
+};
diff --git a/apps/backend/src/calculateRank.js b/packages/core/src/calculateRank.js
similarity index 100%
rename from apps/backend/src/calculateRank.js
rename to packages/core/src/calculateRank.js
diff --git a/apps/backend/src/cards/gist.js b/packages/core/src/cards/gist.js
similarity index 100%
rename from apps/backend/src/cards/gist.js
rename to packages/core/src/cards/gist.js
diff --git a/apps/backend/src/cards/repo.js b/packages/core/src/cards/repo.js
similarity index 100%
rename from apps/backend/src/cards/repo.js
rename to packages/core/src/cards/repo.js
diff --git a/apps/backend/src/cards/stats.js b/packages/core/src/cards/stats.js
similarity index 99%
rename from apps/backend/src/cards/stats.js
rename to packages/core/src/cards/stats.js
index 09aef286db9f1..940271a381dc1 100644
--- a/apps/backend/src/cards/stats.js
+++ b/packages/core/src/cards/stats.js
@@ -233,7 +233,7 @@ const getStyles = ({
transform: rotate(-90deg);
animation: rankAnimation 1s forwards ease-in-out;
}
- ${process.env.NODE_ENV === "test" ? "" : getProgressAnimation({ progress })}
+ ${getProgressAnimation({ progress })}
`;
};
diff --git a/apps/backend/src/cards/top-languages.js b/packages/core/src/cards/top-languages.js
similarity index 99%
rename from apps/backend/src/cards/top-languages.js
rename to packages/core/src/cards/top-languages.js
index 85739c463f3cb..4b91a73946107 100644
--- a/apps/backend/src/cards/top-languages.js
+++ b/packages/core/src/cards/top-languages.js
@@ -647,7 +647,7 @@ const renderPieLayout = (langs, totalLanguageSize, statsFormat) => {
const createDonutPaths = (cx, cy, radius, percentages) => {
const paths = [];
let startAngle = 0;
- let endAngle = 0;
+ let endAngle;
const totalPercent = percentages.reduce((acc, curr) => acc + curr, 0);
for (let i = 0; i < percentages.length; i++) {
@@ -840,7 +840,7 @@ const renderTopLanguages = (topLangs, options = {}) => {
theme,
});
- let finalLayout = "";
+ let finalLayout;
if (langs.length === 0) {
height = COMPACT_LAYOUT_BASE_HEIGHT;
finalLayout = noLanguagesDataNode({
diff --git a/apps/backend/src/cards/types.d.ts b/packages/core/src/cards/types.d.ts
similarity index 88%
rename from apps/backend/src/cards/types.d.ts
rename to packages/core/src/cards/types.d.ts
index 27e04c2a73faf..1d72262e16cb8 100644
--- a/apps/backend/src/cards/types.d.ts
+++ b/packages/core/src/cards/types.d.ts
@@ -1,7 +1,7 @@
-type ThemeNames = keyof typeof import("../../themes/index.js");
+type ThemeNames = keyof typeof import("../themes/index.ts");
type RankIcon = "default" | "github" | "percentile";
-type CommonOptions = {
+interface CommonOptions {
title_color: string;
icon_color: string;
text_color: string;
@@ -11,10 +11,10 @@ type CommonOptions = {
border_color: string;
locale: string;
hide_border: boolean;
-};
+}
export type StatCardOptions = CommonOptions & {
- hide: string[];
+ hide: Array;
show_icons: boolean;
hide_title: boolean;
card_width: number;
@@ -29,14 +29,14 @@ export type StatCardOptions = CommonOptions & {
ring_color: string;
text_bold: boolean;
rank_icon: RankIcon;
- show: string[];
+ show: Array;
};
export type RepoCardOptions = CommonOptions & {
show_owner: boolean;
description_lines_count: number;
card_width_input;
- show: string[];
+ show: Array;
show_icons: boolean;
number_format: string;
text_bold: boolean;
@@ -47,7 +47,7 @@ export type RepoCardOptions = CommonOptions & {
export type TopLangOptions = CommonOptions & {
hide_title: boolean;
card_width: number;
- hide: string[];
+ hide: Array;
layout: "compact" | "normal" | "donut" | "donut-vertical" | "pie";
custom_title: string;
langs_count: number;
@@ -59,7 +59,7 @@ export type TopLangOptions = CommonOptions & {
export type WakaTimeOptions = CommonOptions & {
hide_title: boolean;
- hide: string[];
+ hide: Array;
card_width: number;
line_height: string;
hide_progress: boolean;
diff --git a/apps/backend/src/cards/wakatime.js b/packages/core/src/cards/wakatime.js
similarity index 99%
rename from apps/backend/src/cards/wakatime.js
rename to packages/core/src/cards/wakatime.js
index 02eac53900003..f7eeeee395cc2 100644
--- a/apps/backend/src/cards/wakatime.js
+++ b/packages/core/src/cards/wakatime.js
@@ -305,7 +305,7 @@ const renderWakatimeCard = (stats = {}, options = { hide: [] }) => {
textColor,
});
- let finalLayout = "";
+ let finalLayout;
// RENDER COMPACT LAYOUT
if (layout === "compact") {
diff --git a/apps/backend/src/common/Card.js b/packages/core/src/common/Card.js
similarity index 98%
rename from apps/backend/src/common/Card.js
rename to packages/core/src/common/Card.js
index 45bc81fe11460..6345b662c639b 100644
--- a/apps/backend/src/common/Card.js
+++ b/packages/core/src/common/Card.js
@@ -230,7 +230,7 @@ class Card {
}
${this.css}
- ${process.env.NODE_ENV === "test" ? "" : this.getAnimations()}
+ ${this.getAnimations()}
${
this.animations === false
? `* { animation-duration: 0s !important; animation-delay: 0s !important; }`
diff --git a/apps/backend/src/common/I18n.js b/packages/core/src/common/I18n.js
similarity index 100%
rename from apps/backend/src/common/I18n.js
rename to packages/core/src/common/I18n.js
diff --git a/apps/backend/src/common/color.js b/packages/core/src/common/color.js
similarity index 98%
rename from apps/backend/src/common/color.js
rename to packages/core/src/common/color.js
index 622ed161121bb..bc1395e8f737f 100644
--- a/apps/backend/src/common/color.js
+++ b/packages/core/src/common/color.js
@@ -1,6 +1,6 @@
// @ts-check
-import { themes } from "../../themes/index.js";
+import { themes } from "../themes/index.js";
/**
* Checks if a string is a valid hex color.
diff --git a/packages/core/src/common/config.js b/packages/core/src/common/config.js
new file mode 100644
index 0000000000000..32456ce8ae3d1
--- /dev/null
+++ b/packages/core/src/common/config.js
@@ -0,0 +1,78 @@
+// @ts-check
+
+/**
+ * @param {string | undefined} value Comma-separated string.
+ * @returns {string[] | undefined} Parsed string values.
+ */
+const parseCsv = (value) => {
+ if (!value) {
+ return undefined;
+ }
+ return value.split(",");
+};
+
+/**
+ * @param {Record} env Environment variables to inspect.
+ * @returns {{name: string, value: string}[]} Personal access tokens found in the environment.
+ */
+const parsePATsFromEnv = (env) => {
+ return Object.keys(env)
+ .filter((key) => /PAT_\d*$/.exec(key))
+ .map((name) => ({
+ name,
+ value: env[name],
+ }));
+};
+
+/**
+ * @returns {Record} `process.env` if available, otherwise `{}`.
+ */
+const getDefaultEnv = () => {
+ if (typeof process !== "undefined" && process?.env) {
+ return process.env;
+ }
+ return {};
+};
+
+/**
+ * @param {Partial>} config (Partial) config values to normalize.
+ * @returns {{
+ * whitelist: string[] | undefined,
+ * gistWhitelist: string[] | undefined,
+ * excludeRepositories: string[],
+ * fetchMultiPageStars: string | undefined,
+ * pats: {name: string, value: string}[],
+ * }} Normalized config object with defaults applied.
+ */
+const normalizeConfig = (config = {}) => {
+ return {
+ whitelist: config.whitelist,
+ gistWhitelist: config.gistWhitelist,
+ excludeRepositories: config.excludeRepositories || [],
+ fetchMultiPageStars: config.fetchMultiPageStars,
+ pats: config.pats || [],
+ };
+};
+
+let currentConfig;
+
+/**
+ * @param {Record} env Environment variables used to build the runtime config.
+ */
+export const loadConfigFromEnv = (env = getDefaultEnv()) => {
+ const whitelist = parseCsv(env.WHITELIST);
+ const gistWhitelist = parseCsv(env.GIST_WHITELIST);
+ const excludeRepositories = parseCsv(env.EXCLUDE_REPO) || [];
+
+ currentConfig = normalizeConfig({
+ whitelist,
+ gistWhitelist,
+ excludeRepositories,
+ fetchMultiPageStars: env.FETCH_MULTI_PAGE_STARS,
+ pats: parsePATsFromEnv(env),
+ });
+};
+
+loadConfigFromEnv();
+
+export const getConfig = () => currentConfig;
diff --git a/apps/backend/src/common/constants.js b/packages/core/src/common/constants.js
similarity index 100%
rename from apps/backend/src/common/constants.js
rename to packages/core/src/common/constants.js
diff --git a/apps/backend/src/common/error.js b/packages/core/src/common/error.js
similarity index 100%
rename from apps/backend/src/common/error.js
rename to packages/core/src/common/error.js
diff --git a/apps/backend/src/common/fmt.js b/packages/core/src/common/fmt.js
similarity index 99%
rename from apps/backend/src/common/fmt.js
rename to packages/core/src/common/fmt.js
index 16c80b8b9b391..5bc3ad14804ac 100644
--- a/apps/backend/src/common/fmt.js
+++ b/packages/core/src/common/fmt.js
@@ -66,7 +66,7 @@ const wrapTextMultiline = (text, width = 59, maxLines = 3) => {
const encoded = encodeHTML(text);
const isChinese = encoded.includes(fullWidthComma);
- let wrapped = [];
+ let wrapped;
if (isChinese) {
wrapped = encoded.split(fullWidthComma); // Chinese full punctuation
diff --git a/apps/backend/src/common/html.js b/packages/core/src/common/html.js
similarity index 100%
rename from apps/backend/src/common/html.js
rename to packages/core/src/common/html.js
diff --git a/apps/backend/src/common/http.js b/packages/core/src/common/http.js
similarity index 100%
rename from apps/backend/src/common/http.js
rename to packages/core/src/common/http.js
diff --git a/apps/backend/src/common/icons.js b/packages/core/src/common/icons.js
similarity index 100%
rename from apps/backend/src/common/icons.js
rename to packages/core/src/common/icons.js
diff --git a/apps/backend/src/common/languageColors.json b/packages/core/src/common/languageColors.json
similarity index 99%
rename from apps/backend/src/common/languageColors.json
rename to packages/core/src/common/languageColors.json
index 63bbbe945804e..86adccb2e9dbe 100644
--- a/apps/backend/src/common/languageColors.json
+++ b/packages/core/src/common/languageColors.json
@@ -171,6 +171,7 @@
"Faust": "#c37240",
"Fennel": "#fff3d7",
"Filebench WML": "#F6B900",
+ "FlatBuffers": "#ed284a",
"Flix": "#d44a45",
"Fluent": "#ffcc33",
"Forth": "#341708",
@@ -323,6 +324,7 @@
"LigoLANG": "#0e74ff",
"LilyPond": "#9ccc7c",
"Liquid": "#67b8de",
+ "Liquidsoap": "#990066",
"Literate Agda": "#315665",
"Literate CoffeeScript": "#244776",
"Literate Haskell": "#5e5086",
@@ -348,6 +350,7 @@
"Mask": "#f97732",
"Mathematical Programming System": "#0530ad",
"Max": "#c4a79c",
+ "MeTTa": "#6a5acd",
"Mercury": "#ff2b2b",
"Mermaid": "#ff3670",
"Meson": "#007800",
diff --git a/apps/backend/src/common/log.js b/packages/core/src/common/log.js
similarity index 57%
rename from apps/backend/src/common/log.js
rename to packages/core/src/common/log.js
index 5836d50ad92f6..88cf7dd9ae074 100644
--- a/apps/backend/src/common/log.js
+++ b/packages/core/src/common/log.js
@@ -1,13 +1,9 @@
// @ts-check
-
-const noop = () => {};
-
/**
* Return console instance based on the environment.
*
* @type {Console | {log: () => void, error: () => void}}
*/
-const logger =
- process.env.NODE_ENV === "test" ? { log: noop, error: noop } : console;
+const logger = console;
export { logger };
diff --git a/apps/backend/src/common/ops.js b/packages/core/src/common/ops.js
similarity index 100%
rename from apps/backend/src/common/ops.js
rename to packages/core/src/common/ops.js
diff --git a/apps/backend/src/common/render.js b/packages/core/src/common/render.js
similarity index 99%
rename from apps/backend/src/common/render.js
rename to packages/core/src/common/render.js
index 3653346496082..7aba75c0409d7 100644
--- a/apps/backend/src/common/render.js
+++ b/packages/core/src/common/render.js
@@ -229,7 +229,6 @@ const measureText = (str, fontSize = 10) => {
};
export {
- ERROR_CARD_LENGTH,
renderError,
createLanguageNode,
createProgressNode,
diff --git a/apps/backend/src/common/retryer.js b/packages/core/src/common/retryer.js
similarity index 83%
rename from apps/backend/src/common/retryer.js
rename to packages/core/src/common/retryer.js
index 4661deffe3eda..79d294dc00abb 100644
--- a/apps/backend/src/common/retryer.js
+++ b/packages/core/src/common/retryer.js
@@ -1,6 +1,6 @@
// @ts-check
-import { getUserAccessByName } from "./database.js";
+import { getConfig } from "./config.js";
import { CustomError } from "./error.js";
import { logger } from "./log.js";
@@ -27,24 +27,16 @@ 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 {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.
+ * @param {string | null} pat Optional PAT override.
* @returns {Promise} The response from the fetcher function.
*/
-const retryer = async (fetcher, username, variables) => {
- let userPAT;
- if (username) {
- userPAT = await getUserAccessByName(username);
- }
-
+const retryer = async (fetcher, variables, pat = null) => {
let PATs;
- if (userPAT?.token) {
- PATs = [{ name: `USER_${username}`, value: userPAT.token }];
+ if (pat) {
+ PATs = [{ name: "user PAT from database", value: pat }];
} else {
- const patNames = Object.keys(process.env).filter((key) =>
- /PAT_\d*$/.exec(key),
- );
- PATs = patNames.map((name) => ({ name, value: process.env[name] }));
+ PATs = getConfig().pats;
}
if (!PATs.length) {
@@ -110,4 +102,3 @@ const retryer = async (fetcher, username, variables) => {
};
export { retryer };
-export default retryer;
diff --git a/apps/backend/src/fetchers/gist.js b/packages/core/src/fetchers/gist.js
similarity index 94%
rename from apps/backend/src/fetchers/gist.js
rename to packages/core/src/fetchers/gist.js
index e80264ff6f8ad..450b17119d476 100644
--- a/apps/backend/src/fetchers/gist.js
+++ b/packages/core/src/fetchers/gist.js
@@ -84,13 +84,14 @@ const calculatePrimaryLanguage = (files) => {
* Fetch GitHub gist information by given username and ID.
*
* @param {string} id GitHub gist ID.
+ * @param {string | null} pat Optional PAT override.
* @returns {Promise} Gist data.
*/
-const fetchGist = async (id) => {
+const fetchGist = async (id, pat = null) => {
if (!id) {
throw new MissingParamError(["id"], "/api/gist?id=GIST_ID");
}
- const res = await retryer(fetcher, null, { gistName: id });
+ const res = await retryer(fetcher, { gistName: id }, pat);
if (res.data.errors) {
throw new Error(res.data.errors[0].message);
}
diff --git a/apps/backend/src/fetchers/repo.js b/packages/core/src/fetchers/repo.js
similarity index 97%
rename from apps/backend/src/fetchers/repo.js
rename to packages/core/src/fetchers/repo.js
index 90b269c4af9a1..96d727f6b050b 100644
--- a/apps/backend/src/fetchers/repo.js
+++ b/packages/core/src/fetchers/repo.js
@@ -76,6 +76,7 @@ const fetchRepo = async (
include_prs_reviewed = false,
include_issues_authored = false,
include_issues_commented = false,
+ pat = null,
) => {
let owner = username;
if (reponame && reponame.includes("/")) {
@@ -100,7 +101,7 @@ const fetchRepo = async (
throw new MissingParamError(["repo"], urlExample);
}
- let res = await retryer(fetcher, username, { login: owner, repo: reponame });
+ let res = await retryer(fetcher, { login: owner, repo: reponame }, pat);
const data = res.data.data;
@@ -124,6 +125,7 @@ const fetchRepo = async (
include_prs_reviewed,
include_issues_authored,
include_issues_commented,
+ pat,
);
return {
...repoUserStats,
@@ -148,6 +150,7 @@ const fetchRepo = async (
include_prs_reviewed,
include_issues_authored,
include_issues_commented,
+ pat,
);
return {
...repoUserStats,
diff --git a/apps/backend/src/fetchers/stats.js b/packages/core/src/fetchers/stats.js
similarity index 91%
rename from apps/backend/src/fetchers/stats.js
rename to packages/core/src/fetchers/stats.js
index f4a6be868ec97..1e2f3a551544b 100644
--- a/apps/backend/src/fetchers/stats.js
+++ b/packages/core/src/fetchers/stats.js
@@ -4,7 +4,7 @@ import axios from "axios";
import githubUsernameRegex from "github-username-regex";
import { calculateRank } from "../calculateRank.js";
-import { excludeRepositories } from "../common/envs.js";
+import { getConfig } from "../common/config.js";
import { CustomError, MissingParamError } from "../common/error.js";
import { wrapTextMultiline } from "../common/fmt.js";
import { request } from "../common/http.js";
@@ -106,7 +106,8 @@ const fetcher = (variables, token) => {
* @param {boolean} variables.includeDiscussions Include discussions.
* @param {boolean} variables.includeDiscussionsAnswers Include discussions answers.
* @param {string|undefined} variables.startTime Time to start the count of total commits.
- * @param {string[]} ownerAffiliations The owner affiliations to filter by. Default: OWNER.
+ * @param {string[]} variables.ownerAffiliations The owner affiliations to filter by. Default: OWNER.
+ * @param {string | null} variables.pat PAT override or null.
* @returns {Promise} Axios response.
*
* @description This function supports multi-page fetching if the 'FETCH_MULTI_PAGE_STARS' environment variable is set to true or a limit of fetches.
@@ -118,6 +119,7 @@ const statsFetcher = async ({
includeDiscussionsAnswers,
startTime,
ownerAffiliations,
+ pat,
}) => {
let stats;
let hasNextPage = true;
@@ -134,7 +136,7 @@ const statsFetcher = async ({
startTime,
ownerAffiliations,
};
- let res = await retryer(fetcher, username, variables);
+ let res = await retryer(fetcher, variables, pat);
if (res.data.errors) {
return res;
}
@@ -161,8 +163,8 @@ const statsFetcher = async ({
);
hasNextPage =
- (process.env.FETCH_MULTI_PAGE_STARS === "true" ||
- process.env.FETCH_MULTI_PAGE_STARS > fetchedPages) &&
+ (getConfig().fetchMultiPageStars === "true" ||
+ getConfig().fetchMultiPageStars > fetchedPages) &&
repoNodes.length === repoNodesWithStars.length &&
res.data.data.user.repositories.pageInfo.hasNextPage;
@@ -207,7 +209,7 @@ const fetchTotalItems = (variables, token) => {
* @description Done like this because the GitHub API does not provide a way to fetch all the commits. See
* #92#issuecomment-661026467 and #211 for more information.
*/
-const totalItemsFetcher = async (username, repo, owner, type, filter) => {
+const totalItemsFetcher = async (username, repo, owner, type, filter, pat) => {
if (!githubUsernameRegex.test(username)) {
logger.log("Invalid username provided.");
throw new Error("Invalid username provided.");
@@ -215,16 +217,20 @@ const totalItemsFetcher = async (username, repo, owner, type, filter) => {
let res;
try {
- res = await retryer(fetchTotalItems, username, {
- login: username,
- repo,
- owner,
- type,
- filter,
- });
+ res = await retryer(
+ fetchTotalItems,
+ {
+ login: username,
+ repo,
+ owner,
+ type,
+ filter,
+ },
+ pat,
+ );
} catch (err) {
logger.log(err);
- throw new Error(err);
+ throw err;
}
const totalCount = res.data.total_count;
@@ -247,6 +253,7 @@ const fetchRepoUserStats = async (
include_prs_reviewed,
include_issues_authored,
include_issues_commented,
+ pat,
) => {
let stats = {};
if (include_prs_authored) {
@@ -256,6 +263,7 @@ const fetchRepoUserStats = async (
owner,
"issues",
`author:${username}+type:pr`,
+ pat,
);
}
if (include_prs_commented) {
@@ -265,6 +273,7 @@ const fetchRepoUserStats = async (
owner,
"issues",
`commenter:${username}+-author:${username}+type:pr`,
+ pat,
);
}
if (include_prs_reviewed) {
@@ -274,6 +283,7 @@ const fetchRepoUserStats = async (
owner,
"issues",
`reviewed-by:${username}+-author:${username}+type:pr`,
+ pat,
);
}
if (include_issues_authored) {
@@ -283,6 +293,7 @@ const fetchRepoUserStats = async (
owner,
"issues",
`author:${username}+type:issue`,
+ pat,
);
}
if (include_issues_commented) {
@@ -292,6 +303,7 @@ const fetchRepoUserStats = async (
owner,
"issues",
`commenter:${username}+-author:${username}+type:issue`,
+ pat,
);
}
return stats;
@@ -326,6 +338,7 @@ const fetchStats = async (
include_issues_authored = false,
include_issues_commented = false,
ownerAffiliations = [],
+ pat = null,
) => {
if (!username) {
throw new MissingParamError(["username"]);
@@ -352,14 +365,17 @@ const fetchStats = async (
};
ownerAffiliations = parseOwnerAffiliations(ownerAffiliations);
- let res = await statsFetcher({
- username,
- includeMergedPullRequests: include_merged_pull_requests,
- includeDiscussions: include_discussions,
- includeDiscussionsAnswers: include_discussions_answers,
- startTime: commits_year ? `${commits_year}-01-01T00:00:00Z` : undefined,
- ownerAffiliations,
- });
+ let res = await statsFetcher(
+ {
+ username,
+ includeMergedPullRequests: include_merged_pull_requests,
+ includeDiscussions: include_discussions,
+ includeDiscussionsAnswers: include_discussions_answers,
+ startTime: commits_year ? `${commits_year}-01-01T00:00:00Z` : undefined,
+ ownerAffiliations,
+ },
+ pat,
+ );
// Catch GraphQL errors.
if (res.data.errors) {
@@ -394,6 +410,7 @@ const fetchStats = async (
owner,
"commits",
`author:${username}`,
+ pat,
);
} else {
stats.totalCommits = user.commits.totalCommitContributions;
@@ -407,6 +424,7 @@ const fetchStats = async (
include_prs_reviewed,
include_issues_authored,
include_issues_commented,
+ pat,
);
Object.assign(stats, repoUserStats);
@@ -429,7 +447,10 @@ const fetchStats = async (
stats.contributedTo = user.repositoriesContributedTo.totalCount;
// Retrieve stars while filtering out repositories to be hidden.
- const allExcludedRepos = [...exclude_repo, ...excludeRepositories];
+ const allExcludedRepos = [
+ ...exclude_repo,
+ ...getConfig().excludeRepositories,
+ ];
let repoToHide = new Set(allExcludedRepos);
stats.totalStars = user.repositories.nodes
diff --git a/apps/backend/src/fetchers/top-languages.js b/packages/core/src/fetchers/top-languages.js
similarity index 93%
rename from apps/backend/src/fetchers/top-languages.js
rename to packages/core/src/fetchers/top-languages.js
index dad43ebe904c7..541bb3b15de30 100644
--- a/apps/backend/src/fetchers/top-languages.js
+++ b/packages/core/src/fetchers/top-languages.js
@@ -1,6 +1,6 @@
// @ts-check
-import { excludeRepositories } from "../common/envs.js";
+import { getConfig } from "../common/config.js";
import { CustomError, MissingParamError } from "../common/error.js";
import { wrapTextMultiline } from "../common/fmt.js";
import { request } from "../common/http.js";
@@ -59,6 +59,7 @@ const fetcher = (variables, token) => {
* @param {number} size_weight Weightage to be given to size.
* @param {number} count_weight Weightage to be given to count.
* @param {string[]} ownerAffiliations The owner affiliations to filter by. Default: OWNER.
+ * @param {string|null} pat Optional PAT override.
* @returns {Promise} Top languages data.
*/
const fetchTopLanguages = async (
@@ -67,16 +68,21 @@ const fetchTopLanguages = async (
size_weight = 1,
count_weight = 0,
ownerAffiliations = [],
+ pat = null,
) => {
if (!username) {
throw new MissingParamError(["username"]);
}
ownerAffiliations = parseOwnerAffiliations(ownerAffiliations);
- const res = await retryer(fetcher, username, {
- login: username,
- ownerAffiliations,
- });
+ const res = await retryer(
+ fetcher,
+ {
+ login: username,
+ ownerAffiliations,
+ },
+ pat,
+ );
if (res.data.errors) {
logger.error(res.data.errors);
@@ -101,7 +107,10 @@ const fetchTopLanguages = async (
let repoNodes = res.data.data.user.repositories.nodes;
/** @type {Record} */
let repoToHide = {};
- const allExcludedRepos = [...exclude_repo, ...excludeRepositories];
+ const allExcludedRepos = [
+ ...exclude_repo,
+ ...getConfig().excludeRepositories,
+ ];
// populate repoToHide map for quick lookup
// while filtering out
diff --git a/apps/backend/src/fetchers/types.d.ts b/packages/core/src/fetchers/types.d.ts
similarity index 89%
rename from apps/backend/src/fetchers/types.d.ts
rename to packages/core/src/fetchers/types.d.ts
index 1588c921290b6..b26b066199bb3 100644
--- a/apps/backend/src/fetchers/types.d.ts
+++ b/packages/core/src/fetchers/types.d.ts
@@ -1,13 +1,13 @@
-export type GistData = {
+export interface GistData {
name: string;
nameWithOwner: string;
description: string | null;
language: string | null;
starsCount: number;
forksCount: number;
-};
+}
-export type RepositoryData = {
+export interface RepositoryData {
name: string;
nameWithOwner: string;
isPrivate: boolean;
@@ -27,9 +27,9 @@ export type RepositoryData = {
totalPRsReviewed: number;
totalIssuesAuthored: number;
totalIssuesCommented: number;
-};
+}
-export type StatsData = {
+export interface StatsData {
name: string;
totalPRs: number;
totalPRsMerged: number;
@@ -47,18 +47,18 @@ export type StatsData = {
totalIssuesAuthored: number;
totalIssuesCommented: number;
rank: { level: string; percentile: number };
-};
+}
-export type Lang = {
+export interface Lang {
name: string;
color: string;
size: number;
-};
+}
export type TopLangData = Record;
-export type WakaTimeData = {
- categories: {
+export interface WakaTimeData {
+ categories: Array<{
digital: string;
hours: number;
minutes: number;
@@ -66,12 +66,12 @@ export type WakaTimeData = {
percent: number;
text: string;
total_seconds: number;
- }[];
+ }>;
daily_average: number;
daily_average_including_other_language: number;
days_including_holidays: number;
days_minus_holidays: number;
- editors: {
+ editors: Array<{
digital: string;
hours: number;
minutes: number;
@@ -79,7 +79,7 @@ export type WakaTimeData = {
percent: number;
text: string;
total_seconds: number;
- }[];
+ }>;
holidays: number;
human_readable_daily_average: string;
human_readable_daily_average_including_other_language: string;
@@ -92,7 +92,7 @@ export type WakaTimeData = {
is_other_usage_visible: boolean;
is_stuck: boolean;
is_up_to_date: boolean;
- languages: {
+ languages: Array<{
digital: string;
hours: number;
minutes: number;
@@ -100,8 +100,8 @@ export type WakaTimeData = {
percent: number;
text: string;
total_seconds: number;
- }[];
- operating_systems: {
+ }>;
+ operating_systems: Array<{
digital: string;
hours: number;
minutes: number;
@@ -109,7 +109,7 @@ export type WakaTimeData = {
percent: number;
text: string;
total_seconds: number;
- }[];
+ }>;
percent_calculated: number;
range: string;
status: string;
@@ -119,10 +119,10 @@ export type WakaTimeData = {
user_id: string;
username: string;
writes_only: boolean;
-};
+}
-export type WakaTimeLang = {
+export interface WakaTimeLang {
name: string;
text: string;
percent: number;
-};
+}
diff --git a/apps/backend/src/fetchers/wakatime.js b/packages/core/src/fetchers/wakatime.js
similarity index 100%
rename from apps/backend/src/fetchers/wakatime.js
rename to packages/core/src/fetchers/wakatime.js
diff --git a/packages/core/src/index.ts b/packages/core/src/index.ts
new file mode 100644
index 0000000000000..23606735c96c4
--- /dev/null
+++ b/packages/core/src/index.ts
@@ -0,0 +1,25 @@
+/**
+ * We need this file to be in ts to allow custom conditions to work
+ * The package will be converted
+ *
+ * @todo https://github.com/stats-organization/github-stats-extended/issues/140
+ */
+export { fetchWakatimeStats } from "./fetchers/wakatime.js";
+export { retryer } from "./common/retryer.js";
+
+export { renderError } from "./common/render.js";
+
+export { dateDiff, clampValue } from "./common/ops.js";
+
+export { logger } from "./common/log.js";
+export { request } from "./common/http.js";
+
+export { default as gist } from "./api/gist.js";
+export { default as api } from "./api/index.js";
+export { default as pin } from "./api/pin.js";
+export { default as topLangs } from "./api/top-langs.js";
+export { default as wakatime } from "./api/wakatime.js";
+
+export { getConfig } from "./common/config.js";
+
+export { themes } from "./themes/index.js";
diff --git a/apps/backend/themes/README.md b/packages/core/src/themes/README.md
similarity index 100%
rename from apps/backend/themes/README.md
rename to packages/core/src/themes/README.md
diff --git a/apps/backend/themes/index.js b/packages/core/src/themes/index.ts
similarity index 97%
rename from apps/backend/themes/index.js
rename to packages/core/src/themes/index.ts
index 98fab2d304fe8..565cd3a9413f0 100644
--- a/apps/backend/themes/index.js
+++ b/packages/core/src/themes/index.ts
@@ -1,3 +1,14 @@
+interface Theme {
+ title_color: string;
+ icon_color: string;
+ text_color: string;
+ bg_color: string;
+ border_color?: string;
+}
+
+/**
+ * Collection of available themes.
+ */
export const themes = {
default: {
title_color: "2f80ed",
@@ -462,4 +473,4 @@ export const themes = {
icon_color: "ffffff",
bg_color: "35,4158d0,c850c0,ffcc70",
},
-};
+} as const satisfies Record;
diff --git a/apps/backend/src/translations.js b/packages/core/src/translations.js
similarity index 100%
rename from apps/backend/src/translations.js
rename to packages/core/src/translations.js
diff --git a/apps/backend/tests/__snapshots__/renderWakatimeCard.test.js.snap b/packages/core/tests/__snapshots__/renderWakatimeCard.test.js.snap
similarity index 91%
rename from apps/backend/tests/__snapshots__/renderWakatimeCard.test.js.snap
rename to packages/core/tests/__snapshots__/renderWakatimeCard.test.js.snap
index c60b504f2c666..206a71b8dfa5e 100644
--- a/apps/backend/tests/__snapshots__/renderWakatimeCard.test.js.snap
+++ b/packages/core/tests/__snapshots__/renderWakatimeCard.test.js.snap
@@ -65,6 +65,24 @@ exports[`Test Render WakaTime Card > should render correctly 1`] = `
+ /* Animations */
+ @keyframes scaleInAnimation {
+ from {
+ transform: translate(-5px, 5px) scale(0);
+ }
+ to {
+ transform: translate(-5px, 5px) scale(1);
+ }
+ }
+ @keyframes fadeInAnimation {
+ from {
+ opacity: 0;
+ }
+ to {
+ opacity: 1;
+ }
+ }
+
@@ -223,6 +241,24 @@ exports[`Test Render WakaTime Card > should render correctly with compact layout
+ /* Animations */
+ @keyframes scaleInAnimation {
+ from {
+ transform: translate(-5px, 5px) scale(0);
+ }
+ to {
+ transform: translate(-5px, 5px) scale(1);
+ }
+ }
+ @keyframes fadeInAnimation {
+ from {
+ opacity: 0;
+ }
+ to {
+ opacity: 1;
+ }
+ }
+
@@ -375,6 +411,24 @@ exports[`Test Render WakaTime Card > should render correctly with compact layout
+ /* Animations */
+ @keyframes scaleInAnimation {
+ from {
+ transform: translate(-5px, 5px) scale(0);
+ }
+ to {
+ transform: translate(-5px, 5px) scale(1);
+ }
+ }
+ @keyframes fadeInAnimation {
+ from {
+ opacity: 0;
+ }
+ to {
+ opacity: 1;
+ }
+ }
+
@@ -527,6 +581,24 @@ exports[`Test Render WakaTime Card > should render correctly with percent displa
+ /* Animations */
+ @keyframes scaleInAnimation {
+ from {
+ transform: translate(-5px, 5px) scale(0);
+ }
+ to {
+ transform: translate(-5px, 5px) scale(1);
+ }
+ }
+ @keyframes fadeInAnimation {
+ from {
+ opacity: 0;
+ }
+ to {
+ opacity: 1;
+ }
+ }
+
diff --git a/packages/core/tests/_setup.js b/packages/core/tests/_setup.js
new file mode 100644
index 0000000000000..3956661798162
--- /dev/null
+++ b/packages/core/tests/_setup.js
@@ -0,0 +1,7 @@
+import * as matchers from "@testing-library/jest-dom/matchers";
+import { expect } from "vitest";
+
+expect.extend(matchers);
+
+process.env.PAT_1 = "dummyPAT1";
+process.env.PAT_2 = "dummyPAT2";
diff --git a/apps/backend/tests/calculateRank.test.js b/packages/core/tests/calculateRank.test.js
similarity index 98%
rename from apps/backend/tests/calculateRank.test.js
rename to packages/core/tests/calculateRank.test.js
index 3997f6664e9c4..e432cef15bd14 100644
--- a/apps/backend/tests/calculateRank.test.js
+++ b/packages/core/tests/calculateRank.test.js
@@ -4,8 +4,6 @@ import { calculateRank } from "../src/calculateRank.js";
import { approxNumber } from "./utils.js";
-import "@testing-library/jest-dom/vitest";
-
describe("Test calculateRank", () => {
it("new user gets C rank", () => {
expect(
diff --git a/apps/backend/tests/card.test.js b/packages/core/tests/card.test.js
similarity index 99%
rename from apps/backend/tests/card.test.js
rename to packages/core/tests/card.test.js
index 960bc34403cb3..c9d289d4f8249 100644
--- a/apps/backend/tests/card.test.js
+++ b/packages/core/tests/card.test.js
@@ -6,8 +6,6 @@ import { Card } from "../src/common/Card.js";
import { getCardColors } from "../src/common/color.js";
import { icons } from "../src/common/icons.js";
-import "@testing-library/jest-dom/vitest";
-
describe("Card", () => {
it("should hide border", () => {
const card = new Card({});
diff --git a/apps/backend/tests/color.test.js b/packages/core/tests/color.test.js
similarity index 100%
rename from apps/backend/tests/color.test.js
rename to packages/core/tests/color.test.js
diff --git a/apps/backend/tests/fetchGist.test.js b/packages/core/tests/fetchGist.test.js
similarity index 98%
rename from apps/backend/tests/fetchGist.test.js
rename to packages/core/tests/fetchGist.test.js
index ca0d41ed27c0c..a0336e6748002 100644
--- a/apps/backend/tests/fetchGist.test.js
+++ b/packages/core/tests/fetchGist.test.js
@@ -4,8 +4,6 @@ import { afterEach, describe, expect, it } from "vitest";
import { fetchGist } from "../src/fetchers/gist.js";
-import "@testing-library/jest-dom/vitest";
-
const gist_data = {
data: {
viewer: {
diff --git a/apps/backend/tests/fetchRepo.test.js b/packages/core/tests/fetchRepo.test.js
similarity index 98%
rename from apps/backend/tests/fetchRepo.test.js
rename to packages/core/tests/fetchRepo.test.js
index b98eca4771908..d72b1e4b05d06 100644
--- a/apps/backend/tests/fetchRepo.test.js
+++ b/packages/core/tests/fetchRepo.test.js
@@ -4,8 +4,6 @@ import { afterEach, describe, expect, it } from "vitest";
import { fetchRepo } from "../src/fetchers/repo.js";
-import "@testing-library/jest-dom/vitest";
-
const data_repo = {
repository: {
name: "convoychat",
diff --git a/apps/backend/tests/fetchStats.test.js b/packages/core/tests/fetchStats.test.js
similarity index 98%
rename from apps/backend/tests/fetchStats.test.js
rename to packages/core/tests/fetchStats.test.js
index b4681f5908656..7d4531aaf124e 100644
--- a/apps/backend/tests/fetchStats.test.js
+++ b/packages/core/tests/fetchStats.test.js
@@ -3,10 +3,9 @@ import MockAdapter from "axios-mock-adapter";
import { afterEach, beforeEach, describe, expect, it } from "vitest";
import { calculateRank } from "../src/calculateRank.js";
+import { loadConfigFromEnv } from "../src/common/config.js";
import { fetchStats } from "../src/fetchers/stats.js";
-import "@testing-library/jest-dom/vitest";
-
// Test parameters.
const data_stats = {
data: {
@@ -107,6 +106,7 @@ const mock = new MockAdapter(axios);
beforeEach(() => {
process.env.FETCH_MULTI_PAGE_STARS = "false"; // Set to `false` to fetch only one page of stars.
+ loadConfigFromEnv();
mock.onPost("https://api.github.com/graphql").reply((cfg) => {
let req = JSON.parse(cfg.data);
@@ -313,6 +313,7 @@ describe("Test fetchStats", () => {
it("should fetch two pages of stars if 'FETCH_MULTI_PAGE_STARS' env variable is set to `true`", async () => {
process.env.FETCH_MULTI_PAGE_STARS = true;
+ loadConfigFromEnv();
let stats = await fetchStats("anuraghazra");
const rank = calculateRank({
@@ -349,6 +350,7 @@ describe("Test fetchStats", () => {
it("should fetch one page of stars if 'FETCH_MULTI_PAGE_STARS' env variable is set to `false`", async () => {
process.env.FETCH_MULTI_PAGE_STARS = "false";
+ loadConfigFromEnv();
let stats = await fetchStats("anuraghazra");
const rank = calculateRank({
@@ -385,6 +387,7 @@ describe("Test fetchStats", () => {
it("should fetch one page of stars if 'FETCH_MULTI_PAGE_STARS' env variable is not set", async () => {
process.env.FETCH_MULTI_PAGE_STARS = undefined;
+ loadConfigFromEnv();
let stats = await fetchStats("anuraghazra");
const rank = calculateRank({
diff --git a/apps/backend/tests/fetchTopLanguages.test.js b/packages/core/tests/fetchTopLanguages.test.js
similarity index 99%
rename from apps/backend/tests/fetchTopLanguages.test.js
rename to packages/core/tests/fetchTopLanguages.test.js
index f4f2485988f40..798c5a38476f0 100644
--- a/apps/backend/tests/fetchTopLanguages.test.js
+++ b/packages/core/tests/fetchTopLanguages.test.js
@@ -6,8 +6,6 @@ import { fetchTopLanguages } from "../src/fetchers/top-languages.js";
import { approxNumber } from "./utils.js";
-import "@testing-library/jest-dom/vitest";
-
const mock = new MockAdapter(axios);
afterEach(() => {
diff --git a/apps/backend/tests/fetchWakatime.test.js b/packages/core/tests/fetchWakatime.test.js
similarity index 98%
rename from apps/backend/tests/fetchWakatime.test.js
rename to packages/core/tests/fetchWakatime.test.js
index 5149b49f8520a..cc1ac289f71fd 100644
--- a/apps/backend/tests/fetchWakatime.test.js
+++ b/packages/core/tests/fetchWakatime.test.js
@@ -4,8 +4,6 @@ import { afterEach, describe, expect, it } from "vitest";
import { fetchWakatimeStats } from "../src/fetchers/wakatime.js";
-import "@testing-library/jest-dom/vitest";
-
const mock = new MockAdapter(axios);
afterEach(() => {
diff --git a/apps/backend/tests/flexLayout.test.js b/packages/core/tests/flexLayout.test.js
similarity index 100%
rename from apps/backend/tests/flexLayout.test.js
rename to packages/core/tests/flexLayout.test.js
diff --git a/apps/backend/tests/fmt.test.js b/packages/core/tests/fmt.test.js
similarity index 100%
rename from apps/backend/tests/fmt.test.js
rename to packages/core/tests/fmt.test.js
diff --git a/apps/backend/tests/html.test.js b/packages/core/tests/html.test.js
similarity index 100%
rename from apps/backend/tests/html.test.js
rename to packages/core/tests/html.test.js
diff --git a/apps/backend/tests/i18n.test.js b/packages/core/tests/i18n.test.js
similarity index 100%
rename from apps/backend/tests/i18n.test.js
rename to packages/core/tests/i18n.test.js
diff --git a/apps/backend/tests/ops.test.js b/packages/core/tests/ops.test.js
similarity index 100%
rename from apps/backend/tests/ops.test.js
rename to packages/core/tests/ops.test.js
diff --git a/apps/backend/tests/render.test.js b/packages/core/tests/render.test.js
similarity index 95%
rename from apps/backend/tests/render.test.js
rename to packages/core/tests/render.test.js
index d5cd6ca446867..9eb55d019ec2f 100644
--- a/apps/backend/tests/render.test.js
+++ b/packages/core/tests/render.test.js
@@ -5,8 +5,6 @@ import { describe, expect, it } from "vitest";
import { renderError } from "../src/common/render.js";
-import "@testing-library/jest-dom/vitest";
-
describe("Test render.js", () => {
it("should test renderError", () => {
document.body.innerHTML = renderError({ message: "Something went wrong" });
diff --git a/apps/backend/tests/renderGistCard.test.js b/packages/core/tests/renderGistCard.test.js
similarity index 98%
rename from apps/backend/tests/renderGistCard.test.js
rename to packages/core/tests/renderGistCard.test.js
index 28fbfa339191f..fe06b4b82f4e8 100644
--- a/apps/backend/tests/renderGistCard.test.js
+++ b/packages/core/tests/renderGistCard.test.js
@@ -3,9 +3,7 @@ import { cssToObject } from "@uppercod/css-to-object";
import { describe, expect, it } from "vitest";
import { renderGistCard } from "../src/cards/gist.js";
-import { themes } from "../themes/index.js";
-
-import "@testing-library/jest-dom/vitest";
+import { themes } from "../src/themes/index.js";
/**
* @type {import("../src/fetchers/gist").GistData}
diff --git a/apps/backend/tests/renderRepoCard.test.js b/packages/core/tests/renderRepoCard.test.js
similarity index 99%
rename from apps/backend/tests/renderRepoCard.test.js
rename to packages/core/tests/renderRepoCard.test.js
index 4efecf57f31b0..f14f7998863d8 100644
--- a/apps/backend/tests/renderRepoCard.test.js
+++ b/packages/core/tests/renderRepoCard.test.js
@@ -3,9 +3,7 @@ import { cssToObject } from "@uppercod/css-to-object";
import { describe, expect, it } from "vitest";
import { renderRepoCard } from "../src/cards/repo.js";
-import { themes } from "../themes/index.js";
-
-import "@testing-library/jest-dom/vitest";
+import { themes } from "../src/themes/index.js";
const data_repo = {
repository: {
diff --git a/apps/backend/tests/renderStatsCard.test.js b/packages/core/tests/renderStatsCard.test.js
similarity index 99%
rename from apps/backend/tests/renderStatsCard.test.js
rename to packages/core/tests/renderStatsCard.test.js
index c21bcf4580c75..c5e459feec772 100644
--- a/apps/backend/tests/renderStatsCard.test.js
+++ b/packages/core/tests/renderStatsCard.test.js
@@ -8,9 +8,7 @@ import { describe, expect, it } from "vitest";
import { renderStatsCard } from "../src/cards/stats.js";
import { CustomError } from "../src/common/error.js";
-import { themes } from "../themes/index.js";
-
-import "@testing-library/jest-dom/vitest";
+import { themes } from "../src/themes/index.js";
const stats = {
name: "Anurag Hazra",
diff --git a/apps/backend/tests/renderTopLanguagesCard.test.js b/packages/core/tests/renderTopLanguagesCard.test.js
similarity index 99%
rename from apps/backend/tests/renderTopLanguagesCard.test.js
rename to packages/core/tests/renderTopLanguagesCard.test.js
index ad50cc2dd8e56..3cd8c63eebf94 100644
--- a/apps/backend/tests/renderTopLanguagesCard.test.js
+++ b/packages/core/tests/renderTopLanguagesCard.test.js
@@ -20,12 +20,10 @@ import {
renderTopLanguages,
trimTopLanguages,
} from "../src/cards/top-languages.js";
-import { themes } from "../themes/index.js";
+import { themes } from "../src/themes/index.js";
import { approxNumber } from "./utils.js";
-import "@testing-library/jest-dom/vitest";
-
const langs = {
HTML: {
color: "#0f0",
diff --git a/apps/backend/tests/renderWakatimeCard.test.js b/packages/core/tests/renderWakatimeCard.test.js
similarity index 98%
rename from apps/backend/tests/renderWakatimeCard.test.js
rename to packages/core/tests/renderWakatimeCard.test.js
index 8c4971cf3413c..ba8577c79b6a8 100644
--- a/apps/backend/tests/renderWakatimeCard.test.js
+++ b/packages/core/tests/renderWakatimeCard.test.js
@@ -5,8 +5,6 @@ import { renderWakatimeCard } from "../src/cards/wakatime.js";
import { wakaTimeData } from "./fetchWakatime.test.js";
-import "@testing-library/jest-dom/vitest";
-
describe("Test Render WakaTime Card", () => {
it("should render correctly", () => {
const card = renderWakatimeCard(wakaTimeData.data);
diff --git a/apps/backend/tests/retryer.test.js b/packages/core/tests/retryer.test.js
similarity index 83%
rename from apps/backend/tests/retryer.test.js
rename to packages/core/tests/retryer.test.js
index b82716b376483..105720810cdf7 100644
--- a/apps/backend/tests/retryer.test.js
+++ b/packages/core/tests/retryer.test.js
@@ -2,8 +2,6 @@
import { describe, expect, it, vi } from "vitest";
-import "@testing-library/jest-dom/vitest";
-
import { logger } from "../src/common/log.js";
import { retryer } from "../src/common/retryer.js";
@@ -51,6 +49,11 @@ const fetcherFailWithMessageBasedRateLimitErr = vi.fn(
},
);
+const customFetcher = vi.fn((variables, token) => {
+ logger.log(variables, token);
+ return Promise.resolve({ data: { token } });
+});
+
describe("Test Retryer", () => {
it("retryer should return value and have zero retries on first try", async () => {
let res = await retryer(fetcher, {});
@@ -82,4 +85,12 @@ describe("Test Retryer", () => {
expect(err.message).toBe("Downtime due to GitHub API rate limiting");
}
});
+
+ it("retryer should use injected PATs when provided", async () => {
+ const res = await retryer(customFetcher, {}, "user-pat-token");
+
+ expect(customFetcher).toHaveBeenCalledTimes(1);
+ expect(customFetcher).toHaveBeenCalledWith({}, "user-pat-token", 0);
+ expect(res).toStrictEqual({ data: { token: "user-pat-token" } });
+ });
});
diff --git a/packages/core/tests/utils.js b/packages/core/tests/utils.js
new file mode 100644
index 0000000000000..21b9f1f887391
--- /dev/null
+++ b/packages/core/tests/utils.js
@@ -0,0 +1,41 @@
+// @ts-check
+
+/**
+ * Creates an asymmetric matcher for approximate numeric equality.
+ *
+ * This helper is intended for use in test frameworks (e.g., Jest) where
+ * values need to be compared within a configurable decimal precision
+ * instead of strict equality.
+ *
+ * The comparison succeeds when:
+ *
+ * |actual - expected| < 10^(-precision)
+ *
+ * For example, with `precision = 3`, values must be within `0.001`.
+ *
+ * @param {number} expected The expected numeric value to compare against.
+ *
+ * @param {number} [precision=10]
+ * The number of decimal places of tolerance. Higher values mean stricter
+ * comparison. Internally converted to epsilon = 10^-precision.
+ *
+ * @returns {{
+ * asymmetricMatch(actual: unknown): boolean,
+ * toAsymmetricMatcher(): string
+ * }} An object implementing Jest-style asymmetric matcher methods.
+ *
+ */
+export function approxNumber(expected, precision = 10) {
+ return {
+ asymmetricMatch(actual) {
+ if (typeof actual !== "number" || typeof expected !== "number") {
+ return false;
+ }
+ const epsilon = Math.pow(10, -precision);
+ return Math.abs(actual - expected) < epsilon;
+ },
+ toAsymmetricMatcher() {
+ return `≈ ${expected} (precision ${precision})`;
+ },
+ };
+}
diff --git a/packages/core/tsconfig.build.json b/packages/core/tsconfig.build.json
new file mode 100644
index 0000000000000..e58f87a714aed
--- /dev/null
+++ b/packages/core/tsconfig.build.json
@@ -0,0 +1,9 @@
+{
+ "extends": ["./tsconfig.json"],
+ "include": ["src"],
+ "compilerOptions": {
+ "declaration": true,
+ "declarationMap": true,
+ "customConditions": []
+ }
+}
diff --git a/packages/core/tsconfig.json b/packages/core/tsconfig.json
new file mode 100644
index 0000000000000..c1b9c120b8b6d
--- /dev/null
+++ b/packages/core/tsconfig.json
@@ -0,0 +1,21 @@
+{
+ "extends": ["../../tsconfig.base.json"],
+ "include": ["src", "tests", "vitest.config.ts"],
+ "compilerOptions": {
+ /**
+ * This workspace contains a lot of type errors.
+ * We will add typecheck later:
+ * @see https://github.com/stats-organization/github-stats-extended/issues/140
+ *
+ * At the moment we just need to output `js` and `.d.ts` files
+ */
+ "noCheck": true,
+
+ "allowJs": true,
+ "checkJs": true,
+
+ "module": "nodenext",
+ "moduleResolution": "nodenext",
+ "outDir": "build"
+ }
+}
diff --git a/packages/core/tsconfig.typecheck.json b/packages/core/tsconfig.typecheck.json
new file mode 100644
index 0000000000000..4c13d7b105526
--- /dev/null
+++ b/packages/core/tsconfig.typecheck.json
@@ -0,0 +1,7 @@
+{
+ "extends": ["./tsconfig.json"],
+ "compilerOptions": {
+ "noEmit": true,
+ "customConditions": []
+ }
+}
diff --git a/packages/core/vitest.config.ts b/packages/core/vitest.config.ts
new file mode 100644
index 0000000000000..4e11ea5320975
--- /dev/null
+++ b/packages/core/vitest.config.ts
@@ -0,0 +1,9 @@
+import { defineProject } from "vitest/config";
+
+export default defineProject({
+ test: {
+ environment: "jsdom",
+ include: ["./tests/*.test.{ts,js}"],
+ setupFiles: ["./tests/_setup.js"],
+ },
+});
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index a23363aa14f71..be659cdcf9d95 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -7,125 +7,119 @@ settings:
catalogs:
default:
'@vitest/coverage-v8':
- specifier: 4.0.18
- version: 4.0.18
+ specifier: 4.1.2
+ version: 4.1.2
vite:
specifier: 7.3.1
version: 7.3.1
vitest:
- specifier: 4.0.18
- version: 4.0.18
+ specifier: 4.1.2
+ version: 4.1.2
importers:
.:
devDependencies:
'@eslint/compat':
- specifier: 2.0.2
- version: 2.0.2(eslint@9.39.2(jiti@2.6.1))
+ specifier: 2.0.3
+ version: 2.0.3(eslint@10.1.0(jiti@2.6.1))
'@eslint/js':
- specifier: 9.39.3
- version: 9.39.3
+ specifier: 10.0.1
+ version: 10.0.1(eslint@10.1.0(jiti@2.6.1))
'@playwright/test':
- specifier: 1.58.2
- version: 1.58.2
+ specifier: 1.59.1
+ version: 1.59.1
'@types/node':
- specifier: 24.10.13
- version: 24.10.13
+ specifier: 24.12.0
+ version: 24.12.0
+ '@vitest/coverage-v8':
+ specifier: catalog:default
+ version: 4.1.2(vitest@4.1.2(@types/node@24.12.0)(happy-dom@20.6.1)(jsdom@28.1.0)(vite@7.3.1(@types/node@24.12.0)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.44.1)(yaml@2.8.3)))
eslint:
- specifier: 9.39.2
- version: 9.39.2(jiti@2.6.1)
+ specifier: 10.1.0
+ version: 10.1.0(jiti@2.6.1)
eslint-import-resolver-typescript:
specifier: 4.4.4
- version: 4.4.4(eslint-plugin-import-x@4.16.1(@typescript-eslint/utils@8.56.1(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.2(jiti@2.6.1)))(eslint@9.39.2(jiti@2.6.1))
+ version: 4.4.4(eslint-plugin-import-x@4.16.2(@typescript-eslint/utils@8.58.0(eslint@10.1.0(jiti@2.6.1))(typescript@5.9.3))(eslint@10.1.0(jiti@2.6.1)))(eslint@10.1.0(jiti@2.6.1))
eslint-plugin-import-x:
- specifier: 4.16.1
- version: 4.16.1(@typescript-eslint/utils@8.56.1(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.2(jiti@2.6.1))
+ specifier: 4.16.2
+ version: 4.16.2(@typescript-eslint/utils@8.58.0(eslint@10.1.0(jiti@2.6.1))(typescript@5.9.3))(eslint@10.1.0(jiti@2.6.1))
eslint-plugin-jsdoc:
- specifier: 62.7.1
- version: 62.7.1(eslint@9.39.2(jiti@2.6.1))
+ specifier: 62.9.0
+ version: 62.9.0(eslint@10.1.0(jiti@2.6.1))
eslint-plugin-react:
specifier: 7.37.5
- version: 7.37.5(eslint@9.39.2(jiti@2.6.1))
+ version: 7.37.5(eslint@10.1.0(jiti@2.6.1))
eslint-plugin-react-hooks:
specifier: 7.0.1
- version: 7.0.1(eslint@9.39.2(jiti@2.6.1))
+ version: 7.0.1(eslint@10.1.0(jiti@2.6.1))
globals:
- specifier: 17.3.0
- version: 17.3.0
+ specifier: 17.4.0
+ version: 17.4.0
husky:
specifier: 9.1.7
version: 9.1.7
knip:
- specifier: 5.85.0
- version: 5.85.0(@types/node@24.10.13)(typescript@5.9.3)
+ specifier: 6.2.0
+ version: 6.2.0(@emnapi/core@1.9.2)(@emnapi/runtime@1.9.2)
lint-staged:
- specifier: 16.2.7
- version: 16.2.7
+ specifier: 16.4.0
+ version: 16.4.0
prettier:
specifier: 3.8.1
version: 3.8.1
+ turbo:
+ specifier: 2.9.3
+ version: 2.9.3
typescript:
specifier: 5.9.3
version: 5.9.3
typescript-eslint:
- specifier: 8.56.1
- version: 8.56.1(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3)
+ specifier: 8.58.0
+ version: 8.58.0(eslint@10.1.0(jiti@2.6.1))(typescript@5.9.3)
+ vitest:
+ specifier: catalog:default
+ version: 4.1.2(@types/node@24.12.0)(happy-dom@20.6.1)(jsdom@28.1.0)(vite@7.3.1(@types/node@24.12.0)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.44.1)(yaml@2.8.3))
apps/backend:
dependencies:
+ '@stats-organization/github-readme-stats-core':
+ specifier: workspace:^
+ version: link:../../packages/core
axios:
specifier: ^1.13.5
version: 1.13.5
- emoji-name-map:
- specifier: ^2.0.3
- version: 2.0.3
- github-username-regex:
- specifier: ^1.0.0
- version: 1.0.0
pg:
specifier: ^8.18.0
version: 8.18.0
- word-wrap:
- specifier: ^1.2.5
- version: 1.2.5
devDependencies:
- '@testing-library/dom':
- specifier: ^10.4.1
- version: 10.4.1
- '@testing-library/jest-dom':
- specifier: ^6.9.1
- version: 6.9.1
- '@uppercod/css-to-object':
- specifier: ^1.1.1
- version: 1.1.1
- '@vitest/coverage-v8':
- specifier: catalog:default
- version: 4.0.18(vitest@4.0.18(@types/node@25.2.3)(happy-dom@20.6.1)(jiti@2.6.1)(jsdom@28.1.0)(lightningcss@1.31.1)(terser@5.44.1)(yaml@2.8.2))
axios-mock-adapter:
- specifier: ^2.1.0
+ specifier: 2.1.0
version: 2.1.0(axios@1.13.5)
express:
- specifier: ^5.2.1
+ specifier: 5.2.1
version: 5.2.1
- js-yaml:
- specifier: ^4.1.1
- version: 4.1.1
jsdom:
specifier: 28.1.0
version: 28.1.0
vitest:
specifier: catalog:default
- version: 4.0.18(@types/node@25.2.3)(happy-dom@20.6.1)(jiti@2.6.1)(jsdom@28.1.0)(lightningcss@1.31.1)(terser@5.44.1)(yaml@2.8.2)
+ version: 4.1.2(@types/node@25.5.0)(happy-dom@20.6.1)(jsdom@28.1.0)(vite@7.3.1(@types/node@25.5.0)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.44.1)(yaml@2.8.3))
apps/frontend:
dependencies:
'@reduxjs/toolkit':
- specifier: 2.11.2
+ specifier: ^2.11.2
version: 2.11.2(react-redux@9.2.0(@types/react@18.3.27)(react@18.3.1)(redux@5.0.1))(react@18.3.1)
+ '@stats-organization/github-readme-stats-backend':
+ specifier: workspace:^
+ version: link:../backend
+ '@stats-organization/github-readme-stats-core':
+ specifier: workspace:^
+ version: link:../../packages/core
'@tailwindcss/vite':
- specifier: 4.2.1
- version: 4.2.1(vite@7.3.1(@types/node@25.2.3)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.44.1)(yaml@2.8.2))
+ specifier: ^4.2.1
+ version: 4.2.1(vite@7.3.1(@types/node@25.5.0)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.44.1)(yaml@2.8.3))
axios:
specifier: ^1
version: 1.13.5
@@ -133,19 +127,13 @@ importers:
specifier: ^1
version: 1.11.4(axios@1.13.5)
daisyui:
- specifier: 5.5.19
+ specifier: ^5.5.19
version: 5.5.19
- emoji-name-map:
- specifier: ^2.0.3
- version: 2.0.3
- github-username-regex:
- specifier: ^1.0.0
- version: 1.0.0
react:
- specifier: 18.3.1
+ specifier: ^18.3.1
version: 18.3.1
react-dom:
- specifier: 18.3.1
+ specifier: ^18.3.1
version: 18.3.1(react@18.3.1)
react-icons:
specifier: ^5.5.0
@@ -154,26 +142,23 @@ importers:
specifier: ^3.3.1
version: 3.5.0(react@18.3.1)
react-redux:
- specifier: 9.2.0
+ specifier: ^9.2.0
version: 9.2.0(@types/react@18.3.27)(react@18.3.1)(redux@5.0.1)
react-spinners:
specifier: ^0.17.0
version: 0.17.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
react-toastify:
- specifier: 11.0.5
+ specifier: ^11.0.5
version: 11.0.5(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
redux:
- specifier: 5.0.1
+ specifier: ^5.0.1
version: 5.0.1
save-svg-as-png:
specifier: ^1.4.17
version: 1.4.17
uuid:
- specifier: 13.0.0
+ specifier: ^13.0.0
version: 13.0.0
- word-wrap:
- specifier: ^1.2.5
- version: 1.2.5
devDependencies:
'@types/react':
specifier: 18.3.27
@@ -183,7 +168,7 @@ importers:
version: 18.3.7(@types/react@18.3.27)
'@vitejs/plugin-react-swc':
specifier: 4.2.3
- version: 4.2.3(vite@7.3.1(@types/node@25.2.3)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.44.1)(yaml@2.8.2))
+ version: 4.2.3(vite@7.3.1(@types/node@25.5.0)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.44.1)(yaml@2.8.3))
clsx:
specifier: 2.1.1
version: 2.1.1
@@ -192,16 +177,47 @@ importers:
version: 4.2.1
vite:
specifier: catalog:default
- version: 7.3.1(@types/node@25.2.3)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.44.1)(yaml@2.8.2)
- vite-plugin-node-polyfills:
- specifier: 0.25.0
- version: 0.25.0(rollup@4.59.0)(vite@7.3.1(@types/node@25.2.3)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.44.1)(yaml@2.8.2))
- vite-plugin-string-replace:
- specifier: 1.1.5
- version: 1.1.5
+ version: 7.3.1(@types/node@25.5.0)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.44.1)(yaml@2.8.3)
+ vitest:
+ specifier: catalog:default
+ version: 4.1.2(@types/node@25.5.0)(happy-dom@20.6.1)(jsdom@28.1.0)(vite@7.3.1(@types/node@25.5.0)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.44.1)(yaml@2.8.3))
+
+ packages/core:
+ dependencies:
+ axios:
+ specifier: ^1.13.5
+ version: 1.13.5
+ emoji-name-map:
+ specifier: ^2.0.3
+ version: 2.0.3
+ github-username-regex:
+ specifier: ^1.0.0
+ version: 1.0.0
+ word-wrap:
+ specifier: ^1.2.5
+ version: 1.2.5
+ devDependencies:
+ '@testing-library/dom':
+ specifier: 10.4.1
+ version: 10.4.1
+ '@testing-library/jest-dom':
+ specifier: 6.9.1
+ version: 6.9.1
+ '@uppercod/css-to-object':
+ specifier: 1.1.1
+ version: 1.1.1
+ axios-mock-adapter:
+ specifier: 2.1.0
+ version: 2.1.0(axios@1.13.5)
+ js-yaml:
+ specifier: 4.1.1
+ version: 4.1.1
+ jsdom:
+ specifier: 28.1.0
+ version: 28.1.0
vitest:
specifier: catalog:default
- version: 4.0.18(@types/node@25.2.3)(happy-dom@20.6.1)(jiti@2.6.1)(jsdom@28.1.0)(lightningcss@1.31.1)(terser@5.44.1)(yaml@2.8.2)
+ version: 4.1.2(@types/node@25.5.0)(happy-dom@20.6.1)(jsdom@28.1.0)(vite@7.3.1(@types/node@25.5.0)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.44.1)(yaml@2.8.3))
packages:
@@ -267,12 +283,12 @@ packages:
resolution: {integrity: sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==}
engines: {node: '>=6.9.0'}
- '@babel/helpers@7.28.6':
- resolution: {integrity: sha512-xOBvwq86HHdB7WUDTfKfT/Vuxh7gElQ+Sfti2Cy6yIWNW05P8iUslOVcZ4/sKbE+/jQaukQAdz/gf3724kYdqw==}
+ '@babel/helpers@7.29.2':
+ resolution: {integrity: sha512-HoGuUs4sCZNezVEKdVcwqmZN8GoHirLUcLaYVNBK2J0DadGtdcqgr3BCbvH8+XUo4NGjNl3VOtSjEKNzqfFgKw==}
engines: {node: '>=6.9.0'}
- '@babel/parser@7.29.0':
- resolution: {integrity: sha512-IyDgFV5GeDUVX4YdF/3CPULtVGSXXMLh1xVIgdCgxApktqnQV0r7/8Nqthg+8YLGaAtdyIlo2qIdZrbCv4+7ww==}
+ '@babel/parser@7.29.2':
+ resolution: {integrity: sha512-4GgRzy/+fsBa72/RZVJmGKPmZu9Byn8o4MoLpmNe1m8ZfYnz5emHLQz3U4gLud6Zwl0RZIcgiLD7Uq7ySFuDLA==}
engines: {node: '>=6.0.0'}
hasBin: true
@@ -331,17 +347,17 @@ packages:
resolution: {integrity: sha512-QxULHAm7cNu72w97JUNCBFODFaXpbDg+dP8b/oWFAZ2MTRppA3U00Y2L1HqaS4J6yBqxwa/Y3nMBaxVKbB/NsA==}
engines: {node: '>=20.19.0'}
- '@emnapi/core@1.8.1':
- resolution: {integrity: sha512-AvT9QFpxK0Zd8J0jopedNm+w/2fIzvtPKPjqyw9jwvBaReTTqPBk9Hixaz7KbjimP+QNz605/XnjFcDAL2pqBg==}
+ '@emnapi/core@1.9.2':
+ resolution: {integrity: sha512-UC+ZhH3XtczQYfOlu3lNEkdW/p4dsJ1r/bP7H8+rhao3TTTMO1ATq/4DdIi23XuGoFY+Cz0JmCbdVl0hz9jZcA==}
- '@emnapi/runtime@1.8.1':
- resolution: {integrity: sha512-mehfKSMWjjNol8659Z8KxEMrdSJDDot5SXMq00dM8BN4o+CLNXQ0xH2V7EchNHV4RmbZLmmPdEaXZc5H2FXmDg==}
+ '@emnapi/runtime@1.9.2':
+ resolution: {integrity: sha512-3U4+MIWHImeyu1wnmVygh5WlgfYDtyf0k8AbLhMFxOipihf6nrWC4syIm/SwEeec0mNSafiiNnMJwbza/Is6Lw==}
- '@emnapi/wasi-threads@1.1.0':
- resolution: {integrity: sha512-WI0DdZ8xFSbgMjR1sFsKABJ/C5OnRrjT06JXbZKexJGrDuPTzZdDYfFlsgcCXCyf+suG5QU2e/y1Wo2V/OapLQ==}
+ '@emnapi/wasi-threads@1.2.1':
+ resolution: {integrity: sha512-uTII7OYF+/Mes/MrcIOYp5yOtSMLBWSIoLPpcgwipoiKbli6k322tcoFsxoIIxPDqW01SQGAgko4EzZi2BNv2w==}
- '@es-joy/jsdoccomment@0.84.0':
- resolution: {integrity: sha512-0xew1CxOam0gV5OMjh2KjFQZsKL2bByX1+q4j3E73MpYIdyUxcZb/xQct9ccUb+ve5KGUYbCUxyPnYB7RbuP+w==}
+ '@es-joy/jsdoccomment@0.86.0':
+ resolution: {integrity: sha512-ukZmRQ81WiTpDWO6D/cTBM7XbrNtutHKvAVnZN/8pldAwLoJArGOvkNyxPTBGsPjsoaQBJxlH+tE2TNA/92Qgw==}
engines: {node: ^20.19.0 || ^22.13.0 || >=24}
'@es-joy/resolve.exports@1.2.0':
@@ -514,8 +530,8 @@ packages:
resolution: {integrity: sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==}
engines: {node: ^12.0.0 || ^14.0.0 || >=16.0.0}
- '@eslint/compat@2.0.2':
- resolution: {integrity: sha512-pR1DoD0h3HfF675QZx0xsyrsU8q70Z/plx7880NOhS02NuWLgBCOMDL787nUeQ7EWLkxv3bPQJaarjcPQb2Dwg==}
+ '@eslint/compat@2.0.3':
+ resolution: {integrity: sha512-SjIJhGigp8hmd1YGIBwh7Ovri7Kisl42GYFjrOyHhtfYGGoLW6teYi/5p8W50KSsawUPpuLOSmsq1bD0NGQLBw==}
engines: {node: ^20.19.0 || ^22.13.0 || >=24}
peerDependencies:
eslint: ^8.40 || 9 || 10
@@ -523,41 +539,34 @@ packages:
eslint:
optional: true
- '@eslint/config-array@0.21.1':
- resolution: {integrity: sha512-aw1gNayWpdI/jSYVgzN5pL0cfzU02GT3NBpeT/DXbx1/1x7ZKxFPd9bwrzygx/qiwIQiJ1sw/zD8qY/kRvlGHA==}
- engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
-
- '@eslint/config-helpers@0.4.2':
- resolution: {integrity: sha512-gBrxN88gOIf3R7ja5K9slwNayVcZgK6SOUORm2uBzTeIEfeVaIhOpCtTox3P6R7o2jLFwLFTLnC7kU/RGcYEgw==}
- engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
-
- '@eslint/core@0.17.0':
- resolution: {integrity: sha512-yL/sLrpmtDaFEiUj1osRP4TI2MDz1AddJL+jZ7KSqvBuliN4xqYY54IfdN8qD8Toa6g1iloph1fxQNkjOxrrpQ==}
- engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
-
- '@eslint/core@1.1.0':
- resolution: {integrity: sha512-/nr9K9wkr3P1EzFTdFdMoLuo1PmIxjmwvPozwoSodjNBdefGujXQUF93u1DDZpEaTuDvMsIQddsd35BwtrW9Xw==}
+ '@eslint/config-array@0.23.3':
+ resolution: {integrity: sha512-j+eEWmB6YYLwcNOdlwQ6L2OsptI/LO6lNBuLIqe5R7RetD658HLoF+Mn7LzYmAWWNNzdC6cqP+L6r8ujeYXWLw==}
engines: {node: ^20.19.0 || ^22.13.0 || >=24}
- '@eslint/eslintrc@3.3.4':
- resolution: {integrity: sha512-4h4MVF8pmBsncB60r0wSJiIeUKTSD4m7FmTFThG8RHlsg9ajqckLm9OraguFGZE4vVdpiI1Q4+hFnisopmG6gQ==}
- engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
+ '@eslint/config-helpers@0.5.3':
+ resolution: {integrity: sha512-lzGN0onllOZCGroKJmRwY6QcEHxbjBw1gwB8SgRSqK8YbbtEXMvKynsXc3553ckIEBxsbMBU7oOZXKIPGZNeZw==}
+ engines: {node: ^20.19.0 || ^22.13.0 || >=24}
- '@eslint/js@9.39.2':
- resolution: {integrity: sha512-q1mjIoW1VX4IvSocvM/vbTiveKC4k9eLrajNEuSsmjymSDEbpGddtpfOoN7YGAqBK3NG+uqo8ia4PDTt8buCYA==}
- engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
+ '@eslint/core@1.1.1':
+ resolution: {integrity: sha512-QUPblTtE51/7/Zhfv8BDwO0qkkzQL7P/aWWbqcf4xWLEYn1oKjdO0gglQBB4GAsu7u6wjijbCmzsUTy6mnk6oQ==}
+ engines: {node: ^20.19.0 || ^22.13.0 || >=24}
- '@eslint/js@9.39.3':
- resolution: {integrity: sha512-1B1VkCq6FuUNlQvlBYb+1jDu/gV297TIs/OeiaSR9l1H27SVW55ONE1e1Vp16NqP683+xEGzxYtv4XCiDPaQiw==}
- engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
+ '@eslint/js@10.0.1':
+ resolution: {integrity: sha512-zeR9k5pd4gxjZ0abRoIaxdc7I3nDktoXZk2qOv9gCNWx3mVwEn32VRhyLaRsDiJjTs0xq/T8mfPtyuXu7GWBcA==}
+ engines: {node: ^20.19.0 || ^22.13.0 || >=24}
+ peerDependencies:
+ eslint: ^10.0.0
+ peerDependenciesMeta:
+ eslint:
+ optional: true
- '@eslint/object-schema@2.1.7':
- resolution: {integrity: sha512-VtAOaymWVfZcmZbp6E2mympDIHvyjXs/12LqWYjVw6qjrfF+VK+fyG33kChz3nnK+SU5/NeHOqrTEHS8sXO3OA==}
- engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
+ '@eslint/object-schema@3.0.3':
+ resolution: {integrity: sha512-iM869Pugn9Nsxbh/YHRqYiqd23AmIbxJOcpUMOuWCVNdoQJ5ZtwL6h3t0bcZzJUlC3Dq9jCFCESBZnX0GTv7iQ==}
+ engines: {node: ^20.19.0 || ^22.13.0 || >=24}
- '@eslint/plugin-kit@0.4.1':
- resolution: {integrity: sha512-43/qtrDUokr7LJqoF2c3+RInu/t4zfrpYdoSDfYyhg52rwLV6TnOvdG4fXm7IkSB3wErkcmJS9iEhjVtOSEjjA==}
- engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
+ '@eslint/plugin-kit@0.6.1':
+ resolution: {integrity: sha512-iH1B076HoAshH1mLpHMgwdGeTs0CYwL0SPMkGuSebZrwBp16v415e9NZXg2jtrqPVQjf6IANe2Vtlr5KswtcZQ==}
+ engines: {node: ^20.19.0 || ^22.13.0 || >=24}
'@exodus/bytes@1.14.1':
resolution: {integrity: sha512-OhkBFWI6GcRMUroChZiopRiSp2iAMvEBK47NhJooDqz1RERO4QuZIZnjP63TXX8GAiLABkYmX+fuQsdJ1dd2QQ==}
@@ -606,8 +615,11 @@ packages:
'@napi-rs/wasm-runtime@0.2.12':
resolution: {integrity: sha512-ZVWUcfwY4E/yPitQJl481FjFo3K22D6qF0DuFH6Y/nbnE11GY5uguDxZMGXPQ8WQ0128MXQD7TnfHyK4oWoIJQ==}
- '@napi-rs/wasm-runtime@1.1.1':
- resolution: {integrity: sha512-p64ah1M1ld8xjWv3qbvFwHiFVWrq1yFvV4f7w+mzaqiR4IlSgkqhcRdHwsGgomwzBH51sRY4NEowLxnaBjcW/A==}
+ '@napi-rs/wasm-runtime@1.1.2':
+ resolution: {integrity: sha512-sNXv5oLJ7ob93xkZ1XnxisYhGYXfaG9f65/ZgYuAu3qt7b3NadcOEhLvx28hv31PgX8SZJRYrAIPQilQmFpLVw==}
+ peerDependencies:
+ '@emnapi/core': ^1.7.1
+ '@emnapi/runtime': ^1.7.1
'@nodelib/fs.scandir@2.1.5':
resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==}
@@ -621,116 +633,249 @@ packages:
resolution: {integrity: sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==}
engines: {node: '>= 8'}
- '@oxc-resolver/binding-android-arm-eabi@11.19.0':
- resolution: {integrity: sha512-dlMjjWE3h+qMujLp5nBX/x7R5ny+xfr4YtsyaMNuM5JImOtQBzpFxQr9kJOKGL+9RbaoTOXpt5KF05f9pnOsgw==}
+ '@oxc-parser/binding-android-arm-eabi@0.121.0':
+ resolution: {integrity: sha512-n07FQcySwOlzap424/PLMtOkbS7xOu8nsJduKL8P3COGHKgKoDYXwoAHCbChfgFpHnviehrLWIPX0lKGtbEk/A==}
+ engines: {node: ^20.19.0 || >=22.12.0}
+ cpu: [arm]
+ os: [android]
+
+ '@oxc-parser/binding-android-arm64@0.121.0':
+ resolution: {integrity: sha512-/Dd1xIXboYAicw+twT2utxPD7bL8qh7d3ej0qvaYIMj3/EgIrGR+tSnjCUkiCT6g6uTC0neSS4JY8LxhdSU/sA==}
+ engines: {node: ^20.19.0 || >=22.12.0}
+ cpu: [arm64]
+ os: [android]
+
+ '@oxc-parser/binding-darwin-arm64@0.121.0':
+ resolution: {integrity: sha512-A0jNEvv7QMtCO1yk205t3DWU9sWUjQ2KNF0hSVO5W9R9r/R1BIvzG01UQAfmtC0dQm7sCrs5puixurKSfr2bRQ==}
+ engines: {node: ^20.19.0 || >=22.12.0}
+ cpu: [arm64]
+ os: [darwin]
+
+ '@oxc-parser/binding-darwin-x64@0.121.0':
+ resolution: {integrity: sha512-SsHzipdxTKUs3I9EOAPmnIimEeJOemqRlRDOp9LIj+96wtxZejF51gNibmoGq8KoqbT1ssAI5po/E3J+vEtXGA==}
+ engines: {node: ^20.19.0 || >=22.12.0}
+ cpu: [x64]
+ os: [darwin]
+
+ '@oxc-parser/binding-freebsd-x64@0.121.0':
+ resolution: {integrity: sha512-v1APOTkCp+RWOIDAHRoaeW/UoaHF15a60E8eUL6kUQXh+i4K7PBwq2Wi7jm8p0ymID5/m/oC1w3W31Z/+r7HQw==}
+ engines: {node: ^20.19.0 || >=22.12.0}
+ cpu: [x64]
+ os: [freebsd]
+
+ '@oxc-parser/binding-linux-arm-gnueabihf@0.121.0':
+ resolution: {integrity: sha512-PmqPQuqHZyFVWA4ycr0eu4VnTMmq9laOHZd+8R359w6kzuNZPvmmunmNJ8ybkm769A0nCoVp3TJ6dUz7B3FYIQ==}
+ engines: {node: ^20.19.0 || >=22.12.0}
+ cpu: [arm]
+ os: [linux]
+
+ '@oxc-parser/binding-linux-arm-musleabihf@0.121.0':
+ resolution: {integrity: sha512-vF24htj+MOH+Q7y9A8NuC6pUZu8t/C2Fr/kDOi2OcNf28oogr2xadBPXAbml802E8wRAVfbta6YLDQTearz+jw==}
+ engines: {node: ^20.19.0 || >=22.12.0}
+ cpu: [arm]
+ os: [linux]
+
+ '@oxc-parser/binding-linux-arm64-gnu@0.121.0':
+ resolution: {integrity: sha512-wjH8cIG2Lu/3d64iZpbYr73hREMgKAfu7fqpXjgM2S16y2zhTfDIp8EQjxO8vlDtKP5Rc7waZW72lh8nZtWrpA==}
+ engines: {node: ^20.19.0 || >=22.12.0}
+ cpu: [arm64]
+ os: [linux]
+ libc: [glibc]
+
+ '@oxc-parser/binding-linux-arm64-musl@0.121.0':
+ resolution: {integrity: sha512-qT663J/W8yQFw3dtscbEi9LKJevr20V7uWs2MPGTnvNZ3rm8anhhE16gXGpxDOHeg9raySaSHKhd4IGa3YZvuw==}
+ engines: {node: ^20.19.0 || >=22.12.0}
+ cpu: [arm64]
+ os: [linux]
+ libc: [musl]
+
+ '@oxc-parser/binding-linux-ppc64-gnu@0.121.0':
+ resolution: {integrity: sha512-mYNe4NhVvDBbPkAP8JaVS8lC1dsoJZWH5WCjpw5E+sjhk1R08wt3NnXYUzum7tIiWPfgQxbCMcoxgeemFASbRw==}
+ engines: {node: ^20.19.0 || >=22.12.0}
+ cpu: [ppc64]
+ os: [linux]
+ libc: [glibc]
+
+ '@oxc-parser/binding-linux-riscv64-gnu@0.121.0':
+ resolution: {integrity: sha512-+QiFoGxhAbaI/amqX567784cDyyuZIpinBrJNxUzb+/L2aBRX67mN6Jv40pqduHf15yYByI+K5gUEygCuv0z9w==}
+ engines: {node: ^20.19.0 || >=22.12.0}
+ cpu: [riscv64]
+ os: [linux]
+ libc: [glibc]
+
+ '@oxc-parser/binding-linux-riscv64-musl@0.121.0':
+ resolution: {integrity: sha512-9ykEgyTa5JD/Uhv2sttbKnCfl2PieUfOjyxJC/oDL2UO0qtXOtjPLl7H8Kaj5G7p3hIvFgu3YWvAxvE0sqY+hQ==}
+ engines: {node: ^20.19.0 || >=22.12.0}
+ cpu: [riscv64]
+ os: [linux]
+ libc: [musl]
+
+ '@oxc-parser/binding-linux-s390x-gnu@0.121.0':
+ resolution: {integrity: sha512-DB1EW5VHZdc1lIRjOI3bW/wV6R6y0xlfvdVrqj6kKi7Ayu2U3UqUBdq9KviVkcUGd5Oq+dROqvUEEFRXGAM7EQ==}
+ engines: {node: ^20.19.0 || >=22.12.0}
+ cpu: [s390x]
+ os: [linux]
+ libc: [glibc]
+
+ '@oxc-parser/binding-linux-x64-gnu@0.121.0':
+ resolution: {integrity: sha512-s4lfobX9p4kPTclvMiH3gcQUd88VlnkMTF6n2MTMDAyX5FPNRhhRSFZK05Ykhf8Zy5NibV4PbGR6DnK7FGNN6A==}
+ engines: {node: ^20.19.0 || >=22.12.0}
+ cpu: [x64]
+ os: [linux]
+ libc: [glibc]
+
+ '@oxc-parser/binding-linux-x64-musl@0.121.0':
+ resolution: {integrity: sha512-P9KlyTpuBuMi3NRGpJO8MicuGZfOoqZVRP1WjOecwx8yk4L/+mrCRNc5egSi0byhuReblBF2oVoDSMgV9Bj4Hw==}
+ engines: {node: ^20.19.0 || >=22.12.0}
+ cpu: [x64]
+ os: [linux]
+ libc: [musl]
+
+ '@oxc-parser/binding-openharmony-arm64@0.121.0':
+ resolution: {integrity: sha512-R+4jrWOfF2OAPPhj3Eb3U5CaKNAH9/btMveMULIrcNW/hjfysFQlF8wE0GaVBr81dWz8JLgQlsxwctoL78JwXw==}
+ engines: {node: ^20.19.0 || >=22.12.0}
+ cpu: [arm64]
+ os: [openharmony]
+
+ '@oxc-parser/binding-wasm32-wasi@0.121.0':
+ resolution: {integrity: sha512-5TFISkPTymKvsmIlKasPVTPuWxzCcrT8pM+p77+mtQbIZDd1UC8zww4CJcRI46kolmgrEX6QpKO8AvWMVZ+ifw==}
+ engines: {node: '>=14.0.0'}
+ cpu: [wasm32]
+
+ '@oxc-parser/binding-win32-arm64-msvc@0.121.0':
+ resolution: {integrity: sha512-V0pxh4mql4XTt3aiEtRNUeBAUFOw5jzZNxPABLaOKAWrVzSr9+XUaB095lY7jqMf5t8vkfh8NManGB28zanYKw==}
+ engines: {node: ^20.19.0 || >=22.12.0}
+ cpu: [arm64]
+ os: [win32]
+
+ '@oxc-parser/binding-win32-ia32-msvc@0.121.0':
+ resolution: {integrity: sha512-4Ob1qvYMPnlF2N9rdmKdkQFdrq16QVcQwBsO8yiPZXof0fHKFF+LmQV501XFbi7lHyrKm8rlJRfQ/M8bZZPVLw==}
+ engines: {node: ^20.19.0 || >=22.12.0}
+ cpu: [ia32]
+ os: [win32]
+
+ '@oxc-parser/binding-win32-x64-msvc@0.121.0':
+ resolution: {integrity: sha512-BOp1KCzdboB1tPqoCPXgntgFs0jjeSyOXHzgxVFR7B/qfr3F8r4YDacHkTOUNXtDgM8YwKnkf3rE5gwALYX7NA==}
+ engines: {node: ^20.19.0 || >=22.12.0}
+ cpu: [x64]
+ os: [win32]
+
+ '@oxc-project/types@0.121.0':
+ resolution: {integrity: sha512-CGtOARQb9tyv7ECgdAlFxi0Fv7lmzvmlm2rpD/RdijOO9rfk/JvB1CjT8EnoD+tjna/IYgKKw3IV7objRb+aYw==}
+
+ '@oxc-resolver/binding-android-arm-eabi@11.19.1':
+ resolution: {integrity: sha512-aUs47y+xyXHUKlbhqHUjBABjvycq6YSD7bpxSW7vplUmdzAlJ93yXY6ZR0c1o1x5A/QKbENCvs3+NlY8IpIVzg==}
cpu: [arm]
os: [android]
- '@oxc-resolver/binding-android-arm64@11.19.0':
- resolution: {integrity: sha512-x5P0Y12oMcSC9PKkz1FtdVVLosXYi/05m+ufxPrUggd6vZRBPJhW4zZUsMVbz8dwwk71Dh0f6/2ntw3WPOq+Ig==}
+ '@oxc-resolver/binding-android-arm64@11.19.1':
+ resolution: {integrity: sha512-oolbkRX+m7Pq2LNjr/kKgYeC7bRDMVTWPgxBGMjSpZi/+UskVo4jsMU3MLheZV55jL6c3rNelPl4oD60ggYmqA==}
cpu: [arm64]
os: [android]
- '@oxc-resolver/binding-darwin-arm64@11.19.0':
- resolution: {integrity: sha512-DjnuIPB60IQrVSCiuVBzN8/8AeeIjthdkk+dZYdZzgLeP2T5ZF41u50haJMtIdGr5cRzRH6zPV/gh6+RFjlvKA==}
+ '@oxc-resolver/binding-darwin-arm64@11.19.1':
+ resolution: {integrity: sha512-nUC6d2i3R5B12sUW4O646qD5cnMXf2oBGPLIIeaRfU9doJRORAbE2SGv4eW6rMqhD+G7nf2Y8TTJTLiiO3Q/dQ==}
cpu: [arm64]
os: [darwin]
- '@oxc-resolver/binding-darwin-x64@11.19.0':
- resolution: {integrity: sha512-dVAqIZIIY7xOXCCV0nJPs8ExlYc6R7mcNpFobwNyE3qlXGbgvwb7Gl3iOumOiPBfF+sbJR3MMP7RAPfKqbvYyA==}
+ '@oxc-resolver/binding-darwin-x64@11.19.1':
+ resolution: {integrity: sha512-cV50vE5+uAgNcFa3QY1JOeKDSkM/9ReIcc/9wn4TavhW/itkDGrXhw9jaKnkQnGbjJ198Yh5nbX/Gr2mr4Z5jQ==}
cpu: [x64]
os: [darwin]
- '@oxc-resolver/binding-freebsd-x64@11.19.0':
- resolution: {integrity: sha512-kwcZ30bIpJNFcT22sIlde4mz0EyXmB3lAefCFWtffqpbmLweQUwz1dKDcsutxEjpkbEKLmfrj1wCyRZp7n5Hnw==}
+ '@oxc-resolver/binding-freebsd-x64@11.19.1':
+ resolution: {integrity: sha512-xZOQiYGFxtk48PBKff+Zwoym7ScPAIVp4c14lfLxizO2LTTTJe5sx9vQNGrBymrf/vatSPNMD4FgsaaRigPkqw==}
cpu: [x64]
os: [freebsd]
- '@oxc-resolver/binding-linux-arm-gnueabihf@11.19.0':
- resolution: {integrity: sha512-GImk/cb3X+zBGEwr6l9h0dbiNo5zNd52gamZmluEpbyybiZ8kc5q44/7zRR4ILChWRW7pI92W57CJwhkF+wRmg==}
+ '@oxc-resolver/binding-linux-arm-gnueabihf@11.19.1':
+ resolution: {integrity: sha512-lXZYWAC6kaGe/ky2su94e9jN9t6M0/6c+GrSlCqL//XO1cxi5lpAhnJYdyrKfm0ZEr/c7RNyAx3P7FSBcBd5+A==}
cpu: [arm]
os: [linux]
- '@oxc-resolver/binding-linux-arm-musleabihf@11.19.0':
- resolution: {integrity: sha512-uIEyws3bBD1gif4SZCOV2XIr6q5fd1WbzzBbpL8qk+TbzOvKMWnMNNtfNacnAGGa2lLRNXR1Fffot2mlZ/Xmbw==}
+ '@oxc-resolver/binding-linux-arm-musleabihf@11.19.1':
+ resolution: {integrity: sha512-veG1kKsuK5+t2IsO9q0DErYVSw2azvCVvWHnfTOS73WE0STdLLB7Q1bB9WR+yHPQM76ASkFyRbogWo1GR1+WbQ==}
cpu: [arm]
os: [linux]
- '@oxc-resolver/binding-linux-arm64-gnu@11.19.0':
- resolution: {integrity: sha512-bIkgp+AB+yZfvdKDfjFT7PycsRtih7+zCV5AbnkzfyvNvQ47rfssf8R1IbG++mx+rZ4YUCUu8EbP66HC3O5c5w==}
+ '@oxc-resolver/binding-linux-arm64-gnu@11.19.1':
+ resolution: {integrity: sha512-heV2+jmXyYnUrpUXSPugqWDRpnsQcDm2AX4wzTuvgdlZfoNYO0O3W2AVpJYaDn9AG4JdM6Kxom8+foE7/BcSig==}
cpu: [arm64]
os: [linux]
libc: [glibc]
- '@oxc-resolver/binding-linux-arm64-musl@11.19.0':
- resolution: {integrity: sha512-bOt5pKPcbidTSy64m2CfM0XcaCmxBEFclCMPuOPO08hh8QIFTiZVhFf/OxTFqyRwhq/tlzzKmXpMo7DfzbO5lQ==}
+ '@oxc-resolver/binding-linux-arm64-musl@11.19.1':
+ resolution: {integrity: sha512-jvo2Pjs1c9KPxMuMPIeQsgu0mOJF9rEb3y3TdpsrqwxRM+AN6/nDDwv45n5ZrUnQMsdBy5gIabioMKnQfWo9ew==}
cpu: [arm64]
os: [linux]
libc: [musl]
- '@oxc-resolver/binding-linux-ppc64-gnu@11.19.0':
- resolution: {integrity: sha512-BymEPqVeLZzA/1kXow9U9rdniq1r5kk4u686Cx3ZU77YygR48NJI/2TyjM70vKHZffGx75ZShobcc1M5GXG3WA==}
+ '@oxc-resolver/binding-linux-ppc64-gnu@11.19.1':
+ resolution: {integrity: sha512-vLmdNxWCdN7Uo5suays6A/+ywBby2PWBBPXctWPg5V0+eVuzsJxgAn6MMB4mPlshskYbppjpN2Zg83ArHze9gQ==}
cpu: [ppc64]
os: [linux]
libc: [glibc]
- '@oxc-resolver/binding-linux-riscv64-gnu@11.19.0':
- resolution: {integrity: sha512-aFgPTzZZY+XCYe4B+3A1S63xcIh2i136+2TPXWr9NOwXXTdMdBntb1J9fEgxXDnX82MjBknLUpJqAZHNTJzixA==}
+ '@oxc-resolver/binding-linux-riscv64-gnu@11.19.1':
+ resolution: {integrity: sha512-/b+WgR+VTSBxzgOhDO7TlMXC1ufPIMR6Vj1zN+/x+MnyXGW7prTLzU9eW85Aj7Th7CCEG9ArCbTeqxCzFWdg2w==}
cpu: [riscv64]
os: [linux]
libc: [glibc]
- '@oxc-resolver/binding-linux-riscv64-musl@11.19.0':
- resolution: {integrity: sha512-9WDGt7fV9GK97WrWE/VEDhMFv9m0ZXYn5NQ+16QvyT0ux8yGLAvyadi6viaTjEdJII/OaHBRYHcL+zUjmaWwmg==}
+ '@oxc-resolver/binding-linux-riscv64-musl@11.19.1':
+ resolution: {integrity: sha512-YlRdeWb9j42p29ROh+h4eg/OQ3dTJlpHSa+84pUM9+p6i3djtPz1q55yLJhgW9XfDch7FN1pQ/Vd6YP+xfRIuw==}
cpu: [riscv64]
os: [linux]
libc: [musl]
- '@oxc-resolver/binding-linux-s390x-gnu@11.19.0':
- resolution: {integrity: sha512-SY3di6tccocppAVal5Hev3D6D1N5Y6TCEypAvNCOiPqku2Y8U/aXfvGbthqdPNa72KYqjUR1vomOv6J9thHITA==}
+ '@oxc-resolver/binding-linux-s390x-gnu@11.19.1':
+ resolution: {integrity: sha512-EDpafVOQWF8/MJynsjOGFThcqhRHy417sRyLfQmeiamJ8qVhSKAn2Dn2VVKUGCjVB9C46VGjhNo7nOPUi1x6uA==}
cpu: [s390x]
os: [linux]
libc: [glibc]
- '@oxc-resolver/binding-linux-x64-gnu@11.19.0':
- resolution: {integrity: sha512-SV+4zBeCC3xjSE2wvhN45eyABoVRX3xryWBABFKfLwAWhF3wsB3bUF+CantYfQ/TLpasyvplRS9ovvFT9cb/0A==}
+ '@oxc-resolver/binding-linux-x64-gnu@11.19.1':
+ resolution: {integrity: sha512-NxjZe+rqWhr+RT8/Ik+5ptA3oz7tUw361Wa5RWQXKnfqwSSHdHyrw6IdcTfYuml9dM856AlKWZIUXDmA9kkiBQ==}
cpu: [x64]
os: [linux]
libc: [glibc]
- '@oxc-resolver/binding-linux-x64-musl@11.19.0':
- resolution: {integrity: sha512-LkbjO+r5Isl8Xl29pJYOCB/iSUIULFUJDGdMp+yJD3OgWtSa6VJta2iw7QXmpcoOkq18UIL09yWrlyjLDL0Hug==}
+ '@oxc-resolver/binding-linux-x64-musl@11.19.1':
+ resolution: {integrity: sha512-cM/hQwsO3ReJg5kR+SpI69DMfvNCp+A/eVR4b4YClE5bVZwz8rh2Nh05InhwI5HR/9cArbEkzMjcKgTHS6UaNw==}
cpu: [x64]
os: [linux]
libc: [musl]
- '@oxc-resolver/binding-openharmony-arm64@11.19.0':
- resolution: {integrity: sha512-Ud1gelL5slpEU5AjzBWQz1WheprOAl5CPnCKTWynvvdlBbAZXA6fPYLuCrlRo0uw+x3f37XJ71kirpSew8Zyvg==}
+ '@oxc-resolver/binding-openharmony-arm64@11.19.1':
+ resolution: {integrity: sha512-QF080IowFB0+9Rh6RcD19bdgh49BpQHUW5TajG1qvWHvmrQznTZZjYlgE2ltLXyKY+qs4F/v5xuX1XS7Is+3qA==}
cpu: [arm64]
os: [openharmony]
- '@oxc-resolver/binding-wasm32-wasi@11.19.0':
- resolution: {integrity: sha512-wXLNAVmL4vWXKaYJnFPgg5zQsSr3Rv+ftNReIU3UkzTcoVLK0805Pnbr2NwcBWSO5hhpOEdys02qlT2kxVgjWw==}
+ '@oxc-resolver/binding-wasm32-wasi@11.19.1':
+ resolution: {integrity: sha512-w8UCKhX826cP/ZLokXDS6+milN8y4X7zidsAttEdWlVoamTNf6lhBJldaWr3ukTDiye7s4HRcuPEPOXNC432Vg==}
engines: {node: '>=14.0.0'}
cpu: [wasm32]
- '@oxc-resolver/binding-win32-arm64-msvc@11.19.0':
- resolution: {integrity: sha512-zszvr0dJfvv0Jg49hLwjAJ4SRzfsq28SoearUtT1qv3qXRYsBWuctdlRa/lEZkiuG4tZWiY425Jh9QqLafwsAg==}
+ '@oxc-resolver/binding-win32-arm64-msvc@11.19.1':
+ resolution: {integrity: sha512-nJ4AsUVZrVKwnU/QRdzPCCrO0TrabBqgJ8pJhXITdZGYOV28TIYystV1VFLbQ7DtAcaBHpocT5/ZJnF78YJPtQ==}
cpu: [arm64]
os: [win32]
- '@oxc-resolver/binding-win32-ia32-msvc@11.19.0':
- resolution: {integrity: sha512-I7ZYujr5XL1l7OwuddbOeqdUyFOaf51W1U2xUogInFdupIAKGqbpugpAK6RaccLcSlN0bbuo3CS5h7ue38SUAg==}
+ '@oxc-resolver/binding-win32-ia32-msvc@11.19.1':
+ resolution: {integrity: sha512-EW+ND5q2Tl+a3pH81l1QbfgbF3HmqgwLfDfVithRFheac8OTcnbXt/JxqD2GbDkb7xYEqy1zNaVFRr3oeG8npA==}
cpu: [ia32]
os: [win32]
- '@oxc-resolver/binding-win32-x64-msvc@11.19.0':
- resolution: {integrity: sha512-NxErbI1TmJEZZVvGPePjgXFZCuOzrjQuJ6YwHjcWkelReK7Uhg4QeL05zRdfTpgkH6IY/C8OjbKx5ZilQ4yDFg==}
+ '@oxc-resolver/binding-win32-x64-msvc@11.19.1':
+ resolution: {integrity: sha512-6hIU3RQu45B+VNTY4Ru8ppFwjVS/S5qwYyGhBotmjxfEKk41I2DlGtRfGJndZ5+6lneE2pwloqunlOyZuX/XAw==}
cpu: [x64]
os: [win32]
- '@playwright/test@1.58.2':
- resolution: {integrity: sha512-akea+6bHYBBfA9uQqSYmlJXn61cTa+jbO87xVLCWbTqbWadRVmhxlXATaOjOgcBaWU4ePo0wB41KMFv3o35IXA==}
+ '@package-json/types@0.0.12':
+ resolution: {integrity: sha512-uu43FGU34B5VM9mCNjXCwLaGHYjXdNincqKLaraaCW+7S2+SmiBg1Nv8bPnmschrIfZmfKNY9f3fC376MRrObw==}
+
+ '@playwright/test@1.59.1':
+ resolution: {integrity: sha512-PG6q63nQg5c9rIi4/Z5lR5IVF7yU5MqmKaPOe0HSc0O2cX1fPi96sUQu5j7eo4gKCkB2AnNGoWt7y4/Xx3Kcqg==}
engines: {node: '>=18'}
hasBin: true
@@ -748,24 +893,6 @@ packages:
'@rolldown/pluginutils@1.0.0-rc.2':
resolution: {integrity: sha512-izyXV/v+cHiRfozX62W9htOAvwMo4/bXKDrQ+vom1L1qRuexPock/7VZDAhnpHCLNejd3NJ6hiab+tO0D44Rgw==}
- '@rollup/plugin-inject@5.0.5':
- resolution: {integrity: sha512-2+DEJbNBoPROPkgTDNe8/1YXWcqxbN5DTjASVIOx8HS+pITXushyNiBV56RB08zuptzz8gT3YfkqriTBVycepg==}
- engines: {node: '>=14.0.0'}
- peerDependencies:
- rollup: ^1.20.0||^2.0.0||^3.0.0||^4.0.0
- peerDependenciesMeta:
- rollup:
- optional: true
-
- '@rollup/pluginutils@5.3.0':
- resolution: {integrity: sha512-5EdhGZtnu3V88ces7s53hhfK5KSASnJZv8Lulpc04cWO3REESroJXg73DFsOmgbU2BhwV0E20bu2IDZb3VKW4Q==}
- engines: {node: '>=14.0.0'}
- peerDependencies:
- rollup: ^1.20.0||^2.0.0||^3.0.0||^4.0.0
- peerDependenciesMeta:
- rollup:
- optional: true
-
'@rollup/rollup-android-arm-eabi@4.59.0':
resolution: {integrity: sha512-upnNBkA6ZH2VKGcBj9Fyl9IGNPULcjXRlg0LLeaioQWueH30p6IXtJEbKAgvyv+mJaMxSm1l6xwDXYjpEMiLMg==}
cpu: [arm]
@@ -908,10 +1035,6 @@ packages:
resolution: {integrity: sha512-TeheYy0ILzBEI/CO55CP6zJCSdSWeRtGnHy8U8dWSUH4I68iqTsy7HkMktR4xakThc9jotkPQUXT4ITdbV7cHA==}
engines: {node: '>=18'}
- '@sindresorhus/merge-streams@2.3.0':
- resolution: {integrity: sha512-LtoMMhxAlorcGhmFYI+LhPgbPZCkgP6ra1YL604EeF6U98pLlQ3iWIGMdWSC+vWmPBWBNgmDBAhnAobLROJmwg==}
- engines: {node: '>=18'}
-
'@standard-schema/spec@1.1.0':
resolution: {integrity: sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==}
@@ -1099,6 +1222,36 @@ packages:
resolution: {integrity: sha512-zIcONa+hVtVSSep9UT3jZ5rizo2BsxgyDYU7WFD5eICBE7no3881HGeb/QkGfsJs6JTkY1aQhT7rIPC7e+0nnA==}
engines: {node: '>=14', npm: '>=6', yarn: '>=1'}
+ '@turbo/darwin-64@2.9.3':
+ resolution: {integrity: sha512-P8foouaP+y/p+hhEGBoZpzMbpVvUMwPjDpcy6wN7EYfvvyISD1USuV27qWkczecihwuPJzQ1lDBuL8ERcavTyg==}
+ cpu: [x64]
+ os: [darwin]
+
+ '@turbo/darwin-arm64@2.9.3':
+ resolution: {integrity: sha512-SIzEkvtNdzdI50FJDaIQ6kQGqgSSdFPcdn0wqmmONN6iGKjy6hsT+EH99GP65FsfV7DLZTh2NmtTIRl2kdoz5Q==}
+ cpu: [arm64]
+ os: [darwin]
+
+ '@turbo/linux-64@2.9.3':
+ resolution: {integrity: sha512-pLRwFmcHHNBvsCySLS6OFabr/07kDT2pxEt/k6eBf/3asiVQZKJ7Rk88AafQx2aYA641qek4RsXvYO3JYpiBug==}
+ cpu: [x64]
+ os: [linux]
+
+ '@turbo/linux-arm64@2.9.3':
+ resolution: {integrity: sha512-gy6ApUroC2Nzv+qjGtE/uPNkhHAFU4c8God+zd5Aiv9L9uBgHlxVJpHT3XWl5xwlJZ2KWuMrlHTaS5kmNB+q1Q==}
+ cpu: [arm64]
+ os: [linux]
+
+ '@turbo/windows-64@2.9.3':
+ resolution: {integrity: sha512-d0YelTX6hAsB7kIEtGB3PzIzSfAg3yDoUlHwuwJc3adBXUsyUIs0YLG+1NNtuhcDOUGnWQeKUoJ2pGWvbpRj7w==}
+ cpu: [x64]
+ os: [win32]
+
+ '@turbo/windows-arm64@2.9.3':
+ resolution: {integrity: sha512-/08CwpKJl3oRY8nOlh2YgilZVJDHsr60XTNxRhuDeuFXONpUZ5X+Nv65izbG/xBew9qxcJFbDX9/sAmAX+ITcQ==}
+ cpu: [arm64]
+ os: [win32]
+
'@tybys/wasm-util@0.10.1':
resolution: {integrity: sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg==}
@@ -1111,17 +1264,20 @@ packages:
'@types/deep-eql@4.0.2':
resolution: {integrity: sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==}
+ '@types/esrecurse@4.3.1':
+ resolution: {integrity: sha512-xJBAbDifo5hpffDBuHl0Y8ywswbiAp/Wi7Y/GtAgSlZyIABppyurxVueOPE8LUQOxdlgi6Zqce7uoEpqNTeiUw==}
+
'@types/estree@1.0.8':
resolution: {integrity: sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==}
'@types/json-schema@7.0.15':
resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==}
- '@types/node@24.10.13':
- resolution: {integrity: sha512-oH72nZRfDv9lADUBSo104Aq7gPHpQZc4BTx38r9xf9pg5LfP6EzSyH2n7qFmmxRQXh7YlUXODcYsg6PuTDSxGg==}
+ '@types/node@24.12.0':
+ resolution: {integrity: sha512-GYDxsZi3ChgmckRT9HPU0WEhKLP08ev/Yfcq2AstjrDASOYCSXeyjDsHg4v5t4jOj7cyDX3vmprafKlWIG9MXQ==}
- '@types/node@25.2.3':
- resolution: {integrity: sha512-m0jEgYlYz+mDJZ2+F4v8D1AyQb+QzsNqRuI7xg1VQX/KlKS0qT9r1Mo16yo5F/MtifXFgaofIFsdFMox2SxIbQ==}
+ '@types/node@25.5.0':
+ resolution: {integrity: sha512-jp2P3tQMSxWugkCUKLRPVUpGaL5MVFwF8RDuSRztfwgN1wmqJeMSbKlnEtQqU8UrhTmzEmZdu2I6v2dpp7XIxw==}
'@types/prop-types@15.7.15':
resolution: {integrity: sha512-F6bEyamV9jKGAFBEmlQnesRPGOQqS2+Uwi0Em15xenOxHaf2hv6L8YCVn3rPdPJOiJfPiCnLIRyvwVaqMY3MIw==}
@@ -1143,63 +1299,63 @@ packages:
'@types/ws@8.18.1':
resolution: {integrity: sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg==}
- '@typescript-eslint/eslint-plugin@8.56.1':
- resolution: {integrity: sha512-Jz9ZztpB37dNC+HU2HI28Bs9QXpzCz+y/twHOwhyrIRdbuVDxSytJNDl6z/aAKlaRIwC7y8wJdkBv7FxYGgi0A==}
+ '@typescript-eslint/eslint-plugin@8.58.0':
+ resolution: {integrity: sha512-RLkVSiNuUP1C2ROIWfqX+YcUfLaSnxGE/8M+Y57lopVwg9VTYYfhuz15Yf1IzCKgZj6/rIbYTmJCUSqr76r0Wg==}
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
peerDependencies:
- '@typescript-eslint/parser': ^8.56.1
+ '@typescript-eslint/parser': ^8.58.0
eslint: ^8.57.0 || ^9.0.0 || ^10.0.0
- typescript: '>=4.8.4 <6.0.0'
+ typescript: '>=4.8.4 <6.1.0'
- '@typescript-eslint/parser@8.56.1':
- resolution: {integrity: sha512-klQbnPAAiGYFyI02+znpBRLyjL4/BrBd0nyWkdC0s/6xFLkXYQ8OoRrSkqacS1ddVxf/LDyODIKbQ5TgKAf/Fg==}
+ '@typescript-eslint/parser@8.58.0':
+ resolution: {integrity: sha512-rLoGZIf9afaRBYsPUMtvkDWykwXwUPL60HebR4JgTI8mxfFe2cQTu3AGitANp4b9B2QlVru6WzjgB2IzJKiCSA==}
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
peerDependencies:
eslint: ^8.57.0 || ^9.0.0 || ^10.0.0
- typescript: '>=4.8.4 <6.0.0'
+ typescript: '>=4.8.4 <6.1.0'
- '@typescript-eslint/project-service@8.56.1':
- resolution: {integrity: sha512-TAdqQTzHNNvlVFfR+hu2PDJrURiwKsUvxFn1M0h95BB8ah5jejas08jUWG4dBA68jDMI988IvtfdAI53JzEHOQ==}
+ '@typescript-eslint/project-service@8.58.0':
+ resolution: {integrity: sha512-8Q/wBPWLQP1j16NxoPNIKpDZFMaxl7yWIoqXWYeWO+Bbd2mjgvoF0dxP2jKZg5+x49rgKdf7Ck473M8PC3V9lg==}
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
peerDependencies:
- typescript: '>=4.8.4 <6.0.0'
+ typescript: '>=4.8.4 <6.1.0'
- '@typescript-eslint/scope-manager@8.56.1':
- resolution: {integrity: sha512-YAi4VDKcIZp0O4tz/haYKhmIDZFEUPOreKbfdAN3SzUDMcPhJ8QI99xQXqX+HoUVq8cs85eRKnD+rne2UAnj2w==}
+ '@typescript-eslint/scope-manager@8.58.0':
+ resolution: {integrity: sha512-W1Lur1oF50FxSnNdGp3Vs6P+yBRSmZiw4IIjEeYxd8UQJwhUF0gDgDD/W/Tgmh73mxgEU3qX0Bzdl/NGuSPEpQ==}
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
- '@typescript-eslint/tsconfig-utils@8.56.1':
- resolution: {integrity: sha512-qOtCYzKEeyr3aR9f28mPJqBty7+DBqsdd63eO0yyDwc6vgThj2UjWfJIcsFeSucYydqcuudMOprZ+x1SpF3ZuQ==}
+ '@typescript-eslint/tsconfig-utils@8.58.0':
+ resolution: {integrity: sha512-doNSZEVJsWEu4htiVC+PR6NpM+pa+a4ClH9INRWOWCUzMst/VA9c4gXq92F8GUD1rwhNvRLkgjfYtFXegXQF7A==}
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
peerDependencies:
- typescript: '>=4.8.4 <6.0.0'
+ typescript: '>=4.8.4 <6.1.0'
- '@typescript-eslint/type-utils@8.56.1':
- resolution: {integrity: sha512-yB/7dxi7MgTtGhZdaHCemf7PuwrHMenHjmzgUW1aJpO+bBU43OycnM3Wn+DdvDO/8zzA9HlhaJ0AUGuvri4oGg==}
+ '@typescript-eslint/type-utils@8.58.0':
+ resolution: {integrity: sha512-aGsCQImkDIqMyx1u4PrVlbi/krmDsQUs4zAcCV6M7yPcPev+RqVlndsJy9kJ8TLihW9TZ0kbDAzctpLn5o+lOg==}
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
peerDependencies:
eslint: ^8.57.0 || ^9.0.0 || ^10.0.0
- typescript: '>=4.8.4 <6.0.0'
+ typescript: '>=4.8.4 <6.1.0'
- '@typescript-eslint/types@8.56.1':
- resolution: {integrity: sha512-dbMkdIUkIkchgGDIv7KLUpa0Mda4IYjo4IAMJUZ+3xNoUXxMsk9YtKpTHSChRS85o+H9ftm51gsK1dZReY9CVw==}
+ '@typescript-eslint/types@8.58.0':
+ resolution: {integrity: sha512-O9CjxypDT89fbHxRfETNoAnHj/i6IpRK0CvbVN3qibxlLdo5p5hcLmUuCCrHMpxiWSwKyI8mCP7qRNYuOJ0Uww==}
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
- '@typescript-eslint/typescript-estree@8.56.1':
- resolution: {integrity: sha512-qzUL1qgalIvKWAf9C1HpvBjif+Vm6rcT5wZd4VoMb9+Km3iS3Cv9DY6dMRMDtPnwRAFyAi7YXJpTIEXLvdfPxg==}
+ '@typescript-eslint/typescript-estree@8.58.0':
+ resolution: {integrity: sha512-7vv5UWbHqew/dvs+D3e1RvLv1v2eeZ9txRHPnEEBUgSNLx5ghdzjHa0sgLWYVKssH+lYmV0JaWdoubo0ncGYLA==}
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
peerDependencies:
- typescript: '>=4.8.4 <6.0.0'
+ typescript: '>=4.8.4 <6.1.0'
- '@typescript-eslint/utils@8.56.1':
- resolution: {integrity: sha512-HPAVNIME3tABJ61siYlHzSWCGtOoeP2RTIaHXFMPqjrQKCGB9OgUVdiNgH7TJS2JNIQ5qQ4RsAUDuGaGme/KOA==}
+ '@typescript-eslint/utils@8.58.0':
+ resolution: {integrity: sha512-RfeSqcFeHMHlAWzt4TBjWOAtoW9lnsAGiP3GbaX9uVgTYYrMbVnGONEfUCiSss+xMHFl+eHZiipmA8WkQ7FuNA==}
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
peerDependencies:
eslint: ^8.57.0 || ^9.0.0 || ^10.0.0
- typescript: '>=4.8.4 <6.0.0'
+ typescript: '>=4.8.4 <6.1.0'
- '@typescript-eslint/visitor-keys@8.56.1':
- resolution: {integrity: sha512-KiROIzYdEV85YygXw6BI/Dx4fnBlFQu6Mq4QE4MOH9fFnhohw6wX/OAvDY2/C+ut0I3RSPKenvZJIVYqJNkhEw==}
+ '@typescript-eslint/visitor-keys@8.58.0':
+ resolution: {integrity: sha512-XJ9UD9+bbDo4a4epraTwG3TsNPeiB9aShrUneAVXy8q4LuwowN+qu89/6ByLMINqvIMeI9H9hOHQtg/ijrYXzQ==}
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
'@unrs/resolver-binding-android-arm-eabi@1.11.1':
@@ -1314,43 +1470,43 @@ packages:
peerDependencies:
vite: ^4 || ^5 || ^6 || ^7
- '@vitest/coverage-v8@4.0.18':
- resolution: {integrity: sha512-7i+N2i0+ME+2JFZhfuz7Tg/FqKtilHjGyGvoHYQ6iLV0zahbsJ9sljC9OcFcPDbhYKCet+sG8SsVqlyGvPflZg==}
+ '@vitest/coverage-v8@4.1.2':
+ resolution: {integrity: sha512-sPK//PHO+kAkScb8XITeB1bf7fsk85Km7+rt4eeuRR3VS1/crD47cmV5wicisJmjNdfeokTZwjMk4Mj2d58Mgg==}
peerDependencies:
- '@vitest/browser': 4.0.18
- vitest: 4.0.18
+ '@vitest/browser': 4.1.2
+ vitest: 4.1.2
peerDependenciesMeta:
'@vitest/browser':
optional: true
- '@vitest/expect@4.0.18':
- resolution: {integrity: sha512-8sCWUyckXXYvx4opfzVY03EOiYVxyNrHS5QxX3DAIi5dpJAAkyJezHCP77VMX4HKA2LDT/Jpfo8i2r5BE3GnQQ==}
+ '@vitest/expect@4.1.2':
+ resolution: {integrity: sha512-gbu+7B0YgUJ2nkdsRJrFFW6X7NTP44WlhiclHniUhxADQJH5Szt9mZ9hWnJPJ8YwOK5zUOSSlSvyzRf0u1DSBQ==}
- '@vitest/mocker@4.0.18':
- resolution: {integrity: sha512-HhVd0MDnzzsgevnOWCBj5Otnzobjy5wLBe4EdeeFGv8luMsGcYqDuFRMcttKWZA5vVO8RFjexVovXvAM4JoJDQ==}
+ '@vitest/mocker@4.1.2':
+ resolution: {integrity: sha512-Ize4iQtEALHDttPRCmN+FKqOl2vxTiNUhzobQFFt/BM1lRUTG7zRCLOykG/6Vo4E4hnUdfVLo5/eqKPukcWW7Q==}
peerDependencies:
msw: ^2.4.9
- vite: ^6.0.0 || ^7.0.0-0
+ vite: ^6.0.0 || ^7.0.0 || ^8.0.0
peerDependenciesMeta:
msw:
optional: true
vite:
optional: true
- '@vitest/pretty-format@4.0.18':
- resolution: {integrity: sha512-P24GK3GulZWC5tz87ux0m8OADrQIUVDPIjjj65vBXYG17ZeU3qD7r+MNZ1RNv4l8CGU2vtTRqixrOi9fYk/yKw==}
+ '@vitest/pretty-format@4.1.2':
+ resolution: {integrity: sha512-dwQga8aejqeuB+TvXCMzSQemvV9hNEtDDpgUKDzOmNQayl2OG241PSWeJwKRH3CiC+sESrmoFd49rfnq7T4RnA==}
- '@vitest/runner@4.0.18':
- resolution: {integrity: sha512-rpk9y12PGa22Jg6g5M3UVVnTS7+zycIGk9ZNGN+m6tZHKQb7jrP7/77WfZy13Y/EUDd52NDsLRQhYKtv7XfPQw==}
+ '@vitest/runner@4.1.2':
+ resolution: {integrity: sha512-Gr+FQan34CdiYAwpGJmQG8PgkyFVmARK8/xSijia3eTFgVfpcpztWLuP6FttGNfPLJhaZVP/euvujeNYar36OQ==}
- '@vitest/snapshot@4.0.18':
- resolution: {integrity: sha512-PCiV0rcl7jKQjbgYqjtakly6T1uwv/5BQ9SwBLekVg/EaYeQFPiXcgrC2Y7vDMA8dM1SUEAEV82kgSQIlXNMvA==}
+ '@vitest/snapshot@4.1.2':
+ resolution: {integrity: sha512-g7yfUmxYS4mNxk31qbOYsSt2F4m1E02LFqO53Xpzg3zKMhLAPZAjjfyl9e6z7HrW6LvUdTwAQR3HHfLjpko16A==}
- '@vitest/spy@4.0.18':
- resolution: {integrity: sha512-cbQt3PTSD7P2OARdVW3qWER5EGq7PHlvE+QfzSC0lbwO+xnt7+XH06ZzFjFRgzUX//JmpxrCu92VdwvEPlWSNw==}
+ '@vitest/spy@4.1.2':
+ resolution: {integrity: sha512-DU4fBnbVCJGNBwVA6xSToNXrkZNSiw59H8tcuUspVMsBDBST4nfvsPsEHDHGtWRRnqBERBQu7TrTKskmjqTXKA==}
- '@vitest/utils@4.0.18':
- resolution: {integrity: sha512-msMRKLMVLWygpK3u2Hybgi4MNjcYJvwTb0Ru09+fOyCXIgT5raYP041DRRdiJiI3k/2U6SEbAETB3YtBrUkCFA==}
+ '@vitest/utils@4.1.2':
+ resolution: {integrity: sha512-xw2/TiX82lQHA06cgbqRKFb5lCAy3axQ4H4SoUFhUsg+wztiet+co86IAMDtF6Vm1hc7J6j09oh/rgDn+JdKIQ==}
accepts@2.0.0:
resolution: {integrity: sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==}
@@ -1385,10 +1541,6 @@ packages:
resolution: {integrity: sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==}
engines: {node: '>=12'}
- ansi-styles@4.3.0:
- resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==}
- engines: {node: '>=8'}
-
ansi-styles@5.2.0:
resolution: {integrity: sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==}
engines: {node: '>=10'}
@@ -1439,18 +1591,12 @@ packages:
resolution: {integrity: sha512-BNoCY6SXXPQ7gF2opIP4GBE+Xw7U+pHMYKuzjgCN3GwiaIR09UUeKfheyIry77QtrCBlC0KK0q5/TER/tYh3PQ==}
engines: {node: '>= 0.4'}
- asn1.js@4.10.1:
- resolution: {integrity: sha512-p32cOF5q0Zqs9uBiONKYLm6BClCoBCM5O9JfeUSlnQLBTxYdTK+pW+nXflm8UkKd2UYlEbYz5qEi0JuZR9ckSw==}
-
- assert@2.1.0:
- resolution: {integrity: sha512-eLHpSK/Y4nhMJ07gDaAzoX/XAKS8PSaojml3M0DM4JpV1LAi5JOJ/p6H/XWrl8L+DzVEvVCW1z3vWAaB9oTsQw==}
-
assertion-error@2.0.1:
resolution: {integrity: sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==}
engines: {node: '>=12'}
- ast-v8-to-istanbul@0.3.11:
- resolution: {integrity: sha512-Qya9fkoofMjCBNVdWINMjB5KZvkYfaO9/anwkWnjxibpWUxo5iHl2sOdP7/uAqaRuUYuoo8rDwnbaaKVFxoUvw==}
+ ast-v8-to-istanbul@1.0.0:
+ resolution: {integrity: sha512-1fSfIwuDICFA4LKkCzRPO7F0hzFf0B7+Xqrl27ynQaa+Rh0e1Es0v6kWHPott3lU10AyAr7oKHa65OppjLn3Rg==}
async-function@1.0.0:
resolution: {integrity: sha512-hsU18Ae8CDTR6Kgu9DYf0EbCr/a5iGL0rytQDobUcdpYOKokk8LEjVphnXkDkgpi0wYVsqrXuP0bZxJaTqdgoA==}
@@ -1484,81 +1630,37 @@ packages:
resolution: {integrity: sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==}
engines: {node: 18 || 20 || >=22}
- base64-js@1.5.1:
- resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==}
-
- baseline-browser-mapping@2.10.0:
- resolution: {integrity: sha512-lIyg0szRfYbiy67j9KN8IyeD7q7hcmqnJ1ddWmNt19ItGpNN64mnllmxUNFIOdOm6by97jlL6wfpTTJrmnjWAA==}
+ baseline-browser-mapping@2.10.13:
+ resolution: {integrity: sha512-BL2sTuHOdy0YT1lYieUxTw/QMtPBC3pmlJC6xk8BBYVv6vcw3SGdKemQ+Xsx9ik2F/lYDO9tqsFQH1r9PFuHKw==}
engines: {node: '>=6.0.0'}
hasBin: true
bidi-js@1.0.3:
resolution: {integrity: sha512-RKshQI1R3YQ+n9YJz2QQ147P66ELpa1FQEg20Dk8oW9t2KgLbpDLLp9aGZ7y8WHSshDknG0bknqGw5/tyCs5tw==}
- bn.js@4.12.3:
- resolution: {integrity: sha512-fGTi3gxV/23FTYdAoUtLYp6qySe2KE3teyZitipKNRuVYcBkoP/bB3guXN/XVKUe9mxCHXnc9C4ocyz8OmgN0g==}
-
- bn.js@5.2.3:
- resolution: {integrity: sha512-EAcmnPkxpntVL+DS7bO1zhcZNvCkxqtkd0ZY53h06GNQ3DEkkGZ/gKgmDv6DdZQGj9BgfSPKtJJ7Dp1GPP8f7w==}
-
body-parser@2.2.2:
resolution: {integrity: sha512-oP5VkATKlNwcgvxi0vM0p/D3n2C3EReYVX+DNYs5TjZFn/oQt2j+4sVJtSMr18pdRr8wjTcBl6LoV+FUwzPmNA==}
engines: {node: '>=18'}
- brace-expansion@1.1.12:
- resolution: {integrity: sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==}
+ brace-expansion@1.1.13:
+ resolution: {integrity: sha512-9ZLprWS6EENmhEOpjCYW2c8VkmOvckIJZfkr7rBW6dObmfgJ/L1GpSYW5Hpo9lDz4D1+n0Ckz8rU7FwHDQiG/w==}
- brace-expansion@5.0.3:
- resolution: {integrity: sha512-fy6KJm2RawA5RcHkLa1z/ScpBeA762UF9KmZQxwIbDtRJrgLzM10depAiEQ+CXYcoiqW1/m96OAAoke2nE9EeA==}
+ brace-expansion@5.0.5:
+ resolution: {integrity: sha512-VZznLgtwhn+Mact9tfiwx64fA9erHH/MCXEUfB/0bX/6Fz6ny5EGTXYltMocqg4xFAQZtnO3DHWWXi8RiuN7cQ==}
engines: {node: 18 || 20 || >=22}
braces@3.0.3:
resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==}
engines: {node: '>=8'}
- brorand@1.1.0:
- resolution: {integrity: sha512-cKV8tMCEpQs4hK/ik71d6LrPOnpkpGBR0wzxqr68g2m/LB2GxVYQroAjMJZRVM1Y4BCjCKc3vAamxSzOY2RP+w==}
-
- browser-resolve@2.0.0:
- resolution: {integrity: sha512-7sWsQlYL2rGLy2IWm8WL8DCTJvYLc/qlOnsakDac87SOoCd16WLsaAMdCiAqsTNHIe+SXfaqyxyo6THoWqs8WQ==}
-
- browserify-aes@1.2.0:
- resolution: {integrity: sha512-+7CHXqGuspUn/Sl5aO7Ea0xWGAtETPXNSAjHo48JfLdPWcMng33Xe4znFvQweqc/uzk5zSOI3H52CYnjCfb5hA==}
-
- browserify-cipher@1.0.1:
- resolution: {integrity: sha512-sPhkz0ARKbf4rRQt2hTpAHqn47X3llLkUGn+xEJzLjwY8LRs2p0v7ljvI5EyoRO/mexrNunNECisZs+gw2zz1w==}
-
- browserify-des@1.0.2:
- resolution: {integrity: sha512-BioO1xf3hFwz4kc6iBhI3ieDFompMhrMlnDFC4/0/vd5MokpuAc3R+LYbwTA9A5Yc9pq9UYPqffKpW2ObuwX5A==}
-
- browserify-rsa@4.1.1:
- resolution: {integrity: sha512-YBjSAiTqM04ZVei6sXighu679a3SqWORA3qZTEqZImnlkDIFtKc6pNutpjyZ8RJTjQtuYfeetkxM11GwoYXMIQ==}
- engines: {node: '>= 0.10'}
-
- browserify-sign@4.2.5:
- resolution: {integrity: sha512-C2AUdAJg6rlM2W5QMp2Q4KGQMVBwR1lIimTsUnutJ8bMpW5B52pGpR2gEnNBNwijumDo5FojQ0L9JrXA8m4YEw==}
- engines: {node: '>= 0.10'}
-
- browserify-zlib@0.2.0:
- resolution: {integrity: sha512-Z942RysHXmJrhqk88FmKBVq/v5tqmSkDz7p54G/MGyjMnCFFnC79XWNbg+Vta8W6Wb2qtSZTSxIGkJrRpCFEiA==}
-
- browserslist@4.28.1:
- resolution: {integrity: sha512-ZC5Bd0LgJXgwGqUknZY/vkUQ04r8NXnJZ3yYi4vDmSiZmC/pdSN0NbNRPxZpbtO4uAfDUAFffO8IZoM3Gj8IkA==}
+ browserslist@4.28.2:
+ resolution: {integrity: sha512-48xSriZYYg+8qXna9kwqjIVzuQxi+KYWp2+5nCYnYKPTr0LvD89Jqk2Or5ogxz0NUMfIjhh2lIUX/LyX9B4oIg==}
engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7}
hasBin: true
buffer-from@1.1.2:
resolution: {integrity: sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==}
- buffer-xor@1.0.3:
- resolution: {integrity: sha512-571s0T7nZWK6vB67HI5dyUF7wXiNcfaPPPTl6zYCNApANjIvYJTg7hlud/+cJpdAhS7dVzqMLmfhfHR3rAcOjQ==}
-
- buffer@5.7.1:
- resolution: {integrity: sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==}
-
- builtin-status-codes@3.0.0:
- resolution: {integrity: sha512-HpGFw18DgFWlncDfjTa2rcQ4W88O1mC8e8yZ2AvQY5KDaktSTwo+KRf6nHK6FRI5FyRyb/5T6+TSxfP7QyGsmQ==}
-
bytes@3.1.2:
resolution: {integrity: sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==}
engines: {node: '>= 0.8'}
@@ -1578,44 +1680,25 @@ packages:
resolution: {integrity: sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==}
engines: {node: '>= 0.4'}
- callsites@3.1.0:
- resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==}
- engines: {node: '>=6'}
-
- caniuse-lite@1.0.30001774:
- resolution: {integrity: sha512-DDdwPGz99nmIEv216hKSgLD+D4ikHQHjBC/seF98N9CPqRX4M5mSxT9eTV6oyisnJcuzxtZy4n17yKKQYmYQOA==}
+ caniuse-lite@1.0.30001784:
+ resolution: {integrity: sha512-WU346nBTklUV9YfUl60fqRbU5ZqyXlqvo1SgigE1OAXK5bFL8LL9q1K7aap3N739l4BvNqnkm3YrGHiY9sfUQw==}
chai@6.2.2:
resolution: {integrity: sha512-NUPRluOfOiTKBKvWPtSD4PhFvWCqOi0BGStNWs57X9js7XGTprSmFoz5F0tWhR4WPjNeR9jXqdC7/UpSJTnlRg==}
engines: {node: '>=18'}
- chalk@4.1.2:
- resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==}
- engines: {node: '>=10'}
-
- cipher-base@1.0.7:
- resolution: {integrity: sha512-Mz9QMT5fJe7bKI7MH31UilT5cEK5EHHRCccw/YRFsRY47AuNgaV6HY3rscp0/I4Q+tTW/5zoqpSeRRI54TkDWA==}
- engines: {node: '>= 0.10'}
-
cli-cursor@5.0.0:
resolution: {integrity: sha512-aCj4O5wKyszjMmDT4tZj93kxyydN/K5zPWSCe6/0AV/AA1pqe5ZBIw0a2ZfPQV7lL5/yb5HsUreJ6UFAF1tEQw==}
engines: {node: '>=18'}
- cli-truncate@5.1.1:
- resolution: {integrity: sha512-SroPvNHxUnk+vIW/dOSfNqdy1sPEFkrTk6TUtqLCnBlo3N7TNYYkzzN7uSD6+jVjrdO4+p8nH7JzH6cIvUem6A==}
+ cli-truncate@5.2.0:
+ resolution: {integrity: sha512-xRwvIOMGrfOAnM1JYtqQImuaNtDEv9v6oIYAs4LIHwTiKee8uwvIi363igssOC0O5U04i4AlENs79LQLu9tEMw==}
engines: {node: '>=20'}
clsx@2.1.1:
resolution: {integrity: sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==}
engines: {node: '>=6'}
- color-convert@2.0.1:
- resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==}
- engines: {node: '>=7.0.0'}
-
- color-name@1.1.4:
- resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==}
-
colorette@2.0.20:
resolution: {integrity: sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==}
@@ -1630,19 +1713,13 @@ packages:
commander@2.20.3:
resolution: {integrity: sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==}
- comment-parser@1.4.5:
- resolution: {integrity: sha512-aRDkn3uyIlCFfk5NUA+VdwMmMsh8JGhc4hapfV4yxymHGQ3BVskMQfoXGpCo5IoBuQ9tS5iiVKhCpTcB4pW4qw==}
+ comment-parser@1.4.6:
+ resolution: {integrity: sha512-ObxuY6vnbWTN6Od72xfwN9DbzC7Y2vv8u1Soi9ahRKL37gb6y1qk6/dgjs+3JWuXJHWvsg3BXIwzd/rkmAwavg==}
engines: {node: '>= 12.0.0'}
concat-map@0.0.1:
resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==}
- console-browserify@1.2.0:
- resolution: {integrity: sha512-ZMkYO/LkF17QvCPqM0gxw8yUzigAOZOSWSHg91FH6orS7vcEj5dVZTidN2fQ14yBSdg97RqhSNwLUXInd52OTA==}
-
- constants-browserify@1.0.0:
- resolution: {integrity: sha512-xFxOwqIzR/e1k1gLiWEophSCMqXcwVHIH7akf7b/vxcUeGunlj3hvZaaqxwHsTgn+IndtkQJgSztIDWeumWJDQ==}
-
content-disposition@1.0.1:
resolution: {integrity: sha512-oIXISMynqSqm241k6kcQ5UwttDILMK4BiurCfGEREw6+X9jkkpEe5T9FZaApyLGGOnFuyMWZpdolTXMtvEJ08Q==}
engines: {node: '>=18'}
@@ -1662,29 +1739,10 @@ packages:
resolution: {integrity: sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==}
engines: {node: '>= 0.6'}
- core-util-is@1.0.3:
- resolution: {integrity: sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==}
-
- create-ecdh@4.0.4:
- resolution: {integrity: sha512-mf+TCx8wWc9VpuxfP2ht0iSISLZnt0JgWlrOKZiNqyUZWnjIaCIVNQArMHnCZKfEYRg6IM7A+NeJoN8gf/Ws0A==}
-
- create-hash@1.2.0:
- resolution: {integrity: sha512-z00bCGNHDG8mHAkP7CtT1qVu+bFQUPjYq/4Iv3C3kWjTFV10zIjfSoeqXo9Asws8gwSHDGj/hl2u4OGIjapeCg==}
-
- create-hmac@1.1.7:
- resolution: {integrity: sha512-MJG9liiZ+ogc4TzUwuvbER1JRdgvUFSB5+VR/g5h82fGaIRWMWddtKBHi7/sVhfjQZ6SehlyhvQYrcYkaUIpLg==}
-
- create-require@1.1.1:
- resolution: {integrity: sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==}
-
cross-spawn@7.0.6:
resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==}
engines: {node: '>= 8'}
- crypto-browserify@3.12.1:
- resolution: {integrity: sha512-r4ESw/IlusD17lgQi1O20Fa3qNnsckR126TdUuBgAu7GBYSIPvdNyONd3Zrxh0xCwA4+6w/TDArBPsMvhur+KQ==}
- engines: {node: '>= 0.10'}
-
css-tree@3.1.0:
resolution: {integrity: sha512-0eW44TGN5SQXU1mWSkKwFstI/22X2bG1nYzZTYMAWjylYURhse752YgbE4Cx46AC+bAvI+/dYTPRk1LqSUnu6w==}
engines: {node: ^10 || ^12.20.0 || ^14.13.0 || >=15.0.0}
@@ -1753,16 +1811,10 @@ packages:
resolution: {integrity: sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==}
engines: {node: '>=6'}
- des.js@1.1.0:
- resolution: {integrity: sha512-r17GxjhUCjSRy8aiJpr8/UadFIzMzJGexI3Nmz4ADi9LYSFx4gTBp80+NaX/YsXWWLhpZ7v/v/ubEc/bCNfKwg==}
-
detect-libc@2.1.2:
resolution: {integrity: sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==}
engines: {node: '>=8'}
- diffie-hellman@5.0.3:
- resolution: {integrity: sha512-kqag/Nl+f3GwyK25fhUMYj81BUOrZ9IuJsjIcDE5icNM9FJHAVm3VcUDxdLPoQtTuUylWm6ZIknYJwwaPxsUzg==}
-
doctrine@2.1.0:
resolution: {integrity: sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==}
engines: {node: '>=0.10.0'}
@@ -1773,10 +1825,6 @@ packages:
dom-accessibility-api@0.6.3:
resolution: {integrity: sha512-7ZgogeTnjuHbo+ct10G9Ffp0mif17idi0IyWNVA/wcwcm7NPOD/WEHVP3n7n3MhXqxoIYm8d6MuZohYWIZ4T3w==}
- domain-browser@4.22.0:
- resolution: {integrity: sha512-IGBwjF7tNk3cwypFNH/7bfzBcgSCbaMOD3GsaY1AU/JRrnHnYgEM0+9kQt52iZxjNsjBtJYtao146V+f8jFZNw==}
- engines: {node: '>=10'}
-
dunder-proto@1.0.1:
resolution: {integrity: sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==}
engines: {node: '>= 0.4'}
@@ -1784,11 +1832,8 @@ packages:
ee-first@1.1.1:
resolution: {integrity: sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==}
- electron-to-chromium@1.5.302:
- resolution: {integrity: sha512-sM6HAN2LyK82IyPBpznDRqlTQAtuSaO+ShzFiWTvoMJLHyZ+Y39r8VMfHzwbU8MVBzQ4Wdn85+wlZl2TLGIlwg==}
-
- elliptic@6.6.1:
- resolution: {integrity: sha512-RaddvvMatK2LJHqFJ+YA4WysVN5Ita9E35botqIYspQ4TkRAlCicdzKOjlyv/1Za5RyTNn7di//eEV0uTAfe3g==}
+ electron-to-chromium@1.5.331:
+ resolution: {integrity: sha512-IbxXrsTlD3hRodkLnbxAPP4OuJYdWCeM3IOdT+CpcMoIwIoDfCmRpEtSPfwBXxVkg9xmBeY7Lz2Eo2TDn/HC3Q==}
emoji-name-map@2.0.3:
resolution: {integrity: sha512-3KBuQuhYkRtLd9utBKfTtclbWP3IytC1FNcXg+NKARltPSYpkg/MLiklGv4vLwl8A8jMQjdneXNBYx8k0rrg+g==}
@@ -1824,12 +1869,12 @@ packages:
resolution: {integrity: sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==}
engines: {node: '>= 0.4'}
- es-iterator-helpers@1.2.2:
- resolution: {integrity: sha512-BrUQ0cPTB/IwXj23HtwHjS9n7O4h9FX94b4xc5zlTHxeLgTAdzYUDyy6KdExAl9lbN5rtfe44xpjpmj9grxs5w==}
+ es-iterator-helpers@1.3.1:
+ resolution: {integrity: sha512-zWwRvqWiuBPr0muUG/78cW3aHROFCNIQ3zpmYDpwdbnt2m+xlNyRWpHBpa2lJjSBit7BQ+RXA1iwbSmu5yJ/EQ==}
engines: {node: '>= 0.4'}
- es-module-lexer@1.7.0:
- resolution: {integrity: sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==}
+ es-module-lexer@2.0.0:
+ resolution: {integrity: sha512-5POEcUuZybH7IdmGsD8wlf0AI55wMecM9rVBTI/qEAy2c1kTOm3DjFYjrBdI2K3BaJjJYfYFeRtM0t9ssnRuxw==}
es-object-atoms@1.1.1:
resolution: {integrity: sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==}
@@ -1863,10 +1908,6 @@ packages:
resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==}
engines: {node: '>=10'}
- escape-string-regexp@5.0.0:
- resolution: {integrity: sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==}
- engines: {node: '>=12'}
-
eslint-import-context@0.1.9:
resolution: {integrity: sha512-K9Hb+yRaGAGUbwjhFNHvSmmkZs9+zbuoe3kFQ4V1wYjrepUFYM2dZAfNtjbbj3qsPfUfsA68Bx/ICWQMi+C8Eg==}
engines: {node: ^12.20.0 || ^14.18.0 || >=16.0.0}
@@ -1889,12 +1930,12 @@ packages:
eslint-plugin-import-x:
optional: true
- eslint-plugin-import-x@4.16.1:
- resolution: {integrity: sha512-vPZZsiOKaBAIATpFE2uMI4w5IRwdv/FpQ+qZZMR4E+PeOcM4OeoEbqxRMnywdxP19TyB/3h6QBB0EWon7letSQ==}
+ eslint-plugin-import-x@4.16.2:
+ resolution: {integrity: sha512-rM9K8UBHcWKpzQzStn1YRN2T5NvdeIfSVoKu/lKF41znQXHAUcBbYXe5wd6GNjZjTrP7viQ49n1D83x/2gYgIw==}
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
peerDependencies:
- '@typescript-eslint/utils': ^8.0.0
- eslint: ^8.57.0 || ^9.0.0
+ '@typescript-eslint/utils': ^8.56.0
+ eslint: ^8.57.0 || ^9.0.0 || ^10.0.0
eslint-import-resolver-node: '*'
peerDependenciesMeta:
'@typescript-eslint/utils':
@@ -1902,8 +1943,8 @@ packages:
eslint-import-resolver-node:
optional: true
- eslint-plugin-jsdoc@62.7.1:
- resolution: {integrity: sha512-4Zvx99Q7d1uggYBUX/AIjvoyqXhluGbbKrRmG8SQTLprPFg6fa293tVJH1o1GQwNe3lUydd8ZHzn37OaSncgSQ==}
+ eslint-plugin-jsdoc@62.9.0:
+ resolution: {integrity: sha512-PY7/X4jrVgoIDncUmITlUqK546Ltmx/Pd4Hdsu4CvSjryQZJI2mEV4vrdMufyTetMiZ5taNSqvK//BTgVUlNkA==}
engines: {node: ^20.19.0 || ^22.13.0 || >=24}
peerDependencies:
eslint: ^7.0.0 || ^8.0.0 || ^9.0.0 || ^10.0.0
@@ -1920,25 +1961,21 @@ packages:
peerDependencies:
eslint: ^3 || ^4 || ^5 || ^6 || ^7 || ^8 || ^9.7
- eslint-scope@8.4.0:
- resolution: {integrity: sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg==}
- engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
+ eslint-scope@9.1.2:
+ resolution: {integrity: sha512-xS90H51cKw0jltxmvmHy2Iai1LIqrfbw57b79w/J7MfvDfkIkFZ+kj6zC3BjtUwh150HsSSdxXZcsuv72miDFQ==}
+ engines: {node: ^20.19.0 || ^22.13.0 || >=24}
eslint-visitor-keys@3.4.3:
resolution: {integrity: sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==}
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
- eslint-visitor-keys@4.2.1:
- resolution: {integrity: sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==}
- engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
-
eslint-visitor-keys@5.0.1:
resolution: {integrity: sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA==}
engines: {node: ^20.19.0 || ^22.13.0 || >=24}
- eslint@9.39.2:
- resolution: {integrity: sha512-LEyamqS7W5HB3ujJyvi0HQK/dtVINZvd5mAAp9eT5S/ujByGjiZLCzPcHVzuXbpJDJF/cxwHlfceVUDZ2lnSTw==}
- engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
+ eslint@10.1.0:
+ resolution: {integrity: sha512-S9jlY/ELKEUwwQnqWDO+f+m6sercqOPSqXM5Go94l7DOmxHVDgmSFGWEzeE/gwgTAr0W103BWt0QLe/7mabIvA==}
+ engines: {node: ^20.19.0 || ^22.13.0 || >=24}
hasBin: true
peerDependencies:
jiti: '*'
@@ -1946,12 +1983,8 @@ packages:
jiti:
optional: true
- espree@10.4.0:
- resolution: {integrity: sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==}
- engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
-
- espree@11.1.1:
- resolution: {integrity: sha512-AVHPqQoZYc+RUM4/3Ly5udlZY/U4LS8pIG05jEjWM2lQMU/oaZ7qshzAl2YP1tfNmXfftH3ohurfwNAug+MnsQ==}
+ espree@11.2.0:
+ resolution: {integrity: sha512-7p3DrVEIopW1B1avAGLuCSh1jubc01H2JHc8B4qqGblmg5gI9yumBgACjWo4JlIc04ufug4xJ3SQI8HkS/Rgzw==}
engines: {node: ^20.19.0 || ^22.13.0 || >=24}
esquery@1.7.0:
@@ -1966,9 +1999,6 @@ packages:
resolution: {integrity: sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==}
engines: {node: '>=4.0'}
- estree-walker@2.0.2:
- resolution: {integrity: sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==}
-
estree-walker@3.0.3:
resolution: {integrity: sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==}
@@ -1983,13 +2013,6 @@ packages:
eventemitter3@5.0.4:
resolution: {integrity: sha512-mlsTRyGaPBjPedk6Bvw+aqbsXDtoAyAzm5MO7JgU+yVRyMQ5O8bD4Kcci7BS85f93veegeCPkL8R4GLClnjLFw==}
- events@3.3.0:
- resolution: {integrity: sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==}
- engines: {node: '>=0.8.x'}
-
- evp_bytestokey@1.0.3:
- resolution: {integrity: sha512-/f2Go4TognH/KvCISP7OUsHn85hT9nUkxxA9BEWxFn+Oj9o8ZNLm/40hdlgSLyuOimsrTKLUMEorQexp/aPQeA==}
-
expect-type@1.3.0:
resolution: {integrity: sha512-knvyeauYhqjOYvQ66MznSMs83wmHrCycNEN6Ao+2AeYEfxUIkuiVxdEa1qlGEPK+We3n0THiDciYSsCcgW/DoA==}
engines: {node: '>=12.0.0'}
@@ -2049,8 +2072,8 @@ packages:
resolution: {integrity: sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==}
engines: {node: '>=16'}
- flatted@3.3.3:
- resolution: {integrity: sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==}
+ flatted@3.4.2:
+ resolution: {integrity: sha512-PjDse7RzhcPkIJwy5t7KPWQSZ9cAbzQXcafsetQoD7sOJRQlGikNbx7yZp2OotDnJyrDcbyRq3Ttb18iYOqkxA==}
follow-redirects@1.15.11:
resolution: {integrity: sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FO/0gfIES8C/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQ==}
@@ -2126,8 +2149,8 @@ packages:
resolution: {integrity: sha512-w9UMqWwJxHNOvoNzSJ2oPF5wvYcvP7jUvYzhp67yEhTi17ZDBBC1z9pTdGuzjD+EFIqLSYRweZjqfiPzQ06Ebg==}
engines: {node: '>= 0.4'}
- get-tsconfig@4.13.6:
- resolution: {integrity: sha512-shZT/QMiSHc/YBLxxOkMtgSid5HFoauqCE3/exfsEcwg1WkeqjG+V40yBbBrsD+jW2HDXcs28xOfcbm2jI8Ddw==}
+ get-tsconfig@4.13.7:
+ resolution: {integrity: sha512-7tN6rFgBlMgpBML5j8typ92BKFi2sFQvIdpAqLA2beia5avZDrMs0FLZiM5etShWq5irVyGcGMEA1jcDaK7A/Q==}
github-username-regex@1.0.0:
resolution: {integrity: sha512-EqDVkN0/5MQyDPOSDLInVRRXdeISRfcN1UW/1FUqD2knV1HHw8DndMB3UPNn5lO51DvRnjzbLXwWqNNV86PLOw==}
@@ -2140,22 +2163,14 @@ packages:
resolution: {integrity: sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==}
engines: {node: '>=10.13.0'}
- globals@14.0.0:
- resolution: {integrity: sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==}
- engines: {node: '>=18'}
-
- globals@17.3.0:
- resolution: {integrity: sha512-yMqGUQVVCkD4tqjOJf3TnrvaaHDMYp4VlUSObbkIiuCPe/ofdMBFIAcBbCSRFWOnos6qRiTVStDwqPLUclaxIw==}
+ globals@17.4.0:
+ resolution: {integrity: sha512-hjrNztw/VajQwOLsMNT1cbJiH2muO3OROCHnbehc8eY5JyD2gqz4AcMHPqgaOR59DjgUjYAYLeH699g/eWi2jw==}
engines: {node: '>=18'}
globalthis@1.0.4:
resolution: {integrity: sha512-DpLKbNU4WylpxJykQujfCcwYWiV/Jhm50Goo0wrVILAv5jOr9d+H+UR3PhSCD2rCCEIg0uc+G+muBTwD54JhDQ==}
engines: {node: '>= 0.4'}
- globby@14.1.0:
- resolution: {integrity: sha512-0Ia46fDOaT7k4og1PDW4YbodWWr3scS2vAr2lTbsplOt2WkKp0vQbkI9wKis/T5LV/dqPjO3bpS/z6GTJB82LA==}
- engines: {node: '>=18'}
-
gopd@1.2.0:
resolution: {integrity: sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==}
engines: {node: '>= 0.4'}
@@ -2190,17 +2205,6 @@ packages:
resolution: {integrity: sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==}
engines: {node: '>= 0.4'}
- hash-base@3.0.5:
- resolution: {integrity: sha512-vXm0l45VbcHEVlTCzs8M+s0VeYsB2lnlAaThoLKGXr3bE/VWDOelNUnycUPEhKEaXARL2TEFjBOyUiM6+55KBg==}
- engines: {node: '>= 0.10'}
-
- hash-base@3.1.2:
- resolution: {integrity: sha512-Bb33KbowVTIj5s7Ked1OsqHUeCpz//tPwR+E2zJgJKo9Z5XolZ9b6bdUgjmYlwnWhoOQKoTd1TYToZGn5mAYOg==}
- engines: {node: '>= 0.8'}
-
- hash.js@1.1.7:
- resolution: {integrity: sha512-taOaskGt4z4SOANNseOviYDvjEJinIkRgmp7LbKP2YTTmVxWBl87s/uzK9r+44BclBSp2X7K1hqeNfz9JbBeXA==}
-
hasown@2.0.2:
resolution: {integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==}
engines: {node: '>= 0.4'}
@@ -2211,9 +2215,6 @@ packages:
hermes-parser@0.25.1:
resolution: {integrity: sha512-6pEjquH3rqaI6cYAXYPcz9MS4rY6R4ngRgrgfDshRptUZIc3lw0MCIJIGDj9++mfySOuPTHB4nrSW99BCvOPIA==}
- hmac-drbg@1.0.1:
- resolution: {integrity: sha512-Tti3gMqLdZfhOQY1Mzf/AanLiqh1WTiJgEj26ZuYQ9fbkLomzGchCws4FyrSd4VkpBfiNhaE1On+lOz894jvXg==}
-
html-encoding-sniffer@6.0.0:
resolution: {integrity: sha512-CV9TW3Y3f8/wT0BRFc1/KAVQ3TUHiXmaAb6VW9vtiMFf7SLoMd1PdAc4W3KFOFETBJUb90KatHqlsZMWV+R9Gg==}
engines: {node: ^20.19.0 || ^22.12.0 || >=24.0.0}
@@ -2235,9 +2236,6 @@ packages:
http-vary@1.0.3:
resolution: {integrity: sha512-sx7Y8YTqF3o0mFJJvF66n8dbaE8v3liV1RgCz46XP5xK7dnzyZHvwMWRA115q5kjbCPBV65/nOMlgW54WLyiag==}
- https-browserify@1.0.0:
- resolution: {integrity: sha512-J+FkSdyD+0mA0N+81tMotaRMfSL9SGi+xpD3T6YApKsc3bGSXJlfXri3VyFOeYkfLRQisDk1W+jIFFKBeUBbBg==}
-
https-proxy-agent@7.0.6:
resolution: {integrity: sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==}
engines: {node: '>= 14'}
@@ -2251,9 +2249,6 @@ packages:
resolution: {integrity: sha512-im9DjEDQ55s9fL4EYzOAv0yMqmMBSZp6G0VvFyTMPKWxiSBHUj9NW/qqLmXUwXrrM7AvqSlTCfvqRb0cM8yYqw==}
engines: {node: '>=0.10.0'}
- ieee754@1.2.1:
- resolution: {integrity: sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==}
-
ignore@5.3.2:
resolution: {integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==}
engines: {node: '>= 4'}
@@ -2265,10 +2260,6 @@ packages:
immer@11.1.4:
resolution: {integrity: sha512-XREFCPo6ksxVzP4E0ekD5aMdf8WMwmdNaz6vuvxgI40UaEiu6q3p8X52aU6GdyvLY3XXX/8R7JOTXStz/nBbRw==}
- import-fresh@3.3.1:
- resolution: {integrity: sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==}
- engines: {node: '>=6'}
-
imurmurhash@0.1.4:
resolution: {integrity: sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==}
engines: {node: '>=0.8.19'}
@@ -2288,10 +2279,6 @@ packages:
resolution: {integrity: sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==}
engines: {node: '>= 0.10'}
- is-arguments@1.2.0:
- resolution: {integrity: sha512-7bVbi0huj/wrIAOzb8U1aszg9kdi3KN/CyU19CTI7tAoZYEZoL9yCDXpbXN+uPsuWnP02cyug1gleqq+TU+YCA==}
- engines: {node: '>= 0.4'}
-
is-array-buffer@3.0.5:
resolution: {integrity: sha512-DDfANUiiG2wC1qawP66qlTugJeL5HyzMpfr8lLK+jMQirGzNod0B12cFB/9q838Ru27sBwfw78/rdoU7RERz6A==}
engines: {node: '>= 0.4'}
@@ -2355,10 +2342,6 @@ packages:
resolution: {integrity: sha512-1Qed0/Hr2m+YqxnM09CjA2d/i6YZNfF6R2oRAOj36eUdS6qIV/huPJNSEpKbupewFs+ZsJlxsjjPbc0/afW6Lw==}
engines: {node: '>= 0.4'}
- is-nan@1.3.2:
- resolution: {integrity: sha512-E+zBKpQ2t6MEo1VsonYmluk9NxGrbzpeeLC2xIViuO2EjU2xsXsBPwTr3Ykv9l08UYEVEdWeRZNouaZqF6RN0w==}
- engines: {node: '>= 0.4'}
-
is-negative-zero@2.0.3:
resolution: {integrity: sha512-5KoIu2Ngpyek75jXodFvnafB6DJgr3u8uuK0LEZJjrU19DrMD3EVERaR8sjz8CCGgpZvxPl9SuE1GMVPFHx1mw==}
engines: {node: '>= 0.4'}
@@ -2413,19 +2396,12 @@ packages:
resolution: {integrity: sha512-mfcwb6IzQyOKTs84CQMrOwW4gQcaTOAWJ0zzJCl2WSPDrWk/OzDaImWFH3djXhb24g4eudZfLRozAvPGw4d9hQ==}
engines: {node: '>= 0.4'}
- isarray@1.0.0:
- resolution: {integrity: sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==}
-
isarray@2.0.5:
resolution: {integrity: sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==}
isexe@2.0.0:
resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==}
- isomorphic-timers-promises@1.0.1:
- resolution: {integrity: sha512-u4sej9B1LPSxTGKB/HiuzvEQnXH0ECYkSVQU39koSwmFAxhlEAFl9RdTvLv4TOTQUgBS5O3O5fwUxk6byBZ+IQ==}
- engines: {node: '>=10'}
-
istanbul-lib-coverage@3.2.2:
resolution: {integrity: sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==}
engines: {node: '>=8'}
@@ -2456,8 +2432,8 @@ packages:
resolution: {integrity: sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==}
hasBin: true
- jsdoc-type-pratt-parser@7.1.1:
- resolution: {integrity: sha512-/2uqY7x6bsrpi3i9LVU6J89352C0rpMk0as8trXxCtvd4kPk1ke/Eyif6wqfSLvoNJqcDG9Vk4UsXgygzCt2xA==}
+ jsdoc-type-pratt-parser@7.2.0:
+ resolution: {integrity: sha512-dh140MMgjyg3JhJZY/+iEzW+NO5xR2gpbDFKHqotCmexElVntw7GjWjt511+C/Ef02RU5TKYrJo/Xlzk+OLaTw==}
engines: {node: '>=20.0.0'}
jsdom@28.1.0:
@@ -2495,13 +2471,10 @@ packages:
keyv@4.5.4:
resolution: {integrity: sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==}
- knip@5.85.0:
- resolution: {integrity: sha512-V2kyON+DZiYdNNdY6GALseiNCwX7dYdpz9Pv85AUn69Gk0UKCts+glOKWfe5KmaMByRjM9q17Mzj/KinTVOyxg==}
- engines: {node: '>=18.18.0'}
+ knip@6.2.0:
+ resolution: {integrity: sha512-4OMUMJARvNble8e8TeFv12flp4fKzAITrQec1eKO4g2eA4HnNqEa8CXy2UOPLjuYuAETpe0N0r25jF9yY9FLig==}
+ engines: {node: ^20.19.0 || >=22.12.0}
hasBin: true
- peerDependencies:
- '@types/node': '>=18'
- typescript: '>=5.0.4 <7'
levn@0.4.1:
resolution: {integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==}
@@ -2581,8 +2554,8 @@ packages:
resolution: {integrity: sha512-l51N2r93WmGUye3WuFoN5k10zyvrVs0qfKBhyC5ogUQ6Ew6JUSswh78mbSO+IU3nTWsyOArqPCcShdQSadghBQ==}
engines: {node: '>= 12.0.0'}
- lint-staged@16.2.7:
- resolution: {integrity: sha512-lDIj4RnYmK7/kXMya+qJsmkRFkGolciXjrsZ6PC25GdTfWOAWetR0ZbsNXRAj1EHHImRSalc+whZFg56F5DVow==}
+ lint-staged@16.4.0:
+ resolution: {integrity: sha512-lBWt8hujh/Cjysw5GYVmZpFHXDCgZzhrOm8vbcUdobADZNOK/bRshr2kM3DfgrrtR1DQhfupW9gnIXOfiFi+bw==}
engines: {node: '>=20.17'}
hasBin: true
@@ -2594,9 +2567,6 @@ packages:
resolution: {integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==}
engines: {node: '>=10'}
- lodash.merge@4.6.2:
- resolution: {integrity: sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==}
-
log-update@6.1.0:
resolution: {integrity: sha512-9ie8ItPR6tjY5uYJh8K/Zrv/RMZ5VOlOWvtZdEHYSTFKZfIBPQa9tOAEeAWhd+AnIneLJ22w5fjOYtoutpWq5w==}
engines: {node: '>=18'}
@@ -2630,9 +2600,6 @@ packages:
resolution: {integrity: sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==}
engines: {node: '>= 0.4'}
- md5.js@1.3.5:
- resolution: {integrity: sha512-xitP+WxNPcTTOgnTJcrhM0xvdPepipPSf3I8EIpGKeFLjt3PlJLIDG3u8EX53ZIubkb+5U2+3rELYpEhHhzdkg==}
-
mdn-data@2.12.2:
resolution: {integrity: sha512-IEn+pegP1aManZuckezWCO+XZQDplx1366JoVhTpMpBB1sPey/SbveZQUosKiKiGYjg1wH4pMlNgXbCiYgihQA==}
@@ -2652,10 +2619,6 @@ packages:
resolution: {integrity: sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==}
engines: {node: '>=8.6'}
- miller-rabin@4.0.1:
- resolution: {integrity: sha512-115fLhvZVqWwHPbClyntxEVfVDfl9DLLTuJvq3g2O/Oxi8AiNouAHvDSzHS0viUJc+V5vm3eq91Xwqn9dp4jRA==}
- hasBin: true
-
mime-db@1.52.0:
resolution: {integrity: sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==}
engines: {node: '>= 0.6'}
@@ -2680,18 +2643,12 @@ packages:
resolution: {integrity: sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==}
engines: {node: '>=4'}
- minimalistic-assert@1.0.1:
- resolution: {integrity: sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==}
-
- minimalistic-crypto-utils@1.0.1:
- resolution: {integrity: sha512-JIYlbt6g8i5jKfJ3xz7rF0LXmv2TkDxBLUkiBeZ7bAx4GnnNMr8xFpGnOxn6GhTEHx3SjRrZEoU+j04prX1ktg==}
-
- minimatch@10.2.3:
- resolution: {integrity: sha512-Rwi3pnapEqirPSbWbrZaa6N3nmqq4Xer/2XooiOKyV3q12ML06f7MOuc5DVH8ONZIFhwIYQ3yzPH4nt7iWHaTg==}
+ minimatch@10.2.5:
+ resolution: {integrity: sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg==}
engines: {node: 18 || 20 || >=22}
- minimatch@3.1.4:
- resolution: {integrity: sha512-twmL+S8+7yIsE9wsqgzU3E8/LumN3M3QELrBZ20OdmQ9jB2JvW5oZtBEmft84k/Gs5CG9mqtWc6Y9vW+JEzGxw==}
+ minimatch@3.1.5:
+ resolution: {integrity: sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==}
minimist@1.2.8:
resolution: {integrity: sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==}
@@ -2699,10 +2656,6 @@ packages:
ms@2.1.3:
resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==}
- nano-spawn@2.0.0:
- resolution: {integrity: sha512-tacvGzUY5o2D8CBh2rrwxyNojUsZNU2zjNTzKQrkgGJQTbGAfArVWXSKMBokBeeg6C7OLRGUEyoFlYbfeWQIqw==}
- engines: {node: '>=20.17'}
-
nanoid@3.3.11:
resolution: {integrity: sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==}
engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1}
@@ -2724,12 +2677,8 @@ packages:
resolution: {integrity: sha512-pyFS63ptit/P5WqUkt+UUfe+4oevH+bFeIiPPdfb0pFeYEu/1ELnJu5l+5EcTKYL5M7zaAa7S8ddywgXypqKCw==}
engines: {node: '>= 0.4'}
- node-releases@2.0.27:
- resolution: {integrity: sha512-nmh3lCkYZ3grZvqcCH+fjmQ7X+H0OeZgP40OierEaAptX4XofMh5kwNbWh7lBduUzCcV/8kZ+NDLCwm2iorIlA==}
-
- node-stdlib-browser@1.3.1:
- resolution: {integrity: sha512-X75ZN8DCLftGM5iKwoYLA3rjnrAEs97MkzvSd4q2746Tgpg8b8XWiBGiBG4ZpgcAqBgtgPHTiAc8ZMCvZuikDw==}
- engines: {node: '>=10'}
+ node-releases@2.0.37:
+ resolution: {integrity: sha512-1h5gKZCF+pO/o3Iqt5Jp7wc9rH3eJJ0+nh/CIoiRwjRxde/hAHyLPXYN4V3CqKAbiZPSeJFSWHmJsbkicta0Eg==}
object-assign@4.1.1:
resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==}
@@ -2745,10 +2694,6 @@ packages:
resolution: {integrity: sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==}
engines: {node: '>= 0.4'}
- object-is@1.1.6:
- resolution: {integrity: sha512-F8cZ+KfGlSGi09lJT7/Nd6KJZ9ygtvYC0/UYYLI9nmQKLMnydpB9yvbv9K1uSkEu7FU9vYPmVwLg328tX+ot3Q==}
- engines: {node: '>= 0.4'}
-
object-keys@1.1.1:
resolution: {integrity: sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==}
engines: {node: '>= 0.4'}
@@ -2787,15 +2732,16 @@ packages:
resolution: {integrity: sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==}
engines: {node: '>= 0.8.0'}
- os-browserify@0.3.0:
- resolution: {integrity: sha512-gjcpUc3clBf9+210TRaDWbf+rZZZEshZ+DlXMRCeAjp0xhTrnQsKHypIy1J3d5hKdUzj69t708EHtU8P6bUn0A==}
-
own-keys@1.0.1:
resolution: {integrity: sha512-qFOyK5PjiWZd+QQIh+1jhdb9LpxTF0qs7Pm8o5QHYZ0M3vKqSqzsZaEB6oWlxZ+q2sJBMI/Ktgd2N5ZwQoRHfg==}
engines: {node: '>= 0.4'}
- oxc-resolver@11.19.0:
- resolution: {integrity: sha512-oEe42WEoZc2T5sCQqgaRBx8huzP4cJvrnm+BfNTJESdtM633Tqs6iowkpsMTXgnb7SLwU6N6D9bqwW/PULjo6A==}
+ oxc-parser@0.121.0:
+ resolution: {integrity: sha512-ek9o58+SCv6AV7nchiAcUJy1DNE2CC5WRdBcO0mF+W4oRjNQfPO7b3pLjTHSFECpHkKGOZSQxx3hk8viIL5YCg==}
+ engines: {node: ^20.19.0 || >=22.12.0}
+
+ oxc-resolver@11.19.1:
+ resolution: {integrity: sha512-qE/CIg/spwrTBFt5aKmwe3ifeDdLfA2NESN30E42X/lII5ClF8V7Wt6WIJhcGZjp0/Q+nQ+9vgxGk//xZNX2hg==}
p-limit@3.1.0:
resolution: {integrity: sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==}
@@ -2805,17 +2751,6 @@ packages:
resolution: {integrity: sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==}
engines: {node: '>=10'}
- pako@1.0.11:
- resolution: {integrity: sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==}
-
- parent-module@1.0.1:
- resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==}
- engines: {node: '>=6'}
-
- parse-asn1@5.1.9:
- resolution: {integrity: sha512-fIYNuZ/HastSb80baGOuPRo1O9cf4baWw5WsAp7dBuUzeTD/BoaG8sVTdlPFksBE2lF21dN+A1AnrpIjSWqHHg==}
- engines: {node: '>= 0.10'}
-
parse-imports-exports@0.2.4:
resolution: {integrity: sha512-4s6vd6dx1AotCx/RCI2m7t7GCh5bDRUtGNvRfHSP2wbBQdMi67pPe7mtzmgwcaQ8VKK/6IB7Glfyu3qdZJPybQ==}
@@ -2829,9 +2764,6 @@ packages:
resolution: {integrity: sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==}
engines: {node: '>= 0.8'}
- path-browserify@1.0.1:
- resolution: {integrity: sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g==}
-
path-exists@4.0.0:
resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==}
engines: {node: '>=8'}
@@ -2846,17 +2778,9 @@ packages:
path-to-regexp@8.3.0:
resolution: {integrity: sha512-7jdwVIRtsP8MYpdXSwOS0YdD0Du+qOoF/AEPIt88PcCFrZCzx41oxku1jD88hZBwbNUIEfpqvuhjFaMAqMTWnA==}
- path-type@6.0.0:
- resolution: {integrity: sha512-Vj7sf++t5pBD637NSfkxpHSMfWaeig5+DKWLhcqIYx6mWQz5hdJTGDVMQiJcw1ZYkhs7AazKDGpRVji1LJCZUQ==}
- engines: {node: '>=18'}
-
pathe@2.0.3:
resolution: {integrity: sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==}
- pbkdf2@3.1.5:
- resolution: {integrity: sha512-Q3CG/cYvCO1ye4QKkuH7EXxs3VC/rI1/trd+qX2+PolbaKG0H+bgcZzrTt96mMyRtejk+JMCiLUn3y29W8qmFQ==}
- engines: {node: '>= 0.10'}
-
pg-cloudflare@1.3.0:
resolution: {integrity: sha512-6lswVVSztmHiRtD6I8hw4qP/nDm1EJbKMRhf3HCYaqud7frGysPv7FYJ5noZQdhQtN2xJnimfMtvQq21pdbzyQ==}
@@ -2894,30 +2818,21 @@ packages:
picocolors@1.1.1:
resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==}
- picomatch@2.3.1:
- resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==}
+ picomatch@2.3.2:
+ resolution: {integrity: sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==}
engines: {node: '>=8.6'}
- picomatch@4.0.3:
- resolution: {integrity: sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==}
+ picomatch@4.0.4:
+ resolution: {integrity: sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==}
engines: {node: '>=12'}
- pidtree@0.6.0:
- resolution: {integrity: sha512-eG2dWTVw5bzqGRztnHExczNxt5VGsE6OwTeCG3fdUf9KBsZzO3R5OIIIzWR+iZA0NtZ+RDVdaoE2dK1cn6jH4g==}
- engines: {node: '>=0.10'}
- hasBin: true
-
- pkg-dir@5.0.0:
- resolution: {integrity: sha512-NPE8TDbzl/3YQYY7CSS228s3g2ollTFnc+Qi3tqmqJp9Vg2ovUpixcJEo2HJScN2Ez+kEaal6y70c0ehqJBJeA==}
- engines: {node: '>=10'}
-
- playwright-core@1.58.2:
- resolution: {integrity: sha512-yZkEtftgwS8CsfYo7nm0KE8jsvm6i/PTgVtB8DL726wNf6H2IMsDuxCpJj59KDaxCtSnrWan2AeDqM7JBaultg==}
+ playwright-core@1.59.1:
+ resolution: {integrity: sha512-HBV/RJg81z5BiiZ9yPzIiClYV/QMsDCKUyogwH9p3MCP6IYjUFu/MActgYAvK0oWyV9NlwM3GLBjADyWgydVyg==}
engines: {node: '>=18'}
hasBin: true
- playwright@1.58.2:
- resolution: {integrity: sha512-vA30H8Nvkq/cPBnNw4Q8TWz1EJyqgpuinBcHET0YVJVFldr8JDNiU9LaWAE1KqSkRYazuaBhTpB5ZzShOezQ6A==}
+ playwright@1.59.1:
+ resolution: {integrity: sha512-C8oWjPR3F81yljW9o5OxcWzfh6avkVwDD2VYdwIGqTkl+OGFISgypqzfu7dOe4QNLL2aqcWBmI3PMtLIK233lw==}
engines: {node: '>=18'}
hasBin: true
@@ -2958,13 +2873,6 @@ packages:
resolution: {integrity: sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ==}
engines: {node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0}
- process-nextick-args@2.0.1:
- resolution: {integrity: sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==}
-
- process@0.11.10:
- resolution: {integrity: sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==}
- engines: {node: '>= 0.6.0'}
-
prop-types@15.8.1:
resolution: {integrity: sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==}
@@ -2975,12 +2883,6 @@ packages:
proxy-from-env@1.1.0:
resolution: {integrity: sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==}
- public-encrypt@4.0.3:
- resolution: {integrity: sha512-zVpa8oKZSz5bTMTFClc1fQOnyyEzpl5ozpi1B5YcvBrdohMjH2rfsBtyXcuNuwjsDIXmBYlF2N5FlJYhR29t8Q==}
-
- punycode@1.4.1:
- resolution: {integrity: sha512-jmYNElW7yvO7TV33CjSmvSiE2yco3bV2czu/OzDKdMNVZQWfxCblURLhf+47syQRBntjfLdd/H0egrzIG+oaFQ==}
-
punycode@2.3.1:
resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==}
engines: {node: '>=6'}
@@ -2989,19 +2891,9 @@ packages:
resolution: {integrity: sha512-mAZTtNCeetKMH+pSjrb76NAM8V9a05I9aBZOHztWy/UqcJdQYNsf59vrRKWnojAT9Y+GbIvoTBC++CPHqpDBhQ==}
engines: {node: '>=0.6'}
- querystring-es3@0.2.1:
- resolution: {integrity: sha512-773xhDQnZBMFobEiztv8LIl70ch5MSF/jUQVlhwFyBILqq96anmoctVIYz+ZRp0qbCKATTn6ev02M3r7Ga5vqA==}
- engines: {node: '>=0.4.x'}
-
queue-microtask@1.2.3:
resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==}
- randombytes@2.1.0:
- resolution: {integrity: sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==}
-
- randomfill@1.0.4:
- resolution: {integrity: sha512-87lcbR8+MhcWcUiQ+9e+Rwx8MyR2P7qnt15ynUlbm3TU/fjbgz4GsvfSUDTemtCCtVCqb4ZcEFlyPNTh9bBTLw==}
-
range-parser@1.2.1:
resolution: {integrity: sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==}
engines: {node: '>= 0.6'}
@@ -3059,13 +2951,6 @@ packages:
resolution: {integrity: sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==}
engines: {node: '>=0.10.0'}
- readable-stream@2.3.8:
- resolution: {integrity: sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==}
-
- readable-stream@3.6.2:
- resolution: {integrity: sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==}
- engines: {node: '>= 6'}
-
redent@3.0.0:
resolution: {integrity: sha512-6tDA8g98We0zd0GvVeMT9arEOnTw9qM03L9cJXaCjrip1OO764RDBLBfrB4cwzNGDj5OA5ioymC9GkizgWJDUg==}
engines: {node: '>=8'}
@@ -3097,18 +2982,9 @@ packages:
resolution: {integrity: sha512-yE7KUfFvaBFzGPs5H3Ops1RevfUEsDc5Iz65rOwWg4lE8HJSYtle77uul3+573457oHvBKuHYDl/xqUkKpEEdw==}
engines: {node: '>=18'}
- resolve-from@4.0.0:
- resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==}
- engines: {node: '>=4'}
-
resolve-pkg-maps@1.0.0:
resolution: {integrity: sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==}
- resolve@1.22.11:
- resolution: {integrity: sha512-RfqAvLnMl313r7c9oclB1HhUEAezcpLjz95wFH4LVuhk9JF/r22qmVP9AMmOU4vMX7Q8pN8jwNg/CSpdFnMjTQ==}
- engines: {node: '>= 0.4'}
- hasBin: true
-
resolve@2.0.0-next.6:
resolution: {integrity: sha512-3JmVl5hMGtJ3kMmB3zi3DL25KfkCEyy3Tw7Gmw7z5w8M9WlwoPFnIvwChzu1+cF3iaK3sp18hhPz8ANeimdJfA==}
engines: {node: '>= 0.4'}
@@ -3125,10 +3001,6 @@ packages:
rfdc@1.4.1:
resolution: {integrity: sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA==}
- ripemd160@2.0.3:
- resolution: {integrity: sha512-5Di9UC0+8h1L6ZD2d7awM7E/T4uA1fJRlx6zk/NvdCCVEoAnFqvHmCuNeIKoCeIixBX/q8uM+6ycDvF8woqosA==}
- engines: {node: '>= 0.8'}
-
rollup@4.59.0:
resolution: {integrity: sha512-2oMpl67a3zCH9H79LeMcbDhXW/UmWG/y2zuqnF2jQq5uq9TbM9TVyXvA4+t+ne2IIkBdrLpAaRQAvo7YI/Yyeg==}
engines: {node: '>=18.0.0', npm: '>=8.0.0'}
@@ -3145,12 +3017,6 @@ packages:
resolution: {integrity: sha512-AURm5f0jYEOydBj7VQlVvDrjeFgthDdEF5H1dP+6mNpoXOMo1quQqJ4wvJDyRZ9+pO3kGWoOdmV08cSv2aJV6Q==}
engines: {node: '>=0.4'}
- safe-buffer@5.1.2:
- resolution: {integrity: sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==}
-
- safe-buffer@5.2.1:
- resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==}
-
safe-push-apply@1.0.0:
resolution: {integrity: sha512-iKE9w/Z7xCzUMIZqdBsp6pEQvwuEebH4vdpjcDWnyzaI6yl6O9FHvVpmGelvEHNsoY6wGblkxR6Zty/h00WiSA==}
engines: {node: '>= 0.4'}
@@ -3201,17 +3067,9 @@ packages:
resolution: {integrity: sha512-RJRdvCo6IAnPdsvP/7m6bsQqNnn1FCBX5ZNtFL98MmFF/4xAIJTIg1YbHW5DC2W5SKZanrC6i4HsJqlajw/dZw==}
engines: {node: '>= 0.4'}
- setimmediate@1.0.5:
- resolution: {integrity: sha512-MATJdZp8sLqDl/68LfQmbP8zKPLQNV6BIZoIgrscFDQ+RsvK/BxeDQOgyxKKoh0y/8h3BqVFnCqQ/gd+reiIXA==}
-
setprototypeof@1.2.0:
resolution: {integrity: sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==}
- sha.js@2.4.12:
- resolution: {integrity: sha512-8LzC5+bvI45BjpfXU8V5fdU2mfeKiQe1D1gIMn7XUlF3OTUrpdJpPPH4EMAnF0DsHHdSZqCdSss5qCmJKuiO3w==}
- engines: {node: '>= 0.10'}
- hasBin: true
-
shebang-command@2.0.0:
resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==}
engines: {node: '>=8'}
@@ -3243,16 +3101,16 @@ packages:
resolution: {integrity: sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==}
engines: {node: '>=14'}
- slash@5.1.0:
- resolution: {integrity: sha512-ZA6oR3T/pEyuqwMgAKT0/hAv8oAXckzbkmR0UkUosQ+Mc4RxGoJkRmwHgHufaenlyAgE1Mxgpdcrf75y6XcnDg==}
- engines: {node: '>=14.16'}
-
slice-ansi@7.1.2:
resolution: {integrity: sha512-iOBWFgUX7caIZiuutICxVgX1SdxwAVFFKwt1EvMYYec/NWO5meOJ6K5uQxhrYBdQJne4KxiqZc+KptFOWFSI9w==}
engines: {node: '>=18'}
- smol-toml@1.6.0:
- resolution: {integrity: sha512-4zemZi0HvTnYwLfrpk/CF9LOd9Lt87kAt50GnqhMpyF9U3poDAP2+iukq2bZsO/ufegbYehBkqINbsWxj4l4cw==}
+ slice-ansi@8.0.0:
+ resolution: {integrity: sha512-stxByr12oeeOyY2BlviTNQlYV5xOj47GirPr4yA1hE9JCtxfQN0+tVbkxwCtYDQWhEKWFHsEK48ORg5jrouCAg==}
+ engines: {node: '>=20'}
+
+ smol-toml@1.6.1:
+ resolution: {integrity: sha512-dWUG8F5sIIARXih1DTaQAX4SsiTXhInKf1buxdY9DIg4ZYPZK5nGM1VRIYmEbDbsHt7USo99xSLFu5Q1IqTmsg==}
engines: {node: '>= 18'}
source-map-js@1.2.1:
@@ -3290,19 +3148,13 @@ packages:
resolution: {integrity: sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==}
engines: {node: '>= 0.8'}
- std-env@3.10.0:
- resolution: {integrity: sha512-5GS12FdOZNliM5mAOxFRg7Ir0pWz8MdpYm6AY6VPkGpbA7ZzmbzNcBJQ0GPvvyWgcY7QAhCgf9Uy89I03faLkg==}
+ std-env@4.0.0:
+ resolution: {integrity: sha512-zUMPtQ/HBY3/50VbpkupYHbRroTRZJPRLvreamgErJVys0ceuzMkD44J/QjqhHjOzK42GQ3QZIeFG1OYfOtKqQ==}
stop-iteration-iterator@1.1.0:
resolution: {integrity: sha512-eLoXW/DHyl62zxY4SCaIgnRhuMr6ri4juEYARS8E6sCEqzKpOiE521Ucofdx+KnDZl5xmvGYaaKCk5FEOxJCoQ==}
engines: {node: '>= 0.4'}
- stream-browserify@3.0.0:
- resolution: {integrity: sha512-H73RAHsVBapbim0tU2JwwOiXUj+fikfiaoYAKHF3VJfA0pe2BCzkhAHBlLG6REzE+2WNZcxOXjK7lkso+9euLA==}
-
- stream-http@3.2.0:
- resolution: {integrity: sha512-Oq1bLqisTyK3TSCXpPbT4sdeYNdmyZJv1LxpEm2vu1ZhK89kSE5YXwZc3cWk0MagGaKriBh9mCFbVGtO+vY29A==}
-
string-argv@0.3.2:
resolution: {integrity: sha512-aqD2Q0144Z+/RqG52NeHEkZauTAUWJO8c6yTftGJKO3Tja5tUgIfmIl6kExvhtxSDP7fXB6DvzkfMpCd/F3G+Q==}
engines: {node: '>=0.6.19'}
@@ -3334,24 +3186,14 @@ packages:
resolution: {integrity: sha512-UXSH262CSZY1tfu3G3Secr6uGLCFVPMhIqHjlgCUtCCcgihYc/xKs9djMTMUOb2j1mVSeU8EU6NWc/iQKU6Gfg==}
engines: {node: '>= 0.4'}
- string_decoder@1.1.1:
- resolution: {integrity: sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==}
-
- string_decoder@1.3.0:
- resolution: {integrity: sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==}
-
- strip-ansi@7.1.2:
- resolution: {integrity: sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA==}
+ strip-ansi@7.2.0:
+ resolution: {integrity: sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==}
engines: {node: '>=12'}
strip-indent@3.0.0:
resolution: {integrity: sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ==}
engines: {node: '>=8'}
- strip-json-comments@3.1.1:
- resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==}
- engines: {node: '>=8'}
-
strip-json-comments@5.0.3:
resolution: {integrity: sha512-1tB5mhVo7U+ETBKNf92xT4hrQa3pm0MZ0PQvuDnWgAAGHDsfp4lPSpiS6psrSiet87wyGPh9ft6wmhOMQ0hDiw==}
engines: {node: '>=14.16'}
@@ -3379,23 +3221,19 @@ packages:
engines: {node: '>=10'}
hasBin: true
- timers-browserify@2.0.12:
- resolution: {integrity: sha512-9phl76Cqm6FhSX9Xe1ZUAMLtm1BLkKj2Qd5ApyWkXzsMRaA7dgr81kf4wJmQf/hAvg8EEyJxDo3du/0KlhPiKQ==}
- engines: {node: '>=0.6.0'}
-
tinybench@2.9.0:
resolution: {integrity: sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==}
- tinyexec@1.0.2:
- resolution: {integrity: sha512-W/KYk+NFhkmsYpuHq5JykngiOCnxeVL8v8dFnqxSD8qEEdRfXk1SDM6JzNqcERbcGYj9tMrDQBYV9cjgnunFIg==}
+ tinyexec@1.0.4:
+ resolution: {integrity: sha512-u9r3uZC0bdpGOXtlxUIdwf9pkmvhqJdrVCH9fapQtgy/OeTTMZ1nqH7agtvEfmGui6e1XxjcdrlxvxJvc3sMqw==}
engines: {node: '>=18'}
tinyglobby@0.2.15:
resolution: {integrity: sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==}
engines: {node: '>=12.0.0'}
- tinyrainbow@3.0.3:
- resolution: {integrity: sha512-PSkbLUoxOFRzJYjjxHJt9xro7D+iilgMX/C9lawzVuYiIdcihh9DXmVibBe8lmcFrRi/VzlPjBxbN7rH24q8/Q==}
+ tinyrainbow@3.1.0:
+ resolution: {integrity: sha512-Bf+ILmBgretUrdJxzXM0SgXLZ3XfiaUuOj/IKQHuTXip+05Xn+uyEYdVg0kYDipTBcLrCVyUzAPz7QmArb0mmw==}
engines: {node: '>=14.0.0'}
tldts-core@7.0.23:
@@ -3405,10 +3243,6 @@ packages:
resolution: {integrity: sha512-ASdhgQIBSay0R/eXggAkQ53G4nTJqTXqC2kbaBbdDwM7SkjyZyO0OaaN1/FH7U/yCeqOHDwFO5j8+Os/IS1dXw==}
hasBin: true
- to-buffer@1.2.2:
- resolution: {integrity: sha512-db0E3UJjcFhpDhAF4tLo03oli3pwl3dbnzXOUIlRKrp+ldk/VUxzpWYZENsw2SZiuBjHAk7DfB0VU7NKdpb6sw==}
- engines: {node: '>= 0.4'}
-
to-regex-range@5.0.1:
resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==}
engines: {node: '>=8.0'}
@@ -3432,8 +3266,8 @@ packages:
try@1.0.3:
resolution: {integrity: sha512-AHA8khVCII6zKyRkyPo6pRwoR9v5jb7QFw6e5avtaVSkxVfaEucYIo06xnwB+pJaEarfYNbs7W3Vq+LZLZiWyA==}
- ts-api-utils@2.4.0:
- resolution: {integrity: sha512-3TaVTaAv2gTiMB35i3FiGJaRfwb3Pyn/j3m/bfAvGe8FB7CF6u+LMYqYlDh7reQf7UNvoTvdfAqHGmPGOSsPmA==}
+ ts-api-utils@2.5.0:
+ resolution: {integrity: sha512-OJ/ibxhPlqrMM0UiNHJ/0CKQkoKF243/AEmplt3qpRgkW8VG7IfOS41h7V8TjITqdByHzrjcS/2si+y4lIh8NA==}
engines: {node: '>=18.12'}
peerDependencies:
typescript: '>=4.8.4'
@@ -3441,8 +3275,9 @@ packages:
tslib@2.8.1:
resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==}
- tty-browserify@0.0.1:
- resolution: {integrity: sha512-C3TaO7K81YvjCgQH9Q1S3R3P3BtN3RIM8n+OvX4il1K1zgE8ZhI0op7kClgkxtutIE8hQrcrHBXvIheqKUUCxw==}
+ turbo@2.9.3:
+ resolution: {integrity: sha512-J/VUvsGRykPb9R8Kh8dHVBOqioDexLk9BhLCU/ZybRR+HN9UR3cURdazFvNgMDt9zPP8TF6K73Z+tplfmi0PqQ==}
+ hasBin: true
type-check@0.4.0:
resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==}
@@ -3468,18 +3303,22 @@ packages:
resolution: {integrity: sha512-3KS2b+kL7fsuk/eJZ7EQdnEmQoaho/r6KUef7hxvltNA5DR8NAUM+8wJMbJyZ4G9/7i3v5zPBIMN5aybAh2/Jg==}
engines: {node: '>= 0.4'}
- typescript-eslint@8.56.1:
- resolution: {integrity: sha512-U4lM6pjmBX7J5wk4szltF7I1cGBHXZopnAXCMXb3+fZ3B/0Z3hq3wS/CCUB2NZBNAExK92mCU2tEohWuwVMsDQ==}
+ typescript-eslint@8.58.0:
+ resolution: {integrity: sha512-e2TQzKfaI85fO+F3QywtX+tCTsu/D3WW5LVU6nz8hTFKFZ8yBJ6mSYRpXqdR3mFjPWmO0eWsTa5f+UpAOe/FMA==}
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
peerDependencies:
eslint: ^8.57.0 || ^9.0.0 || ^10.0.0
- typescript: '>=4.8.4 <6.0.0'
+ typescript: '>=4.8.4 <6.1.0'
typescript@5.9.3:
resolution: {integrity: sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==}
engines: {node: '>=14.17'}
hasBin: true
+ unbash@2.2.0:
+ resolution: {integrity: sha512-X2wH19RAPZE3+ldGicOkoj/SIA83OIxcJ6Cuaw23hf8Xc6fQpvZXY0SftE2JgS0QhYLUG4uwodSI3R53keyh7w==}
+ engines: {node: '>=14'}
+
unbox-primitive@1.1.0:
resolution: {integrity: sha512-nWJ91DjeOkej/TA8pXQ3myruKpKEYgqvpw9lz4OPHj/NWFNluYrjbz9j01CJ8yKQd2g4jFoOkINCTW2I5LEEyw==}
engines: {node: '>= 0.4'}
@@ -3487,14 +3326,13 @@ packages:
undici-types@7.16.0:
resolution: {integrity: sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==}
+ undici-types@7.18.2:
+ resolution: {integrity: sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w==}
+
undici@7.22.0:
resolution: {integrity: sha512-RqslV2Us5BrllB+JeiZnK4peryVTndy9Dnqq62S3yYRRTj0tFQCwEniUy2167skdGOy3vqRzEvl1Dm4sV2ReDg==}
engines: {node: '>=20.18.1'}
- unicorn-magic@0.3.0:
- resolution: {integrity: sha512-+QBBXBCvifc56fsbuxZQ6Sic3wqqc3WWaqxs58gvJrcOuN83HGTCwz3oS5phzU9LthRNE9VrJCFCLUgHeeFnfA==}
- engines: {node: '>=18'}
-
unpipe@1.0.0:
resolution: {integrity: sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==}
engines: {node: '>= 0.8'}
@@ -3511,21 +3349,11 @@ packages:
uri-js@4.4.1:
resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==}
- url@0.11.4:
- resolution: {integrity: sha512-oCwdVC7mTuWiPyjLUz/COz5TLk6wgp0RCsN+wHZ2Ekneac9w8uuV0njcbbie2ME+Vs+d6duwmYuR3HgQXs1fOg==}
- engines: {node: '>= 0.4'}
-
use-sync-external-store@1.6.0:
resolution: {integrity: sha512-Pp6GSwGP/NrPIrxVFAIkOQeyw8lFenOHijQWkUTrDvrF4ALqylP2C/KCkeS9dpUM3KvYRQhna5vt7IL95+ZQ9w==}
peerDependencies:
react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0
- util-deprecate@1.0.2:
- resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==}
-
- util@0.12.5:
- resolution: {integrity: sha512-kZf/K6hEIrWHI6XqOFUiiMa+79wE/D8Q+NCNAWclkyg3b4d2k7s0QGepNjiABc+aR3N1PAyHL7p6UcLY6LmrnA==}
-
uuid@13.0.0:
resolution: {integrity: sha512-XQegIaBTVUjSHliKqcnFqYypAd4S+WCYt5NIeRs6w/UAry7z8Y9j5ZwRRL4kzq9U3sD6v+85er9FvkEaBpji2w==}
hasBin: true
@@ -3534,14 +3362,6 @@ packages:
resolution: {integrity: sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==}
engines: {node: '>= 0.8'}
- vite-plugin-node-polyfills@0.25.0:
- resolution: {integrity: sha512-rHZ324W3LhfGPxWwQb2N048TThB6nVvnipsqBUJEzh3R9xeK9KI3si+GMQxCuAcpPJBVf0LpDtJ+beYzB3/chg==}
- peerDependencies:
- vite: ^2.0.0 || ^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0
-
- vite-plugin-string-replace@1.1.5:
- resolution: {integrity: sha512-6kxox6rs/v5mbmV7BvRmSjALyYEmXC/IRBVS5ymgaV9iXe/OW84s3XNJrQX8B8/u9M/LK3gbQ6hWiYiOzqSRYQ==}
-
vite@7.3.1:
resolution: {integrity: sha512-w+N7Hifpc3gRjZ63vYBXA56dvvRlNWRczTdmCBBa+CotUzAPf5b7YMdMR/8CQoeYE5LX3W4wj6RYTgonm1b9DA==}
engines: {node: ^20.19.0 || >=22.12.0}
@@ -3582,20 +3402,21 @@ packages:
yaml:
optional: true
- vitest@4.0.18:
- resolution: {integrity: sha512-hOQuK7h0FGKgBAas7v0mSAsnvrIgAvWmRFjmzpJ7SwFHH3g1k2u37JtYwOwmEKhK6ZO3v9ggDBBm0La1LCK4uQ==}
+ vitest@4.1.2:
+ resolution: {integrity: sha512-xjR1dMTVHlFLh98JE3i/f/WePqJsah4A0FK9cc8Ehp9Udk0AZk6ccpIZhh1qJ/yxVWRZ+Q54ocnD8TXmkhspGg==}
engines: {node: ^20.0.0 || ^22.0.0 || >=24.0.0}
hasBin: true
peerDependencies:
'@edge-runtime/vm': '*'
'@opentelemetry/api': ^1.9.0
'@types/node': ^20.0.0 || ^22.0.0 || >=24.0.0
- '@vitest/browser-playwright': 4.0.18
- '@vitest/browser-preview': 4.0.18
- '@vitest/browser-webdriverio': 4.0.18
- '@vitest/ui': 4.0.18
+ '@vitest/browser-playwright': 4.1.2
+ '@vitest/browser-preview': 4.1.2
+ '@vitest/browser-webdriverio': 4.1.2
+ '@vitest/ui': 4.1.2
happy-dom: '*'
jsdom: '*'
+ vite: ^6.0.0 || ^7.0.0 || ^8.0.0
peerDependenciesMeta:
'@edge-runtime/vm':
optional: true
@@ -3616,9 +3437,6 @@ packages:
jsdom:
optional: true
- vm-browserify@1.1.2:
- resolution: {integrity: sha512-2ham8XPWTONajOR0ohOKOHXkm3+gaBmGut3SRuu75xLd/RRaY6vqgh8NBYYk7+RW3u5AtzPQZG8F10LHkl0lAQ==}
-
w3c-xmlserializer@5.0.0:
resolution: {integrity: sha512-o8qghlI8NZHU1lLPrpi2+Uq7abh4GGPpYANlalzWxyWteJOCsr/P+oPBA49TOLu5FTZO4d3F9MnWJfiMo4BkmA==}
engines: {node: '>=18'}
@@ -3680,8 +3498,8 @@ packages:
wrappy@1.0.2:
resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==}
- ws@8.19.0:
- resolution: {integrity: sha512-blAT2mjOEIi0ZzruJfIhb3nps74PRWTCz1IjglWEEpQl5XS/UNama6u2/rjFkDDouqr4L67ry+1aGIALViWjDg==}
+ ws@8.20.0:
+ resolution: {integrity: sha512-sAt8BhgNbzCtgGbt2OxmpuryO63ZoDk/sqaB/znQm94T4fCEsy/yV+7CdC1kJhOU9lboAEU7R3kquuycDoibVA==}
engines: {node: '>=10.0.0'}
peerDependencies:
bufferutil: ^4.0.1
@@ -3706,8 +3524,8 @@ packages:
yallist@3.1.1:
resolution: {integrity: sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==}
- yaml@2.8.2:
- resolution: {integrity: sha512-mplynKqc1C2hTVYxd0PU2xQAc22TI1vShAYGksCCfxbn/dFwnHTNi1bvYsBTkhdUNtGIf5xNOg938rrSSYvS9A==}
+ yaml@2.8.3:
+ resolution: {integrity: sha512-AvbaCLOO2Otw/lW5bmh9d/WEdcDFdQp2Z2ZUH3pX9U2ihyUY0nvLv7J6TrWowklRGPYbB/IuIMfYgxaCPg5Bpg==}
engines: {node: '>= 14.6'}
hasBin: true
@@ -3762,8 +3580,8 @@ snapshots:
'@babel/generator': 7.29.1
'@babel/helper-compilation-targets': 7.28.6
'@babel/helper-module-transforms': 7.28.6(@babel/core@7.29.0)
- '@babel/helpers': 7.28.6
- '@babel/parser': 7.29.0
+ '@babel/helpers': 7.29.2
+ '@babel/parser': 7.29.2
'@babel/template': 7.28.6
'@babel/traverse': 7.29.0
'@babel/types': 7.29.0
@@ -3778,7 +3596,7 @@ snapshots:
'@babel/generator@7.29.1':
dependencies:
- '@babel/parser': 7.29.0
+ '@babel/parser': 7.29.2
'@babel/types': 7.29.0
'@jridgewell/gen-mapping': 0.3.13
'@jridgewell/trace-mapping': 0.3.31
@@ -3788,7 +3606,7 @@ snapshots:
dependencies:
'@babel/compat-data': 7.29.0
'@babel/helper-validator-option': 7.27.1
- browserslist: 4.28.1
+ browserslist: 4.28.2
lru-cache: 5.1.1
semver: 6.3.1
@@ -3816,12 +3634,12 @@ snapshots:
'@babel/helper-validator-option@7.27.1': {}
- '@babel/helpers@7.28.6':
+ '@babel/helpers@7.29.2':
dependencies:
'@babel/template': 7.28.6
'@babel/types': 7.29.0
- '@babel/parser@7.29.0':
+ '@babel/parser@7.29.2':
dependencies:
'@babel/types': 7.29.0
@@ -3830,7 +3648,7 @@ snapshots:
'@babel/template@7.28.6':
dependencies:
'@babel/code-frame': 7.29.0
- '@babel/parser': 7.29.0
+ '@babel/parser': 7.29.2
'@babel/types': 7.29.0
'@babel/traverse@7.29.0':
@@ -3838,7 +3656,7 @@ snapshots:
'@babel/code-frame': 7.29.0
'@babel/generator': 7.29.1
'@babel/helper-globals': 7.28.0
- '@babel/parser': 7.29.0
+ '@babel/parser': 7.29.2
'@babel/template': 7.28.6
'@babel/types': 7.29.0
debug: 4.4.3
@@ -3878,29 +3696,29 @@ snapshots:
'@csstools/css-tokenizer@4.0.0': {}
- '@emnapi/core@1.8.1':
+ '@emnapi/core@1.9.2':
dependencies:
- '@emnapi/wasi-threads': 1.1.0
+ '@emnapi/wasi-threads': 1.2.1
tslib: 2.8.1
optional: true
- '@emnapi/runtime@1.8.1':
+ '@emnapi/runtime@1.9.2':
dependencies:
tslib: 2.8.1
optional: true
- '@emnapi/wasi-threads@1.1.0':
+ '@emnapi/wasi-threads@1.2.1':
dependencies:
tslib: 2.8.1
optional: true
- '@es-joy/jsdoccomment@0.84.0':
+ '@es-joy/jsdoccomment@0.86.0':
dependencies:
'@types/estree': 1.0.8
- '@typescript-eslint/types': 8.56.1
- comment-parser: 1.4.5
+ '@typescript-eslint/types': 8.58.0
+ comment-parser: 1.4.6
esquery: 1.7.0
- jsdoc-type-pratt-parser: 7.1.1
+ jsdoc-type-pratt-parser: 7.2.0
'@es-joy/resolve.exports@1.2.0': {}
@@ -3982,62 +3800,44 @@ snapshots:
'@esbuild/win32-x64@0.27.3':
optional: true
- '@eslint-community/eslint-utils@4.9.1(eslint@9.39.2(jiti@2.6.1))':
+ '@eslint-community/eslint-utils@4.9.1(eslint@10.1.0(jiti@2.6.1))':
dependencies:
- eslint: 9.39.2(jiti@2.6.1)
+ eslint: 10.1.0(jiti@2.6.1)
eslint-visitor-keys: 3.4.3
'@eslint-community/regexpp@4.12.2': {}
- '@eslint/compat@2.0.2(eslint@9.39.2(jiti@2.6.1))':
+ '@eslint/compat@2.0.3(eslint@10.1.0(jiti@2.6.1))':
dependencies:
- '@eslint/core': 1.1.0
+ '@eslint/core': 1.1.1
optionalDependencies:
- eslint: 9.39.2(jiti@2.6.1)
+ eslint: 10.1.0(jiti@2.6.1)
- '@eslint/config-array@0.21.1':
+ '@eslint/config-array@0.23.3':
dependencies:
- '@eslint/object-schema': 2.1.7
+ '@eslint/object-schema': 3.0.3
debug: 4.4.3
- minimatch: 3.1.4
+ minimatch: 10.2.5
transitivePeerDependencies:
- supports-color
- '@eslint/config-helpers@0.4.2':
+ '@eslint/config-helpers@0.5.3':
dependencies:
- '@eslint/core': 0.17.0
+ '@eslint/core': 1.1.1
- '@eslint/core@0.17.0':
+ '@eslint/core@1.1.1':
dependencies:
'@types/json-schema': 7.0.15
- '@eslint/core@1.1.0':
- dependencies:
- '@types/json-schema': 7.0.15
-
- '@eslint/eslintrc@3.3.4':
- dependencies:
- ajv: 6.14.0
- debug: 4.4.3
- espree: 10.4.0
- globals: 14.0.0
- ignore: 5.3.2
- import-fresh: 3.3.1
- js-yaml: 4.1.1
- minimatch: 3.1.4
- strip-json-comments: 3.1.1
- transitivePeerDependencies:
- - supports-color
-
- '@eslint/js@9.39.2': {}
-
- '@eslint/js@9.39.3': {}
+ '@eslint/js@10.0.1(eslint@10.1.0(jiti@2.6.1))':
+ optionalDependencies:
+ eslint: 10.1.0(jiti@2.6.1)
- '@eslint/object-schema@2.1.7': {}
+ '@eslint/object-schema@3.0.3': {}
- '@eslint/plugin-kit@0.4.1':
+ '@eslint/plugin-kit@0.6.1':
dependencies:
- '@eslint/core': 0.17.0
+ '@eslint/core': 1.1.1
levn: 0.4.1
'@exodus/bytes@1.14.1': {}
@@ -4080,15 +3880,15 @@ snapshots:
'@napi-rs/wasm-runtime@0.2.12':
dependencies:
- '@emnapi/core': 1.8.1
- '@emnapi/runtime': 1.8.1
+ '@emnapi/core': 1.9.2
+ '@emnapi/runtime': 1.9.2
'@tybys/wasm-util': 0.10.1
optional: true
- '@napi-rs/wasm-runtime@1.1.1':
+ '@napi-rs/wasm-runtime@1.1.2(@emnapi/core@1.9.2)(@emnapi/runtime@1.9.2)':
dependencies:
- '@emnapi/core': 1.8.1
- '@emnapi/runtime': 1.8.1
+ '@emnapi/core': 1.9.2
+ '@emnapi/runtime': 1.9.2
'@tybys/wasm-util': 0.10.1
optional: true
@@ -4104,71 +3904,143 @@ snapshots:
'@nodelib/fs.scandir': 2.1.5
fastq: 1.20.1
- '@oxc-resolver/binding-android-arm-eabi@11.19.0':
+ '@oxc-parser/binding-android-arm-eabi@0.121.0':
optional: true
- '@oxc-resolver/binding-android-arm64@11.19.0':
+ '@oxc-parser/binding-android-arm64@0.121.0':
optional: true
- '@oxc-resolver/binding-darwin-arm64@11.19.0':
+ '@oxc-parser/binding-darwin-arm64@0.121.0':
optional: true
- '@oxc-resolver/binding-darwin-x64@11.19.0':
+ '@oxc-parser/binding-darwin-x64@0.121.0':
optional: true
- '@oxc-resolver/binding-freebsd-x64@11.19.0':
+ '@oxc-parser/binding-freebsd-x64@0.121.0':
optional: true
- '@oxc-resolver/binding-linux-arm-gnueabihf@11.19.0':
+ '@oxc-parser/binding-linux-arm-gnueabihf@0.121.0':
optional: true
- '@oxc-resolver/binding-linux-arm-musleabihf@11.19.0':
+ '@oxc-parser/binding-linux-arm-musleabihf@0.121.0':
optional: true
- '@oxc-resolver/binding-linux-arm64-gnu@11.19.0':
+ '@oxc-parser/binding-linux-arm64-gnu@0.121.0':
optional: true
- '@oxc-resolver/binding-linux-arm64-musl@11.19.0':
+ '@oxc-parser/binding-linux-arm64-musl@0.121.0':
optional: true
- '@oxc-resolver/binding-linux-ppc64-gnu@11.19.0':
+ '@oxc-parser/binding-linux-ppc64-gnu@0.121.0':
optional: true
- '@oxc-resolver/binding-linux-riscv64-gnu@11.19.0':
+ '@oxc-parser/binding-linux-riscv64-gnu@0.121.0':
optional: true
- '@oxc-resolver/binding-linux-riscv64-musl@11.19.0':
+ '@oxc-parser/binding-linux-riscv64-musl@0.121.0':
optional: true
- '@oxc-resolver/binding-linux-s390x-gnu@11.19.0':
+ '@oxc-parser/binding-linux-s390x-gnu@0.121.0':
optional: true
- '@oxc-resolver/binding-linux-x64-gnu@11.19.0':
+ '@oxc-parser/binding-linux-x64-gnu@0.121.0':
optional: true
- '@oxc-resolver/binding-linux-x64-musl@11.19.0':
+ '@oxc-parser/binding-linux-x64-musl@0.121.0':
optional: true
- '@oxc-resolver/binding-openharmony-arm64@11.19.0':
+ '@oxc-parser/binding-openharmony-arm64@0.121.0':
optional: true
- '@oxc-resolver/binding-wasm32-wasi@11.19.0':
+ '@oxc-parser/binding-wasm32-wasi@0.121.0(@emnapi/core@1.9.2)(@emnapi/runtime@1.9.2)':
dependencies:
- '@napi-rs/wasm-runtime': 1.1.1
+ '@napi-rs/wasm-runtime': 1.1.2(@emnapi/core@1.9.2)(@emnapi/runtime@1.9.2)
+ transitivePeerDependencies:
+ - '@emnapi/core'
+ - '@emnapi/runtime'
+ optional: true
+
+ '@oxc-parser/binding-win32-arm64-msvc@0.121.0':
+ optional: true
+
+ '@oxc-parser/binding-win32-ia32-msvc@0.121.0':
+ optional: true
+
+ '@oxc-parser/binding-win32-x64-msvc@0.121.0':
+ optional: true
+
+ '@oxc-project/types@0.121.0': {}
+
+ '@oxc-resolver/binding-android-arm-eabi@11.19.1':
+ optional: true
+
+ '@oxc-resolver/binding-android-arm64@11.19.1':
+ optional: true
+
+ '@oxc-resolver/binding-darwin-arm64@11.19.1':
optional: true
- '@oxc-resolver/binding-win32-arm64-msvc@11.19.0':
+ '@oxc-resolver/binding-darwin-x64@11.19.1':
optional: true
- '@oxc-resolver/binding-win32-ia32-msvc@11.19.0':
+ '@oxc-resolver/binding-freebsd-x64@11.19.1':
optional: true
- '@oxc-resolver/binding-win32-x64-msvc@11.19.0':
+ '@oxc-resolver/binding-linux-arm-gnueabihf@11.19.1':
optional: true
- '@playwright/test@1.58.2':
+ '@oxc-resolver/binding-linux-arm-musleabihf@11.19.1':
+ optional: true
+
+ '@oxc-resolver/binding-linux-arm64-gnu@11.19.1':
+ optional: true
+
+ '@oxc-resolver/binding-linux-arm64-musl@11.19.1':
+ optional: true
+
+ '@oxc-resolver/binding-linux-ppc64-gnu@11.19.1':
+ optional: true
+
+ '@oxc-resolver/binding-linux-riscv64-gnu@11.19.1':
+ optional: true
+
+ '@oxc-resolver/binding-linux-riscv64-musl@11.19.1':
+ optional: true
+
+ '@oxc-resolver/binding-linux-s390x-gnu@11.19.1':
+ optional: true
+
+ '@oxc-resolver/binding-linux-x64-gnu@11.19.1':
+ optional: true
+
+ '@oxc-resolver/binding-linux-x64-musl@11.19.1':
+ optional: true
+
+ '@oxc-resolver/binding-openharmony-arm64@11.19.1':
+ optional: true
+
+ '@oxc-resolver/binding-wasm32-wasi@11.19.1(@emnapi/core@1.9.2)(@emnapi/runtime@1.9.2)':
dependencies:
- playwright: 1.58.2
+ '@napi-rs/wasm-runtime': 1.1.2(@emnapi/core@1.9.2)(@emnapi/runtime@1.9.2)
+ transitivePeerDependencies:
+ - '@emnapi/core'
+ - '@emnapi/runtime'
+ optional: true
+
+ '@oxc-resolver/binding-win32-arm64-msvc@11.19.1':
+ optional: true
+
+ '@oxc-resolver/binding-win32-ia32-msvc@11.19.1':
+ optional: true
+
+ '@oxc-resolver/binding-win32-x64-msvc@11.19.1':
+ optional: true
+
+ '@package-json/types@0.0.12': {}
+
+ '@playwright/test@1.59.1':
+ dependencies:
+ playwright: 1.59.1
'@reduxjs/toolkit@2.11.2(react-redux@9.2.0(@types/react@18.3.27)(react@18.3.1)(redux@5.0.1))(react@18.3.1)':
dependencies:
@@ -4184,22 +4056,6 @@ snapshots:
'@rolldown/pluginutils@1.0.0-rc.2': {}
- '@rollup/plugin-inject@5.0.5(rollup@4.59.0)':
- dependencies:
- '@rollup/pluginutils': 5.3.0(rollup@4.59.0)
- estree-walker: 2.0.2
- magic-string: 0.30.21
- optionalDependencies:
- rollup: 4.59.0
-
- '@rollup/pluginutils@5.3.0(rollup@4.59.0)':
- dependencies:
- '@types/estree': 1.0.8
- estree-walker: 2.0.2
- picomatch: 4.0.3
- optionalDependencies:
- rollup: 4.59.0
-
'@rollup/rollup-android-arm-eabi@4.59.0':
optional: true
@@ -4277,8 +4133,6 @@ snapshots:
'@sindresorhus/base62@1.0.0': {}
- '@sindresorhus/merge-streams@2.3.0': {}
-
'@standard-schema/spec@1.1.0': {}
'@standard-schema/utils@0.3.0': {}
@@ -4396,12 +4250,12 @@ snapshots:
'@tailwindcss/oxide-win32-arm64-msvc': 4.2.1
'@tailwindcss/oxide-win32-x64-msvc': 4.2.1
- '@tailwindcss/vite@4.2.1(vite@7.3.1(@types/node@25.2.3)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.44.1)(yaml@2.8.2))':
+ '@tailwindcss/vite@4.2.1(vite@7.3.1(@types/node@25.5.0)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.44.1)(yaml@2.8.3))':
dependencies:
'@tailwindcss/node': 4.2.1
'@tailwindcss/oxide': 4.2.1
tailwindcss: 4.2.1
- vite: 7.3.1(@types/node@25.2.3)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.44.1)(yaml@2.8.2)
+ vite: 7.3.1(@types/node@25.5.0)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.44.1)(yaml@2.8.3)
'@testing-library/dom@10.4.1':
dependencies:
@@ -4423,6 +4277,24 @@ snapshots:
picocolors: 1.1.1
redent: 3.0.0
+ '@turbo/darwin-64@2.9.3':
+ optional: true
+
+ '@turbo/darwin-arm64@2.9.3':
+ optional: true
+
+ '@turbo/linux-64@2.9.3':
+ optional: true
+
+ '@turbo/linux-arm64@2.9.3':
+ optional: true
+
+ '@turbo/windows-64@2.9.3':
+ optional: true
+
+ '@turbo/windows-arm64@2.9.3':
+ optional: true
+
'@tybys/wasm-util@0.10.1':
dependencies:
tslib: 2.8.1
@@ -4437,17 +4309,19 @@ snapshots:
'@types/deep-eql@4.0.2': {}
+ '@types/esrecurse@4.3.1': {}
+
'@types/estree@1.0.8': {}
'@types/json-schema@7.0.15': {}
- '@types/node@24.10.13':
+ '@types/node@24.12.0':
dependencies:
undici-types: 7.16.0
- '@types/node@25.2.3':
+ '@types/node@25.5.0':
dependencies:
- undici-types: 7.16.0
+ undici-types: 7.18.2
optional: true
'@types/prop-types@15.7.15': {}
@@ -4468,98 +4342,98 @@ snapshots:
'@types/ws@8.18.1':
dependencies:
- '@types/node': 24.10.13
+ '@types/node': 24.12.0
optional: true
- '@typescript-eslint/eslint-plugin@8.56.1(@typescript-eslint/parser@8.56.1(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3)':
+ '@typescript-eslint/eslint-plugin@8.58.0(@typescript-eslint/parser@8.58.0(eslint@10.1.0(jiti@2.6.1))(typescript@5.9.3))(eslint@10.1.0(jiti@2.6.1))(typescript@5.9.3)':
dependencies:
'@eslint-community/regexpp': 4.12.2
- '@typescript-eslint/parser': 8.56.1(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3)
- '@typescript-eslint/scope-manager': 8.56.1
- '@typescript-eslint/type-utils': 8.56.1(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3)
- '@typescript-eslint/utils': 8.56.1(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3)
- '@typescript-eslint/visitor-keys': 8.56.1
- eslint: 9.39.2(jiti@2.6.1)
+ '@typescript-eslint/parser': 8.58.0(eslint@10.1.0(jiti@2.6.1))(typescript@5.9.3)
+ '@typescript-eslint/scope-manager': 8.58.0
+ '@typescript-eslint/type-utils': 8.58.0(eslint@10.1.0(jiti@2.6.1))(typescript@5.9.3)
+ '@typescript-eslint/utils': 8.58.0(eslint@10.1.0(jiti@2.6.1))(typescript@5.9.3)
+ '@typescript-eslint/visitor-keys': 8.58.0
+ eslint: 10.1.0(jiti@2.6.1)
ignore: 7.0.5
natural-compare: 1.4.0
- ts-api-utils: 2.4.0(typescript@5.9.3)
+ ts-api-utils: 2.5.0(typescript@5.9.3)
typescript: 5.9.3
transitivePeerDependencies:
- supports-color
- '@typescript-eslint/parser@8.56.1(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3)':
+ '@typescript-eslint/parser@8.58.0(eslint@10.1.0(jiti@2.6.1))(typescript@5.9.3)':
dependencies:
- '@typescript-eslint/scope-manager': 8.56.1
- '@typescript-eslint/types': 8.56.1
- '@typescript-eslint/typescript-estree': 8.56.1(typescript@5.9.3)
- '@typescript-eslint/visitor-keys': 8.56.1
+ '@typescript-eslint/scope-manager': 8.58.0
+ '@typescript-eslint/types': 8.58.0
+ '@typescript-eslint/typescript-estree': 8.58.0(typescript@5.9.3)
+ '@typescript-eslint/visitor-keys': 8.58.0
debug: 4.4.3
- eslint: 9.39.2(jiti@2.6.1)
+ eslint: 10.1.0(jiti@2.6.1)
typescript: 5.9.3
transitivePeerDependencies:
- supports-color
- '@typescript-eslint/project-service@8.56.1(typescript@5.9.3)':
+ '@typescript-eslint/project-service@8.58.0(typescript@5.9.3)':
dependencies:
- '@typescript-eslint/tsconfig-utils': 8.56.1(typescript@5.9.3)
- '@typescript-eslint/types': 8.56.1
+ '@typescript-eslint/tsconfig-utils': 8.58.0(typescript@5.9.3)
+ '@typescript-eslint/types': 8.58.0
debug: 4.4.3
typescript: 5.9.3
transitivePeerDependencies:
- supports-color
- '@typescript-eslint/scope-manager@8.56.1':
+ '@typescript-eslint/scope-manager@8.58.0':
dependencies:
- '@typescript-eslint/types': 8.56.1
- '@typescript-eslint/visitor-keys': 8.56.1
+ '@typescript-eslint/types': 8.58.0
+ '@typescript-eslint/visitor-keys': 8.58.0
- '@typescript-eslint/tsconfig-utils@8.56.1(typescript@5.9.3)':
+ '@typescript-eslint/tsconfig-utils@8.58.0(typescript@5.9.3)':
dependencies:
typescript: 5.9.3
- '@typescript-eslint/type-utils@8.56.1(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3)':
+ '@typescript-eslint/type-utils@8.58.0(eslint@10.1.0(jiti@2.6.1))(typescript@5.9.3)':
dependencies:
- '@typescript-eslint/types': 8.56.1
- '@typescript-eslint/typescript-estree': 8.56.1(typescript@5.9.3)
- '@typescript-eslint/utils': 8.56.1(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3)
+ '@typescript-eslint/types': 8.58.0
+ '@typescript-eslint/typescript-estree': 8.58.0(typescript@5.9.3)
+ '@typescript-eslint/utils': 8.58.0(eslint@10.1.0(jiti@2.6.1))(typescript@5.9.3)
debug: 4.4.3
- eslint: 9.39.2(jiti@2.6.1)
- ts-api-utils: 2.4.0(typescript@5.9.3)
+ eslint: 10.1.0(jiti@2.6.1)
+ ts-api-utils: 2.5.0(typescript@5.9.3)
typescript: 5.9.3
transitivePeerDependencies:
- supports-color
- '@typescript-eslint/types@8.56.1': {}
+ '@typescript-eslint/types@8.58.0': {}
- '@typescript-eslint/typescript-estree@8.56.1(typescript@5.9.3)':
+ '@typescript-eslint/typescript-estree@8.58.0(typescript@5.9.3)':
dependencies:
- '@typescript-eslint/project-service': 8.56.1(typescript@5.9.3)
- '@typescript-eslint/tsconfig-utils': 8.56.1(typescript@5.9.3)
- '@typescript-eslint/types': 8.56.1
- '@typescript-eslint/visitor-keys': 8.56.1
+ '@typescript-eslint/project-service': 8.58.0(typescript@5.9.3)
+ '@typescript-eslint/tsconfig-utils': 8.58.0(typescript@5.9.3)
+ '@typescript-eslint/types': 8.58.0
+ '@typescript-eslint/visitor-keys': 8.58.0
debug: 4.4.3
- minimatch: 10.2.3
+ minimatch: 10.2.5
semver: 7.7.4
tinyglobby: 0.2.15
- ts-api-utils: 2.4.0(typescript@5.9.3)
+ ts-api-utils: 2.5.0(typescript@5.9.3)
typescript: 5.9.3
transitivePeerDependencies:
- supports-color
- '@typescript-eslint/utils@8.56.1(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3)':
+ '@typescript-eslint/utils@8.58.0(eslint@10.1.0(jiti@2.6.1))(typescript@5.9.3)':
dependencies:
- '@eslint-community/eslint-utils': 4.9.1(eslint@9.39.2(jiti@2.6.1))
- '@typescript-eslint/scope-manager': 8.56.1
- '@typescript-eslint/types': 8.56.1
- '@typescript-eslint/typescript-estree': 8.56.1(typescript@5.9.3)
- eslint: 9.39.2(jiti@2.6.1)
+ '@eslint-community/eslint-utils': 4.9.1(eslint@10.1.0(jiti@2.6.1))
+ '@typescript-eslint/scope-manager': 8.58.0
+ '@typescript-eslint/types': 8.58.0
+ '@typescript-eslint/typescript-estree': 8.58.0(typescript@5.9.3)
+ eslint: 10.1.0(jiti@2.6.1)
typescript: 5.9.3
transitivePeerDependencies:
- supports-color
- '@typescript-eslint/visitor-keys@8.56.1':
+ '@typescript-eslint/visitor-keys@8.58.0':
dependencies:
- '@typescript-eslint/types': 8.56.1
+ '@typescript-eslint/types': 8.58.0
eslint-visitor-keys: 5.0.1
'@unrs/resolver-binding-android-arm-eabi@1.11.1':
@@ -4623,66 +4497,76 @@ snapshots:
'@uppercod/css-to-object@1.1.1': {}
- '@vitejs/plugin-react-swc@4.2.3(vite@7.3.1(@types/node@25.2.3)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.44.1)(yaml@2.8.2))':
+ '@vitejs/plugin-react-swc@4.2.3(vite@7.3.1(@types/node@25.5.0)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.44.1)(yaml@2.8.3))':
dependencies:
'@rolldown/pluginutils': 1.0.0-rc.2
'@swc/core': 1.15.13
- vite: 7.3.1(@types/node@25.2.3)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.44.1)(yaml@2.8.2)
+ vite: 7.3.1(@types/node@25.5.0)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.44.1)(yaml@2.8.3)
transitivePeerDependencies:
- '@swc/helpers'
- '@vitest/coverage-v8@4.0.18(vitest@4.0.18(@types/node@25.2.3)(happy-dom@20.6.1)(jiti@2.6.1)(jsdom@28.1.0)(lightningcss@1.31.1)(terser@5.44.1)(yaml@2.8.2))':
+ '@vitest/coverage-v8@4.1.2(vitest@4.1.2(@types/node@24.12.0)(happy-dom@20.6.1)(jsdom@28.1.0)(vite@7.3.1(@types/node@24.12.0)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.44.1)(yaml@2.8.3)))':
dependencies:
'@bcoe/v8-coverage': 1.0.2
- '@vitest/utils': 4.0.18
- ast-v8-to-istanbul: 0.3.11
+ '@vitest/utils': 4.1.2
+ ast-v8-to-istanbul: 1.0.0
istanbul-lib-coverage: 3.2.2
istanbul-lib-report: 3.0.1
istanbul-reports: 3.2.0
magicast: 0.5.2
obug: 2.1.1
- std-env: 3.10.0
- tinyrainbow: 3.0.3
- vitest: 4.0.18(@types/node@25.2.3)(happy-dom@20.6.1)(jiti@2.6.1)(jsdom@28.1.0)(lightningcss@1.31.1)(terser@5.44.1)(yaml@2.8.2)
+ std-env: 4.0.0
+ tinyrainbow: 3.1.0
+ vitest: 4.1.2(@types/node@24.12.0)(happy-dom@20.6.1)(jsdom@28.1.0)(vite@7.3.1(@types/node@24.12.0)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.44.1)(yaml@2.8.3))
- '@vitest/expect@4.0.18':
+ '@vitest/expect@4.1.2':
dependencies:
'@standard-schema/spec': 1.1.0
'@types/chai': 5.2.3
- '@vitest/spy': 4.0.18
- '@vitest/utils': 4.0.18
+ '@vitest/spy': 4.1.2
+ '@vitest/utils': 4.1.2
chai: 6.2.2
- tinyrainbow: 3.0.3
+ tinyrainbow: 3.1.0
+
+ '@vitest/mocker@4.1.2(vite@7.3.1(@types/node@24.12.0)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.44.1)(yaml@2.8.3))':
+ dependencies:
+ '@vitest/spy': 4.1.2
+ estree-walker: 3.0.3
+ magic-string: 0.30.21
+ optionalDependencies:
+ vite: 7.3.1(@types/node@24.12.0)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.44.1)(yaml@2.8.3)
- '@vitest/mocker@4.0.18(vite@7.3.1(@types/node@25.2.3)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.44.1)(yaml@2.8.2))':
+ '@vitest/mocker@4.1.2(vite@7.3.1(@types/node@25.5.0)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.44.1)(yaml@2.8.3))':
dependencies:
- '@vitest/spy': 4.0.18
+ '@vitest/spy': 4.1.2
estree-walker: 3.0.3
magic-string: 0.30.21
optionalDependencies:
- vite: 7.3.1(@types/node@25.2.3)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.44.1)(yaml@2.8.2)
+ vite: 7.3.1(@types/node@25.5.0)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.44.1)(yaml@2.8.3)
- '@vitest/pretty-format@4.0.18':
+ '@vitest/pretty-format@4.1.2':
dependencies:
- tinyrainbow: 3.0.3
+ tinyrainbow: 3.1.0
- '@vitest/runner@4.0.18':
+ '@vitest/runner@4.1.2':
dependencies:
- '@vitest/utils': 4.0.18
+ '@vitest/utils': 4.1.2
pathe: 2.0.3
- '@vitest/snapshot@4.0.18':
+ '@vitest/snapshot@4.1.2':
dependencies:
- '@vitest/pretty-format': 4.0.18
+ '@vitest/pretty-format': 4.1.2
+ '@vitest/utils': 4.1.2
magic-string: 0.30.21
pathe: 2.0.3
- '@vitest/spy@4.0.18': {}
+ '@vitest/spy@4.1.2': {}
- '@vitest/utils@4.0.18':
+ '@vitest/utils@4.1.2':
dependencies:
- '@vitest/pretty-format': 4.0.18
- tinyrainbow: 3.0.3
+ '@vitest/pretty-format': 4.1.2
+ convert-source-map: 2.0.0
+ tinyrainbow: 3.1.0
accepts@2.0.0:
dependencies:
@@ -4712,10 +4596,6 @@ snapshots:
ansi-regex@6.2.2: {}
- ansi-styles@4.3.0:
- dependencies:
- color-convert: 2.0.1
-
ansi-styles@5.2.0: {}
ansi-styles@6.2.3: {}
@@ -4787,23 +4667,9 @@ snapshots:
get-intrinsic: 1.3.0
is-array-buffer: 3.0.5
- asn1.js@4.10.1:
- dependencies:
- bn.js: 4.12.3
- inherits: 2.0.4
- minimalistic-assert: 1.0.1
-
- assert@2.1.0:
- dependencies:
- call-bind: 1.0.8
- is-nan: 1.3.2
- object-is: 1.1.6
- object.assign: 4.1.7
- util: 0.12.5
-
assertion-error@2.0.1: {}
- ast-v8-to-istanbul@0.3.11:
+ ast-v8-to-istanbul@1.0.0:
dependencies:
'@jridgewell/trace-mapping': 0.3.31
estree-walker: 3.0.3
@@ -4844,18 +4710,12 @@ snapshots:
balanced-match@4.0.4: {}
- base64-js@1.5.1: {}
-
- baseline-browser-mapping@2.10.0: {}
+ baseline-browser-mapping@2.10.13: {}
bidi-js@1.0.3:
dependencies:
require-from-string: 2.0.2
- bn.js@4.12.3: {}
-
- bn.js@5.2.3: {}
-
body-parser@2.2.2:
dependencies:
bytes: 3.1.2
@@ -4870,12 +4730,12 @@ snapshots:
transitivePeerDependencies:
- supports-color
- brace-expansion@1.1.12:
+ brace-expansion@1.1.13:
dependencies:
balanced-match: 1.0.2
concat-map: 0.0.1
- brace-expansion@5.0.3:
+ brace-expansion@5.0.5:
dependencies:
balanced-match: 4.0.4
@@ -4883,76 +4743,17 @@ snapshots:
dependencies:
fill-range: 7.1.1
- brorand@1.1.0: {}
-
- browser-resolve@2.0.0:
- dependencies:
- resolve: 1.22.11
-
- browserify-aes@1.2.0:
- dependencies:
- buffer-xor: 1.0.3
- cipher-base: 1.0.7
- create-hash: 1.2.0
- evp_bytestokey: 1.0.3
- inherits: 2.0.4
- safe-buffer: 5.2.1
-
- browserify-cipher@1.0.1:
- dependencies:
- browserify-aes: 1.2.0
- browserify-des: 1.0.2
- evp_bytestokey: 1.0.3
-
- browserify-des@1.0.2:
- dependencies:
- cipher-base: 1.0.7
- des.js: 1.1.0
- inherits: 2.0.4
- safe-buffer: 5.2.1
-
- browserify-rsa@4.1.1:
+ browserslist@4.28.2:
dependencies:
- bn.js: 5.2.3
- randombytes: 2.1.0
- safe-buffer: 5.2.1
-
- browserify-sign@4.2.5:
- dependencies:
- bn.js: 5.2.3
- browserify-rsa: 4.1.1
- create-hash: 1.2.0
- create-hmac: 1.1.7
- elliptic: 6.6.1
- inherits: 2.0.4
- parse-asn1: 5.1.9
- readable-stream: 2.3.8
- safe-buffer: 5.2.1
-
- browserify-zlib@0.2.0:
- dependencies:
- pako: 1.0.11
-
- browserslist@4.28.1:
- dependencies:
- baseline-browser-mapping: 2.10.0
- caniuse-lite: 1.0.30001774
- electron-to-chromium: 1.5.302
- node-releases: 2.0.27
- update-browserslist-db: 1.2.3(browserslist@4.28.1)
+ baseline-browser-mapping: 2.10.13
+ caniuse-lite: 1.0.30001784
+ electron-to-chromium: 1.5.331
+ node-releases: 2.0.37
+ update-browserslist-db: 1.2.3(browserslist@4.28.2)
buffer-from@1.1.2:
optional: true
- buffer-xor@1.0.3: {}
-
- buffer@5.7.1:
- dependencies:
- base64-js: 1.5.1
- ieee754: 1.2.1
-
- builtin-status-codes@3.0.0: {}
-
bytes@3.1.2: {}
cache-parser@1.2.6: {}
@@ -4974,40 +4775,21 @@ snapshots:
call-bind-apply-helpers: 1.0.2
get-intrinsic: 1.3.0
- callsites@3.1.0: {}
-
- caniuse-lite@1.0.30001774: {}
+ caniuse-lite@1.0.30001784: {}
chai@6.2.2: {}
- chalk@4.1.2:
- dependencies:
- ansi-styles: 4.3.0
- supports-color: 7.2.0
-
- cipher-base@1.0.7:
- dependencies:
- inherits: 2.0.4
- safe-buffer: 5.2.1
- to-buffer: 1.2.2
-
cli-cursor@5.0.0:
dependencies:
restore-cursor: 5.1.0
- cli-truncate@5.1.1:
+ cli-truncate@5.2.0:
dependencies:
- slice-ansi: 7.1.2
+ slice-ansi: 8.0.0
string-width: 8.2.0
clsx@2.1.1: {}
- color-convert@2.0.1:
- dependencies:
- color-name: 1.1.4
-
- color-name@1.1.4: {}
-
colorette@2.0.20: {}
combined-stream@1.0.8:
@@ -5019,14 +4801,10 @@ snapshots:
commander@2.20.3:
optional: true
- comment-parser@1.4.5: {}
+ comment-parser@1.4.6: {}
concat-map@0.0.1: {}
- console-browserify@1.2.0: {}
-
- constants-browserify@1.0.0: {}
-
content-disposition@1.0.1: {}
content-type@1.0.5: {}
@@ -5037,53 +4815,12 @@ snapshots:
cookie@0.7.2: {}
- core-util-is@1.0.3: {}
-
- create-ecdh@4.0.4:
- dependencies:
- bn.js: 4.12.3
- elliptic: 6.6.1
-
- create-hash@1.2.0:
- dependencies:
- cipher-base: 1.0.7
- inherits: 2.0.4
- md5.js: 1.3.5
- ripemd160: 2.0.3
- sha.js: 2.4.12
-
- create-hmac@1.1.7:
- dependencies:
- cipher-base: 1.0.7
- create-hash: 1.2.0
- inherits: 2.0.4
- ripemd160: 2.0.3
- safe-buffer: 5.2.1
- sha.js: 2.4.12
-
- create-require@1.1.1: {}
-
cross-spawn@7.0.6:
dependencies:
path-key: 3.1.1
shebang-command: 2.0.0
which: 2.0.2
- crypto-browserify@3.12.1:
- dependencies:
- browserify-cipher: 1.0.1
- browserify-sign: 4.2.5
- create-ecdh: 4.0.4
- create-hash: 1.2.0
- create-hmac: 1.1.7
- diffie-hellman: 5.0.3
- hash-base: 3.0.5
- inherits: 2.0.4
- pbkdf2: 3.1.5
- public-encrypt: 4.0.3
- randombytes: 2.1.0
- randomfill: 1.0.4
-
css-tree@3.1.0:
dependencies:
mdn-data: 2.12.2
@@ -5153,19 +4890,8 @@ snapshots:
dequal@2.0.3: {}
- des.js@1.1.0:
- dependencies:
- inherits: 2.0.4
- minimalistic-assert: 1.0.1
-
detect-libc@2.1.2: {}
- diffie-hellman@5.0.3:
- dependencies:
- bn.js: 4.12.3
- miller-rabin: 4.0.1
- randombytes: 2.1.0
-
doctrine@2.1.0:
dependencies:
esutils: 2.0.3
@@ -5174,8 +4900,6 @@ snapshots:
dom-accessibility-api@0.6.3: {}
- domain-browser@4.22.0: {}
-
dunder-proto@1.0.1:
dependencies:
call-bind-apply-helpers: 1.0.2
@@ -5184,17 +4908,7 @@ snapshots:
ee-first@1.1.1: {}
- electron-to-chromium@1.5.302: {}
-
- elliptic@6.6.1:
- dependencies:
- bn.js: 4.12.3
- brorand: 1.1.0
- hash.js: 1.1.7
- hmac-drbg: 1.0.1
- inherits: 2.0.4
- minimalistic-assert: 1.0.1
- minimalistic-crypto-utils: 1.0.1
+ electron-to-chromium@1.5.331: {}
emoji-name-map@2.0.3: {}
@@ -5272,7 +4986,7 @@ snapshots:
es-errors@1.3.0: {}
- es-iterator-helpers@1.2.2:
+ es-iterator-helpers@1.3.1:
dependencies:
call-bind: 1.0.8
call-bound: 1.0.4
@@ -5289,9 +5003,10 @@ snapshots:
has-symbols: 1.1.0
internal-slot: 1.1.0
iterator.prototype: 1.1.5
+ math-intrinsics: 1.1.0
safe-array-concat: 1.1.3
- es-module-lexer@1.7.0: {}
+ es-module-lexer@2.0.0: {}
es-object-atoms@1.1.1:
dependencies:
@@ -5349,57 +5064,56 @@ snapshots:
escape-string-regexp@4.0.0: {}
- escape-string-regexp@5.0.0: {}
-
eslint-import-context@0.1.9(unrs-resolver@1.11.1):
dependencies:
- get-tsconfig: 4.13.6
+ get-tsconfig: 4.13.7
stable-hash-x: 0.2.0
optionalDependencies:
unrs-resolver: 1.11.1
- eslint-import-resolver-typescript@4.4.4(eslint-plugin-import-x@4.16.1(@typescript-eslint/utils@8.56.1(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.2(jiti@2.6.1)))(eslint@9.39.2(jiti@2.6.1)):
+ eslint-import-resolver-typescript@4.4.4(eslint-plugin-import-x@4.16.2(@typescript-eslint/utils@8.58.0(eslint@10.1.0(jiti@2.6.1))(typescript@5.9.3))(eslint@10.1.0(jiti@2.6.1)))(eslint@10.1.0(jiti@2.6.1)):
dependencies:
debug: 4.4.3
- eslint: 9.39.2(jiti@2.6.1)
+ eslint: 10.1.0(jiti@2.6.1)
eslint-import-context: 0.1.9(unrs-resolver@1.11.1)
- get-tsconfig: 4.13.6
+ get-tsconfig: 4.13.7
is-bun-module: 2.0.0
stable-hash-x: 0.2.0
tinyglobby: 0.2.15
unrs-resolver: 1.11.1
optionalDependencies:
- eslint-plugin-import-x: 4.16.1(@typescript-eslint/utils@8.56.1(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.2(jiti@2.6.1))
+ eslint-plugin-import-x: 4.16.2(@typescript-eslint/utils@8.58.0(eslint@10.1.0(jiti@2.6.1))(typescript@5.9.3))(eslint@10.1.0(jiti@2.6.1))
transitivePeerDependencies:
- supports-color
- eslint-plugin-import-x@4.16.1(@typescript-eslint/utils@8.56.1(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.2(jiti@2.6.1)):
+ eslint-plugin-import-x@4.16.2(@typescript-eslint/utils@8.58.0(eslint@10.1.0(jiti@2.6.1))(typescript@5.9.3))(eslint@10.1.0(jiti@2.6.1)):
dependencies:
- '@typescript-eslint/types': 8.56.1
- comment-parser: 1.4.5
+ '@package-json/types': 0.0.12
+ '@typescript-eslint/types': 8.58.0
+ comment-parser: 1.4.6
debug: 4.4.3
- eslint: 9.39.2(jiti@2.6.1)
+ eslint: 10.1.0(jiti@2.6.1)
eslint-import-context: 0.1.9(unrs-resolver@1.11.1)
is-glob: 4.0.3
- minimatch: 10.2.3
+ minimatch: 10.2.5
semver: 7.7.4
stable-hash-x: 0.2.0
unrs-resolver: 1.11.1
optionalDependencies:
- '@typescript-eslint/utils': 8.56.1(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3)
+ '@typescript-eslint/utils': 8.58.0(eslint@10.1.0(jiti@2.6.1))(typescript@5.9.3)
transitivePeerDependencies:
- supports-color
- eslint-plugin-jsdoc@62.7.1(eslint@9.39.2(jiti@2.6.1)):
+ eslint-plugin-jsdoc@62.9.0(eslint@10.1.0(jiti@2.6.1)):
dependencies:
- '@es-joy/jsdoccomment': 0.84.0
+ '@es-joy/jsdoccomment': 0.86.0
'@es-joy/resolve.exports': 1.2.0
are-docs-informative: 0.0.2
- comment-parser: 1.4.5
+ comment-parser: 1.4.6
debug: 4.4.3
escape-string-regexp: 4.0.0
- eslint: 9.39.2(jiti@2.6.1)
- espree: 11.1.1
+ eslint: 10.1.0(jiti@2.6.1)
+ espree: 11.2.0
esquery: 1.7.0
html-entities: 2.6.0
object-deep-merge: 2.0.0
@@ -5410,30 +5124,30 @@ snapshots:
transitivePeerDependencies:
- supports-color
- eslint-plugin-react-hooks@7.0.1(eslint@9.39.2(jiti@2.6.1)):
+ eslint-plugin-react-hooks@7.0.1(eslint@10.1.0(jiti@2.6.1)):
dependencies:
'@babel/core': 7.29.0
- '@babel/parser': 7.29.0
- eslint: 9.39.2(jiti@2.6.1)
+ '@babel/parser': 7.29.2
+ eslint: 10.1.0(jiti@2.6.1)
hermes-parser: 0.25.1
zod: 4.3.6
zod-validation-error: 4.0.2(zod@4.3.6)
transitivePeerDependencies:
- supports-color
- eslint-plugin-react@7.37.5(eslint@9.39.2(jiti@2.6.1)):
+ eslint-plugin-react@7.37.5(eslint@10.1.0(jiti@2.6.1)):
dependencies:
array-includes: 3.1.9
array.prototype.findlast: 1.2.5
array.prototype.flatmap: 1.3.3
array.prototype.tosorted: 1.1.4
doctrine: 2.1.0
- es-iterator-helpers: 1.2.2
- eslint: 9.39.2(jiti@2.6.1)
+ es-iterator-helpers: 1.3.1
+ eslint: 10.1.0(jiti@2.6.1)
estraverse: 5.3.0
hasown: 2.0.2
jsx-ast-utils: 3.3.5
- minimatch: 3.1.4
+ minimatch: 3.1.5
object.entries: 1.1.9
object.fromentries: 2.0.8
object.values: 1.2.1
@@ -5443,39 +5157,36 @@ snapshots:
string.prototype.matchall: 4.0.12
string.prototype.repeat: 1.0.0
- eslint-scope@8.4.0:
+ eslint-scope@9.1.2:
dependencies:
+ '@types/esrecurse': 4.3.1
+ '@types/estree': 1.0.8
esrecurse: 4.3.0
estraverse: 5.3.0
eslint-visitor-keys@3.4.3: {}
- eslint-visitor-keys@4.2.1: {}
-
eslint-visitor-keys@5.0.1: {}
- eslint@9.39.2(jiti@2.6.1):
+ eslint@10.1.0(jiti@2.6.1):
dependencies:
- '@eslint-community/eslint-utils': 4.9.1(eslint@9.39.2(jiti@2.6.1))
+ '@eslint-community/eslint-utils': 4.9.1(eslint@10.1.0(jiti@2.6.1))
'@eslint-community/regexpp': 4.12.2
- '@eslint/config-array': 0.21.1
- '@eslint/config-helpers': 0.4.2
- '@eslint/core': 0.17.0
- '@eslint/eslintrc': 3.3.4
- '@eslint/js': 9.39.2
- '@eslint/plugin-kit': 0.4.1
+ '@eslint/config-array': 0.23.3
+ '@eslint/config-helpers': 0.5.3
+ '@eslint/core': 1.1.1
+ '@eslint/plugin-kit': 0.6.1
'@humanfs/node': 0.16.7
'@humanwhocodes/module-importer': 1.0.1
'@humanwhocodes/retry': 0.4.3
'@types/estree': 1.0.8
ajv: 6.14.0
- chalk: 4.1.2
cross-spawn: 7.0.6
debug: 4.4.3
escape-string-regexp: 4.0.0
- eslint-scope: 8.4.0
- eslint-visitor-keys: 4.2.1
- espree: 10.4.0
+ eslint-scope: 9.1.2
+ eslint-visitor-keys: 5.0.1
+ espree: 11.2.0
esquery: 1.7.0
esutils: 2.0.3
fast-deep-equal: 3.1.3
@@ -5486,8 +5197,7 @@ snapshots:
imurmurhash: 0.1.4
is-glob: 4.0.3
json-stable-stringify-without-jsonify: 1.0.1
- lodash.merge: 4.6.2
- minimatch: 3.1.4
+ minimatch: 10.2.5
natural-compare: 1.4.0
optionator: 0.9.4
optionalDependencies:
@@ -5495,13 +5205,7 @@ snapshots:
transitivePeerDependencies:
- supports-color
- espree@10.4.0:
- dependencies:
- acorn: 8.16.0
- acorn-jsx: 5.3.2(acorn@8.16.0)
- eslint-visitor-keys: 4.2.1
-
- espree@11.1.1:
+ espree@11.2.0:
dependencies:
acorn: 8.16.0
acorn-jsx: 5.3.2(acorn@8.16.0)
@@ -5517,8 +5221,6 @@ snapshots:
estraverse@5.3.0: {}
- estree-walker@2.0.2: {}
-
estree-walker@3.0.3:
dependencies:
'@types/estree': 1.0.8
@@ -5529,13 +5231,6 @@ snapshots:
eventemitter3@5.0.4: {}
- events@3.3.0: {}
-
- evp_bytestokey@1.0.3:
- dependencies:
- md5.js: 1.3.5
- safe-buffer: 5.2.1
-
expect-type@1.3.0: {}
express@5.2.1:
@@ -5595,9 +5290,9 @@ snapshots:
dependencies:
walk-up-path: 4.0.0
- fdir@6.5.0(picomatch@4.0.3):
+ fdir@6.5.0(picomatch@4.0.4):
optionalDependencies:
- picomatch: 4.0.3
+ picomatch: 4.0.4
file-entry-cache@8.0.0:
dependencies:
@@ -5625,10 +5320,10 @@ snapshots:
flat-cache@4.0.1:
dependencies:
- flatted: 3.3.3
+ flatted: 3.4.2
keyv: 4.5.4
- flatted@3.3.3: {}
+ flatted@3.4.2: {}
follow-redirects@1.15.11: {}
@@ -5701,7 +5396,7 @@ snapshots:
es-errors: 1.3.0
get-intrinsic: 1.3.0
- get-tsconfig@4.13.6:
+ get-tsconfig@4.13.7:
dependencies:
resolve-pkg-maps: 1.0.0
@@ -5715,36 +5410,25 @@ snapshots:
dependencies:
is-glob: 4.0.3
- globals@14.0.0: {}
-
- globals@17.3.0: {}
+ globals@17.4.0: {}
globalthis@1.0.4:
dependencies:
define-properties: 1.2.1
gopd: 1.2.0
- globby@14.1.0:
- dependencies:
- '@sindresorhus/merge-streams': 2.3.0
- fast-glob: 3.3.3
- ignore: 7.0.5
- path-type: 6.0.0
- slash: 5.1.0
- unicorn-magic: 0.3.0
-
gopd@1.2.0: {}
graceful-fs@4.2.11: {}
happy-dom@20.6.1:
dependencies:
- '@types/node': 24.10.13
+ '@types/node': 24.12.0
'@types/whatwg-mimetype': 3.0.2
'@types/ws': 8.18.1
entities: 6.0.1
whatwg-mimetype: 3.0.0
- ws: 8.19.0
+ ws: 8.20.0
transitivePeerDependencies:
- bufferutil
- utf-8-validate
@@ -5768,23 +5452,6 @@ snapshots:
dependencies:
has-symbols: 1.1.0
- hash-base@3.0.5:
- dependencies:
- inherits: 2.0.4
- safe-buffer: 5.2.1
-
- hash-base@3.1.2:
- dependencies:
- inherits: 2.0.4
- readable-stream: 2.3.8
- safe-buffer: 5.2.1
- to-buffer: 1.2.2
-
- hash.js@1.1.7:
- dependencies:
- inherits: 2.0.4
- minimalistic-assert: 1.0.1
-
hasown@2.0.2:
dependencies:
function-bind: 1.1.2
@@ -5795,12 +5462,6 @@ snapshots:
dependencies:
hermes-estree: 0.25.1
- hmac-drbg@1.0.1:
- dependencies:
- hash.js: 1.1.7
- minimalistic-assert: 1.0.1
- minimalistic-crypto-utils: 1.0.1
-
html-encoding-sniffer@6.0.0:
dependencies:
'@exodus/bytes': 1.14.1
@@ -5828,8 +5489,6 @@ snapshots:
http-vary@1.0.3: {}
- https-browserify@1.0.0: {}
-
https-proxy-agent@7.0.6:
dependencies:
agent-base: 7.1.4
@@ -5843,19 +5502,12 @@ snapshots:
dependencies:
safer-buffer: 2.1.2
- ieee754@1.2.1: {}
-
ignore@5.3.2: {}
ignore@7.0.5: {}
immer@11.1.4: {}
- import-fresh@3.3.1:
- dependencies:
- parent-module: 1.0.1
- resolve-from: 4.0.0
-
imurmurhash@0.1.4: {}
indent-string@4.0.0: {}
@@ -5870,11 +5522,6 @@ snapshots:
ipaddr.js@1.9.1: {}
- is-arguments@1.2.0:
- dependencies:
- call-bound: 1.0.4
- has-tostringtag: 1.0.2
-
is-array-buffer@3.0.5:
dependencies:
call-bind: 1.0.8
@@ -5945,11 +5592,6 @@ snapshots:
is-map@2.0.3: {}
- is-nan@1.3.2:
- dependencies:
- call-bind: 1.0.8
- define-properties: 1.2.1
-
is-negative-zero@2.0.3: {}
is-number-object@1.1.1:
@@ -6002,14 +5644,10 @@ snapshots:
call-bound: 1.0.4
get-intrinsic: 1.3.0
- isarray@1.0.0: {}
-
isarray@2.0.5: {}
isexe@2.0.0: {}
- isomorphic-timers-promises@1.0.1: {}
-
istanbul-lib-coverage@3.2.2: {}
istanbul-lib-report@3.0.1:
@@ -6042,7 +5680,7 @@ snapshots:
dependencies:
argparse: 2.0.1
- jsdoc-type-pratt-parser@7.1.1: {}
+ jsdoc-type-pratt-parser@7.2.0: {}
jsdom@28.1.0:
dependencies:
@@ -6092,22 +5730,26 @@ snapshots:
dependencies:
json-buffer: 3.0.1
- knip@5.85.0(@types/node@24.10.13)(typescript@5.9.3):
+ knip@6.2.0(@emnapi/core@1.9.2)(@emnapi/runtime@1.9.2):
dependencies:
'@nodelib/fs.walk': 1.2.8
- '@types/node': 24.10.13
fast-glob: 3.3.3
formatly: 0.3.0
+ get-tsconfig: 4.13.7
jiti: 2.6.1
- js-yaml: 4.1.1
minimist: 1.2.8
- oxc-resolver: 11.19.0
+ oxc-parser: 0.121.0(@emnapi/core@1.9.2)(@emnapi/runtime@1.9.2)
+ oxc-resolver: 11.19.1(@emnapi/core@1.9.2)(@emnapi/runtime@1.9.2)
picocolors: 1.1.1
- picomatch: 4.0.3
- smol-toml: 1.6.0
+ picomatch: 4.0.4
+ smol-toml: 1.6.1
strip-json-comments: 5.0.3
- typescript: 5.9.3
+ unbash: 2.2.0
+ yaml: 2.8.3
zod: 4.3.6
+ transitivePeerDependencies:
+ - '@emnapi/core'
+ - '@emnapi/runtime'
levn@0.4.1:
dependencies:
@@ -6163,19 +5805,18 @@ snapshots:
lightningcss-win32-arm64-msvc: 1.31.1
lightningcss-win32-x64-msvc: 1.31.1
- lint-staged@16.2.7:
+ lint-staged@16.4.0:
dependencies:
commander: 14.0.3
listr2: 9.0.5
- micromatch: 4.0.8
- nano-spawn: 2.0.0
- pidtree: 0.6.0
+ picomatch: 4.0.4
string-argv: 0.3.2
- yaml: 2.8.2
+ tinyexec: 1.0.4
+ yaml: 2.8.3
listr2@9.0.5:
dependencies:
- cli-truncate: 5.1.1
+ cli-truncate: 5.2.0
colorette: 2.0.20
eventemitter3: 5.0.4
log-update: 6.1.0
@@ -6186,14 +5827,12 @@ snapshots:
dependencies:
p-locate: 5.0.0
- lodash.merge@4.6.2: {}
-
log-update@6.1.0:
dependencies:
ansi-escapes: 7.3.0
cli-cursor: 5.0.0
slice-ansi: 7.1.2
- strip-ansi: 7.1.2
+ strip-ansi: 7.2.0
wrap-ansi: 9.0.2
loose-envify@1.4.0:
@@ -6214,7 +5853,7 @@ snapshots:
magicast@0.5.2:
dependencies:
- '@babel/parser': 7.29.0
+ '@babel/parser': 7.29.2
'@babel/types': 7.29.0
source-map-js: 1.2.1
@@ -6224,12 +5863,6 @@ snapshots:
math-intrinsics@1.1.0: {}
- md5.js@1.3.5:
- dependencies:
- hash-base: 3.0.5
- inherits: 2.0.4
- safe-buffer: 5.2.1
-
mdn-data@2.12.2: {}
media-typer@1.1.0: {}
@@ -6241,12 +5874,7 @@ snapshots:
micromatch@4.0.8:
dependencies:
braces: 3.0.3
- picomatch: 2.3.1
-
- miller-rabin@4.0.1:
- dependencies:
- bn.js: 4.12.3
- brorand: 1.1.0
+ picomatch: 2.3.2
mime-db@1.52.0: {}
@@ -6264,24 +5892,18 @@ snapshots:
min-indent@1.0.1: {}
- minimalistic-assert@1.0.1: {}
-
- minimalistic-crypto-utils@1.0.1: {}
-
- minimatch@10.2.3:
+ minimatch@10.2.5:
dependencies:
- brace-expansion: 5.0.3
+ brace-expansion: 5.0.5
- minimatch@3.1.4:
+ minimatch@3.1.5:
dependencies:
- brace-expansion: 1.1.12
+ brace-expansion: 1.1.13
minimist@1.2.8: {}
ms@2.1.3: {}
- nano-spawn@2.0.0: {}
-
nanoid@3.3.11: {}
napi-postinstall@0.3.4: {}
@@ -6297,37 +5919,7 @@ snapshots:
object.entries: 1.1.9
semver: 6.3.1
- node-releases@2.0.27: {}
-
- node-stdlib-browser@1.3.1:
- dependencies:
- assert: 2.1.0
- browser-resolve: 2.0.0
- browserify-zlib: 0.2.0
- buffer: 5.7.1
- console-browserify: 1.2.0
- constants-browserify: 1.0.0
- create-require: 1.1.1
- crypto-browserify: 3.12.1
- domain-browser: 4.22.0
- events: 3.3.0
- https-browserify: 1.0.0
- isomorphic-timers-promises: 1.0.1
- os-browserify: 0.3.0
- path-browserify: 1.0.1
- pkg-dir: 5.0.0
- process: 0.11.10
- punycode: 1.4.1
- querystring-es3: 0.2.1
- readable-stream: 3.6.2
- stream-browserify: 3.0.0
- stream-http: 3.2.0
- string_decoder: 1.3.0
- timers-browserify: 2.0.12
- tty-browserify: 0.0.1
- url: 0.11.4
- util: 0.12.5
- vm-browserify: 1.1.2
+ node-releases@2.0.37: {}
object-assign@4.1.1: {}
@@ -6337,11 +5929,6 @@ snapshots:
object-inspect@1.13.4: {}
- object-is@1.1.6:
- dependencies:
- call-bind: 1.0.8
- define-properties: 1.2.1
-
object-keys@1.1.1: {}
object.assign@4.1.7:
@@ -6397,36 +5984,65 @@ snapshots:
type-check: 0.4.0
word-wrap: 1.2.5
- os-browserify@0.3.0: {}
-
own-keys@1.0.1:
dependencies:
get-intrinsic: 1.3.0
object-keys: 1.1.1
safe-push-apply: 1.0.0
- oxc-resolver@11.19.0:
+ oxc-parser@0.121.0(@emnapi/core@1.9.2)(@emnapi/runtime@1.9.2):
+ dependencies:
+ '@oxc-project/types': 0.121.0
+ optionalDependencies:
+ '@oxc-parser/binding-android-arm-eabi': 0.121.0
+ '@oxc-parser/binding-android-arm64': 0.121.0
+ '@oxc-parser/binding-darwin-arm64': 0.121.0
+ '@oxc-parser/binding-darwin-x64': 0.121.0
+ '@oxc-parser/binding-freebsd-x64': 0.121.0
+ '@oxc-parser/binding-linux-arm-gnueabihf': 0.121.0
+ '@oxc-parser/binding-linux-arm-musleabihf': 0.121.0
+ '@oxc-parser/binding-linux-arm64-gnu': 0.121.0
+ '@oxc-parser/binding-linux-arm64-musl': 0.121.0
+ '@oxc-parser/binding-linux-ppc64-gnu': 0.121.0
+ '@oxc-parser/binding-linux-riscv64-gnu': 0.121.0
+ '@oxc-parser/binding-linux-riscv64-musl': 0.121.0
+ '@oxc-parser/binding-linux-s390x-gnu': 0.121.0
+ '@oxc-parser/binding-linux-x64-gnu': 0.121.0
+ '@oxc-parser/binding-linux-x64-musl': 0.121.0
+ '@oxc-parser/binding-openharmony-arm64': 0.121.0
+ '@oxc-parser/binding-wasm32-wasi': 0.121.0(@emnapi/core@1.9.2)(@emnapi/runtime@1.9.2)
+ '@oxc-parser/binding-win32-arm64-msvc': 0.121.0
+ '@oxc-parser/binding-win32-ia32-msvc': 0.121.0
+ '@oxc-parser/binding-win32-x64-msvc': 0.121.0
+ transitivePeerDependencies:
+ - '@emnapi/core'
+ - '@emnapi/runtime'
+
+ oxc-resolver@11.19.1(@emnapi/core@1.9.2)(@emnapi/runtime@1.9.2):
optionalDependencies:
- '@oxc-resolver/binding-android-arm-eabi': 11.19.0
- '@oxc-resolver/binding-android-arm64': 11.19.0
- '@oxc-resolver/binding-darwin-arm64': 11.19.0
- '@oxc-resolver/binding-darwin-x64': 11.19.0
- '@oxc-resolver/binding-freebsd-x64': 11.19.0
- '@oxc-resolver/binding-linux-arm-gnueabihf': 11.19.0
- '@oxc-resolver/binding-linux-arm-musleabihf': 11.19.0
- '@oxc-resolver/binding-linux-arm64-gnu': 11.19.0
- '@oxc-resolver/binding-linux-arm64-musl': 11.19.0
- '@oxc-resolver/binding-linux-ppc64-gnu': 11.19.0
- '@oxc-resolver/binding-linux-riscv64-gnu': 11.19.0
- '@oxc-resolver/binding-linux-riscv64-musl': 11.19.0
- '@oxc-resolver/binding-linux-s390x-gnu': 11.19.0
- '@oxc-resolver/binding-linux-x64-gnu': 11.19.0
- '@oxc-resolver/binding-linux-x64-musl': 11.19.0
- '@oxc-resolver/binding-openharmony-arm64': 11.19.0
- '@oxc-resolver/binding-wasm32-wasi': 11.19.0
- '@oxc-resolver/binding-win32-arm64-msvc': 11.19.0
- '@oxc-resolver/binding-win32-ia32-msvc': 11.19.0
- '@oxc-resolver/binding-win32-x64-msvc': 11.19.0
+ '@oxc-resolver/binding-android-arm-eabi': 11.19.1
+ '@oxc-resolver/binding-android-arm64': 11.19.1
+ '@oxc-resolver/binding-darwin-arm64': 11.19.1
+ '@oxc-resolver/binding-darwin-x64': 11.19.1
+ '@oxc-resolver/binding-freebsd-x64': 11.19.1
+ '@oxc-resolver/binding-linux-arm-gnueabihf': 11.19.1
+ '@oxc-resolver/binding-linux-arm-musleabihf': 11.19.1
+ '@oxc-resolver/binding-linux-arm64-gnu': 11.19.1
+ '@oxc-resolver/binding-linux-arm64-musl': 11.19.1
+ '@oxc-resolver/binding-linux-ppc64-gnu': 11.19.1
+ '@oxc-resolver/binding-linux-riscv64-gnu': 11.19.1
+ '@oxc-resolver/binding-linux-riscv64-musl': 11.19.1
+ '@oxc-resolver/binding-linux-s390x-gnu': 11.19.1
+ '@oxc-resolver/binding-linux-x64-gnu': 11.19.1
+ '@oxc-resolver/binding-linux-x64-musl': 11.19.1
+ '@oxc-resolver/binding-openharmony-arm64': 11.19.1
+ '@oxc-resolver/binding-wasm32-wasi': 11.19.1(@emnapi/core@1.9.2)(@emnapi/runtime@1.9.2)
+ '@oxc-resolver/binding-win32-arm64-msvc': 11.19.1
+ '@oxc-resolver/binding-win32-ia32-msvc': 11.19.1
+ '@oxc-resolver/binding-win32-x64-msvc': 11.19.1
+ transitivePeerDependencies:
+ - '@emnapi/core'
+ - '@emnapi/runtime'
p-limit@3.1.0:
dependencies:
@@ -6436,20 +6052,6 @@ snapshots:
dependencies:
p-limit: 3.1.0
- pako@1.0.11: {}
-
- parent-module@1.0.1:
- dependencies:
- callsites: 3.1.0
-
- parse-asn1@5.1.9:
- dependencies:
- asn1.js: 4.10.1
- browserify-aes: 1.2.0
- evp_bytestokey: 1.0.3
- pbkdf2: 3.1.5
- safe-buffer: 5.2.1
-
parse-imports-exports@0.2.4:
dependencies:
parse-statements: 1.0.11
@@ -6462,8 +6064,6 @@ snapshots:
parseurl@1.3.3: {}
- path-browserify@1.0.1: {}
-
path-exists@4.0.0: {}
path-key@3.1.1: {}
@@ -6472,19 +6072,8 @@ snapshots:
path-to-regexp@8.3.0: {}
- path-type@6.0.0: {}
-
pathe@2.0.3: {}
- pbkdf2@3.1.5:
- dependencies:
- create-hash: 1.2.0
- create-hmac: 1.1.7
- ripemd160: 2.0.3
- safe-buffer: 5.2.1
- sha.js: 2.4.12
- to-buffer: 1.2.2
-
pg-cloudflare@1.3.0:
optional: true
@@ -6522,21 +6111,15 @@ snapshots:
picocolors@1.1.1: {}
- picomatch@2.3.1: {}
-
- picomatch@4.0.3: {}
-
- pidtree@0.6.0: {}
+ picomatch@2.3.2: {}
- pkg-dir@5.0.0:
- dependencies:
- find-up: 5.0.0
+ picomatch@4.0.4: {}
- playwright-core@1.58.2: {}
+ playwright-core@1.59.1: {}
- playwright@1.58.2:
+ playwright@1.59.1:
dependencies:
- playwright-core: 1.58.2
+ playwright-core: 1.59.1
optionalDependencies:
fsevents: 2.3.2
@@ -6568,10 +6151,6 @@ snapshots:
ansi-styles: 5.2.0
react-is: 17.0.2
- process-nextick-args@2.0.1: {}
-
- process@0.11.10: {}
-
prop-types@15.8.1:
dependencies:
loose-envify: 1.4.0
@@ -6585,36 +6164,14 @@ snapshots:
proxy-from-env@1.1.0: {}
- public-encrypt@4.0.3:
- dependencies:
- bn.js: 4.12.3
- browserify-rsa: 4.1.1
- create-hash: 1.2.0
- parse-asn1: 5.1.9
- randombytes: 2.1.0
- safe-buffer: 5.2.1
-
- punycode@1.4.1: {}
-
punycode@2.3.1: {}
qs@6.15.0:
dependencies:
side-channel: 1.1.0
- querystring-es3@0.2.1: {}
-
queue-microtask@1.2.3: {}
- randombytes@2.1.0:
- dependencies:
- safe-buffer: 5.2.1
-
- randomfill@1.0.4:
- dependencies:
- randombytes: 2.1.0
- safe-buffer: 5.2.1
-
range-parser@1.2.1: {}
raw-body@3.0.2:
@@ -6666,22 +6223,6 @@ snapshots:
dependencies:
loose-envify: 1.4.0
- readable-stream@2.3.8:
- dependencies:
- core-util-is: 1.0.3
- inherits: 2.0.4
- isarray: 1.0.0
- process-nextick-args: 2.0.1
- safe-buffer: 5.1.2
- string_decoder: 1.1.1
- util-deprecate: 1.0.2
-
- readable-stream@3.6.2:
- dependencies:
- inherits: 2.0.4
- string_decoder: 1.3.0
- util-deprecate: 1.0.2
-
redent@3.0.0:
dependencies:
indent-string: 4.0.0
@@ -6719,16 +6260,8 @@ snapshots:
reserved-identifiers@1.2.0: {}
- resolve-from@4.0.0: {}
-
resolve-pkg-maps@1.0.0: {}
- resolve@1.22.11:
- dependencies:
- is-core-module: 2.16.1
- path-parse: 1.0.7
- supports-preserve-symlinks-flag: 1.0.0
-
resolve@2.0.0-next.6:
dependencies:
es-errors: 1.3.0
@@ -6747,11 +6280,6 @@ snapshots:
rfdc@1.4.1: {}
- ripemd160@2.0.3:
- dependencies:
- hash-base: 3.1.2
- inherits: 2.0.4
-
rollup@4.59.0:
dependencies:
'@types/estree': 1.0.8
@@ -6805,10 +6333,6 @@ snapshots:
has-symbols: 1.1.0
isarray: 2.0.5
- safe-buffer@5.1.2: {}
-
- safe-buffer@5.2.1: {}
-
safe-push-apply@1.0.0:
dependencies:
es-errors: 1.3.0
@@ -6883,16 +6407,8 @@ snapshots:
es-errors: 1.3.0
es-object-atoms: 1.1.1
- setimmediate@1.0.5: {}
-
setprototypeof@1.2.0: {}
- sha.js@2.4.12:
- dependencies:
- inherits: 2.0.4
- safe-buffer: 5.2.1
- to-buffer: 1.2.2
-
shebang-command@2.0.0:
dependencies:
shebang-regex: 3.0.0
@@ -6931,14 +6447,17 @@ snapshots:
signal-exit@4.1.0: {}
- slash@5.1.0: {}
-
slice-ansi@7.1.2:
dependencies:
ansi-styles: 6.2.3
is-fullwidth-code-point: 5.1.0
- smol-toml@1.6.0: {}
+ slice-ansi@8.0.0:
+ dependencies:
+ ansi-styles: 6.2.3
+ is-fullwidth-code-point: 5.1.0
+
+ smol-toml@1.6.1: {}
source-map-js@1.2.1: {}
@@ -6968,37 +6487,25 @@ snapshots:
statuses@2.0.2: {}
- std-env@3.10.0: {}
+ std-env@4.0.0: {}
stop-iteration-iterator@1.1.0:
dependencies:
es-errors: 1.3.0
internal-slot: 1.1.0
- stream-browserify@3.0.0:
- dependencies:
- inherits: 2.0.4
- readable-stream: 3.6.2
-
- stream-http@3.2.0:
- dependencies:
- builtin-status-codes: 3.0.0
- inherits: 2.0.4
- readable-stream: 3.6.2
- xtend: 4.0.2
-
string-argv@0.3.2: {}
string-width@7.2.0:
dependencies:
emoji-regex: 10.6.0
get-east-asian-width: 1.5.0
- strip-ansi: 7.1.2
+ strip-ansi: 7.2.0
string-width@8.2.0:
dependencies:
get-east-asian-width: 1.5.0
- strip-ansi: 7.1.2
+ strip-ansi: 7.2.0
string.prototype.matchall@4.0.12:
dependencies:
@@ -7044,15 +6551,7 @@ snapshots:
define-properties: 1.2.1
es-object-atoms: 1.1.1
- string_decoder@1.1.1:
- dependencies:
- safe-buffer: 5.1.2
-
- string_decoder@1.3.0:
- dependencies:
- safe-buffer: 5.2.1
-
- strip-ansi@7.1.2:
+ strip-ansi@7.2.0:
dependencies:
ansi-regex: 6.2.2
@@ -7060,8 +6559,6 @@ snapshots:
dependencies:
min-indent: 1.0.1
- strip-json-comments@3.1.1: {}
-
strip-json-comments@5.0.3: {}
supports-color@7.2.0:
@@ -7084,20 +6581,16 @@ snapshots:
source-map-support: 0.5.21
optional: true
- timers-browserify@2.0.12:
- dependencies:
- setimmediate: 1.0.5
-
tinybench@2.9.0: {}
- tinyexec@1.0.2: {}
+ tinyexec@1.0.4: {}
tinyglobby@0.2.15:
dependencies:
- fdir: 6.5.0(picomatch@4.0.3)
- picomatch: 4.0.3
+ fdir: 6.5.0(picomatch@4.0.4)
+ picomatch: 4.0.4
- tinyrainbow@3.0.3: {}
+ tinyrainbow@3.1.0: {}
tldts-core@7.0.23: {}
@@ -7105,12 +6598,6 @@ snapshots:
dependencies:
tldts-core: 7.0.23
- to-buffer@1.2.2:
- dependencies:
- isarray: 2.0.5
- safe-buffer: 5.2.1
- typed-array-buffer: 1.0.3
-
to-regex-range@5.0.1:
dependencies:
is-number: 7.0.0
@@ -7132,14 +6619,21 @@ snapshots:
try@1.0.3: {}
- ts-api-utils@2.4.0(typescript@5.9.3):
+ ts-api-utils@2.5.0(typescript@5.9.3):
dependencies:
typescript: 5.9.3
tslib@2.8.1:
optional: true
- tty-browserify@0.0.1: {}
+ turbo@2.9.3:
+ optionalDependencies:
+ '@turbo/darwin-64': 2.9.3
+ '@turbo/darwin-arm64': 2.9.3
+ '@turbo/linux-64': 2.9.3
+ '@turbo/linux-arm64': 2.9.3
+ '@turbo/windows-64': 2.9.3
+ '@turbo/windows-arm64': 2.9.3
type-check@0.4.0:
dependencies:
@@ -7184,19 +6678,21 @@ snapshots:
possible-typed-array-names: 1.1.0
reflect.getprototypeof: 1.0.10
- typescript-eslint@8.56.1(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3):
+ typescript-eslint@8.58.0(eslint@10.1.0(jiti@2.6.1))(typescript@5.9.3):
dependencies:
- '@typescript-eslint/eslint-plugin': 8.56.1(@typescript-eslint/parser@8.56.1(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3)
- '@typescript-eslint/parser': 8.56.1(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3)
- '@typescript-eslint/typescript-estree': 8.56.1(typescript@5.9.3)
- '@typescript-eslint/utils': 8.56.1(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3)
- eslint: 9.39.2(jiti@2.6.1)
+ '@typescript-eslint/eslint-plugin': 8.58.0(@typescript-eslint/parser@8.58.0(eslint@10.1.0(jiti@2.6.1))(typescript@5.9.3))(eslint@10.1.0(jiti@2.6.1))(typescript@5.9.3)
+ '@typescript-eslint/parser': 8.58.0(eslint@10.1.0(jiti@2.6.1))(typescript@5.9.3)
+ '@typescript-eslint/typescript-estree': 8.58.0(typescript@5.9.3)
+ '@typescript-eslint/utils': 8.58.0(eslint@10.1.0(jiti@2.6.1))(typescript@5.9.3)
+ eslint: 10.1.0(jiti@2.6.1)
typescript: 5.9.3
transitivePeerDependencies:
- supports-color
typescript@5.9.3: {}
+ unbash@2.2.0: {}
+
unbox-primitive@1.1.0:
dependencies:
call-bound: 1.0.4
@@ -7206,9 +6702,10 @@ snapshots:
undici-types@7.16.0: {}
- undici@7.22.0: {}
+ undici-types@7.18.2:
+ optional: true
- unicorn-magic@0.3.0: {}
+ undici@7.22.0: {}
unpipe@1.0.0: {}
@@ -7236,9 +6733,9 @@ snapshots:
'@unrs/resolver-binding-win32-ia32-msvc': 1.11.1
'@unrs/resolver-binding-win32-x64-msvc': 1.11.1
- update-browserslist-db@1.2.3(browserslist@4.28.1):
+ update-browserslist-db@1.2.3(browserslist@4.28.2):
dependencies:
- browserslist: 4.28.1
+ browserslist: 4.28.2
escalade: 3.2.0
picocolors: 1.1.1
@@ -7246,98 +6743,103 @@ snapshots:
dependencies:
punycode: 2.3.1
- url@0.11.4:
- dependencies:
- punycode: 1.4.1
- qs: 6.15.0
-
use-sync-external-store@1.6.0(react@18.3.1):
dependencies:
react: 18.3.1
- util-deprecate@1.0.2: {}
-
- util@0.12.5:
- dependencies:
- inherits: 2.0.4
- is-arguments: 1.2.0
- is-generator-function: 1.1.2
- is-typed-array: 1.1.15
- which-typed-array: 1.1.20
-
uuid@13.0.0: {}
vary@1.1.2: {}
- vite-plugin-node-polyfills@0.25.0(rollup@4.59.0)(vite@7.3.1(@types/node@25.2.3)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.44.1)(yaml@2.8.2)):
- dependencies:
- '@rollup/plugin-inject': 5.0.5(rollup@4.59.0)
- node-stdlib-browser: 1.3.1
- vite: 7.3.1(@types/node@25.2.3)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.44.1)(yaml@2.8.2)
- transitivePeerDependencies:
- - rollup
-
- vite-plugin-string-replace@1.1.5:
+ vite@7.3.1(@types/node@24.12.0)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.44.1)(yaml@2.8.3):
dependencies:
- escape-string-regexp: 5.0.0
- globby: 14.1.0
+ esbuild: 0.27.3
+ fdir: 6.5.0(picomatch@4.0.4)
+ picomatch: 4.0.4
+ postcss: 8.5.6
+ rollup: 4.59.0
+ tinyglobby: 0.2.15
+ optionalDependencies:
+ '@types/node': 24.12.0
+ fsevents: 2.3.3
+ jiti: 2.6.1
+ lightningcss: 1.31.1
+ terser: 5.44.1
+ yaml: 2.8.3
- vite@7.3.1(@types/node@25.2.3)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.44.1)(yaml@2.8.2):
+ vite@7.3.1(@types/node@25.5.0)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.44.1)(yaml@2.8.3):
dependencies:
esbuild: 0.27.3
- fdir: 6.5.0(picomatch@4.0.3)
- picomatch: 4.0.3
+ fdir: 6.5.0(picomatch@4.0.4)
+ picomatch: 4.0.4
postcss: 8.5.6
rollup: 4.59.0
tinyglobby: 0.2.15
optionalDependencies:
- '@types/node': 25.2.3
+ '@types/node': 25.5.0
fsevents: 2.3.3
jiti: 2.6.1
lightningcss: 1.31.1
terser: 5.44.1
- yaml: 2.8.2
-
- vitest@4.0.18(@types/node@25.2.3)(happy-dom@20.6.1)(jiti@2.6.1)(jsdom@28.1.0)(lightningcss@1.31.1)(terser@5.44.1)(yaml@2.8.2):
- dependencies:
- '@vitest/expect': 4.0.18
- '@vitest/mocker': 4.0.18(vite@7.3.1(@types/node@25.2.3)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.44.1)(yaml@2.8.2))
- '@vitest/pretty-format': 4.0.18
- '@vitest/runner': 4.0.18
- '@vitest/snapshot': 4.0.18
- '@vitest/spy': 4.0.18
- '@vitest/utils': 4.0.18
- es-module-lexer: 1.7.0
+ yaml: 2.8.3
+
+ vitest@4.1.2(@types/node@24.12.0)(happy-dom@20.6.1)(jsdom@28.1.0)(vite@7.3.1(@types/node@24.12.0)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.44.1)(yaml@2.8.3)):
+ dependencies:
+ '@vitest/expect': 4.1.2
+ '@vitest/mocker': 4.1.2(vite@7.3.1(@types/node@24.12.0)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.44.1)(yaml@2.8.3))
+ '@vitest/pretty-format': 4.1.2
+ '@vitest/runner': 4.1.2
+ '@vitest/snapshot': 4.1.2
+ '@vitest/spy': 4.1.2
+ '@vitest/utils': 4.1.2
+ es-module-lexer: 2.0.0
expect-type: 1.3.0
magic-string: 0.30.21
obug: 2.1.1
pathe: 2.0.3
- picomatch: 4.0.3
- std-env: 3.10.0
+ picomatch: 4.0.4
+ std-env: 4.0.0
tinybench: 2.9.0
- tinyexec: 1.0.2
+ tinyexec: 1.0.4
tinyglobby: 0.2.15
- tinyrainbow: 3.0.3
- vite: 7.3.1(@types/node@25.2.3)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.44.1)(yaml@2.8.2)
+ tinyrainbow: 3.1.0
+ vite: 7.3.1(@types/node@24.12.0)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.44.1)(yaml@2.8.3)
why-is-node-running: 2.3.0
optionalDependencies:
- '@types/node': 25.2.3
+ '@types/node': 24.12.0
happy-dom: 20.6.1
jsdom: 28.1.0
transitivePeerDependencies:
- - jiti
- - less
- - lightningcss
- msw
- - sass
- - sass-embedded
- - stylus
- - sugarss
- - terser
- - tsx
- - yaml
- vm-browserify@1.1.2: {}
+ vitest@4.1.2(@types/node@25.5.0)(happy-dom@20.6.1)(jsdom@28.1.0)(vite@7.3.1(@types/node@25.5.0)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.44.1)(yaml@2.8.3)):
+ dependencies:
+ '@vitest/expect': 4.1.2
+ '@vitest/mocker': 4.1.2(vite@7.3.1(@types/node@25.5.0)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.44.1)(yaml@2.8.3))
+ '@vitest/pretty-format': 4.1.2
+ '@vitest/runner': 4.1.2
+ '@vitest/snapshot': 4.1.2
+ '@vitest/spy': 4.1.2
+ '@vitest/utils': 4.1.2
+ es-module-lexer: 2.0.0
+ expect-type: 1.3.0
+ magic-string: 0.30.21
+ obug: 2.1.1
+ pathe: 2.0.3
+ picomatch: 4.0.4
+ std-env: 4.0.0
+ tinybench: 2.9.0
+ tinyexec: 1.0.4
+ tinyglobby: 0.2.15
+ tinyrainbow: 3.1.0
+ vite: 7.3.1(@types/node@25.5.0)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.44.1)(yaml@2.8.3)
+ why-is-node-running: 2.3.0
+ optionalDependencies:
+ '@types/node': 25.5.0
+ happy-dom: 20.6.1
+ jsdom: 28.1.0
+ transitivePeerDependencies:
+ - msw
w3c-xmlserializer@5.0.0:
dependencies:
@@ -7416,11 +6918,11 @@ snapshots:
dependencies:
ansi-styles: 6.2.3
string-width: 7.2.0
- strip-ansi: 7.1.2
+ strip-ansi: 7.2.0
wrappy@1.0.2: {}
- ws@8.19.0:
+ ws@8.20.0:
optional: true
xml-name-validator@5.0.0: {}
@@ -7431,7 +6933,7 @@ snapshots:
yallist@3.1.1: {}
- yaml@2.8.2: {}
+ yaml@2.8.3: {}
yocto-queue@0.1.0: {}
diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml
index 6e443191f7041..a01c6b710267e 100644
--- a/pnpm-workspace.yaml
+++ b/pnpm-workspace.yaml
@@ -1,10 +1,11 @@
packages:
- apps/*
+ - packages/*
catalog:
- "@vitest/coverage-v8": 4.0.18
+ "@vitest/coverage-v8": 4.1.2
vite: 7.3.1
- vitest: 4.0.18
+ vitest: 4.1.2
minimumReleaseAge: 10080
diff --git a/tsconfig.base.json b/tsconfig.base.json
index 39e84b3a6255c..00912579bd52b 100644
--- a/tsconfig.base.json
+++ b/tsconfig.base.json
@@ -18,6 +18,15 @@
"types": [],
"noUncheckedSideEffectImports": true,
"moduleDetection": "force",
+ "customConditions": [
+ /**
+ * Custom condition that resolved to original typescript source
+ * It allows realtime typechecking between packages without the need to rebuild them.
+
+ * Keep in sync with `eslint.config.js` `createTypeScriptImportResolver`
+ */
+ "@stats/source"
+ ],
// Interop Constraints
"verbatimModuleSyntax": true,
diff --git a/tsconfig.json b/tsconfig.json
index 2a537f3a563e1..3dcd084049b51 100644
--- a/tsconfig.json
+++ b/tsconfig.json
@@ -1,11 +1,4 @@
{
- "files": [],
- "references": [
- {
- "path": "./apps/backend"
- },
- {
- "path": "./apps/frontend"
- }
- ]
+ // Visit https://aka.ms/tsconfig to read more about this file
+ "extends": "./tsconfig.base.json"
}
diff --git a/turbo.json b/turbo.json
new file mode 100644
index 0000000000000..48832af8d678e
--- /dev/null
+++ b/turbo.json
@@ -0,0 +1,16 @@
+{
+ "$schema": "./node_modules/turbo/schema.json",
+ "globalDependencies": ["tsconfig.base.json"],
+ "tasks": {
+ "build": {
+ "dependsOn": ["^build"],
+ "outputs": ["build/**"]
+ },
+ "lint": {
+ "dependsOn": ["^build"]
+ },
+ "typecheck": {
+ "dependsOn": ["^build"]
+ }
+ }
+}
diff --git a/vercel-preparation.sh b/vercel-preparation.sh
index 35ebfe01297c9..29b82d840a064 100755
--- a/vercel-preparation.sh
+++ b/vercel-preparation.sh
@@ -13,4 +13,3 @@ cp -RP apps/backend/. apps/backend-copy/
(shopt -s dotglob && mv apps/backend-copy/* apps/backend/.vercel/output/functions/api.func/)
cp -RP apps/backend/.vercel/output/functions/api.func/_dot_vercel_copy/output apps/backend/.vercel/
rm -rf apps/backend/node_modules
-cp -RP apps/backend apps/frontend/src/backend/
diff --git a/vitest.config.coverage.ts b/vitest.config.coverage.ts
new file mode 100644
index 0000000000000..22fc386c98905
--- /dev/null
+++ b/vitest.config.coverage.ts
@@ -0,0 +1,13 @@
+import { defineConfig } from "vitest/config";
+
+export default defineConfig({
+ test: {
+ coverage: {
+ enabled: true,
+ },
+ projects: [
+ "packages/core/vitest.config.ts",
+ "apps/backend/vitest.config.ts",
+ ],
+ },
+});
diff --git a/vitest.config.ts b/vitest.config.ts
new file mode 100644
index 0000000000000..c3c14e5ba319e
--- /dev/null
+++ b/vitest.config.ts
@@ -0,0 +1,11 @@
+import { defineConfig } from "vitest/config";
+
+export default defineConfig({
+ test: {
+ projects: [
+ "packages/core/vitest.config.ts",
+ "apps/backend/vitest.config.ts",
+ "apps/frontend/vite.config.ts",
+ ],
+ },
+});