diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md index a304b2f9..0ad080bd 100644 --- a/.github/CONTRIBUTING.md +++ b/.github/CONTRIBUTING.md @@ -5,9 +5,9 @@ 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 901d2f63..ba1ccbae 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 e0eeed90..5e7101a8 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)" @@ -15,14 +14,56 @@ updates: ignore: - dependency-name: "@types/node" update-types: ["version-update:semver-major"] + groups: + react: + patterns: + - "react" + - "react-dom" + - "react-*" + - "@types/react" + - "@types/react-dom" + redux: + patterns: + - "redux" + - "@reduxjs/*" + - "react-redux" + vite: + patterns: + - "vite" + - "@vitejs/*" + tailwind: + patterns: + - "tailwindcss" + - "@tailwindcss/*" + - "daisyui" + vitest: + patterns: + - "vitest" + - "@vitest/*" + eslint: + patterns: + - "eslint" + - "@eslint/*" + - "eslint-*" + - "typescript-eslint" + typescript: + patterns: + - "typescript" + - "@types/*" + playwright: + patterns: + - "@playwright/*" + axios: + patterns: + - "axios" + - "axios-*" # Maintain dependencies for GitHub Actions - 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 +74,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 67d0bad2..2ca6ea81 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -31,26 +31,21 @@ jobs: steps: - name: Checkout code - uses: actions/checkout@v6 - - - 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 with: node-version: ${{ matrix.node }} + - name: Build packages + run: pnpm run build:packages + - name: Build frontend - run: pnpm --filter frontend run build + run: pnpm run build:frontend - - name: Run frontend tests - run: pnpm --filter frontend run test - - - name: Run backend tests - run: pnpm --filter github-readme-stats run test + - name: Run tests + run: pnpm run test frontend-test-e2e: name: Frontend E2E test @@ -64,21 +59,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 +106,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 c985fded..4e151147 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@5f6978faf089d4d20b00c7766989d076bb2fc7f1 # 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 00000000..f8ba92bc --- /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 ebf23adf..6e4d6801 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 8ead85e2..8e0f7ce4 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@5f6978faf089d4d20b00c7766989d076bb2fc7f1 # v8 with: commit-message: "feat(backend): update languages JSON" branch: "update_langs/patch" diff --git a/.gitignore b/.gitignore index bd17695b..2890613a 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 b4c15f46..e742101e 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/.devcontainer/devcontainer.json b/apps/backend/.devcontainer/devcontainer.json index 7e8a6f89..8a65cd7f 100644 --- a/apps/backend/.devcontainer/devcontainer.json +++ b/apps/backend/.devcontainer/devcontainer.json @@ -2,7 +2,7 @@ "name": "GitHub Stats Extended Dev", "image": "mcr.microsoft.com/devcontainers/base:ubuntu", "features": { - "ghcr.io/devcontainers/features/node:1": { "version": "22" } + "ghcr.io/devcontainers/features/node:1": { "version": "24" } }, "forwardPorts": [3000], "portsAttributes": { diff --git a/apps/backend/_dot_vercel_copy/output/functions/api.func/.vc-config.json b/apps/backend/_dot_vercel_copy/output/functions/api.func/.vc-config.json index 605ee7c0..5b8cb30a 100644 --- a/apps/backend/_dot_vercel_copy/output/functions/api.func/.vc-config.json +++ b/apps/backend/_dot_vercel_copy/output/functions/api.func/.vc-config.json @@ -1,5 +1,5 @@ { - "runtime": "nodejs22.x", + "runtime": "nodejs24.x", "handler": "router.js", "launcherType": "Nodejs" } 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 b9a98e1e..00000000 --- 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 18511a22..bcfaa3ec 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 83c073e9..f19c3672 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 519d3f9f..e52600d7 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 26e973ee..00000000 --- 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 998d7cbc..5725fc12 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 3afe5853..66162b67 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 218ceac0..0e868216 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 34dc4611..d370ed0e 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 dd1c3ead..00000000 --- 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 35a24a36..099c7254 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 00000000..5dce81e4 --- /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 e6ef6ed0..8d245407 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", - "jsdom": "28.1.0", + "axios-mock-adapter": "2.1.0", + "express": "5.2.1", + "jsdom": "29.0.2", "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" + "axios": "^1.15.2", + "@stats-organization/github-readme-stats-core": "workspace:^", + "pg": "^8.20.0" } } diff --git a/apps/backend/powered-by-vercel.svg b/apps/backend/powered-by-vercel.svg deleted file mode 100644 index 87782868..00000000 --- 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 00000000..3da27ad5 --- /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 5ca3a97a..00000000 --- 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 05a5dbe5..09cf5b30 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 6fe2ea59..f71f300b 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 e76c3109..8bf2e83f 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 5f131966..00000000 --- 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 db914b86..00000000 --- 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/common/log.js b/apps/backend/src/common/log.js deleted file mode 100644 index 5836d50a..00000000 --- a/apps/backend/src/common/log.js +++ /dev/null @@ -1,13 +0,0 @@ -// @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; - -export { logger }; diff --git a/apps/backend/src/index.js b/apps/backend/src/index.js deleted file mode 100644 index ca8d586d..00000000 --- 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 12e87457..3f40c8f8 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 34f060a5..00000000 --- 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`] = ` -" - - - - - - - - - - - - - WakaTime Stats - - - - - - - - - WakaTime user profile not public - - - - - - " -`; diff --git a/apps/backend/tests/_setup.private-instance.js b/apps/backend/tests/_setup.private-instance.js deleted file mode 100644 index f4f094e5..00000000 --- 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 7aeef4ca..ec5d7ca1 100644 --- a/apps/backend/tests/api.test.js +++ b/apps/backend/tests/api.test.js @@ -1,345 +1,181 @@ // @ts-check -import axios from "axios"; -import MockAdapter from "axios-mock-adapter"; -import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; +import { api, getConfig } from "@stats-organization/github-readme-stats-core"; +import { 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 router from "../router.js"; import { CACHE_TTL, DURATIONS } from "../src/common/cache.js"; -import { renderError } from "../src/common/render.js"; +import { getUserAccessByName, storeRequest } from "../src/common/database.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, +vi.mock(import("@stats-organization/github-readme-stats-core"), async () => { + const { mockCore } = await import("./utils.js"); + return mockCore(); }); -const error = { - errors: [ - { - type: "NOT_FOUND", - path: ["user"], - locations: [], - message: "Could not fetch user", - }, - ], -}; +vi.mock(import("../src/common/database.js"), async (importOriginal) => ({ + ...(await importOriginal()), + storeRequest: vi.fn(), + getUserAccessByName: vi.fn(), +})); -const mock = new MockAdapter(axios); +const apiMock = vi.mocked(api); +const getConfigMock = vi.mocked(getConfig); +const storeRequestMock = vi.mocked(storeRequest); +const getUserAccessByNameMock = vi.mocked(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); +const createRequest = (search = "") => ({ + headers: {}, + url: `/api?${search}`, +}); - return { req, res }; -}; +const createResponse = () => ({ + end: vi.fn(), + setHeader: vi.fn(), +}); + +const defaultCacheHeader = + `max-age=${CACHE_TTL.STATS_CARD.DEFAULT}, ` + + `s-maxage=${CACHE_TTL.STATS_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(() => { - process.env.CACHE_SECONDS = undefined; + apiMock.mockReset(); + getConfigMock.mockReset().mockReturnValue({}); + storeRequestMock.mockReset().mockResolvedValue(undefined); + getUserAccessByNameMock.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; }); -afterEach(() => { - mock.reset(); -}); +describe("Test /api backend routing", () => { + it("happy path should pass query params and user PAT, respond with stats content and persist request", async () => { + getUserAccessByNameMock.mockResolvedValue({ token: "user-pat" }); + apiMock.mockResolvedValue({ + status: "success", + content: "mock-stats-svg", + }); -describe("Test /api/", () => { - it("should test the request", async () => { - const { req, res } = faker({}, data_stats); - - await api(req, res); - - expect(res.setHeader).toHaveBeenCalledWith("Content-Type", "image/svg+xml"); - expect(res.send).toHaveBeenCalledWith( - renderStatsCard(stats, { ...req.query }), + const req = createRequest( + "username=anuraghazra&theme=dark&hide=issues,prs,contribs", ); - }); + const res = createResponse(); - it("should render error card on error", async () => { - const { req, res } = faker({}, 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", - }), - ); - }); - - it("should render error card in same theme as requested card", async () => { - const { req, res } = faker({ theme: "merko" }, error); - - 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(getUserAccessByNameMock).toHaveBeenCalledWith("anuraghazra"); + expect(apiMock).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, + "user-pat", ); - - 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", - }), - ); - }); - - 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(storeRequestMock).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 () => { + apiMock.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(res.setHeader.mock.calls).toEqual([ - ["Content-Type", "image/svg+xml"], - [ - "Cache-Control", - `max-age=${CACHE_TTL.ERROR}, ` + - `s-maxage=${CACHE_TTL.ERROR}, ` + - `stale-while-revalidate=${DURATIONS.ONE_DAY}`, - ], - ]); - }); - - it("should properly set cache using CACHE_SECONDS env variable", async () => { - const cacheSeconds = "10000"; - process.env.CACHE_SECONDS = cacheSeconds; - - const { req, res } = faker({}, data_stats); - await api(req, res); - - expect(res.setHeader.mock.calls).toEqual([ - ["Content-Type", "image/svg+xml"], - [ - "Cache-Control", - `max-age=${cacheSeconds}, ` + - `s-maxage=${cacheSeconds}, ` + - `stale-while-revalidate=${DURATIONS.ONE_DAY}`, - ], - ]); - }); - - it("should disable cache when CACHE_SECONDS is set to 0", async () => { - process.env.CACHE_SECONDS = "0"; - - const { req, res } = faker({}, data_stats); - await api(req, res); - - expect(res.setHeader.mock.calls).toEqual([ - ["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( + expect(getUserAccessByNameMock).toHaveBeenCalledWith("anuraghazra"); + expect(apiMock).toHaveBeenCalledWith( { 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", - }), + null, ); + expect(res.setHeader.mock.calls).toEqual([ + ["Cache-Control", errorCacheHeader], + ["Content-Type", "image/svg+xml"], + ]); + expect(res.end).toHaveBeenCalledExactlyOnceWith("temporary-error-svg"); + expect(storeRequestMock).toHaveBeenCalledExactlyOnceWith(req); }); - it("should render error card when wrong locale is provided", async () => { - const { req, res } = faker({ locale: "asdf" }, data_stats); + it("should not persist permanent stats errors returned by core", async () => { + apiMock.mockResolvedValue({ + status: "error - permanent", + content: "permanent-error-svg", + }); - await api(req, res); + const req = createRequest("username=anuraghazra"); + const res = createResponse(); - expect(res.setHeader).toHaveBeenCalledWith("Content-Type", "image/svg+xml"); - expect(res.send).toHaveBeenCalledWith( - renderError({ - message: "Something went wrong", - secondaryMessage: "Language not found", - }), + await router(req, res); + + expect(getUserAccessByNameMock).toHaveBeenCalledWith("anuraghazra"); + expect(apiMock).toHaveBeenCalledWith( + { + username: "anuraghazra", + }, + null, ); + expect(res.setHeader.mock.calls).toEqual([ + ["Cache-Control", defaultCacheHeader], + ["Content-Type", "image/svg+xml"], + ]); + expect(res.end).toHaveBeenCalledExactlyOnceWith("permanent-error-svg"); + expect(storeRequestMock).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 blacklisted usernames before calling core logic", async () => { + const req = createRequest("username=renovate-bot"); + const res = createResponse(); - const { req, res } = faker( - { username: "anuraghazra", include_all_commits: true }, - data_stats, - ); + await router(req, res); - await api(req, res); + expect(apiMock).not.toHaveBeenCalled(); + expect(getUserAccessByNameMock).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 blacklisted", + ); + expect(storeRequestMock).not.toHaveBeenCalled(); + }); - 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", + it("should reject non-whitelisted usernames before calling core logic", async () => { + getConfigMock.mockReturnValue({ whitelist: ["allowed-user"] }); + + const req = createRequest("username=blocked-user"); + const res = createResponse(); + + await router(req, res); + + expect(apiMock).not.toHaveBeenCalled(); + expect(getUserAccessByNameMock).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(storeRequestMock).not.toHaveBeenCalled(); }); }); diff --git a/apps/backend/tests/bench/api.bench.js b/apps/backend/tests/bench/api.bench.js index a7326840..6b4a47ae 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"; - -const stats = { - name: "Anurag Hazra", - totalStars: 100, - totalCommits: 200, - totalIssues: 300, - totalPRs: 400, - totalPRsMerged: 320, - mergedPRsPercentage: 80, - totalReviews: 50, - totalDiscussionsStarted: 10, - totalDiscussionsAnswered: 40, - contributedTo: 50, - rank: null, -}; - -const data_stats = { - data: { - user: { - name: stats.name, - repositoriesContributedTo: { totalCount: stats.contributedTo }, - 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", - }, - }, - }, - }, -}; +import { data_stats } from "../utils.js"; const mock = new MockAdapter(axios); -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); +const createResponse = () => ({ + end: vi.fn(), + setHeader: vi.fn(), +}); - return { req, res }; -}; +let router; -describe("/api", () => { +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_stats); +}); + +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 a9e52d09..00000000 --- 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 9b15c47c..997a28f9 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 { beforeAll, 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 { 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 790c3a1b..f57acdda 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 a923922a..3b13143f 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", - }, - forkCount: 0, - starCount: 1, }; -/** - * @typedef {import("../../src/fetchers/types").GistData} GistData Gist data type. - */ - -/** - * @type {GistData} - */ -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, +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, + }, + ], + }, + }, + }, }; const CACHE_BURST_STRING = `v=${new Date().getTime()}`; -describe("Fetch Cards", () => { - let VERCEL_PREVIEW_URL = "https://github-stats-extended.vercel.app"; +const mock = new MockAdapter(axios, { onNoMatch: "passthrough" }); - beforeAll(() => { - process.env.NODE_ENV = "development"; +const createResponse = () => ({ + end: vi.fn(), + setHeader: vi.fn(), +}); + +let router; + +/** + * Renders a card locally through the backend router. + * @param {string} url Card URL to render through the router. + * @returns {Promise} Rendered SVG markup. + */ +async function getLocalSvg(url) { + const req = { + headers: {}, + url, + }; + const res = createResponse(); + + await router(req, res); + + 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")); + + 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 2cb6d4c3..564a733d 100644 --- a/apps/backend/tests/gist.test.js +++ b/apps/backend/tests/gist.test.js @@ -1,161 +1,149 @@ // @ts-check -import axios from "axios"; -import MockAdapter from "axios-mock-adapter"; -import { afterEach, describe, expect, it, vi } from "vitest"; +import { getConfig, gist } from "@stats-organization/github-readme-stats-core"; +import { beforeEach, describe, expect, it, vi } from "vitest"; -import gist from "../api-renamed/gist.js"; -import { renderGistCard } from "../src/cards/gist.js"; +import router from "../router.js"; import { CACHE_TTL, DURATIONS } from "../src/common/cache.js"; -import { renderError } from "../src/common/render.js"; +import { getUserAccessByName, storeRequest } from "../src/common/database.js"; -import { gist_data } from "./test-data/gist-data.js"; - -import "@testing-library/jest-dom/vitest"; - -const gist_not_found_data = { - data: { - viewer: { - gist: null, - }, - }, -}; - -const mock = new MockAdapter(axios); - -afterEach(() => { - mock.reset(); +vi.mock(import("@stats-organization/github-readme-stats-core"), async () => { + const { mockCore } = await import("./utils.js"); + return mockCore(); }); -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); +vi.mock(import("../src/common/database.js"), async (importOriginal) => ({ + ...(await importOriginal()), + storeRequest: vi.fn(), + getUserAccessByName: vi.fn(), +})); - await gist(req, res); +const gistMock = vi.mocked(gist); +const getConfigMock = vi.mocked(getConfig); +const storeRequestMock = vi.mocked(storeRequest); +const getUserAccessByNameMock = vi.mocked(getUserAccessByName); - 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, - }), - ); +const createRequest = (search) => ({ + headers: {}, + url: `/api/gist?${search}`, +}); + +const createResponse = () => ({ + end: vi.fn(), + setHeader: vi.fn(), +}); + +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(() => { + gistMock.mockReset(); + getConfigMock.mockReset().mockReturnValue({}); + storeRequestMock.mockReset().mockResolvedValue(undefined); + getUserAccessByNameMock.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; +}); + +describe("Test /api/gist backend routing", () => { + it("happy path should pass query params, respond with gist content and persist request", async () => { + gistMock.mockResolvedValue({ + status: "success", + content: "mock-gist-svg", + }); + + const req = createRequest("id=bbfce31e0217a3689c8d961a356cb10d&theme=dark"); + const res = createResponse(); + + await router(req, res); + + expect(gistMock).toHaveBeenCalledWith({ + id: "bbfce31e0217a3689c8d961a356cb10d", + theme: "dark", + }); + expect(getUserAccessByNameMock).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(storeRequestMock).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); + it("should use the shorter error cache for temporary gist errors", async () => { + gistMock.mockResolvedValue({ + status: "error - temporary", + content: "temporary-error-svg", + }); - await gist(req, res); + const req = createRequest("id=bbfce31e0217a3689c8d961a356cb10d"); + const res = createResponse(); - 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 }, - ), - ); + await router(req, res); + + expect(gistMock).toHaveBeenCalledWith({ + id: "bbfce31e0217a3689c8d961a356cb10d", + }); + expect(getUserAccessByNameMock).not.toHaveBeenCalled(); + expect(res.setHeader.mock.calls).toEqual([ + ["Cache-Control", errorCacheHeader], + ["Content-Type", "image/svg+xml"], + ]); + expect(res.end).toHaveBeenCalledExactlyOnceWith("temporary-error-svg"); + expect(storeRequestMock).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); + it("should not persist permanent gist errors returned by core", async () => { + gistMock.mockResolvedValue({ + status: "error - permanent", + content: "permanent-error-svg", + }); - await gist(req, res); + const req = createRequest("id=bbfce31e0217a3689c8d961a356cb10d"); + const res = createResponse(); - expect(res.setHeader).toHaveBeenCalledWith("Content-Type", "image/svg+xml"); - expect(res.send).toHaveBeenCalledWith( - renderError({ message: "Gist not found" }), - ); + await router(req, res); + + expect(gistMock).toHaveBeenCalledWith({ + id: "bbfce31e0217a3689c8d961a356cb10d", + }); + expect(getUserAccessByNameMock).not.toHaveBeenCalled(); + expect(res.setHeader.mock.calls).toEqual([ + ["Cache-Control", defaultCacheHeader], + ["Content-Type", "image/svg+xml"], + ]); + expect(res.end).toHaveBeenCalledExactlyOnceWith("permanent-error-svg"); + expect(storeRequestMock).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(), - }; - mock.onPost("https://api.github.com/graphql").reply(200, gist_data); + it("should reject non-whitelisted gist ids before calling core logic", async () => { + getConfigMock.mockReturnValue({ gistWhitelist: ["allowed-gist-id"] }); - await gist(req, res); + const req = createRequest("id=blocked-gist-id"); + const res = createResponse(); - expect(res.setHeader).toHaveBeenCalledWith("Content-Type", "image/svg+xml"); - expect(res.send).toHaveBeenCalledWith( - renderError({ - message: "Something went wrong", - secondaryMessage: "Language not found", - }), - ); - }); + await router(req, res); - 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); - - await gist(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(gistMock).not.toHaveBeenCalled(); + expect(getUserAccessByNameMock).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(storeRequestMock).not.toHaveBeenCalled(); }); }); diff --git a/apps/backend/tests/pat-info.test.js b/apps/backend/tests/pat-info.test.js index 3e98fa8b..36665116 100644 --- a/apps/backend/tests/pat-info.test.js +++ b/apps/backend/tests/pat-info.test.js @@ -4,9 +4,15 @@ 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"; +import { + afterAll, + afterEach, + beforeAll, + describe, + expect, + it, + vi, +} from "vitest"; const mock = new MockAdapter(axios); @@ -57,20 +63,35 @@ 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"); + + const { logger } = + await import("@stats-organization/github-readme-stats-core"); + vi.spyOn(logger, "log").mockImplementation(() => {}); + vi.spyOn(logger, "error").mockImplementation(() => {}); + + ({ 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(); +}); + +afterAll(() => { + vi.restoreAllMocks(); }); 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 +264,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 b9fa0356..90eb6b8a 100644 --- a/apps/backend/tests/pin.test.js +++ b/apps/backend/tests/pin.test.js @@ -1,175 +1,84 @@ // @ts-check -import axios from "axios"; -import MockAdapter from "axios-mock-adapter"; -import { afterEach, describe, expect, it, vi } from "vitest"; +import { pin } from "@stats-organization/github-readme-stats-core"; +import { beforeEach, describe, expect, it, vi } from "vitest"; -import pin from "../api-renamed/pin.js"; -import { renderRepoCard } from "../src/cards/repo.js"; +import router from "../router.js"; import { CACHE_TTL, DURATIONS } from "../src/common/cache.js"; -import { renderError } from "../src/common/render.js"; +import { getUserAccessByName, storeRequest } from "../src/common/database.js"; -import { data_repo, data_user } from "./test-data/pin-data.js"; - -import "@testing-library/jest-dom/vitest"; - -const mock = new MockAdapter(axios); - -afterEach(() => { - mock.reset(); +vi.mock(import("@stats-organization/github-readme-stats-core"), async () => { + const { mockCore } = await import("./utils.js"); + return mockCore(); }); -describe("Test /api/pin", () => { - it("should test the request", async () => { - const req = { - query: { +vi.mock(import("../src/common/database.js"), async (importOriginal) => ({ + ...(await importOriginal()), + storeRequest: vi.fn(), + getUserAccessByName: vi.fn(), +})); + +const pinMock = vi.mocked(pin); +const storeRequestMock = vi.mocked(storeRequest); +const getUserAccessByNameMock = vi.mocked(getUserAccessByName); + +const createRequest = (search = "") => ({ + headers: {}, + url: `/api/pin?${search}`, +}); + +const createResponse = () => ({ + end: vi.fn(), + setHeader: vi.fn(), +}); + +const defaultCacheHeader = + `max-age=${CACHE_TTL.PIN_CARD.DEFAULT}, ` + + `s-maxage=${CACHE_TTL.PIN_CARD.DEFAULT}, ` + + `stale-while-revalidate=${DURATIONS.ONE_DAY}`; + +beforeEach(() => { + pinMock.mockReset(); + storeRequestMock.mockReset().mockResolvedValue(undefined); + getUserAccessByNameMock.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; +}); + +describe("Test /api/pin backend routing", () => { + it("happy path should pass query params and user PAT, respond with pin content and persist request", async () => { + getUserAccessByNameMock.mockResolvedValue({ token: "user-pat" }); + pinMock.mockResolvedValue({ + status: "success", + content: "mock-pin-svg", + }); + + const req = createRequest( + "username=anuraghazra&repo=convoychat&theme=dark", + ); + const res = createResponse(); + + await router(req, res); + + expect(getUserAccessByNameMock).toHaveBeenCalledWith("anuraghazra"); + expect(pinMock).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.send).toHaveBeenCalledWith( - // @ts-ignore - renderRepoCard({ - ...data_repo.repository, - starCount: data_repo.repository.stargazers.totalCount, - }), - ); - }); - - 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 } }); - - await pin(req, res); - - expect(res.setHeader).toHaveBeenCalledWith("Content-Type", "image/svg+xml"); - expect(res.send).toHaveBeenCalledWith( - renderError({ message: "User Repository Not found" }), - ); - }); - - 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 } } }); - - await pin(req, res); - - expect(res.setHeader).toHaveBeenCalledWith("Content-Type", "image/svg+xml"); - expect(res.send).toHaveBeenCalledWith( - renderError({ message: "Organization Repository Not found" }), - ); - }); - - 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); - - await pin(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: { - 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.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(storeRequestMock).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 b422a3b1..00000000 --- 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 9a23afea..00000000 --- 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 fb81dc55..00000000 --- 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 c72f4d6f..00000000 --- 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 00000000..1edbcd22 --- /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 + + + + + + + + + + Anurag Hazra's GitHub Stats + + + + + + + + + + + + + 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 + + + + + + + + + + a custom title + + + + + + + + + + + + + + + + + + + + + + + + कुल अर्जित सितारे: + 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 00000000..6e8e2779 --- /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": " + + + + + + + + + + + + + + + + countries.json + + + + + + + + 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": " + + + + + + + + + + + + + + + + Yizack/countries.json + + + + + + + + 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 00000000..959c008a --- /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": " + + + + + + + + + + + + + + + + convoychat + + + + + + + + + + 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": " + + + + + + + + + + + + + + + + anuraghazra/convoychat + + + + + + + + + + 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 00000000..589f315e --- /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": " + + + + + + + + + + + + Most Used Languages + + + + + + + + + + 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 00000000..a1a3075a --- /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": " + + + + + + + + + + + + WakaTime Stats (last 7 days) + + + + + + + + + + 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 Stats + + + + + + + + + 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": " + + + + + + + + + + + + a custom title + + + + + + + + + + + + + + + + + + + + + + + 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 52080c89..34a8d98f 100644 --- a/apps/backend/tests/public-instance/api.test.js +++ b/apps/backend/tests/public-instance/api.test.js @@ -1,41 +1,196 @@ // @ts-check +import { logger } from "@stats-organization/github-readme-stats-core"; import axios from "axios"; import MockAdapter from "axios-mock-adapter"; -import { afterEach, describe, expect, it, vi } from "vitest"; +import { + afterAll, + afterEach, + beforeAll, + 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); +}); + +beforeAll(() => { + vi.spyOn(logger, "log").mockImplementation(() => {}); + vi.spyOn(logger, "error").mockImplementation(() => {}); +}); + 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 () => { +afterAll(() => { + vi.restoreAllMocks(); +}); + +describe("Test /api contract", () => { + it("should match the public happy-path response snapshot", async () => { + const { default: router } = await import("../../router.js"); + const req = { - query: { - username: "renovate-bot", - }, + headers: {}, + url: "/api?username=anuraghazra", }; - 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 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 = { + headers: {}, + url: `/api?${params.toString()}`, }; - mock.onPost("https://api.github.com/graphql").replyOnce(200, data_stats); + 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: "This username is blacklisted", - secondaryMessage: "Please deploy your own instance", - renderOptions: { show_repo_link: false }, - }), - ); + 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", + }; + 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 42154736..693617cf 100644 --- a/apps/backend/tests/public-instance/gist.test.js +++ b/apps/backend/tests/public-instance/gist.test.js @@ -1,39 +1,143 @@ // @ts-check +import { logger } from "@stats-organization/github-readme-stats-core"; import axios from "axios"; import MockAdapter from "axios-mock-adapter"; -import { afterEach, describe, expect, it, vi } from "vitest"; +import { + afterAll, + afterEach, + beforeAll, + 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); +}); + +beforeAll(() => { + vi.spyOn(logger, "log").mockImplementation(() => {}); + vi.spyOn(logger, "error").mockImplementation(() => {}); +}); + 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 () => { +afterAll(() => { + vi.restoreAllMocks(); +}); + +describe("Test /api/gist contract", () => { + it("should match the public happy-path response snapshot", async () => { + const { default: router } = await import("../../router.js"); + const req = { - query: {}, + headers: {}, + url: "/api/gist?id=happy-gist-id", }; - 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 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()}`, }; - mock.onPost("https://api.github.com/graphql").reply(200, gist_data); + const res = createResponse(); - await gist(req, res); + await router(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 }, - }), - ); + 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 = { + headers: {}, + url: "/api/gist", + }; + 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", + }; + 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 fdbdfe98..0c86dfe1 100644 --- a/apps/backend/tests/public-instance/pin.test.js +++ b/apps/backend/tests/public-instance/pin.test.js @@ -1,65 +1,235 @@ // @ts-check +import { logger } from "@stats-organization/github-readme-stats-core"; import axios from "axios"; import MockAdapter from "axios-mock-adapter"; -import { afterEach, describe, expect, it, vi } from "vitest"; +import { + afterAll, + afterEach, + beforeAll, + 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); +}); + +beforeAll(() => { + vi.spyOn(logger, "log").mockImplementation(() => {}); + vi.spyOn(logger, "error").mockImplementation(() => {}); +}); + 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 () => { +afterAll(() => { + vi.restoreAllMocks(); +}); + +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", - }, + headers: {}, + url: "/api/pin?username=anuraghazra&repo=convoychat", }; - 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: "This username is blacklisted", - secondaryMessage: "Please deploy your own instance", - renderOptions: { show_repo_link: false }, - }), - ); + expect(res.end).toHaveBeenCalledOnce(); + + expect({ + headers: res.setHeader.mock.calls, + content: normalizeSvg(res.end.mock.calls[0][0]), + }).toMatchSnapshot(); }); - it("should render error card if missing required parameters", async () => { - const req = { - query: {}, - }; - const res = { - setHeader: vi.fn(), - send: vi.fn(), - }; + 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 }); - await pin(req, res); + const { default: router } = await import("../../router.js"); - 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 }, - }), - ); + 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", + }); + + const req = { + headers: {}, + url: `/api/pin?${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-params response snapshot", async () => { + const { default: router } = await import("../../router.js"); + + const req = { + headers: {}, + url: "/api/pin", + }; + 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", + }; + 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(); + + 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 2e180e34..11739f21 100644 --- a/apps/backend/tests/public-instance/top-langs.test.js +++ b/apps/backend/tests/public-instance/top-langs.test.js @@ -1,41 +1,210 @@ // @ts-check +import { logger } from "@stats-organization/github-readme-stats-core"; import axios from "axios"; import MockAdapter from "axios-mock-adapter"; -import { afterEach, describe, expect, it, vi } from "vitest"; +import { + afterAll, + afterEach, + beforeAll, + 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); +}); + +beforeAll(() => { + vi.spyOn(logger, "log").mockImplementation(() => {}); + vi.spyOn(logger, "error").mockImplementation(() => {}); +}); + 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 () => { +afterAll(() => { + vi.restoreAllMocks(); +}); + +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"); - 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 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({ + 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 00000000..aaa98bbd --- /dev/null +++ b/apps/backend/tests/public-instance/wakatime.test.js @@ -0,0 +1,231 @@ +// @ts-check + +import { logger } from "@stats-organization/github-readme-stats-core"; +import axios from "axios"; +import MockAdapter from "axios-mock-adapter"; +import { + afterAll, + afterEach, + beforeAll, + 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); +}); + +beforeAll(() => { + vi.spyOn(logger, "log").mockImplementation(() => {}); + vi.spyOn(logger, "error").mockImplementation(() => {}); +}); + +afterEach(() => { + mock.reset(); + vi.unstubAllEnvs(); + // modules may cache environment variables, so we need to reset them + vi.resetModules(); +}); + +afterAll(() => { + vi.restoreAllMocks(); +}); + +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/retryer.test.js b/apps/backend/tests/retryer.test.js deleted file mode 100644 index b82716b3..00000000 --- a/apps/backend/tests/retryer.test.js +++ /dev/null @@ -1,85 +0,0 @@ -// @ts-check - -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"; - -const fetcher = vi.fn((variables, token) => { - logger.log(variables, token); - return new Promise((res) => res({ data: "ok" })); -}); - -const fetcherFail = vi.fn(() => { - return new Promise((res) => - res({ data: { errors: [{ type: "RATE_LIMITED" }] } }), - ); -}); - -const fetcherFailOnSecondTry = vi.fn((_vars, _token, retries) => { - return new Promise((res) => { - // faking rate limit - // @ts-ignore - if (retries < 1) { - return res({ data: { errors: [{ type: "RATE_LIMITED" }] } }); - } - return res({ data: "ok" }); - }); -}); - -const fetcherFailWithMessageBasedRateLimitErr = vi.fn( - (_vars, _token, retries) => { - return new Promise((res) => { - // faking rate limit - // @ts-ignore - if (retries < 1) { - return res({ - data: { - errors: [ - { - type: "ASDF", - message: "API rate limit already exceeded for user ID 11111111", - }, - ], - }, - }); - } - return res({ data: "ok" }); - }); - }, -); - -describe("Test Retryer", () => { - it("retryer should return value and have zero retries on first try", async () => { - let res = await retryer(fetcher, {}); - - expect(fetcher).toHaveBeenCalledTimes(1); - expect(res).toStrictEqual({ data: "ok" }); - }); - - it("retryer should return value and have 2 retries", async () => { - let res = await retryer(fetcherFailOnSecondTry, {}); - - expect(fetcherFailOnSecondTry).toHaveBeenCalledTimes(2); - expect(res).toStrictEqual({ data: "ok" }); - }); - - it("retryer should return value and have 2 retries with message based rate limit error", async () => { - let res = await retryer(fetcherFailWithMessageBasedRateLimitErr, {}); - - expect(fetcherFailWithMessageBasedRateLimitErr).toHaveBeenCalledTimes(2); - expect(res).toStrictEqual({ data: "ok" }); - }); - - it("retryer should throw specific error if maximum retries reached", async () => { - try { - await retryer(fetcherFail, {}); - } catch (err) { - expect(fetcherFail).toHaveBeenCalledTimes(2); - // @ts-ignore - expect(err.message).toBe("Downtime due to GitHub API rate limiting"); - } - }); -}); diff --git a/apps/backend/tests/status.up.test.js b/apps/backend/tests/status.up.test.js index de3a5a7f..02b9a28b 100644 --- a/apps/backend/tests/status.up.test.js +++ b/apps/backend/tests/status.up.test.js @@ -2,9 +2,18 @@ * @file Tests for the status/up cloud function. */ +import { logger } from "@stats-organization/github-readme-stats-core"; import axios from "axios"; import MockAdapter from "axios-mock-adapter"; -import { afterEach, describe, expect, it, vi } from "vitest"; +import { + afterAll, + afterEach, + beforeAll, + describe, + expect, + it, + vi, +} from "vitest"; import up, { RATE_LIMIT_SECONDS } from "../api-renamed/status/up.js"; @@ -55,10 +64,19 @@ const shields_down = { color: "red", }; +beforeAll(() => { + vi.spyOn(logger, "log").mockImplementation(() => {}); + vi.spyOn(logger, "error").mockImplementation(() => {}); +}); + afterEach(() => { mock.reset(); }); +afterAll(() => { + vi.restoreAllMocks(); +}); + describe("Test /api/status/up", () => { it("should return `true` if request was successful", async () => { mock.onPost("https://api.github.com/graphql").replyOnce(200, successData); 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 cd0c1e64..00000000 --- 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 8bab8d2a..00000000 --- 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 febec1c7..00000000 --- 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 549f2c61..00000000 --- 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 f0519629..eb7a3ebc 100644 --- a/apps/backend/tests/top-langs.test.js +++ b/apps/backend/tests/top-langs.test.js @@ -1,192 +1,84 @@ // @ts-check -import axios from "axios"; -import MockAdapter from "axios-mock-adapter"; -import { afterEach, describe, expect, it, vi } from "vitest"; +import { topLangs } from "@stats-organization/github-readme-stats-core"; +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 router from "../router.js"; import { CACHE_TTL, DURATIONS } from "../src/common/cache.js"; -import { renderError } from "../src/common/render.js"; +import { getUserAccessByName, storeRequest } from "../src/common/database.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 mock = new MockAdapter(axios); - -afterEach(() => { - mock.reset(); +vi.mock(import("@stats-organization/github-readme-stats-core"), async () => { + const { mockCore } = await import("./utils.js"); + return mockCore(); }); -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(import("../src/common/database.js"), async (importOriginal) => ({ + ...(await importOriginal()), + storeRequest: vi.fn(), + getUserAccessByName: vi.fn(), +})); - await topLangs(req, res); +const topLangsMock = vi.mocked(topLangs); +const storeRequestMock = vi.mocked(storeRequest); +const getUserAccessByNameMock = vi.mocked(getUserAccessByName); - expect(res.setHeader).toHaveBeenCalledWith("Content-Type", "image/svg+xml"); - expect(res.send).toHaveBeenCalledWith(renderTopLanguages(langs)); - }); +const createRequest = (search = "") => ({ + headers: {}, + url: `/api/top-langs?${search}`, +}); - 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); +const createResponse = () => ({ + end: vi.fn(), + setHeader: vi.fn(), +}); - await topLangs(req, res); +const defaultCacheHeader = + `max-age=${CACHE_TTL.TOP_LANGS_CARD.DEFAULT}, ` + + `s-maxage=${CACHE_TTL.TOP_LANGS_CARD.DEFAULT}, ` + + `stale-while-revalidate=${DURATIONS.ONE_DAY}`; - 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", - }), +beforeEach(() => { + topLangsMock.mockReset(); + storeRequestMock.mockReset().mockResolvedValue(undefined); + getUserAccessByNameMock.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; +}); + +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 () => { + getUserAccessByNameMock.mockResolvedValue({ token: "user-pat" }); + topLangsMock.mockResolvedValue({ + status: "success", + content: "mock-top-langs-svg", + }); + + const req = createRequest( + "username=anuraghazra&layout=compact&langs_count=5", ); - }); + const res = createResponse(); - it("should render error card on user data fetch error", async () => { - const req = { - query: { + await router(req, res); + + expect(getUserAccessByNameMock).toHaveBeenCalledWith("anuraghazra"); + expect(topLangsMock).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, error); - - await topLangs(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", - }), - ); - }); - - 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); - - await topLangs(req, res); - - expect(res.setHeader).toHaveBeenCalledWith("Content-Type", "image/svg+xml"); - expect(res.send).toHaveBeenCalledWith( - renderError({ - message: "Something went wrong", - secondaryMessage: "Incorrect layout input", - }), - ); - }); - - 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 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: { - username: "anuraghazra", - }, - }; - 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(storeRequestMock).toHaveBeenCalledExactlyOnceWith(req); }); }); diff --git a/apps/backend/tests/utils.js b/apps/backend/tests/utils.js index 21b9f1f8..8602f932 100644 --- a/apps/backend/tests/utils.js +++ b/apps/backend/tests/utils.js @@ -1,41 +1,299 @@ // @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; +import { vi } from "vitest"; + +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", + }, + }, }, - toAsymmetricMatcher() { - return `≈ ${expected} (precision ${precision})`; + }, +}; + +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, + }, +}; + +/** @typedef {import('@stats-organization/github-readme-stats-core')} CoreModule */ + +/** + * Creates a mock module for @stats-organization/github-readme-stats-core. + * @returns {CoreModule} Mocked core module. + */ +export function mockCore() { + return { + // @ts-expect-error no need to mock themes at the moment + themes: {}, + request: vi.fn(), + fetchWakatimeStats: vi.fn(), + retryer: vi.fn(), + dateDiff: vi.fn(), + api: vi.fn(), + gist: vi.fn(), + pin: vi.fn(), + topLangs: vi.fn(), + wakatime: vi.fn(), + getConfig: vi.fn().mockReturnValue({}), + renderError: ({ message }) => `render-error:${message}`, + clampValue: (value, min, max) => Math.min(Math.max(value, min), max), + logger: { + log: vi.fn(), + error: vi.fn(), }, }; } + +/** + * 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 f27eec0b..0be8df96 100644 --- a/apps/backend/tests/wakatime.test.js +++ b/apps/backend/tests/wakatime.test.js @@ -1,195 +1,78 @@ -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 { wakatime } from "@stats-organization/github-readme-stats-core"; +import { beforeEach, describe, expect, it, vi } from "vitest"; + +import router from "../router.js"; import { CACHE_TTL, DURATIONS } from "../src/common/cache.js"; -import { renderError } from "../src/index.js"; +import { getUserAccessByName, storeRequest } from "../src/common/database.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, - }, -}; - -const wakaTimeNotFoundData = { - data: { - viewer: { - gist: null, - }, - }, -}; - -const mock = new MockAdapter(axios); - -afterEach(() => { - mock.reset(); +vi.mock(import("@stats-organization/github-readme-stats-core"), async () => { + const { mockCore } = await import("./utils.js"); + return mockCore(); }); -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); +vi.mock(import("../src/common/database.js"), async (importOriginal) => ({ + ...(await importOriginal()), + storeRequest: vi.fn(), + getUserAccessByName: vi.fn(), +})); - await wakatime(req, res); +const wakatimeMock = vi.mocked(wakatime); +const storeRequestMock = vi.mocked(storeRequest); +const getUserAccessByNameMock = vi.mocked(getUserAccessByName); - expect(res.setHeader).toHaveBeenCalledWith("Content-Type", "image/svg+xml"); - expect(res.send).toHaveBeenCalledWith( - renderWakatimeCard(wakaTimeData.data, {}), - ); - }); +const createRequest = (search = "") => ({ + headers: {}, + url: `/api/wakatime?${search}`, +}); - 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); +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}`; - expect(res.setHeader).toHaveBeenCalledWith("Content-Type", "image/svg+xml"); - expect(res.send).toHaveBeenCalledWith( - renderError({ - message: "Something went wrong", - secondaryMessage: "Language not found", - }), - ); - }); +beforeEach(() => { + wakatimeMock.mockReset(); + storeRequestMock.mockReset().mockResolvedValue(undefined); + getUserAccessByNameMock.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; +}); - 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); +describe("Test /api/wakatime backend routing", () => { + it("happy path should pass query params, respond with wakatime content and persist request", async () => { + wakatimeMock.mockResolvedValue({ + status: "success", + content: "mock-wakatime-svg", + }); - await wakatime(req, res); + const req = createRequest("username=anuraghazra&theme=dark&layout=compact"); + const res = createResponse(); - expect(res.setHeader).toHaveBeenCalledWith("Content-Type", "image/svg+xml"); - expect(res.send).toHaveBeenCalledTimes(1); - expect(res.send.mock.calls[0][0]).toMatchSnapshot(); - }); + await router(req, res); - 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); - - await wakatime(req, res); - - 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}`, - ); + expect(wakatimeMock).toHaveBeenCalledWith({ + username: "anuraghazra", + theme: "dark", + layout: "compact", + }); + expect(getUserAccessByNameMock).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(storeRequestMock).toHaveBeenCalledExactlyOnceWith(req); }); }); diff --git a/apps/backend/tsconfig.typecheck.json b/apps/backend/tsconfig.typecheck.json new file mode 100644 index 00000000..4c13d7b1 --- /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 dcc97d68..af0b1da5 100644 --- a/apps/backend/vercel.json +++ b/apps/backend/vercel.json @@ -1,16 +1,10 @@ { "$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 --prod deploy ./apps/deployment/ && mv ./apps/deployment/node_modules/ ./apps/backend/node_modules/ && ./vercel-preparation.sh", "redirects": [ { "source": "/", "destination": "https://github.com/stats-organization/github-stats-extended" } - ], - "rewrites": [ - { - "source": "/frontend/:match*", - "destination": "https://gse-frontend.vercel.app/:match*" - } ] } diff --git a/apps/backend/vitest.config.bench.ts b/apps/backend/vitest.config.bench.ts index 9fe2e73d..1f3eef9d 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 0e6c6312..f7433de3 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 5da6c76b..54dcb613 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 aa6d33c5..a1bbab74 100644 --- a/apps/frontend/package.json +++ b/apps/frontend/package.json @@ -1,48 +1,45 @@ { - "name": "frontend", - "version": "0.1.0", - "private": true, + "name": "@stats-organization/github-readme-stats-frontend", "type": "module", - "dependencies": { - "@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", - "react-icons": "^5.5.0", - "react-loading-skeleton": "^3.3.1", - "react-redux": "9.2.0", - "react-spinners": "^0.17.0", - "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" - }, - "devDependencies": { - "@types/react": "18.3.27", - "@types/react-dom": "18.3.7", - "@vitejs/plugin-react-swc": "4.2.3", - "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" - }, + "license": "MIT", + "private": true, "scripts": { "dev": "vite", "build": "vite build", "preview": "vite preview", "test": "vitest", "test:e2e": "playwright test", - "typecheck": "tsc --noEmit" + "lint": "eslint", + "typecheck": "tsc -p tsconfig.typecheck.json" + }, + "dependencies": { + "@reduxjs/toolkit": "^2.11.2", + "@tailwindcss/vite": "^4.2.2", + "axios": "^1", + "axios-cache-interceptor": "^1", + "daisyui": "^5.5.19", + "@stats-organization/github-readme-stats-backend": "workspace:^", + "@stats-organization/github-readme-stats-core": "workspace:^", + "react": "^19.2.5", + "react-dom": "^19.2.5", + "react-icons": "^5.6.0", + "react-loading-skeleton": "^3.3.1", + "react-redux": "^9.2.0", + "react-spinners": "^0.17.0", + "react-toastify": "^11.1.0", + "redux": "^5.0.1", + "save-svg-as-png": "^1.4.17", + "uuid": "^14.0.0" + }, + "devDependencies": { + "@types/react": "19.2.14", + "@types/react-dom": "19.2.3", + "@vitejs/plugin-react": "6.0.1", + "clsx": "2.1.1", + "tailwindcss": "4.2.2", + "vite": "catalog:default", + "vitest": "catalog:default" }, - "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 7c85a010..5fd0c9b6 100644 --- a/apps/frontend/src/components/Card/SvgInline.tsx +++ b/apps/frontend/src/components/Card/SvgInline.tsx @@ -1,3 +1,6 @@ +// @ts-expect-error type info should be added later +import { router } from "@stats-organization/github-readme-stats-backend"; +import { loadConfigFromEnv } from "@stats-organization/github-readme-stats-core"; import axios from "axios"; import { useEffect, useRef, useState } from "react"; import type { JSX } from "react"; @@ -5,8 +8,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, @@ -45,7 +46,12 @@ export function SvgInline(props: SvgInlineProps): JSX.Element { let isCurrent = true; const loadSvg = async () => { - window.process.env.PAT_1 = userToken as string; + const config: Record = { + FETCH_MULTI_PAGE_STARS: "10", + PAT_1: userToken as string, // even if it's null, core's retryer.js sees there is 1 PAT and sets `RETRIES` accordingly + }; + // eslint-disable-next-line @typescript-eslint/no-unsafe-call + loadConfigFromEnv(config); setLoaded(false); diff --git a/apps/frontend/src/constants.ts b/apps/frontend/src/constants.ts index 5f8354b6..0da4e657 100644 --- a/apps/frontend/src/constants.ts +++ b/apps/frontend/src/constants.ts @@ -1,26 +1,20 @@ -const PROD = true as boolean; - export const USE_LOGGER = true as boolean; export const CLIENT_ID = "Ov23liZSweT9LJrck9i8"; -export const HOST = PROD - ? "github-stats-extended.vercel.app" - : "github-stats-extended-preview.vercel.app"; +export const HOST = window.location.host; 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; +// imported backend code expects process.env to be defined window.process = { - env: { - FETCH_MULTI_PAGE_STARS: "10", - PAT_1: "placeholderPAT", // so the backend's retryer.js sees there is 1 PAT and sets `RETRIES` accordingly - }, + env: {}, } as (typeof window)["process"]; diff --git a/apps/frontend/src/pages/Home/Home.tsx b/apps/frontend/src/pages/Home/Home.tsx index e73cb397..62a92bbb 100644 --- a/apps/frontend/src/pages/Home/Home.tsx +++ b/apps/frontend/src/pages/Home/Home.tsx @@ -2,7 +2,7 @@ import axios from "axios"; import { useEffect, useMemo, useRef, useState } from "react"; import type { JSX } from "react"; import { useDispatch } from "react-redux"; -import BounceLoader from "react-spinners/BounceLoader"; +import { BounceLoader } from "react-spinners"; import { v4 as uuidv4 } from "uuid"; import { authenticate } from "../../api/user"; @@ -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); @@ -76,6 +76,7 @@ export function HomeScreen({ stage, setStage }: HomeScreenProps): JSX.Element { >(); const [customTitle, setCustomTitle] = useState(""); const [langsCount, setLangsCount] = useState(); + const [hideValues, setHideValues] = useState(false); const [showAllStats, setShowAllStats] = useState(false); const [showIcons, setShowIcons] = useState(false); const [includeAllCommits, setIncludeAllCommits] = useState(true); @@ -121,6 +122,7 @@ export function HomeScreen({ stage, setStage }: HomeScreenProps): JSX.Element { descriptionLines, customTitle, langsCount, + hideValues, showAllStats, showIcons, includeAllCommits, @@ -306,6 +308,8 @@ export function HomeScreen({ stage, setStage }: HomeScreenProps): JSX.Element { setCustomTitle={setCustomTitle} langsCount={langsCount} setLangsCount={setLangsCount} + hideValues={hideValues} + setHideValues={setHideValues} showAllStats={showAllStats} setShowAllStats={setShowAllStats} showIcons={showIcons} diff --git a/apps/frontend/src/pages/Home/getFullSuffix.test.ts b/apps/frontend/src/pages/Home/getFullSuffix.test.ts index aa22224f..5351cb9f 100644 --- a/apps/frontend/src/pages/Home/getFullSuffix.test.ts +++ b/apps/frontend/src/pages/Home/getFullSuffix.test.ts @@ -22,6 +22,7 @@ const baseOptions = { descriptionLines: undefined, customTitle: "", langsCount: undefined, + hideValues: false, showAllStats: false, showIcons: false, includeAllCommits: false, diff --git a/apps/frontend/src/pages/Home/getFullSuffix.ts b/apps/frontend/src/pages/Home/getFullSuffix.ts index b333b429..e6dc776d 100644 --- a/apps/frontend/src/pages/Home/getFullSuffix.ts +++ b/apps/frontend/src/pages/Home/getFullSuffix.ts @@ -19,6 +19,7 @@ interface Options { descriptionLines: number | undefined; customTitle: string; langsCount: number | undefined; + hideValues: boolean; showAllStats: boolean; showIcons: boolean; includeAllCommits: boolean; @@ -41,6 +42,7 @@ export function getFullSuffix({ descriptionLines, customTitle, langsCount, + hideValues, showAllStats, showIcons, includeAllCommits, @@ -138,6 +140,10 @@ export function getFullSuffix({ fullSuffix += `&langs_count=${langsCount}`; } + if (hideValues && selectedCard === CardType.TOP_LANGS) { + fullSuffix += `&hide_values=true`; + } + if (showAllStats && selectedCard === CardType.STATS) { fullSuffix += `&show=reviews,discussions_started,discussions_answered,prs_merged,prs_merged_percentage,prs_commented,prs_reviewed,issues_commented`; } diff --git a/apps/frontend/src/pages/Home/stages/Customize.tsx b/apps/frontend/src/pages/Home/stages/Customize.tsx index 43b65f18..9443c9f0 100644 --- a/apps/frontend/src/pages/Home/stages/Customize.tsx +++ b/apps/frontend/src/pages/Home/stages/Customize.tsx @@ -47,6 +47,8 @@ interface CustomizeStageProps { setCustomTitle: Updater; langsCount: number | undefined; setLangsCount: Updater; + hideValues: boolean; + setHideValues: Updater; showIcons: boolean; setShowIcons: Updater; showAllStats: boolean; @@ -87,6 +89,8 @@ export function CustomizeStage({ setCustomTitle, langsCount, setLangsCount, + hideValues, + setHideValues, showIcons, setShowIcons, showAllStats, @@ -321,6 +325,15 @@ export function CustomizeStage({ max={20} /> )} + {cardType === CardType.TOP_LANGS && ( + + )} {cardType === CardType.WAKATIME && ( ; - 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 00000000..4c13d7b1 --- /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 138025f9..d9bd05cb 100644 --- a/apps/frontend/vercel.json +++ b/apps/frontend/vercel.json @@ -1,6 +1,4 @@ { "$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/", - "buildCommand": "pnpm run build", "outputDirectory": "build" } diff --git a/apps/frontend/vite.config.ts b/apps/frontend/vite.config.ts index b6c6e152..dc47bb39 100644 --- a/apps/frontend/vite.config.ts +++ b/apps/frontend/vite.config.ts @@ -1,69 +1,29 @@ 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 react from "@vitejs/plugin-react"; +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, /** @todo use chunks to split bundle? */ chunkSizeWarningLimit: 800, + + rolldownOptions: { + external: ["pg"], + }, }, resolve: { + conditions: ["@stats/source"], alias: [ { - find: "../src/fetchers/wakatime.js", + find: "../fetchers/wakatime.js", replacement: path.resolve( import.meta.dirname, "src/wakatime-override.ts", @@ -72,7 +32,6 @@ export default defineConfig({ ], }, test: { - dir: "./src", - exclude: ["**/backend/**"], + dir: path.join(import.meta.dirname, "./src"), }, }); diff --git a/docs/advanced_documentation.md b/docs/advanced_documentation.md index 10965e45..868979da 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` GitHub Stats Extended Themes -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 ![Anurag's GitHub stats](https://github-stats-extended.vercel.app/api?username=anuraghazra&show_icons=true&bg_color=00000000) @@ -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` | @@ -417,6 +417,7 @@ You can customize the appearance and behavior of the top languages card using th | `disable_animations` | Disables all animations in the card. | boolean | `false` | | `prog_bar_bg_color` | Background color of the bars. (Applies only to `normal` layout.) | string (hex color) | `#ddd` | | `hide_progress` | Uses the compact layout option, hides percentages, and removes the bars. | boolean | `false` | +| `hide_values` | Hides language percentages or bytes while keeping the progress bars or chart. | boolean | `false` | | `size_weight` | Configures language stats algorithm (see [Language stats algorithm](#language-stats-algorithm)). | integer | `1` | | `count_weight` | Configures language stats algorithm (see [Language stats algorithm](#language-stats-algorithm)). | integer | `0` | | `stats_format` | Switches between two available formats for language's stats `percentages` and `bytes`. | enum | `percentages` | diff --git a/docs/deploy.md b/docs/deploy.md index 1ad48734..637cb46a 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. @@ -163,12 +164,12 @@ GitHub Stats Extended provides several environment variables that can be used to UPDATE_AFTER_HOURS - Sets the duration in hours after which the server proactively regenerates a previously requested card. + Sets the duration in hours after which the server proactively regenerates a previously requested card. Defaults to 11 hours. Any int or float DELETE_AFTER_HOURS - Sets the duration in hours after which the server stops proactively regenerating a previously requested card if it hasn't been requested again in the meantime. + Sets the duration in hours after which the server stops proactively regenerating a previously requested card if it hasn't been requested again in the meantime. Defaults to 8 days, i.e. 192 hours. Any int or float diff --git a/eslint.config.js b/eslint.config.js index b1b9de20..270d6cbb 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 5de96712..1980b45b 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 b0ed2b1c..0218d635 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.2+sha512.a90faf6feeab71ad6c6e57f94e0fe1a12f5dcc22cd754db40ae9593eb6a3e0b6b12e3540218bb37ae083404b1f2ce6db2a4121e979829b4aff94b99f49da1cf8", "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.5", + "@eslint/js": "10.0.1", + "@playwright/test": "1.59.1", + "@types/node": "24.12.2", + "@vitest/coverage-v8": "catalog:default", + "eslint": "10.2.1", "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", + "eslint-plugin-react-hooks": "7.1.1", + "globals": "17.5.0", "husky": "9.1.7", - "knip": "5.85.0", - "lint-staged": "16.2.7", - "prettier": "3.8.1", - "typescript": "5.9.3", - "typescript-eslint": "8.56.1" + "knip": "6.7.0", + "lint-staged": "16.4.0", + "prettier": "3.8.3", + "turbo": "2.9.6", + "typescript": "6.0.3", + "typescript-eslint": "8.59.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 00000000..585d6264 --- /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": "29.0.2", + "vitest": "catalog:default" + }, + "dependencies": { + "axios": "^1.15.2", + "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 93c8e6a7..afd4b1b3 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 00000000..ebce051e --- /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 55ac2e72..5709e30a 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 b79a74f6..77107373 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 056193c8..9d5c9670 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, @@ -44,29 +35,15 @@ export default async (req, res) => { role, disable_animations, hide_progress, + hide_values, 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 +54,7 @@ export default async (req, res) => { theme, }, }), - ); + }; } if ( @@ -85,8 +62,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 +75,7 @@ export default async (req, res) => { theme, }, }), - ); + }; } if ( @@ -105,8 +83,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 +96,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), @@ -157,14 +129,15 @@ export default async (req, res) => { locale: locale ? locale.toLowerCase() : null, disable_animations: parseBoolean(disable_animations), hide_progress: parseBoolean(hide_progress), + hide_values: parseBoolean(hide_values), 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 +149,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 +163,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 00000000..eaf02daf --- /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 95% rename from apps/backend/src/cards/gist.js rename to packages/core/src/cards/gist.js index b630dad8..c8d6437d 100644 --- a/apps/backend/src/cards/gist.js +++ b/packages/core/src/cards/gist.js @@ -16,6 +16,7 @@ import { const ICON_SIZE = 16; const CARD_DEFAULT_WIDTH = 400; +const X_OFFSET = 25; const HEADER_MAX_LENGTH = 35; /** @@ -62,7 +63,9 @@ const renderGistCard = (gistData, options = {}) => { const multiLineDescription = wrapTextMultiline(desc, lineWidth, linesLimit); const descriptionLines = multiLineDescription.length; const descriptionSvg = multiLineDescription - .map((line) => `${encodeHTML(line)}`) + .map( + (line) => `${encodeHTML(line)}`, + ) .join(""); const lineHeight = descriptionLines > 3 ? 12 : 10; @@ -128,7 +131,7 @@ const renderGistCard = (gistData, options = {}) => { card.setHideBorder(hide_border); return card.render(` - + ${descriptionSvg} diff --git a/apps/backend/src/cards/repo.js b/packages/core/src/cards/repo.js similarity index 95% rename from apps/backend/src/cards/repo.js rename to packages/core/src/cards/repo.js index 6b1afb50..d5bab6f1 100644 --- a/apps/backend/src/cards/repo.js +++ b/packages/core/src/cards/repo.js @@ -19,6 +19,8 @@ import { createTextNode } from "./stats.js"; const ICON_SIZE = 16; const DESCRIPTION_LINE_WIDTH = 59; +const CARD_DEFAULT_WIDTH = 400; +const X_OFFSET = 25; const DESCRIPTION_MAX_LINES = 3; /** @@ -96,8 +98,8 @@ const renderRepoCard = (repo, options = {}) => { card_width_input && !isNaN(card_width_input) ? card_width_input : show.length >= 2 - ? 430 - : 400; + ? CARD_DEFAULT_WIDTH + 30 + : CARD_DEFAULT_WIDTH; const i18n = new I18n({ locale, @@ -182,7 +184,9 @@ const renderRepoCard = (repo, options = {}) => { const desc = parseEmojis(description || "No description provided"); const multiLineDescription = wrapTextMultiline( desc, - Math.round((card_width - 400) / 5.93 + DESCRIPTION_LINE_WIDTH), + Math.round( + (card_width - CARD_DEFAULT_WIDTH) / 5.93 + DESCRIPTION_LINE_WIDTH, + ), descriptionMaxLines, ); const descriptionLinesCount = description_lines_count @@ -190,7 +194,9 @@ const renderRepoCard = (repo, options = {}) => { : multiLineDescription.length; const descriptionSvg = multiLineDescription - .map((line) => `${encodeHTML(line)}`) + .map( + (line) => `${encodeHTML(line)}`, + ) .join(""); const extraHeight = Object.keys(STATS).length @@ -298,19 +304,19 @@ const renderRepoCard = (repo, options = {}) => { getBadgeSVG( i18n.t("repocard.template"), colors.textColor, - card_width - 400, + card_width - CARD_DEFAULT_WIDTH, ) : isArchived ? // @ts-ignore getBadgeSVG( i18n.t("repocard.archived"), colors.textColor, - card_width - 400, + card_width - CARD_DEFAULT_WIDTH, ) : "" } - + ${descriptionSvg} 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 09aef286..940271a3 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 94% rename from apps/backend/src/cards/top-languages.js rename to packages/core/src/cards/top-languages.js index 85739c46..8c2d2f18 100644 --- a/apps/backend/src/cards/top-languages.js +++ b/packages/core/src/cards/top-languages.js @@ -221,6 +221,7 @@ const getDisplayValue = (size, percentages, format) => { * @param {number} props.size Size of the programming language. * @param {number} props.totalSize Total size of all languages. * @param {string} props.statsFormat Stats format. + * @param {boolean=} props.hideValues Whether to hide stats values. * @param {number} props.index Index of the programming language. * @returns {string} Programming language SVG node. */ @@ -232,10 +233,11 @@ const createProgressTextNode = ({ size, totalSize, statsFormat, + hideValues, index, }) => { const staggerDelay = (index + 3) * 150; - const paddingRight = 95; + const paddingRight = hideValues ? CARD_PADDING * 2 : 95; const progressTextX = width - paddingRight + 10; const progressWidth = width - paddingRight; @@ -245,7 +247,7 @@ const createProgressTextNode = ({ return ` ${name} - ${displayValue} + ${hideValues ? "" : `${displayValue}`} ${createProgressNode({ x: 0, y: 25, @@ -266,6 +268,7 @@ const createProgressTextNode = ({ * @param {Lang} props.lang Programming language object. * @param {number} props.totalSize Total size of all languages. * @param {boolean=} props.hideProgress Whether to hide percentage. + * @param {boolean=} props.hideValues Whether to hide stats values (percentages/bytes). * @param {string=} props.statsFormat Stats format * @param {number} props.index Index of the programming language. * @returns {string} Compact layout programming language SVG node. @@ -274,6 +277,7 @@ const createCompactLangNode = ({ lang, totalSize, hideProgress, + hideValues, statsFormat = "percentages", index, }) => { @@ -287,7 +291,7 @@ const createCompactLangNode = ({ - ${lang.name} ${hideProgress ? "" : displayValue} + ${lang.name} ${hideProgress || hideValues ? "" : displayValue} `; @@ -300,6 +304,7 @@ const createCompactLangNode = ({ * @param {Lang[]} props.langs Array of programming languages. * @param {number} props.totalSize Total size of all languages. * @param {boolean=} props.hideProgress Whether to hide percentage. + * @param {boolean=} props.hideValues Whether to hide stats values. * @param {string=} props.statsFormat Stats format * @returns {string} Programming languages SVG node. */ @@ -307,6 +312,7 @@ const createLanguageTextNode = ({ langs, totalSize, hideProgress, + hideValues, statsFormat, }) => { const longestLang = getLongestLang(langs); @@ -318,6 +324,7 @@ const createLanguageTextNode = ({ lang, totalSize, hideProgress, + hideValues, statsFormat, index, }), @@ -344,16 +351,23 @@ const createLanguageTextNode = ({ * @param {object} props Function properties. * @param {Lang[]} props.langs Array of programming languages. * @param {number} props.totalSize Total size of all languages. + * @param {boolean=} props.hideValues Whether to hide stats values. * @param {string} props.statsFormat Stats format * @returns {string} Donut layout programming language SVG node. */ -const createDonutLanguagesNode = ({ langs, totalSize, statsFormat }) => { +const createDonutLanguagesNode = ({ + langs, + totalSize, + hideValues, + statsFormat, +}) => { return flexLayout({ items: langs.map((lang, index) => { return createCompactLangNode({ lang, totalSize, hideProgress: false, + hideValues, statsFormat, index, }); @@ -371,6 +385,7 @@ const createDonutLanguagesNode = ({ langs, totalSize, statsFormat }) => { * @param {number} totalLanguageSize Total size of all languages. * @param progBarBgColor Color of the background of progress bar. * @param {string} statsFormat Stats format. + * @param {boolean=} hideValues Whether to hide stats values. * @returns {string} Normal layout card SVG object. */ const renderNormalLayout = ( @@ -379,6 +394,7 @@ const renderNormalLayout = ( totalLanguageSize, progBarBgColor, statsFormat, + hideValues, ) => { return flexLayout({ items: langs.map((lang, index) => { @@ -389,6 +405,7 @@ const renderNormalLayout = ( size: lang.size, totalSize: totalLanguageSize, statsFormat, + hideValues, index, progBarBgColor, }); @@ -406,6 +423,7 @@ const renderNormalLayout = ( * @param {number} totalLanguageSize Total size of all languages. * @param {boolean=} hideProgress Whether to hide progress bar. * @param {string} statsFormat Stats format. + * @param {boolean=} hideValues Whether to hide stats values. * @returns {string} Compact layout card SVG object. */ const renderCompactLayout = ( @@ -414,6 +432,7 @@ const renderCompactLayout = ( totalLanguageSize, hideProgress, statsFormat = "percentages", + hideValues, ) => { const paddingRight = 50; const offsetWidth = width - paddingRight; @@ -461,6 +480,7 @@ const renderCompactLayout = ( totalSize: totalLanguageSize, hideProgress, statsFormat, + hideValues, })} `; @@ -472,9 +492,15 @@ const renderCompactLayout = ( * @param {Lang[]} langs Array of programming languages. * @param {number} totalLanguageSize Total size of all languages. * @param {string} statsFormat Stats format. + * @param {boolean=} hideValues Whether to hide stats values. * @returns {string} Compact layout card SVG object. */ -const renderDonutVerticalLayout = (langs, totalLanguageSize, statsFormat) => { +const renderDonutVerticalLayout = ( + langs, + totalLanguageSize, + statsFormat, + hideValues, +) => { // Donut vertical chart radius and total length const radius = 80; const totalCircleLength = getCircleLength(radius); @@ -531,6 +557,7 @@ const renderDonutVerticalLayout = (langs, totalLanguageSize, statsFormat) => { totalSize: totalLanguageSize, hideProgress: false, statsFormat, + hideValues, })} @@ -544,9 +571,10 @@ const renderDonutVerticalLayout = (langs, totalLanguageSize, statsFormat) => { * @param {Lang[]} langs Array of programming languages. * @param {number} totalLanguageSize Total size of all languages. * @param {string} statsFormat Stats format. + * @param {boolean=} hideValues Whether to hide stats values. * @returns {string} Compact layout card SVG object. */ -const renderPieLayout = (langs, totalLanguageSize, statsFormat) => { +const renderPieLayout = (langs, totalLanguageSize, statsFormat, hideValues) => { // Pie chart radius and center coordinates const radius = 90; const centerX = 150; @@ -628,6 +656,7 @@ const renderPieLayout = (langs, totalLanguageSize, statsFormat) => { totalSize: totalLanguageSize, hideProgress: false, statsFormat, + hideValues, })} @@ -647,7 +676,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++) { @@ -679,9 +708,16 @@ const createDonutPaths = (cx, cy, radius, percentages) => { * @param {number} width Card width. * @param {number} totalLanguageSize Total size of all languages. * @param {string} statsFormat Stats format. + * @param {boolean=} hideValues Whether to hide stats values. * @returns {string} Donut layout card SVG object. */ -const renderDonutLayout = (langs, width, totalLanguageSize, statsFormat) => { +const renderDonutLayout = ( + langs, + width, + totalLanguageSize, + statsFormat, + hideValues, +) => { const centerX = width / 3; const centerY = width / 3; const radius = centerX - 60; @@ -724,7 +760,7 @@ const renderDonutLayout = (langs, width, totalLanguageSize, statsFormat) => { return ` - ${createDonutLanguagesNode({ langs, totalSize: totalLanguageSize, statsFormat })} + ${createDonutLanguagesNode({ langs, totalSize: totalLanguageSize, hideValues, statsFormat })} @@ -800,6 +836,7 @@ const renderTopLanguages = (topLangs, options = {}) => { prog_bar_bg_color, hide, hide_progress, + hide_values, theme, layout, custom_title, @@ -840,7 +877,7 @@ const renderTopLanguages = (topLangs, options = {}) => { theme, }); - let finalLayout = ""; + let finalLayout; if (langs.length === 0) { height = COMPACT_LAYOUT_BASE_HEIGHT; finalLayout = noLanguagesDataNode({ @@ -850,13 +887,19 @@ const renderTopLanguages = (topLangs, options = {}) => { }); } else if (layout === "pie") { height = calculatePieLayoutHeight(langs.length); - finalLayout = renderPieLayout(langs, totalLanguageSize, stats_format); + finalLayout = renderPieLayout( + langs, + totalLanguageSize, + stats_format, + hide_values, + ); } else if (layout === "donut-vertical") { height = calculateDonutVerticalLayoutHeight(langs.length); finalLayout = renderDonutVerticalLayout( langs, totalLanguageSize, stats_format, + hide_values, ); } else if (layout === "compact" || hide_progress == true) { height = @@ -868,6 +911,7 @@ const renderTopLanguages = (topLangs, options = {}) => { totalLanguageSize, hide_progress, stats_format, + hide_values, ); } else if (layout === "donut") { height = calculateDonutLayoutHeight(langs.length); @@ -877,6 +921,7 @@ const renderTopLanguages = (topLangs, options = {}) => { width, totalLanguageSize, stats_format, + hide_values, ); } else { finalLayout = renderNormalLayout( @@ -885,6 +930,7 @@ const renderTopLanguages = (topLangs, options = {}) => { totalLanguageSize, fallbackColor(prog_bar_bg_color, "#ddd"), stats_format, + hide_values, ); } diff --git a/apps/backend/src/cards/types.d.ts b/packages/core/src/cards/types.d.ts similarity index 87% rename from apps/backend/src/cards/types.d.ts rename to packages/core/src/cards/types.d.ts index 27e04c2a..08b9f933 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,19 +47,20 @@ 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; disable_animations: boolean; hide_progress: boolean; + hide_values: boolean; prog_bar_bg_color: string; stats_format: "percentages" | "bytes"; }; 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 02eac539..f7eeeee3 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 45bc81fe..6345b662 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 622ed161..bc1395e8 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 00000000..32456ce8 --- /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 16c80b8b..5bc3ad14 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 86% rename from apps/backend/src/common/icons.js rename to packages/core/src/common/icons.js index 4a805d44..9b0c33ac 100644 --- a/apps/backend/src/common/icons.js +++ b/packages/core/src/common/icons.js @@ -1,5 +1,31 @@ // @ts-check +/* +The icons in this file are based on https://github.com/primer/octicons which is released under the MIT license: + +MIT License + +Copyright (c) 2026 GitHub Inc. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +*/ + const icons = { star: ``, commits: ``, 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 63bbbe94..c59f870c 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", @@ -535,6 +538,7 @@ "Snakemake": "#419179", "Solidity": "#AA6746", "SourcePawn": "#f69e1d", + "SpiceDB Schema": "#a5318a", "Squirrel": "#800000", "Stan": "#b2011d", "Standard ML": "#dc566d", diff --git a/packages/core/src/common/log.ts b/packages/core/src/common/log.ts new file mode 100644 index 00000000..17c081e4 --- /dev/null +++ b/packages/core/src/common/log.ts @@ -0,0 +1,12 @@ +/** + * Return console instance based on the environment. + */ +const logger: { + log: (...args: Array) => void; + error: (...args: Array) => void; +} = { + log: console.log, + error: console.error, +}; + +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 36533464..7aba75c0 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 4661deff..79d294dc 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 e80264ff..450b1711 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 90b269c4..96d727f6 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 f4a6be86..1e2f3a55 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 dad43ebe..541bb3b1 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 1588c921..b26b0661 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 00000000..241db1eb --- /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, loadConfigFromEnv } 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 98fab2d3..565cd3a9 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 c60b504f..206a71b8 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 00000000..39566617 --- /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 3997f666..e432cef1 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 960bc344..c9d289d4 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 ca0d41ed..a0336e67 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 b98eca47..d72b1e4b 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 97% rename from apps/backend/tests/fetchStats.test.js rename to packages/core/tests/fetchStats.test.js index b4681f59..0593560c 100644 --- a/apps/backend/tests/fetchStats.test.js +++ b/packages/core/tests/fetchStats.test.js @@ -1,11 +1,15 @@ import axios from "axios"; import MockAdapter from "axios-mock-adapter"; -import { afterEach, beforeEach, describe, expect, it } from "vitest"; +import { afterEach, beforeEach, describe, expect, it, vi } 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"; +vi.mock(import("../src/common/log.js"), async () => { + const { createLoggerMock } = await import("./utils.js"); + return createLoggerMock(); +}); // Test parameters. const data_stats = { @@ -107,6 +111,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 +318,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 +355,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 +392,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({ @@ -531,7 +539,6 @@ describe("Test fetchStats", () => { }); it("should return correct data when user don't have any pull requests", async () => { - mock.reset(); mock .onPost("https://api.github.com/graphql") .reply(200, data_without_pull_requests); diff --git a/apps/backend/tests/fetchTopLanguages.test.js b/packages/core/tests/fetchTopLanguages.test.js similarity index 89% rename from apps/backend/tests/fetchTopLanguages.test.js rename to packages/core/tests/fetchTopLanguages.test.js index f4f24859..abf746b9 100644 --- a/apps/backend/tests/fetchTopLanguages.test.js +++ b/packages/core/tests/fetchTopLanguages.test.js @@ -1,17 +1,25 @@ import axios from "axios"; import MockAdapter from "axios-mock-adapter"; -import { afterEach, describe, expect, it } from "vitest"; +import { afterEach, describe, expect, it, vi } from "vitest"; import { fetchTopLanguages } from "../src/fetchers/top-languages.js"; import { approxNumber } from "./utils.js"; -import "@testing-library/jest-dom/vitest"; +vi.mock(import("../src/common/log.js"), async () => { + const { createLoggerMock } = await import("./utils.js"); + return createLoggerMock(); +}); + +const { logger } = await import("../src/index.js"); + +const loggerErrorSpy = vi.mocked(logger.error); const mock = new MockAdapter(axios); afterEach(() => { mock.reset(); + loggerErrorSpy.mockClear(); }); const data_langs = { @@ -151,6 +159,8 @@ describe("FetchTopLanguages", () => { await expect(fetchTopLanguages("anuraghazra")).rejects.toThrow( "Could not resolve to a User with the login of 'noname'.", ); + + expect(loggerErrorSpy).toHaveBeenCalledOnce(); }); it("should throw other errors with their message", async () => { @@ -161,6 +171,8 @@ describe("FetchTopLanguages", () => { await expect(fetchTopLanguages("anuraghazra")).rejects.toThrow( "Some test GraphQL error", ); + + expect(loggerErrorSpy).toHaveBeenCalledOnce(); }); it("should throw error with specific message when error does not contain message property", async () => { @@ -171,5 +183,7 @@ describe("FetchTopLanguages", () => { await expect(fetchTopLanguages("anuraghazra")).rejects.toThrow( "Something went wrong while trying to retrieve the language data using the GraphQL API.", ); + + expect(loggerErrorSpy).toHaveBeenCalledOnce(); }); }); 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 5149b49f..cc1ac289 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 d5cd6ca4..9eb55d01 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 28fbfa33..fe06b4b8 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 4efecf57..f14f7998 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 c21bcf45..c5e459fe 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 92% rename from apps/backend/tests/renderTopLanguagesCard.test.js rename to packages/core/tests/renderTopLanguagesCard.test.js index ad50cc2d..d80e2860 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", @@ -911,4 +909,89 @@ describe("Test renderTopLanguages", () => { "css 100.0 B", ); }); + + it("should hide stats values when hide_values is true (compact layout)", () => { + document.body.innerHTML = renderTopLanguages(langs, { + layout: "compact", + hide_values: true, + }); + + expect(queryAllByTestId(document.body, "lang-name")[0]).toHaveTextContent( + "HTML", + ); + + expect(queryAllByTestId(document.body, "lang-name")[1]).toHaveTextContent( + "javascript", + ); + }); + + it("should hide stats values when hide_values is true (normal layout)", () => { + document.body.innerHTML = renderTopLanguages(langs, { + hide_values: true, + }); + + expect(queryAllByTestId(document.body, "lang-name")[0]).toHaveTextContent( + "HTML", + ); + + expect(queryAllByTestId(document.body, "lang-name").length).toBe(3); + }); + + it("should hide stats values when hide_values is true (donut layout)", () => { + document.body.innerHTML = renderTopLanguages(langs, { + layout: "donut", + hide_values: true, + }); + + expect(queryAllByTestId(document.body, "lang-name")[0]).toHaveTextContent( + "HTML", + ); + expect(queryAllByTestId(document.body, "lang-name")[1]).toHaveTextContent( + "javascript", + ); + expect(queryAllByTestId(document.body, "lang-name")[2]).toHaveTextContent( + "css", + ); + }); + + it("should hide stats values when hide_values is true (pie layout)", () => { + document.body.innerHTML = renderTopLanguages(langs, { + layout: "pie", + hide_values: true, + }); + + expect(queryAllByTestId(document.body, "lang-name")[0]).toHaveTextContent( + "HTML", + ); + expect(queryAllByTestId(document.body, "lang-name")[1]).toHaveTextContent( + "javascript", + ); + expect(queryAllByTestId(document.body, "lang-name")[2]).toHaveTextContent( + "css", + ); + expect(queryAllByTestId(document.body, "lang-pie")[0]).toHaveAttribute( + "size", + "40", + ); + + document.body.innerHTML = renderTopLanguages( + { HTML: langs.HTML }, + { + layout: "pie", + hide_values: true, + }, + ); + + expect(queryAllByTestId(document.body, "lang-name")[0]).toHaveTextContent( + "HTML", + ); + expect(queryAllByTestId(document.body, "lang-pie")).toHaveLength(1); + expect(queryAllByTestId(document.body, "lang-pie")[0]).toHaveAttribute( + "size", + "100", + ); + expect(queryAllByTestId(document.body, "lang-pie")[0].tagName).toBe( + "circle", + ); + }); }); 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 8c4971cf..ba8577c7 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/packages/core/tests/retryer.test.js b/packages/core/tests/retryer.test.js new file mode 100644 index 00000000..d869296b --- /dev/null +++ b/packages/core/tests/retryer.test.js @@ -0,0 +1,87 @@ +// @ts-check + +import { describe, expect, it, vi } from "vitest"; + +import { retryer } from "../src/common/retryer.js"; + +vi.mock(import("../src/common/log.js"), async () => { + const { createLoggerMock } = await import("./utils.js"); + return createLoggerMock(); +}); + +const fetcher = vi.fn().mockResolvedValue({ data: "ok" }); + +const fetcherFail = vi.fn().mockResolvedValue({ + data: { errors: [{ type: "RATE_LIMITED" }] }, +}); + +const fetcherFailOnSecondTry = vi.fn((_vars, _token, retries) => { + if (retries < 1) { + return Promise.resolve({ data: { errors: [{ type: "RATE_LIMITED" }] } }); + } + return Promise.resolve({ data: "ok" }); +}); + +const fetcherFailWithMessageBasedRateLimitErr = vi.fn( + (_vars, _token, retries) => { + if (retries < 1) { + return Promise.resolve({ + data: { + errors: [ + { + type: "ASDF", + message: "API rate limit already exceeded for user ID 11111111", + }, + ], + }, + }); + } + return Promise.resolve({ data: "ok" }); + }, +); + +const customFetcher = vi.fn((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, {}); + + expect(fetcher).toHaveBeenCalledTimes(1); + expect(res).toStrictEqual({ data: "ok" }); + }); + + it("retryer should return value and have 2 retries", async () => { + let res = await retryer(fetcherFailOnSecondTry, {}); + + expect(fetcherFailOnSecondTry).toHaveBeenCalledTimes(2); + expect(res).toStrictEqual({ data: "ok" }); + }); + + it("retryer should return value and have 2 retries with message based rate limit error", async () => { + let res = await retryer(fetcherFailWithMessageBasedRateLimitErr, {}); + + expect(fetcherFailWithMessageBasedRateLimitErr).toHaveBeenCalledTimes(2); + expect(res).toStrictEqual({ data: "ok" }); + }); + + it("retryer should throw specific error if maximum retries reached", async () => { + await expect(retryer(fetcherFail, {})).rejects.toThrow( + "Downtime due to GitHub API rate limiting", + ); + + expect(fetcherFail).toHaveBeenCalledTimes(2); + }); + + it("retryer should use injected PATs when provided", async () => { + const res = await retryer(customFetcher, {}, "user-pat-token"); + + expect(customFetcher).toHaveBeenCalledExactlyOnceWith( + {}, + "user-pat-token", + 0, + ); + expect(res).toStrictEqual({ data: { token: "user-pat-token" } }); + }); +}); diff --git a/packages/core/tests/utils.ts b/packages/core/tests/utils.ts new file mode 100644 index 00000000..1a4c5205 --- /dev/null +++ b/packages/core/tests/utils.ts @@ -0,0 +1,73 @@ +import { vi } from "vitest"; + +import type * as loggerModule from "../src/common/log.js"; + +/** + * 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 expected The expected numeric value to compare against. + * + * @param + * The number of decimal places of tolerance. Higher values mean stricter + * comparison. Internally converted to epsilon = 10^-precision. + * + * @returns An object implementing Jest-style asymmetric matcher methods. + * + */ +export function approxNumber( + expected: number, + precision = 10, +): { + asymmetricMatch(actual: unknown): boolean; + toAsymmetricMatcher(): string; +} { + 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})`; + }, + }; +} + +/** + * Helper to create logger module mocks to use in unit tests. + * If you need to perform assertions on logger use `vi.mocked`. + * + * @example + * ```ts + * import { logger } from "../src/common/log.js"; + * + * vi.mock(import("../src/common/log.js"), async () => { + * const { createLoggerMock } = await import("./utils.js"); + * return createLoggerMock(); + * }); + * + * const logSpy = vi.mocked(logger.log); + * ``` + * + * @returns mocked logger module + */ +export function createLoggerMock(): typeof loggerModule { + return { + logger: { + log: vi.fn(), + error: vi.fn(), + }, + }; +} diff --git a/packages/core/tsconfig.build.json b/packages/core/tsconfig.build.json new file mode 100644 index 00000000..0167680b --- /dev/null +++ b/packages/core/tsconfig.build.json @@ -0,0 +1,10 @@ +{ + "extends": ["./tsconfig.json"], + "include": ["src"], + "compilerOptions": { + "rootDir": "./src", + "declaration": true, + "declarationMap": true, + "customConditions": [] + } +} diff --git a/packages/core/tsconfig.json b/packages/core/tsconfig.json new file mode 100644 index 00000000..c1b9c120 --- /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 00000000..4c13d7b1 --- /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 00000000..4e11ea53 --- /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 a23363aa..ce310c0c 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -7,216 +7,234 @@ settings: catalogs: default: '@vitest/coverage-v8': - specifier: 4.0.18 - version: 4.0.18 + specifier: 4.1.4 + version: 4.1.4 vite: - specifier: 7.3.1 - version: 7.3.1 + specifier: 8.0.10 + version: 8.0.10 vitest: - specifier: 4.0.18 - version: 4.0.18 + specifier: 4.1.4 + version: 4.1.4 importers: .: devDependencies: '@eslint/compat': - specifier: 2.0.2 - version: 2.0.2(eslint@9.39.2(jiti@2.6.1)) + specifier: 2.0.5 + version: 2.0.5(eslint@10.2.1(jiti@2.6.1)) '@eslint/js': - specifier: 9.39.3 - version: 9.39.3 + specifier: 10.0.1 + version: 10.0.1(eslint@10.2.1(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.2 + version: 24.12.2 + '@vitest/coverage-v8': + specifier: catalog:default + version: 4.1.4(vitest@4.1.4) eslint: - specifier: 9.39.2 - version: 9.39.2(jiti@2.6.1) + specifier: 10.2.1 + version: 10.2.1(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.59.0(eslint@10.2.1(jiti@2.6.1))(typescript@6.0.3))(eslint@10.2.1(jiti@2.6.1)))(eslint@10.2.1(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.59.0(eslint@10.2.1(jiti@2.6.1))(typescript@6.0.3))(eslint@10.2.1(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.2.1(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.2.1(jiti@2.6.1)) eslint-plugin-react-hooks: - specifier: 7.0.1 - version: 7.0.1(eslint@9.39.2(jiti@2.6.1)) + specifier: 7.1.1 + version: 7.1.1(eslint@10.2.1(jiti@2.6.1)) globals: - specifier: 17.3.0 - version: 17.3.0 + specifier: 17.5.0 + version: 17.5.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.7.0 + version: 6.7.0(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0) 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 + specifier: 3.8.3 + version: 3.8.3 + turbo: + specifier: 2.9.6 + version: 2.9.6 typescript: - specifier: 5.9.3 - version: 5.9.3 + specifier: 6.0.3 + version: 6.0.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.59.0 + version: 8.59.0(eslint@10.2.1(jiti@2.6.1))(typescript@6.0.3) + vitest: + specifier: catalog:default + version: 4.1.4(@types/node@24.12.2)(@vitest/coverage-v8@4.1.4)(jsdom@29.0.2)(vite@8.0.10(@types/node@24.12.2)(jiti@2.6.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 + specifier: ^1.15.2 + version: 1.15.2 pg: - specifier: ^8.18.0 - version: 8.18.0 - word-wrap: - specifier: ^1.2.5 - version: 1.2.5 + specifier: ^8.20.0 + version: 8.20.0 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 - version: 2.1.0(axios@1.13.5) + specifier: 2.1.0 + version: 2.1.0(axios@1.15.2) 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 + specifier: 29.0.2 + version: 29.0.2 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.4(@types/node@24.12.2)(@vitest/coverage-v8@4.1.4)(jsdom@29.0.2)(vite@8.0.10(@types/node@24.12.2)(jiti@2.6.1)(yaml@2.8.3)) apps/frontend: dependencies: '@reduxjs/toolkit': - 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) + specifier: ^2.11.2 + version: 2.11.2(react-redux@9.2.0(@types/react@19.2.14)(react@19.2.5)(redux@5.0.1))(react@19.2.5) + '@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.2 + version: 4.2.2(vite@8.0.10(@types/node@24.12.2)(jiti@2.6.1)(yaml@2.8.3)) axios: specifier: ^1 - version: 1.13.5 + version: 1.15.2 axios-cache-interceptor: specifier: ^1 - version: 1.11.4(axios@1.13.5) + version: 1.12.0(axios@1.15.2) 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 - version: 18.3.1 + specifier: ^19.2.5 + version: 19.2.5 react-dom: - specifier: 18.3.1 - version: 18.3.1(react@18.3.1) + specifier: ^19.2.5 + version: 19.2.5(react@19.2.5) react-icons: - specifier: ^5.5.0 - version: 5.5.0(react@18.3.1) + specifier: ^5.6.0 + version: 5.6.0(react@19.2.5) react-loading-skeleton: specifier: ^3.3.1 - version: 3.5.0(react@18.3.1) + version: 3.5.0(react@19.2.5) react-redux: - specifier: 9.2.0 - version: 9.2.0(@types/react@18.3.27)(react@18.3.1)(redux@5.0.1) + specifier: ^9.2.0 + version: 9.2.0(@types/react@19.2.14)(react@19.2.5)(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) + version: 0.17.0(react-dom@19.2.5(react@19.2.5))(react@19.2.5) react-toastify: - specifier: 11.0.5 - version: 11.0.5(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + specifier: ^11.1.0 + version: 11.1.0(react-dom@19.2.5(react@19.2.5))(react@19.2.5) 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 - version: 13.0.0 - word-wrap: - specifier: ^1.2.5 - version: 1.2.5 + specifier: ^14.0.0 + version: 14.0.0 devDependencies: '@types/react': - specifier: 18.3.27 - version: 18.3.27 + specifier: 19.2.14 + version: 19.2.14 '@types/react-dom': - specifier: 18.3.7 - 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)) + specifier: 19.2.3 + version: 19.2.3(@types/react@19.2.14) + '@vitejs/plugin-react': + specifier: 6.0.1 + version: 6.0.1(vite@8.0.10(@types/node@24.12.2)(jiti@2.6.1)(yaml@2.8.3)) clsx: specifier: 2.1.1 version: 2.1.1 tailwindcss: - specifier: 4.2.1 - version: 4.2.1 + specifier: 4.2.2 + version: 4.2.2 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: 8.0.10(@types/node@24.12.2)(jiti@2.6.1)(yaml@2.8.3) 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.4(@types/node@24.12.2)(@vitest/coverage-v8@4.1.4)(jsdom@29.0.2)(vite@8.0.10(@types/node@24.12.2)(jiti@2.6.1)(yaml@2.8.3)) + + packages/core: + dependencies: + axios: + specifier: ^1.15.2 + version: 1.15.2 + 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.15.2) + js-yaml: + specifier: 4.1.1 + version: 4.1.1 + jsdom: + specifier: 29.0.2 + version: 29.0.2 + vitest: + specifier: catalog:default + version: 4.1.4(@types/node@24.12.2)(@vitest/coverage-v8@4.1.4)(jsdom@29.0.2)(vite@8.0.10(@types/node@24.12.2)(jiti@2.6.1)(yaml@2.8.3)) packages: - '@acemir/cssom@0.9.31': - resolution: {integrity: sha512-ZnR3GSaH+/vJ0YlHau21FjfLYjMpYVIzTD8M8vIEQvIGxeOXyXdzCI140rrCY862p/C/BbzWsjc1dgnM9mkoTA==} - '@adobe/css-tools@4.4.4': resolution: {integrity: sha512-Elp+iwUx5rN5+Y8xLt5/GRoG20WGoDCQ/1Fb+1LiGtvwbDavuSk0jhD/eZdckHAuzcDzccnkv+rEjyWfRx18gg==} - '@asamuzakjp/css-color@5.0.1': - resolution: {integrity: sha512-2SZFvqMyvboVV1d15lMf7XiI3m7SDqXUuKaTymJYLN6dSGadqp+fVojqJlVoMlbZnlTmu3S0TLwLTJpvBMO1Aw==} + '@asamuzakjp/css-color@5.1.11': + resolution: {integrity: sha512-KVw6qIiCTUQhByfTd78h2yD1/00waTmm9uy/R7Ck/ctUyAPj+AEDLkQIdJW0T8+qGgj3j5bpNKK7Q3G+LedJWg==} engines: {node: ^20.19.0 || ^22.12.0 || >=24.0.0} - '@asamuzakjp/dom-selector@6.8.1': - resolution: {integrity: sha512-MvRz1nCqW0fsy8Qz4dnLIvhOlMzqDVBabZx6lH+YywFDdjXhMY37SmpV1XFX3JzG5GWHn63j6HX6QPr3lZXHvQ==} + '@asamuzakjp/dom-selector@7.1.1': + resolution: {integrity: sha512-67RZDnYRc8H/8MLDgQCDE//zoqVFwajkepHZgmXrbwybzXOEwOWGPYGmALYl9J2DOLfFPPs6kKCqmbzV895hTQ==} + engines: {node: ^20.19.0 || ^22.12.0 || >=24.0.0} + + '@asamuzakjp/generational-cache@1.0.1': + resolution: {integrity: sha512-wajfB8KqzMCN2KGNFdLkReeHncd0AslUSrvHVvvYWuU8ghncRJoA50kT3zP9MVL0+9g4/67H+cdvBskj9THPzg==} + engines: {node: ^20.19.0 || ^22.12.0 || >=24.0.0} '@asamuzakjp/nwsapi@2.3.9': resolution: {integrity: sha512-n8GuYSrI9bF7FFZ/SjhwevlHc8xaVlb/7HmHelnc/PZXBD2ZR49NnN9sMMuDdEGPeeRQ5d0hqlSlEpgCX3Wl0Q==} @@ -267,17 +285,17 @@ 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 - '@babel/runtime@7.28.6': - resolution: {integrity: sha512-05WQkdpL9COIMz4LjTxGpPNCdlpyimKppYNoJ5Di5EUObifl8t4tuLuUBBZEpoLYOmfvIWrsp9fCl0HoPRVTdA==} + '@babel/runtime@7.29.2': + resolution: {integrity: sha512-JiDShH45zKHWyGe4ZNVRrCjBz8Nh9TMmZG1kh4QTK8hCBTWBi8Da+i7s1fJw7/lYpM4ccepSNfqzZ/QvABBi5g==} engines: {node: '>=6.9.0'} '@babel/template@7.28.6': @@ -304,15 +322,15 @@ packages: resolution: {integrity: sha512-LMGQLS9EuADloEFkcTBR3BwV/CGHV7zyDxVRtVDTwdI2Ca4it0CCVTT9wCkxSgokjE5Ho41hEPgb8OEUwoXr6Q==} engines: {node: '>=20.19.0'} - '@csstools/css-calc@3.1.1': - resolution: {integrity: sha512-HJ26Z/vmsZQqs/o3a6bgKslXGFAungXGbinULZO3eMsOyNJHeBBZfup5FiZInOghgoM4Hwnmw+OgbJCNg1wwUQ==} + '@csstools/css-calc@3.2.0': + resolution: {integrity: sha512-bR9e6o2BDB12jzN/gIbjHa5wLJ4UjD1CB9pM7ehlc0ddk6EBz+yYS1EV2MF55/HUxrHcB/hehAyt5vhsA3hx7w==} engines: {node: '>=20.19.0'} peerDependencies: '@csstools/css-parser-algorithms': ^4.0.0 '@csstools/css-tokenizer': ^4.0.0 - '@csstools/css-color-parser@4.0.2': - resolution: {integrity: sha512-0GEfbBLmTFf0dJlpsNU7zwxRIH0/BGEMuXLTCvFYxuL1tNhqzTbtnFICyJLTNK4a+RechKP75e7w42ClXSnJQw==} + '@csstools/css-color-parser@4.1.0': + resolution: {integrity: sha512-U0KhLYmy2GVj6q4T3WaAe6NPuFYCPQoE3b0dRGxejWDgcPp8TP7S5rVdM5ZrFaqu4N67X8YaPBw14dQSYx3IyQ==} engines: {node: '>=20.19.0'} peerDependencies: '@csstools/css-parser-algorithms': ^4.0.0 @@ -324,186 +342,41 @@ packages: peerDependencies: '@csstools/css-tokenizer': ^4.0.0 - '@csstools/css-syntax-patches-for-csstree@1.0.28': - resolution: {integrity: sha512-1NRf1CUBjnr3K7hu8BLxjQrKCxEe8FP/xmPTenAxCRZWVLbmGotkFvG9mfNpjA6k7Bw1bw4BilZq9cu19RA5pg==} + '@csstools/css-syntax-patches-for-csstree@1.1.3': + resolution: {integrity: sha512-SH60bMfrRCJF3morcdk57WklujF4Jr/EsQUzqkarfHXEFcAR1gg7fS/chAE922Sehgzc1/+Tz5H3Ypa1HiEKrg==} + peerDependencies: + css-tree: ^3.2.1 + peerDependenciesMeta: + css-tree: + optional: true '@csstools/css-tokenizer@4.0.0': 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.10.0': + resolution: {integrity: sha512-yq6OkJ4p82CAfPl0u9mQebQHKPJkY7WrIuk205cTYnYe+k2Z8YBh11FrbRG/H6ihirqcacOgl2BIO8oyMQLeXw==} - '@emnapi/runtime@1.8.1': - resolution: {integrity: sha512-mehfKSMWjjNol8659Z8KxEMrdSJDDot5SXMq00dM8BN4o+CLNXQ0xH2V7EchNHV4RmbZLmmPdEaXZc5H2FXmDg==} + '@emnapi/core@1.9.2': + resolution: {integrity: sha512-UC+ZhH3XtczQYfOlu3lNEkdW/p4dsJ1r/bP7H8+rhao3TTTMO1ATq/4DdIi23XuGoFY+Cz0JmCbdVl0hz9jZcA==} - '@emnapi/wasi-threads@1.1.0': - resolution: {integrity: sha512-WI0DdZ8xFSbgMjR1sFsKABJ/C5OnRrjT06JXbZKexJGrDuPTzZdDYfFlsgcCXCyf+suG5QU2e/y1Wo2V/OapLQ==} + '@emnapi/runtime@1.10.0': + resolution: {integrity: sha512-ewvYlk86xUoGI0zQRNq/mC+16R1QeDlKQy21Ki3oSYXNgLb45GV1P6A0M+/s6nyCuNDqe5VpaY84BzXGwVbwFA==} - '@es-joy/jsdoccomment@0.84.0': - resolution: {integrity: sha512-0xew1CxOam0gV5OMjh2KjFQZsKL2bByX1+q4j3E73MpYIdyUxcZb/xQct9ccUb+ve5KGUYbCUxyPnYB7RbuP+w==} + '@emnapi/runtime@1.9.2': + resolution: {integrity: sha512-3U4+MIWHImeyu1wnmVygh5WlgfYDtyf0k8AbLhMFxOipihf6nrWC4syIm/SwEeec0mNSafiiNnMJwbza/Is6Lw==} + + '@emnapi/wasi-threads@1.2.1': + resolution: {integrity: sha512-uTII7OYF+/Mes/MrcIOYp5yOtSMLBWSIoLPpcgwipoiKbli6k322tcoFsxoIIxPDqW01SQGAgko4EzZi2BNv2w==} + + '@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': resolution: {integrity: sha512-Q9hjxWI5xBM+qW2enxfe8wDKdFWMfd0Z29k5ZJnuBqD/CasY5Zryj09aCA6owbGATWz+39p5uIdaHXpopOcG8g==} engines: {node: '>=10'} - '@esbuild/aix-ppc64@0.27.3': - resolution: {integrity: sha512-9fJMTNFTWZMh5qwrBItuziu834eOCUcEqymSH7pY+zoMVEZg3gcPuBNxH1EvfVYe9h0x/Ptw8KBzv7qxb7l8dg==} - engines: {node: '>=18'} - cpu: [ppc64] - os: [aix] - - '@esbuild/android-arm64@0.27.3': - resolution: {integrity: sha512-YdghPYUmj/FX2SYKJ0OZxf+iaKgMsKHVPF1MAq/P8WirnSpCStzKJFjOjzsW0QQ7oIAiccHdcqjbHmJxRb/dmg==} - engines: {node: '>=18'} - cpu: [arm64] - os: [android] - - '@esbuild/android-arm@0.27.3': - resolution: {integrity: sha512-i5D1hPY7GIQmXlXhs2w8AWHhenb00+GxjxRncS2ZM7YNVGNfaMxgzSGuO8o8SJzRc/oZwU2bcScvVERk03QhzA==} - engines: {node: '>=18'} - cpu: [arm] - os: [android] - - '@esbuild/android-x64@0.27.3': - resolution: {integrity: sha512-IN/0BNTkHtk8lkOM8JWAYFg4ORxBkZQf9zXiEOfERX/CzxW3Vg1ewAhU7QSWQpVIzTW+b8Xy+lGzdYXV6UZObQ==} - engines: {node: '>=18'} - cpu: [x64] - os: [android] - - '@esbuild/darwin-arm64@0.27.3': - resolution: {integrity: sha512-Re491k7ByTVRy0t3EKWajdLIr0gz2kKKfzafkth4Q8A5n1xTHrkqZgLLjFEHVD+AXdUGgQMq+Godfq45mGpCKg==} - engines: {node: '>=18'} - cpu: [arm64] - os: [darwin] - - '@esbuild/darwin-x64@0.27.3': - resolution: {integrity: sha512-vHk/hA7/1AckjGzRqi6wbo+jaShzRowYip6rt6q7VYEDX4LEy1pZfDpdxCBnGtl+A5zq8iXDcyuxwtv3hNtHFg==} - engines: {node: '>=18'} - cpu: [x64] - os: [darwin] - - '@esbuild/freebsd-arm64@0.27.3': - resolution: {integrity: sha512-ipTYM2fjt3kQAYOvo6vcxJx3nBYAzPjgTCk7QEgZG8AUO3ydUhvelmhrbOheMnGOlaSFUoHXB6un+A7q4ygY9w==} - engines: {node: '>=18'} - cpu: [arm64] - os: [freebsd] - - '@esbuild/freebsd-x64@0.27.3': - resolution: {integrity: sha512-dDk0X87T7mI6U3K9VjWtHOXqwAMJBNN2r7bejDsc+j03SEjtD9HrOl8gVFByeM0aJksoUuUVU9TBaZa2rgj0oA==} - engines: {node: '>=18'} - cpu: [x64] - os: [freebsd] - - '@esbuild/linux-arm64@0.27.3': - resolution: {integrity: sha512-sZOuFz/xWnZ4KH3YfFrKCf1WyPZHakVzTiqji3WDc0BCl2kBwiJLCXpzLzUBLgmp4veFZdvN5ChW4Eq/8Fc2Fg==} - engines: {node: '>=18'} - cpu: [arm64] - os: [linux] - - '@esbuild/linux-arm@0.27.3': - resolution: {integrity: sha512-s6nPv2QkSupJwLYyfS+gwdirm0ukyTFNl3KTgZEAiJDd+iHZcbTPPcWCcRYH+WlNbwChgH2QkE9NSlNrMT8Gfw==} - engines: {node: '>=18'} - cpu: [arm] - os: [linux] - - '@esbuild/linux-ia32@0.27.3': - resolution: {integrity: sha512-yGlQYjdxtLdh0a3jHjuwOrxQjOZYD/C9PfdbgJJF3TIZWnm/tMd/RcNiLngiu4iwcBAOezdnSLAwQDPqTmtTYg==} - engines: {node: '>=18'} - cpu: [ia32] - os: [linux] - - '@esbuild/linux-loong64@0.27.3': - resolution: {integrity: sha512-WO60Sn8ly3gtzhyjATDgieJNet/KqsDlX5nRC5Y3oTFcS1l0KWba+SEa9Ja1GfDqSF1z6hif/SkpQJbL63cgOA==} - engines: {node: '>=18'} - cpu: [loong64] - os: [linux] - - '@esbuild/linux-mips64el@0.27.3': - resolution: {integrity: sha512-APsymYA6sGcZ4pD6k+UxbDjOFSvPWyZhjaiPyl/f79xKxwTnrn5QUnXR5prvetuaSMsb4jgeHewIDCIWljrSxw==} - engines: {node: '>=18'} - cpu: [mips64el] - os: [linux] - - '@esbuild/linux-ppc64@0.27.3': - resolution: {integrity: sha512-eizBnTeBefojtDb9nSh4vvVQ3V9Qf9Df01PfawPcRzJH4gFSgrObw+LveUyDoKU3kxi5+9RJTCWlj4FjYXVPEA==} - engines: {node: '>=18'} - cpu: [ppc64] - os: [linux] - - '@esbuild/linux-riscv64@0.27.3': - resolution: {integrity: sha512-3Emwh0r5wmfm3ssTWRQSyVhbOHvqegUDRd0WhmXKX2mkHJe1SFCMJhagUleMq+Uci34wLSipf8Lagt4LlpRFWQ==} - engines: {node: '>=18'} - cpu: [riscv64] - os: [linux] - - '@esbuild/linux-s390x@0.27.3': - resolution: {integrity: sha512-pBHUx9LzXWBc7MFIEEL0yD/ZVtNgLytvx60gES28GcWMqil8ElCYR4kvbV2BDqsHOvVDRrOxGySBM9Fcv744hw==} - engines: {node: '>=18'} - cpu: [s390x] - os: [linux] - - '@esbuild/linux-x64@0.27.3': - resolution: {integrity: sha512-Czi8yzXUWIQYAtL/2y6vogER8pvcsOsk5cpwL4Gk5nJqH5UZiVByIY8Eorm5R13gq+DQKYg0+JyQoytLQas4dA==} - engines: {node: '>=18'} - cpu: [x64] - os: [linux] - - '@esbuild/netbsd-arm64@0.27.3': - resolution: {integrity: sha512-sDpk0RgmTCR/5HguIZa9n9u+HVKf40fbEUt+iTzSnCaGvY9kFP0YKBWZtJaraonFnqef5SlJ8/TiPAxzyS+UoA==} - engines: {node: '>=18'} - cpu: [arm64] - os: [netbsd] - - '@esbuild/netbsd-x64@0.27.3': - resolution: {integrity: sha512-P14lFKJl/DdaE00LItAukUdZO5iqNH7+PjoBm+fLQjtxfcfFE20Xf5CrLsmZdq5LFFZzb5JMZ9grUwvtVYzjiA==} - engines: {node: '>=18'} - cpu: [x64] - os: [netbsd] - - '@esbuild/openbsd-arm64@0.27.3': - resolution: {integrity: sha512-AIcMP77AvirGbRl/UZFTq5hjXK+2wC7qFRGoHSDrZ5v5b8DK/GYpXW3CPRL53NkvDqb9D+alBiC/dV0Fb7eJcw==} - engines: {node: '>=18'} - cpu: [arm64] - os: [openbsd] - - '@esbuild/openbsd-x64@0.27.3': - resolution: {integrity: sha512-DnW2sRrBzA+YnE70LKqnM3P+z8vehfJWHXECbwBmH/CU51z6FiqTQTHFenPlHmo3a8UgpLyH3PT+87OViOh1AQ==} - engines: {node: '>=18'} - cpu: [x64] - os: [openbsd] - - '@esbuild/openharmony-arm64@0.27.3': - resolution: {integrity: sha512-NinAEgr/etERPTsZJ7aEZQvvg/A6IsZG/LgZy+81wON2huV7SrK3e63dU0XhyZP4RKGyTm7aOgmQk0bGp0fy2g==} - engines: {node: '>=18'} - cpu: [arm64] - os: [openharmony] - - '@esbuild/sunos-x64@0.27.3': - resolution: {integrity: sha512-PanZ+nEz+eWoBJ8/f8HKxTTD172SKwdXebZ0ndd953gt1HRBbhMsaNqjTyYLGLPdoWHy4zLU7bDVJztF5f3BHA==} - engines: {node: '>=18'} - cpu: [x64] - os: [sunos] - - '@esbuild/win32-arm64@0.27.3': - resolution: {integrity: sha512-B2t59lWWYrbRDw/tjiWOuzSsFh1Y/E95ofKz7rIVYSQkUYBjfSgf6oeYPNWHToFRr2zx52JKApIcAS/D5TUBnA==} - engines: {node: '>=18'} - cpu: [arm64] - os: [win32] - - '@esbuild/win32-ia32@0.27.3': - resolution: {integrity: sha512-QLKSFeXNS8+tHW7tZpMtjlNb7HKau0QDpwm49u0vUp9y1WOF+PEzkU84y9GqYaAVW8aH8f3GcBck26jh54cX4Q==} - engines: {node: '>=18'} - cpu: [ia32] - os: [win32] - - '@esbuild/win32-x64@0.27.3': - resolution: {integrity: sha512-4uJGhsxuptu3OcpVAzli+/gWusVGwZZHTlS63hh++ehExkVT8SgiEf7/uC/PclrPPkLhZqGgCTjd0VWLo6xMqA==} - engines: {node: '>=18'} - cpu: [x64] - os: [win32] - '@eslint-community/eslint-utils@4.9.1': resolution: {integrity: sha512-phrYmNiYppR7znFEdqgfWHXR6NCkZEK7hwWDHZUjit/2/U0r6XvkDl0SYnoM51Hq7FhCGdLDT6zxCCOY1hexsQ==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} @@ -514,8 +387,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.5': + resolution: {integrity: sha512-IbHDbHJfkVNv6xjlET8AIVo/K1NQt7YT4Rp6ok/clyBGcpRx1l6gv0Rq3vBvYfPJIZt6ODf66Zq08FJNDpnzgg==} engines: {node: ^20.19.0 || ^22.13.0 || >=24} peerDependencies: eslint: ^8.40 || 9 || 10 @@ -523,44 +396,37 @@ 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.5': + resolution: {integrity: sha512-Y3kKLvC1dvTOT+oGlqNQ1XLqK6D1HU2YXPc52NmAlJZbMMWDzGYXMiPRJ8TYD39muD/OTjlZmNJ4ib7dvSrMBA==} 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.5': + resolution: {integrity: sha512-eIJYKTCECbP/nsKaaruF6LW967mtbQbsw4JTtSVkUQc9MneSkbrgPJAbKl9nWr0ZeowV8BfsarBmPpBzGelA2w==} + 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.2.1': + resolution: {integrity: sha512-MwcE1P+AZ4C6DWlpin/OmOA54mmIZ/+xZuJiQd4SyB29oAJjN30UW9wkKNptW2ctp4cEsvhlLY/CsQ1uoHDloQ==} + 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.5': + resolution: {integrity: sha512-vqTaUEgxzm+YDSdElad6PiRoX4t8VGDjCtt05zn4nU810UIx/uNEV7/lZJ6KwFThKZOzOxzXy48da+No7HZaMw==} + 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.7.1': + resolution: {integrity: sha512-rZAP3aVgB9ds9KOeUSL+zZ21hPmo8dh6fnIFwRQj5EAZl9gzR7wxYbYXYysAM8CTqGmUGyp2S4kUdV17MnGuWQ==} + engines: {node: ^20.19.0 || ^22.13.0 || >=24} - '@exodus/bytes@1.14.1': - resolution: {integrity: sha512-OhkBFWI6GcRMUroChZiopRiSp2iAMvEBK47NhJooDqz1RERO4QuZIZnjP63TXX8GAiLABkYmX+fuQsdJ1dd2QQ==} + '@exodus/bytes@1.15.0': + resolution: {integrity: sha512-UY0nlA+feH81UGSHv92sLEPLCeZFjXOuHhrIo0HQydScuQc8s0A7kL/UdgwgDq8g8ilksmuoF35YVTNphV2aBQ==} engines: {node: ^20.19.0 || ^22.12.0 || >=24.0.0} peerDependencies: '@noble/hashes': ^1.8.0 || ^2.0.0 @@ -568,12 +434,16 @@ packages: '@noble/hashes': optional: true - '@humanfs/core@0.19.1': - resolution: {integrity: sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==} + '@humanfs/core@0.19.2': + resolution: {integrity: sha512-UhXNm+CFMWcbChXywFwkmhqjs3PRCmcSa/hfBgLIb7oQ5HNb1wS0icWsGtSAUNgefHeI+eBrA8I1fxmbHsGdvA==} engines: {node: '>=18.18.0'} - '@humanfs/node@0.16.7': - resolution: {integrity: sha512-/zUx+yOsIrG4Y43Eh2peDeKCxlRt/gET6aHfaKpuq267qXdYDFViVHfMaLyygZOnl0kGWxFIgsBy8QFuTLUXEQ==} + '@humanfs/node@0.16.8': + resolution: {integrity: sha512-gE1eQNZ3R++kTzFUpdGlpmy8kDZD/MLyHqDwqjkVQI0JMdI1D51sy1H958PNXYkM2rAac7e5/CnIKZrHtPh3BQ==} + engines: {node: '>=18.18.0'} + + '@humanfs/types@0.15.0': + resolution: {integrity: sha512-ZZ1w0aoQkwuUuC7Yf+7sdeaNfqQiiLcSRbfI08oAxqLtpXQr9AIVX7Ay7HLDuiLYAaFPu8oBYNq/QIi9URHJ3Q==} engines: {node: '>=18.18.0'} '@humanwhocodes/module-importer@1.0.1': @@ -594,9 +464,6 @@ packages: resolution: {integrity: sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==} engines: {node: '>=6.0.0'} - '@jridgewell/source-map@0.3.11': - resolution: {integrity: sha512-ZMp1V8ZFcPG5dIWnQLr3NSI1MiCU7UETdS/A0G8V/XWHvJv3ZsFqutJn1Y5RPmAPX6F3BiE397OqveU/9NCuIA==} - '@jridgewell/sourcemap-codec@1.5.5': resolution: {integrity: sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==} @@ -606,131 +473,255 @@ 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.4': + resolution: {integrity: sha512-3NQNNgA1YSlJb/kMH1ildASP9HW7/7kYnRI2szWJaofaS1hWmbGI4H+d3+22aGzXXN9IJ+n+GiFVcGipJP18ow==} + peerDependencies: + '@emnapi/core': ^1.7.1 + '@emnapi/runtime': ^1.7.1 - '@nodelib/fs.scandir@2.1.5': - resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==} - engines: {node: '>= 8'} - - '@nodelib/fs.stat@2.0.5': - resolution: {integrity: sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==} - engines: {node: '>= 8'} - - '@nodelib/fs.walk@1.2.8': - 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.127.0': + resolution: {integrity: sha512-0LC7ye4hvqbIKxAzThzvswgHLFu2AURKzYLeSVvLdu2TBOYWQDmHnTqPLeA597BcUCxiLqLsS4CJ5uoI5WYWCQ==} + engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm] os: [android] - '@oxc-resolver/binding-android-arm64@11.19.0': - resolution: {integrity: sha512-x5P0Y12oMcSC9PKkz1FtdVVLosXYi/05m+ufxPrUggd6vZRBPJhW4zZUsMVbz8dwwk71Dh0f6/2ntw3WPOq+Ig==} + '@oxc-parser/binding-android-arm64@0.127.0': + resolution: {integrity: sha512-b5jtVTH6AU5CJXHNdj7Jj9IEiR9yVjjnwHzPJhGyHGPdcsZSzBCkS9GBbV33niRMvKthDwQRFRJfI4a+k4PvYg==} + engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [android] - '@oxc-resolver/binding-darwin-arm64@11.19.0': - resolution: {integrity: sha512-DjnuIPB60IQrVSCiuVBzN8/8AeeIjthdkk+dZYdZzgLeP2T5ZF41u50haJMtIdGr5cRzRH6zPV/gh6+RFjlvKA==} + '@oxc-parser/binding-darwin-arm64@0.127.0': + resolution: {integrity: sha512-obCE8B7ISKkJidjlhv9xRGJPOSDG2Yu6PRga9Ruaz35uintHxbp1Ki/Yc71wx4rj3Edrm0a1kzG1TAwit0wFpg==} + engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [darwin] - '@oxc-resolver/binding-darwin-x64@11.19.0': - resolution: {integrity: sha512-dVAqIZIIY7xOXCCV0nJPs8ExlYc6R7mcNpFobwNyE3qlXGbgvwb7Gl3iOumOiPBfF+sbJR3MMP7RAPfKqbvYyA==} + '@oxc-parser/binding-darwin-x64@0.127.0': + resolution: {integrity: sha512-JL6Xb5IwPQT8rUzlpsX7E+AgfcdNklXNPFp8pjCQQ5MQOQo5rtEB2ui+3Hgg9Sn7Y9Egj6YOLLiHhLpdAe12Aw==} + engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [darwin] - '@oxc-resolver/binding-freebsd-x64@11.19.0': - resolution: {integrity: sha512-kwcZ30bIpJNFcT22sIlde4mz0EyXmB3lAefCFWtffqpbmLweQUwz1dKDcsutxEjpkbEKLmfrj1wCyRZp7n5Hnw==} + '@oxc-parser/binding-freebsd-x64@0.127.0': + resolution: {integrity: sha512-SDQ/3MQFw58fqQz3Z1PhSKFF3JoCF4gmlNjziDm8X02tTahCw0qJbd7FGPDKw1i4VTBZene9JPyC3mHtSvi+wA==} + engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [freebsd] - '@oxc-resolver/binding-linux-arm-gnueabihf@11.19.0': - resolution: {integrity: sha512-GImk/cb3X+zBGEwr6l9h0dbiNo5zNd52gamZmluEpbyybiZ8kc5q44/7zRR4ILChWRW7pI92W57CJwhkF+wRmg==} + '@oxc-parser/binding-linux-arm-gnueabihf@0.127.0': + resolution: {integrity: sha512-Av+D1MIqzV0YMGPT9we2SIZaMKD7Cxs4CvXSx/yxaWHewZjYEjScpOf5igc8IILASViw4WTnjlwUdI1KzVtDHQ==} + engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm] os: [linux] - '@oxc-resolver/binding-linux-arm-musleabihf@11.19.0': - resolution: {integrity: sha512-uIEyws3bBD1gif4SZCOV2XIr6q5fd1WbzzBbpL8qk+TbzOvKMWnMNNtfNacnAGGa2lLRNXR1Fffot2mlZ/Xmbw==} + '@oxc-parser/binding-linux-arm-musleabihf@0.127.0': + resolution: {integrity: sha512-Cs2fdJ8cPpFdeebj6p4dag8A4+56hPvZ0AhQQzlaLswGz1tz7bXt1nETLeorrM9+AMcWFFkqxcXwDGfTVidY8g==} + engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm] os: [linux] - '@oxc-resolver/binding-linux-arm64-gnu@11.19.0': - resolution: {integrity: sha512-bIkgp+AB+yZfvdKDfjFT7PycsRtih7+zCV5AbnkzfyvNvQ47rfssf8R1IbG++mx+rZ4YUCUu8EbP66HC3O5c5w==} + '@oxc-parser/binding-linux-arm64-gnu@0.127.0': + resolution: {integrity: sha512-qdOfTcT6SY8gsJrrV92uyEUyjqMGPpIB5JZUG6QN5dukYd+7/j0kX6MwK1DgQj39jtUYixxPiaRUiEN1+0CXgQ==} + engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [linux] libc: [glibc] - '@oxc-resolver/binding-linux-arm64-musl@11.19.0': - resolution: {integrity: sha512-bOt5pKPcbidTSy64m2CfM0XcaCmxBEFclCMPuOPO08hh8QIFTiZVhFf/OxTFqyRwhq/tlzzKmXpMo7DfzbO5lQ==} + '@oxc-parser/binding-linux-arm64-musl@0.127.0': + resolution: {integrity: sha512-EoTCZneNFU/P2qrpEM+RHmQwt+CvDkyGESG6qhr7KaegXLZwePfbrkCDfAk8/rhxbDUVGsZILX+2tqPzFtoFWA==} + engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [linux] libc: [musl] - '@oxc-resolver/binding-linux-ppc64-gnu@11.19.0': - resolution: {integrity: sha512-BymEPqVeLZzA/1kXow9U9rdniq1r5kk4u686Cx3ZU77YygR48NJI/2TyjM70vKHZffGx75ZShobcc1M5GXG3WA==} + '@oxc-parser/binding-linux-ppc64-gnu@0.127.0': + resolution: {integrity: sha512-zALjmZYgxFLHjXeudcDF0xFGNydTAtkAeXAr2EuC17ywCyFxcmQra4w0BMde0Yi/re4Bi4iwEoEXtYN7l6eBLQ==} + engines: {node: ^20.19.0 || >=22.12.0} cpu: [ppc64] os: [linux] libc: [glibc] - '@oxc-resolver/binding-linux-riscv64-gnu@11.19.0': - resolution: {integrity: sha512-aFgPTzZZY+XCYe4B+3A1S63xcIh2i136+2TPXWr9NOwXXTdMdBntb1J9fEgxXDnX82MjBknLUpJqAZHNTJzixA==} + '@oxc-parser/binding-linux-riscv64-gnu@0.127.0': + resolution: {integrity: sha512-fPP8M6zQLS7Jz7o9d5ArUSuAuSK3e+WCYVrCpdzeCOejidtZExJ9tjhDrAd3HEPqARBCPmdpqxESPFqy44vkBQ==} + engines: {node: ^20.19.0 || >=22.12.0} cpu: [riscv64] os: [linux] libc: [glibc] - '@oxc-resolver/binding-linux-riscv64-musl@11.19.0': - resolution: {integrity: sha512-9WDGt7fV9GK97WrWE/VEDhMFv9m0ZXYn5NQ+16QvyT0ux8yGLAvyadi6viaTjEdJII/OaHBRYHcL+zUjmaWwmg==} + '@oxc-parser/binding-linux-riscv64-musl@0.127.0': + resolution: {integrity: sha512-7IcC4Ao02oGpfnjt+X/oF4U2mllo2qoSkw5xxiXNKL9MCTsTiAC6616beOuehdxGcnz1bRoPC1RQ2f1GQDdN+g==} + engines: {node: ^20.19.0 || >=22.12.0} cpu: [riscv64] os: [linux] libc: [musl] - '@oxc-resolver/binding-linux-s390x-gnu@11.19.0': - resolution: {integrity: sha512-SY3di6tccocppAVal5Hev3D6D1N5Y6TCEypAvNCOiPqku2Y8U/aXfvGbthqdPNa72KYqjUR1vomOv6J9thHITA==} + '@oxc-parser/binding-linux-s390x-gnu@0.127.0': + resolution: {integrity: sha512-pbXIhiNFHoqWeqDNLiJ9JkpHz1IM9k4DXa66x+1GTWMG7iLxtkXgE53iiuKSXwmk3zIYmaPVfBvgcAhS583K4Q==} + engines: {node: ^20.19.0 || >=22.12.0} cpu: [s390x] os: [linux] libc: [glibc] - '@oxc-resolver/binding-linux-x64-gnu@11.19.0': - resolution: {integrity: sha512-SV+4zBeCC3xjSE2wvhN45eyABoVRX3xryWBABFKfLwAWhF3wsB3bUF+CantYfQ/TLpasyvplRS9ovvFT9cb/0A==} + '@oxc-parser/binding-linux-x64-gnu@0.127.0': + resolution: {integrity: sha512-MYCguB9RvBvlSd6gbuNI7QwiLoCCAlGnlRJFPrzLI6U1/9wkC/WK6LtBAUln55H1Ctqw45PWmqrobKoMhsYQzQ==} + engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [linux] libc: [glibc] - '@oxc-resolver/binding-linux-x64-musl@11.19.0': - resolution: {integrity: sha512-LkbjO+r5Isl8Xl29pJYOCB/iSUIULFUJDGdMp+yJD3OgWtSa6VJta2iw7QXmpcoOkq18UIL09yWrlyjLDL0Hug==} + '@oxc-parser/binding-linux-x64-musl@0.127.0': + resolution: {integrity: sha512-5eY0B/bxf1xIUxb4NOTvOI3KWtBQfPWYyKAzgcrCt0mDibSZygVpO1Pz8bkeiSZ5Jj9+M09dkggG3H8I5d0Uyg==} + engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [linux] libc: [musl] - '@oxc-resolver/binding-openharmony-arm64@11.19.0': - resolution: {integrity: sha512-Ud1gelL5slpEU5AjzBWQz1WheprOAl5CPnCKTWynvvdlBbAZXA6fPYLuCrlRo0uw+x3f37XJ71kirpSew8Zyvg==} + '@oxc-parser/binding-openharmony-arm64@0.127.0': + resolution: {integrity: sha512-Gld0ajrFTUXNtdw20fVBuTQx66FA75nIVg+//pPfR3sXkuABB4mTBhl3r9JNzrJpgW//qiwxf0nWXUWGJSL3UQ==} + engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [openharmony] - '@oxc-resolver/binding-wasm32-wasi@11.19.0': - resolution: {integrity: sha512-wXLNAVmL4vWXKaYJnFPgg5zQsSr3Rv+ftNReIU3UkzTcoVLK0805Pnbr2NwcBWSO5hhpOEdys02qlT2kxVgjWw==} - engines: {node: '>=14.0.0'} + '@oxc-parser/binding-wasm32-wasi@0.127.0': + resolution: {integrity: sha512-T6KVD7rhLzFlwGRXMnxUFfkCZD8FHnb968wVXW1mXzgRFc5RNXOBY2mPPDZ77x5Ln76ltLMgtPg0cOkU1NSrEQ==} + engines: {node: ^20.19.0 || >=22.12.0} cpu: [wasm32] - '@oxc-resolver/binding-win32-arm64-msvc@11.19.0': - resolution: {integrity: sha512-zszvr0dJfvv0Jg49hLwjAJ4SRzfsq28SoearUtT1qv3qXRYsBWuctdlRa/lEZkiuG4tZWiY425Jh9QqLafwsAg==} + '@oxc-parser/binding-win32-arm64-msvc@0.127.0': + resolution: {integrity: sha512-Ujvw4X+LD1CCGULcsQcvb4YNVoBGqt+JHgNNzGGaCImELiZLk477ifUH53gIbE7EKd933NdTi25JWEr9K2HwXw==} + engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [win32] - '@oxc-resolver/binding-win32-ia32-msvc@11.19.0': - resolution: {integrity: sha512-I7ZYujr5XL1l7OwuddbOeqdUyFOaf51W1U2xUogInFdupIAKGqbpugpAK6RaccLcSlN0bbuo3CS5h7ue38SUAg==} + '@oxc-parser/binding-win32-ia32-msvc@0.127.0': + resolution: {integrity: sha512-0cwxKO7KHQQQfo4Uf4B2SQrhgm+cJaP9OvFFhx52Tkg4bezsacu83GB2/In5bC415Ueeym+kXdnge/57rbSfTw==} + engines: {node: ^20.19.0 || >=22.12.0} cpu: [ia32] os: [win32] - '@oxc-resolver/binding-win32-x64-msvc@11.19.0': - resolution: {integrity: sha512-NxErbI1TmJEZZVvGPePjgXFZCuOzrjQuJ6YwHjcWkelReK7Uhg4QeL05zRdfTpgkH6IY/C8OjbKx5ZilQ4yDFg==} + '@oxc-parser/binding-win32-x64-msvc@0.127.0': + resolution: {integrity: sha512-rOrnSQSCbhI2kowr9XxE7m9a8oQXnBHjnS6j95LxxAnEZ0+Fz20WlRXG4ondQb+ejjt2KOsa65sE6++L6kUd+w==} + engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [win32] - '@playwright/test@1.58.2': - resolution: {integrity: sha512-akea+6bHYBBfA9uQqSYmlJXn61cTa+jbO87xVLCWbTqbWadRVmhxlXATaOjOgcBaWU4ePo0wB41KMFv3o35IXA==} + '@oxc-project/types@0.127.0': + resolution: {integrity: sha512-aIYXQBo4lCbO4z0R3FHeucQHpF46l2LbMdxRvqvuRuW2OxdnSkcng5B8+K12spgLDj93rtN3+J2Vac/TIO+ciQ==} + + '@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.1': + resolution: {integrity: sha512-oolbkRX+m7Pq2LNjr/kKgYeC7bRDMVTWPgxBGMjSpZi/+UskVo4jsMU3MLheZV55jL6c3rNelPl4oD60ggYmqA==} + cpu: [arm64] + os: [android] + + '@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.1': + resolution: {integrity: sha512-cV50vE5+uAgNcFa3QY1JOeKDSkM/9ReIcc/9wn4TavhW/itkDGrXhw9jaKnkQnGbjJ198Yh5nbX/Gr2mr4Z5jQ==} + cpu: [x64] + os: [darwin] + + '@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.1': + resolution: {integrity: sha512-lXZYWAC6kaGe/ky2su94e9jN9t6M0/6c+GrSlCqL//XO1cxi5lpAhnJYdyrKfm0ZEr/c7RNyAx3P7FSBcBd5+A==} + cpu: [arm] + os: [linux] + + '@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.1': + resolution: {integrity: sha512-heV2+jmXyYnUrpUXSPugqWDRpnsQcDm2AX4wzTuvgdlZfoNYO0O3W2AVpJYaDn9AG4JdM6Kxom8+foE7/BcSig==} + cpu: [arm64] + os: [linux] + libc: [glibc] + + '@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.1': + resolution: {integrity: sha512-vLmdNxWCdN7Uo5suays6A/+ywBby2PWBBPXctWPg5V0+eVuzsJxgAn6MMB4mPlshskYbppjpN2Zg83ArHze9gQ==} + cpu: [ppc64] + os: [linux] + libc: [glibc] + + '@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.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.1': + resolution: {integrity: sha512-EDpafVOQWF8/MJynsjOGFThcqhRHy417sRyLfQmeiamJ8qVhSKAn2Dn2VVKUGCjVB9C46VGjhNo7nOPUi1x6uA==} + cpu: [s390x] + os: [linux] + libc: [glibc] + + '@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.1': + resolution: {integrity: sha512-cM/hQwsO3ReJg5kR+SpI69DMfvNCp+A/eVR4b4YClE5bVZwz8rh2Nh05InhwI5HR/9cArbEkzMjcKgTHS6UaNw==} + cpu: [x64] + os: [linux] + libc: [musl] + + '@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.1': + resolution: {integrity: sha512-w8UCKhX826cP/ZLokXDS6+milN8y4X7zidsAttEdWlVoamTNf6lhBJldaWr3ukTDiye7s4HRcuPEPOXNC432Vg==} + engines: {node: '>=14.0.0'} + cpu: [wasm32] + + '@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.1': + resolution: {integrity: sha512-EW+ND5q2Tl+a3pH81l1QbfgbF3HmqgwLfDfVithRFheac8OTcnbXt/JxqD2GbDkb7xYEqy1zNaVFRr3oeG8npA==} + cpu: [ia32] + os: [win32] + + '@oxc-resolver/binding-win32-x64-msvc@11.19.1': + resolution: {integrity: sha512-6hIU3RQu45B+VNTY4Ru8ppFwjVS/S5qwYyGhBotmjxfEKk41I2DlGtRfGJndZ5+6lneE2pwloqunlOyZuX/XAw==} + cpu: [x64] + os: [win32] + + '@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 @@ -745,321 +736,180 @@ packages: react-redux: optional: true - '@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] - os: [android] - - '@rollup/rollup-android-arm64@4.59.0': - resolution: {integrity: sha512-hZ+Zxj3SySm4A/DylsDKZAeVg0mvi++0PYVceVyX7hemkw7OreKdCvW2oQ3T1FMZvCaQXqOTHb8qmBShoqk69Q==} + '@rolldown/binding-android-arm64@1.0.0-rc.17': + resolution: {integrity: sha512-s70pVGhw4zqGeFnXWvAzJDlvxhlRollagdCCKRgOsgUOH3N1l0LIxf83AtGzmb5SiVM4Hjl5HyarMRfdfj3DaQ==} + engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [android] - '@rollup/rollup-darwin-arm64@4.59.0': - resolution: {integrity: sha512-W2Psnbh1J8ZJw0xKAd8zdNgF9HRLkdWwwdWqubSVk0pUuQkoHnv7rx4GiF9rT4t5DIZGAsConRE3AxCdJ4m8rg==} + '@rolldown/binding-darwin-arm64@1.0.0-rc.17': + resolution: {integrity: sha512-4ksWc9n0mhlZpZ9PMZgTGjeOPRu8MB1Z3Tz0Mo02eWfWCHMW1zN82Qz/pL/rC+yQa+8ZnutMF0JjJe7PjwasYw==} + engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [darwin] - '@rollup/rollup-darwin-x64@4.59.0': - resolution: {integrity: sha512-ZW2KkwlS4lwTv7ZVsYDiARfFCnSGhzYPdiOU4IM2fDbL+QGlyAbjgSFuqNRbSthybLbIJ915UtZBtmuLrQAT/w==} + '@rolldown/binding-darwin-x64@1.0.0-rc.17': + resolution: {integrity: sha512-SUSDOI6WwUVNcWxd02QEBjLdY1VPHvlEkw6T/8nYG322iYWCTxRb1vzk4E+mWWYehTp7ERibq54LSJGjmouOsw==} + engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [darwin] - '@rollup/rollup-freebsd-arm64@4.59.0': - resolution: {integrity: sha512-EsKaJ5ytAu9jI3lonzn3BgG8iRBjV4LxZexygcQbpiU0wU0ATxhNVEpXKfUa0pS05gTcSDMKpn3Sx+QB9RlTTA==} - cpu: [arm64] - os: [freebsd] - - '@rollup/rollup-freebsd-x64@4.59.0': - resolution: {integrity: sha512-d3DuZi2KzTMjImrxoHIAODUZYoUUMsuUiY4SRRcJy6NJoZ6iIqWnJu9IScV9jXysyGMVuW+KNzZvBLOcpdl3Vg==} + '@rolldown/binding-freebsd-x64@1.0.0-rc.17': + resolution: {integrity: sha512-hwnz3nw9dbJ05EDO/PvcjaaewqqDy7Y1rn1UO81l8iIK1GjenME75dl16ajbvSSMfv66WXSRCYKIqfgq2KCfxw==} + engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [freebsd] - '@rollup/rollup-linux-arm-gnueabihf@4.59.0': - resolution: {integrity: sha512-t4ONHboXi/3E0rT6OZl1pKbl2Vgxf9vJfWgmUoCEVQVxhW6Cw/c8I6hbbu7DAvgp82RKiH7TpLwxnJeKv2pbsw==} + '@rolldown/binding-linux-arm-gnueabihf@1.0.0-rc.17': + resolution: {integrity: sha512-IS+W7epTcwANmFSQFrS1SivEXHtl1JtuQA9wlxrZTcNi6mx+FDOYrakGevvvTwgj2JvWiK8B29/qD9BELZPyXQ==} + engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm] os: [linux] - libc: [glibc] - '@rollup/rollup-linux-arm-musleabihf@4.59.0': - resolution: {integrity: sha512-CikFT7aYPA2ufMD086cVORBYGHffBo4K8MQ4uPS/ZnY54GKj36i196u8U+aDVT2LX4eSMbyHtyOh7D7Zvk2VvA==} - cpu: [arm] - os: [linux] - libc: [musl] - - '@rollup/rollup-linux-arm64-gnu@4.59.0': - resolution: {integrity: sha512-jYgUGk5aLd1nUb1CtQ8E+t5JhLc9x5WdBKew9ZgAXg7DBk0ZHErLHdXM24rfX+bKrFe+Xp5YuJo54I5HFjGDAA==} + '@rolldown/binding-linux-arm64-gnu@1.0.0-rc.17': + resolution: {integrity: sha512-e6usGaHKW5BMNZOymS1UcEYGowQMWcgZ71Z17Sl/h2+ZziNJ1a9n3Zvcz6LdRyIW5572wBCTH/Z+bKuZouGk9Q==} + engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [linux] libc: [glibc] - '@rollup/rollup-linux-arm64-musl@4.59.0': - resolution: {integrity: sha512-peZRVEdnFWZ5Bh2KeumKG9ty7aCXzzEsHShOZEFiCQlDEepP1dpUl/SrUNXNg13UmZl+gzVDPsiCwnV1uI0RUA==} + '@rolldown/binding-linux-arm64-musl@1.0.0-rc.17': + resolution: {integrity: sha512-b/CgbwAJpmrRLp02RPfhbudf5tZnN9nsPWK82znefso832etkem8H7FSZwxrOI9djcdTP7U6YfNhbRnh7djErg==} + engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [linux] libc: [musl] - '@rollup/rollup-linux-loong64-gnu@4.59.0': - resolution: {integrity: sha512-gbUSW/97f7+r4gHy3Jlup8zDG190AuodsWnNiXErp9mT90iCy9NKKU0Xwx5k8VlRAIV2uU9CsMnEFg/xXaOfXg==} - cpu: [loong64] - os: [linux] - libc: [glibc] - - '@rollup/rollup-linux-loong64-musl@4.59.0': - resolution: {integrity: sha512-yTRONe79E+o0FWFijasoTjtzG9EBedFXJMl888NBEDCDV9I2wGbFFfJQQe63OijbFCUZqxpHz1GzpbtSFikJ4Q==} - cpu: [loong64] - os: [linux] - libc: [musl] - - '@rollup/rollup-linux-ppc64-gnu@4.59.0': - resolution: {integrity: sha512-sw1o3tfyk12k3OEpRddF68a1unZ5VCN7zoTNtSn2KndUE+ea3m3ROOKRCZxEpmT9nsGnogpFP9x6mnLTCaoLkA==} + '@rolldown/binding-linux-ppc64-gnu@1.0.0-rc.17': + resolution: {integrity: sha512-4EII1iNGRUN5WwGbF/kOh/EIkoDN9HsupgLQoXfY+D1oyJm7/F4t5PYU5n8SWZgG0FEwakyM8pGgwcBYruGTlA==} + engines: {node: ^20.19.0 || >=22.12.0} cpu: [ppc64] os: [linux] libc: [glibc] - '@rollup/rollup-linux-ppc64-musl@4.59.0': - resolution: {integrity: sha512-+2kLtQ4xT3AiIxkzFVFXfsmlZiG5FXYW7ZyIIvGA7Bdeuh9Z0aN4hVyXS/G1E9bTP/vqszNIN/pUKCk/BTHsKA==} - cpu: [ppc64] - os: [linux] - libc: [musl] - - '@rollup/rollup-linux-riscv64-gnu@4.59.0': - resolution: {integrity: sha512-NDYMpsXYJJaj+I7UdwIuHHNxXZ/b/N2hR15NyH3m2qAtb/hHPA4g4SuuvrdxetTdndfj9b1WOmy73kcPRoERUg==} - cpu: [riscv64] - os: [linux] - libc: [glibc] - - '@rollup/rollup-linux-riscv64-musl@4.59.0': - resolution: {integrity: sha512-nLckB8WOqHIf1bhymk+oHxvM9D3tyPndZH8i8+35p/1YiVoVswPid2yLzgX7ZJP0KQvnkhM4H6QZ5m0LzbyIAg==} - cpu: [riscv64] - os: [linux] - libc: [musl] - - '@rollup/rollup-linux-s390x-gnu@4.59.0': - resolution: {integrity: sha512-oF87Ie3uAIvORFBpwnCvUzdeYUqi2wY6jRFWJAy1qus/udHFYIkplYRW+wo+GRUP4sKzYdmE1Y3+rY5Gc4ZO+w==} + '@rolldown/binding-linux-s390x-gnu@1.0.0-rc.17': + resolution: {integrity: sha512-AH8oq3XqQo4IibpVXvPeLDI5pzkpYn0WiZAfT05kFzoJ6tQNzwRdDYQ45M8I/gslbodRZwW8uxLhbSBbkv96rA==} + engines: {node: ^20.19.0 || >=22.12.0} cpu: [s390x] os: [linux] libc: [glibc] - '@rollup/rollup-linux-x64-gnu@4.59.0': - resolution: {integrity: sha512-3AHmtQq/ppNuUspKAlvA8HtLybkDflkMuLK4DPo77DfthRb71V84/c4MlWJXixZz4uruIH4uaa07IqoAkG64fg==} + '@rolldown/binding-linux-x64-gnu@1.0.0-rc.17': + resolution: {integrity: sha512-cLnjV3xfo7KslbU41Z7z8BH/E1y5mzUYzAqih1d1MDaIGZRCMqTijqLv76/P7fyHuvUcfGsIpqCdddbxLLK9rA==} + engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [linux] libc: [glibc] - '@rollup/rollup-linux-x64-musl@4.59.0': - resolution: {integrity: sha512-2UdiwS/9cTAx7qIUZB/fWtToJwvt0Vbo0zmnYt7ED35KPg13Q0ym1g442THLC7VyI6JfYTP4PiSOWyoMdV2/xg==} + '@rolldown/binding-linux-x64-musl@1.0.0-rc.17': + resolution: {integrity: sha512-0phclDw1spsL7dUB37sIARuis2tAgomCJXAHZlpt8PXZ4Ba0dRP1e+66lsRqrfhISeN9bEGNjQs+T/Fbd7oYGw==} + engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [linux] libc: [musl] - '@rollup/rollup-openbsd-x64@4.59.0': - resolution: {integrity: sha512-M3bLRAVk6GOwFlPTIxVBSYKUaqfLrn8l0psKinkCFxl4lQvOSz8ZrKDz2gxcBwHFpci0B6rttydI4IpS4IS/jQ==} - cpu: [x64] - os: [openbsd] - - '@rollup/rollup-openharmony-arm64@4.59.0': - resolution: {integrity: sha512-tt9KBJqaqp5i5HUZzoafHZX8b5Q2Fe7UjYERADll83O4fGqJ49O1FsL6LpdzVFQcpwvnyd0i+K/VSwu/o/nWlA==} + '@rolldown/binding-openharmony-arm64@1.0.0-rc.17': + resolution: {integrity: sha512-0ag/hEgXOwgw4t8QyQvUCxvEg+V0KBcA6YuOx9g0r02MprutRF5dyljgm3EmR02O292UX7UeS6HzWHAl6KgyhA==} + engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [openharmony] - '@rollup/rollup-win32-arm64-msvc@4.59.0': - resolution: {integrity: sha512-V5B6mG7OrGTwnxaNUzZTDTjDS7F75PO1ae6MJYdiMu60sq0CqN5CVeVsbhPxalupvTX8gXVSU9gq+Rx1/hvu6A==} + '@rolldown/binding-wasm32-wasi@1.0.0-rc.17': + resolution: {integrity: sha512-LEXei6vo0E5wTGwpkJ4KoT3OZJRnglwldt5ziLzOlc6qqb55z4tWNq2A+PFqCJuvWWdP53CVhG1Z9NtToDPJrA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [wasm32] + + '@rolldown/binding-win32-arm64-msvc@1.0.0-rc.17': + resolution: {integrity: sha512-gUmyzBl3SPMa6hrqFUth9sVfcLBlYsbMzBx5PlexMroZStgzGqlZ26pYG89rBb45Mnia+oil6YAIFeEWGWhoZA==} + engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [win32] - '@rollup/rollup-win32-ia32-msvc@4.59.0': - resolution: {integrity: sha512-UKFMHPuM9R0iBegwzKF4y0C4J9u8C6MEJgFuXTBerMk7EJ92GFVFYBfOZaSGLu6COf7FxpQNqhNS4c4icUPqxA==} - cpu: [ia32] - os: [win32] - - '@rollup/rollup-win32-x64-gnu@4.59.0': - resolution: {integrity: sha512-laBkYlSS1n2L8fSo1thDNGrCTQMmxjYY5G0WFWjFFYZkKPjsMBsgJfGf4TLxXrF6RyhI60L8TMOjBMvXiTcxeA==} + '@rolldown/binding-win32-x64-msvc@1.0.0-rc.17': + resolution: {integrity: sha512-3hkiolcUAvPB9FLb3UZdfjVVNWherN1f/skkGWJP/fgSQhYUZpSIRr0/I8ZK9TkF3F7kxvJAk0+IcKvPHk9qQg==} + engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [win32] - '@rollup/rollup-win32-x64-msvc@4.59.0': - resolution: {integrity: sha512-2HRCml6OztYXyJXAvdDXPKcawukWY2GpR5/nxKp4iBgiO3wcoEGkAaqctIbZcNB6KlUQBIqt8VYkNSj2397EfA==} - cpu: [x64] - os: [win32] + '@rolldown/pluginutils@1.0.0-rc.17': + resolution: {integrity: sha512-n8iosDOt6Ig1UhJ2AYqoIhHWh/isz0xpicHTzpKBeotdVsTEcxsSA/i3EVM7gQAj0rU27OLAxCjzlj15IWY7bg==} + + '@rolldown/pluginutils@1.0.0-rc.7': + resolution: {integrity: sha512-qujRfC8sFVInYSPPMLQByRh7zhwkGFS4+tyMQ83srV1qrxL4g8E2tyxVVyxd0+8QeBM1mIk9KbWxkegRr76XzA==} '@sindresorhus/base62@1.0.0': 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==} '@standard-schema/utils@0.3.0': resolution: {integrity: sha512-e7Mew686owMaPJVNNLs55PUvgz371nKgwsc4vxE49zsODpJEnxgxRo2y/OKrqueavXgZNMDVj3DdHFlaSAeU8g==} - '@swc/core-darwin-arm64@1.15.13': - resolution: {integrity: sha512-ztXusRuC5NV2w+a6pDhX13CGioMLq8CjX5P4XgVJ21ocqz9t19288Do0y8LklplDtwcEhYGTNdMbkmUT7+lDTg==} - engines: {node: '>=10'} - cpu: [arm64] - os: [darwin] + '@tailwindcss/node@4.2.2': + resolution: {integrity: sha512-pXS+wJ2gZpVXqFaUEjojq7jzMpTGf8rU6ipJz5ovJV6PUGmlJ+jvIwGrzdHdQ80Sg+wmQxUFuoW1UAAwHNEdFA==} - '@swc/core-darwin-x64@1.15.13': - resolution: {integrity: sha512-cVifxQUKhaE7qcO/y9Mq6PEhoyvN9tSLzCnnFZ4EIabFHBuLtDDO6a+vLveOy98hAs5Qu1+bb5Nv0oa1Pihe3Q==} - engines: {node: '>=10'} - cpu: [x64] - os: [darwin] - - '@swc/core-linux-arm-gnueabihf@1.15.13': - resolution: {integrity: sha512-t+xxEzZ48enl/wGGy7SRYd7kImWQ/+wvVFD7g5JZo234g6/QnIgZ+YdfIyjHB+ZJI3F7a2IQHS7RNjxF29UkWw==} - engines: {node: '>=10'} - cpu: [arm] - os: [linux] - - '@swc/core-linux-arm64-gnu@1.15.13': - resolution: {integrity: sha512-VndeGvKmTXFn6AGwjy0Kg8i7HccOCE7Jt/vmZwRxGtOfNZM1RLYRQ7MfDLo6T0h1Bq6eYzps3L5Ma4zBmjOnOg==} - engines: {node: '>=10'} - cpu: [arm64] - os: [linux] - libc: [glibc] - - '@swc/core-linux-arm64-musl@1.15.13': - resolution: {integrity: sha512-SmZ9m+XqCB35NddHCctvHFLqPZDAs5j8IgD36GoutufDJmeq2VNfgk5rQoqNqKmAK3Y7iFdEmI76QoHIWiCLyw==} - engines: {node: '>=10'} - cpu: [arm64] - os: [linux] - libc: [musl] - - '@swc/core-linux-x64-gnu@1.15.13': - resolution: {integrity: sha512-5rij+vB9a29aNkHq72EXI2ihDZPszJb4zlApJY4aCC/q6utgqFA6CkrfTfIb+O8hxtG3zP5KERETz8mfFK6A0A==} - engines: {node: '>=10'} - cpu: [x64] - os: [linux] - libc: [glibc] - - '@swc/core-linux-x64-musl@1.15.13': - resolution: {integrity: sha512-OlSlaOK9JplQ5qn07WiBLibkOw7iml2++ojEXhhR3rbWrNEKCD7sd8+6wSavsInyFdw4PhLA+Hy6YyDBIE23Yw==} - engines: {node: '>=10'} - cpu: [x64] - os: [linux] - libc: [musl] - - '@swc/core-win32-arm64-msvc@1.15.13': - resolution: {integrity: sha512-zwQii5YVdsfG8Ti9gIKgBKZg8qMkRZxl+OlYWUT5D93Jl4NuNBRausP20tfEkQdAPSRrMCSUZBM6FhW7izAZRg==} - engines: {node: '>=10'} - cpu: [arm64] - os: [win32] - - '@swc/core-win32-ia32-msvc@1.15.13': - resolution: {integrity: sha512-hYXvyVVntqRlYoAIDwNzkS3tL2ijP3rxyWQMNKaxcCxxkCDto/w3meOK/OB6rbQSkNw0qTUcBfU9k+T0ptYdfQ==} - engines: {node: '>=10'} - cpu: [ia32] - os: [win32] - - '@swc/core-win32-x64-msvc@1.15.13': - resolution: {integrity: sha512-XTzKs7c/vYCcjmcwawnQvlHHNS1naJEAzcBckMI5OJlnrcgW8UtcX9NHFYvNjGtXuKv0/9KvqL4fuahdvlNGKw==} - engines: {node: '>=10'} - cpu: [x64] - os: [win32] - - '@swc/core@1.15.13': - resolution: {integrity: sha512-0l1gl/72PErwUZuavcRpRAQN9uSst+Nk++niC5IX6lmMWpXoScYx3oq/narT64/sKv/eRiPTaAjBFGDEQiWJIw==} - engines: {node: '>=10'} - peerDependencies: - '@swc/helpers': '>=0.5.17' - peerDependenciesMeta: - '@swc/helpers': - optional: true - - '@swc/counter@0.1.3': - resolution: {integrity: sha512-e2BR4lsJkkRlKZ/qCHPw9ZaSxc0MVUd7gtbtaB7aMvHeJVYe8sOB8DBZkP2DtISHGSku9sCK6T6cnY0CtXrOCQ==} - - '@swc/types@0.1.25': - resolution: {integrity: sha512-iAoY/qRhNH8a/hBvm3zKj9qQ4oc2+3w1unPJa2XvTK3XjeLXtzcCingVPw/9e5mn1+0yPqxcBGp9Jf0pkfMb1g==} - - '@tailwindcss/node@4.2.1': - resolution: {integrity: sha512-jlx6sLk4EOwO6hHe1oCGm1Q4AN/s0rSrTTPBGPM0/RQ6Uylwq17FuU8IeJJKEjtc6K6O07zsvP+gDO6MMWo7pg==} - - '@tailwindcss/oxide-android-arm64@4.2.1': - resolution: {integrity: sha512-eZ7G1Zm5EC8OOKaesIKuw77jw++QJ2lL9N+dDpdQiAB/c/B2wDh0QPFHbkBVrXnwNugvrbJFk1gK2SsVjwWReg==} + '@tailwindcss/oxide-android-arm64@4.2.2': + resolution: {integrity: sha512-dXGR1n+P3B6748jZO/SvHZq7qBOqqzQ+yFrXpoOWWALWndF9MoSKAT3Q0fYgAzYzGhxNYOoysRvYlpixRBBoDg==} engines: {node: '>= 20'} cpu: [arm64] os: [android] - '@tailwindcss/oxide-darwin-arm64@4.2.1': - resolution: {integrity: sha512-q/LHkOstoJ7pI1J0q6djesLzRvQSIfEto148ppAd+BVQK0JYjQIFSK3JgYZJa+Yzi0DDa52ZsQx2rqytBnf8Hw==} + '@tailwindcss/oxide-darwin-arm64@4.2.2': + resolution: {integrity: sha512-iq9Qjr6knfMpZHj55/37ouZeykwbDqF21gPFtfnhCCKGDcPI/21FKC9XdMO/XyBM7qKORx6UIhGgg6jLl7BZlg==} engines: {node: '>= 20'} cpu: [arm64] os: [darwin] - '@tailwindcss/oxide-darwin-x64@4.2.1': - resolution: {integrity: sha512-/f/ozlaXGY6QLbpvd/kFTro2l18f7dHKpB+ieXz+Cijl4Mt9AI2rTrpq7V+t04nK+j9XBQHnSMdeQRhbGyt6fw==} + '@tailwindcss/oxide-darwin-x64@4.2.2': + resolution: {integrity: sha512-BlR+2c3nzc8f2G639LpL89YY4bdcIdUmiOOkv2GQv4/4M0vJlpXEa0JXNHhCHU7VWOKWT/CjqHdTP8aUuDJkuw==} engines: {node: '>= 20'} cpu: [x64] os: [darwin] - '@tailwindcss/oxide-freebsd-x64@4.2.1': - resolution: {integrity: sha512-5e/AkgYJT/cpbkys/OU2Ei2jdETCLlifwm7ogMC7/hksI2fC3iiq6OcXwjibcIjPung0kRtR3TxEITkqgn0TcA==} + '@tailwindcss/oxide-freebsd-x64@4.2.2': + resolution: {integrity: sha512-YUqUgrGMSu2CDO82hzlQ5qSb5xmx3RUrke/QgnoEx7KvmRJHQuZHZmZTLSuuHwFf0DJPybFMXMYf+WJdxHy/nQ==} engines: {node: '>= 20'} cpu: [x64] os: [freebsd] - '@tailwindcss/oxide-linux-arm-gnueabihf@4.2.1': - resolution: {integrity: sha512-Uny1EcVTTmerCKt/1ZuKTkb0x8ZaiuYucg2/kImO5A5Y/kBz41/+j0gxUZl+hTF3xkWpDmHX+TaWhOtba2Fyuw==} + '@tailwindcss/oxide-linux-arm-gnueabihf@4.2.2': + resolution: {integrity: sha512-FPdhvsW6g06T9BWT0qTwiVZYE2WIFo2dY5aCSpjG/S/u1tby+wXoslXS0kl3/KXnULlLr1E3NPRRw0g7t2kgaQ==} engines: {node: '>= 20'} cpu: [arm] os: [linux] - '@tailwindcss/oxide-linux-arm64-gnu@4.2.1': - resolution: {integrity: sha512-CTrwomI+c7n6aSSQlsPL0roRiNMDQ/YzMD9EjcR+H4f0I1SQ8QqIuPnsVp7QgMkC1Qi8rtkekLkOFjo7OlEFRQ==} + '@tailwindcss/oxide-linux-arm64-gnu@4.2.2': + resolution: {integrity: sha512-4og1V+ftEPXGttOO7eCmW7VICmzzJWgMx+QXAJRAhjrSjumCwWqMfkDrNu1LXEQzNAwz28NCUpucgQPrR4S2yw==} engines: {node: '>= 20'} cpu: [arm64] os: [linux] libc: [glibc] - '@tailwindcss/oxide-linux-arm64-musl@4.2.1': - resolution: {integrity: sha512-WZA0CHRL/SP1TRbA5mp9htsppSEkWuQ4KsSUumYQnyl8ZdT39ntwqmz4IUHGN6p4XdSlYfJwM4rRzZLShHsGAQ==} + '@tailwindcss/oxide-linux-arm64-musl@4.2.2': + resolution: {integrity: sha512-oCfG/mS+/+XRlwNjnsNLVwnMWYH7tn/kYPsNPh+JSOMlnt93mYNCKHYzylRhI51X+TbR+ufNhhKKzm6QkqX8ag==} engines: {node: '>= 20'} cpu: [arm64] os: [linux] libc: [musl] - '@tailwindcss/oxide-linux-x64-gnu@4.2.1': - resolution: {integrity: sha512-qMFzxI2YlBOLW5PhblzuSWlWfwLHaneBE0xHzLrBgNtqN6mWfs+qYbhryGSXQjFYB1Dzf5w+LN5qbUTPhW7Y5g==} + '@tailwindcss/oxide-linux-x64-gnu@4.2.2': + resolution: {integrity: sha512-rTAGAkDgqbXHNp/xW0iugLVmX62wOp2PoE39BTCGKjv3Iocf6AFbRP/wZT/kuCxC9QBh9Pu8XPkv/zCZB2mcMg==} engines: {node: '>= 20'} cpu: [x64] os: [linux] libc: [glibc] - '@tailwindcss/oxide-linux-x64-musl@4.2.1': - resolution: {integrity: sha512-5r1X2FKnCMUPlXTWRYpHdPYUY6a1Ar/t7P24OuiEdEOmms5lyqjDRvVY1yy9Rmioh+AunQ0rWiOTPE8F9A3v5g==} + '@tailwindcss/oxide-linux-x64-musl@4.2.2': + resolution: {integrity: sha512-XW3t3qwbIwiSyRCggeO2zxe3KWaEbM0/kW9e8+0XpBgyKU4ATYzcVSMKteZJ1iukJ3HgHBjbg9P5YPRCVUxlnQ==} engines: {node: '>= 20'} cpu: [x64] os: [linux] libc: [musl] - '@tailwindcss/oxide-wasm32-wasi@4.2.1': - resolution: {integrity: sha512-MGFB5cVPvshR85MTJkEvqDUnuNoysrsRxd6vnk1Lf2tbiqNlXpHYZqkqOQalydienEWOHHFyyuTSYRsLfxFJ2Q==} + '@tailwindcss/oxide-wasm32-wasi@4.2.2': + resolution: {integrity: sha512-eKSztKsmEsn1O5lJ4ZAfyn41NfG7vzCg496YiGtMDV86jz1q/irhms5O0VrY6ZwTUkFy/EKG3RfWgxSI3VbZ8Q==} engines: {node: '>=14.0.0'} cpu: [wasm32] bundledDependencies: @@ -1070,26 +920,26 @@ packages: - '@emnapi/wasi-threads' - tslib - '@tailwindcss/oxide-win32-arm64-msvc@4.2.1': - resolution: {integrity: sha512-YlUEHRHBGnCMh4Nj4GnqQyBtsshUPdiNroZj8VPkvTZSoHsilRCwXcVKnG9kyi0ZFAS/3u+qKHBdDc81SADTRA==} + '@tailwindcss/oxide-win32-arm64-msvc@4.2.2': + resolution: {integrity: sha512-qPmaQM4iKu5mxpsrWZMOZRgZv1tOZpUm+zdhhQP0VhJfyGGO3aUKdbh3gDZc/dPLQwW4eSqWGrrcWNBZWUWaXQ==} engines: {node: '>= 20'} cpu: [arm64] os: [win32] - '@tailwindcss/oxide-win32-x64-msvc@4.2.1': - resolution: {integrity: sha512-rbO34G5sMWWyrN/idLeVxAZgAKWrn5LiR3/I90Q9MkA67s6T1oB0xtTe+0heoBvHSpbU9Mk7i6uwJnpo4u21XQ==} + '@tailwindcss/oxide-win32-x64-msvc@4.2.2': + resolution: {integrity: sha512-1T/37VvI7WyH66b+vqHj/cLwnCxt7Qt3WFu5Q8hk65aOvlwAhs7rAp1VkulBJw/N4tMirXjVnylTR72uI0HGcA==} engines: {node: '>= 20'} cpu: [x64] os: [win32] - '@tailwindcss/oxide@4.2.1': - resolution: {integrity: sha512-yv9jeEFWnjKCI6/T3Oq50yQEOqmpmpfzG1hcZsAOaXFQPfzWprWrlHSdGPEF3WQTi8zu8ohC9Mh9J470nT5pUw==} + '@tailwindcss/oxide@4.2.2': + resolution: {integrity: sha512-qEUA07+E5kehxYp9BVMpq9E8vnJuBHfJEC0vPC5e7iL/hw7HR61aDKoVoKzrG+QKp56vhNZe4qwkRmMC0zDLvg==} engines: {node: '>= 20'} - '@tailwindcss/vite@4.2.1': - resolution: {integrity: sha512-TBf2sJjYeb28jD2U/OhwdW0bbOsxkWPwQ7SrqGf9sVcoYwZj7rkXljroBO9wKBut9XnmQLXanuDUeqQK0lGg/w==} + '@tailwindcss/vite@4.2.2': + resolution: {integrity: sha512-mEiF5HO1QqCLXoNEfXVA1Tzo+cYsrqV7w9Juj2wdUFyW07JRenqMG225MvPwr3ZD9N1bFQj46X7r33iHxLUW0w==} peerDependencies: - vite: ^5.2.0 || ^6 || ^7 + vite: ^5.2.0 || ^6 || ^7 || ^8 '@testing-library/dom@10.4.1': resolution: {integrity: sha512-o4PXJQidqJl82ckFaXUeoAW+XysPLauYI43Abki5hABd853iMhitooc6znOnczgbTYmEP6U6/y1ZyKAIsvMKGg==} @@ -1099,6 +949,36 @@ packages: resolution: {integrity: sha512-zIcONa+hVtVSSep9UT3jZ5rizo2BsxgyDYU7WFD5eICBE7no3881HGeb/QkGfsJs6JTkY1aQhT7rIPC7e+0nnA==} engines: {node: '>=14', npm: '>=6', yarn: '>=1'} + '@turbo/darwin-64@2.9.6': + resolution: {integrity: sha512-X/56SnVXIQZBLKwniGTwEQTGmtE5brSACnKMBWpY3YafuxVYefrC2acamfjgxP7BG5w3I+6jf0UrLoSzgPcSJg==} + cpu: [x64] + os: [darwin] + + '@turbo/darwin-arm64@2.9.6': + resolution: {integrity: sha512-aalBeSl4agT/QtYGDyf/XLajedWzUC9Vg/pm/YO6QQ93vkQ91Vz5uK1ta5RbVRDozQSz4njxUNqRNmOXDzW+qw==} + cpu: [arm64] + os: [darwin] + + '@turbo/linux-64@2.9.6': + resolution: {integrity: sha512-YKi05jnNHaD7vevgYwahpzGwbsNNTwzU2c7VZdmdFm7+cGDP4oREUWSsainiMfRqjRuolQxBwRn8wf1jmu+YZA==} + cpu: [x64] + os: [linux] + + '@turbo/linux-arm64@2.9.6': + resolution: {integrity: sha512-02o/ZS69cOYEDczXvOB2xmyrtzjQ2hVFtWZK1iqxXUfzMmTjZK4UumrfNnjckSg+gqeBfnPRHa0NstA173Ik3g==} + cpu: [arm64] + os: [linux] + + '@turbo/windows-64@2.9.6': + resolution: {integrity: sha512-wVdQjvnBI15wB6JrA+43CtUtagjIMmX6XYO758oZHAsCNSxqRlJtdyujih0D8OCnwCRWiGWGI63zAxR0hO6s9g==} + cpu: [x64] + os: [win32] + + '@turbo/windows-arm64@2.9.6': + resolution: {integrity: sha512-1XUUyWW0W6FTSqGEhU8RHVqb2wP1SPkr7hIvBlMEwH9jr+sJQK5kqeosLJ/QaUv4ecSAd1ZhIrLoW7qslAzT4A==} + cpu: [arm64] + os: [win32] + '@tybys/wasm-util@0.10.1': resolution: {integrity: sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg==} @@ -1111,95 +991,90 @@ 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.2': + resolution: {integrity: sha512-A1sre26ke7HDIuY/M23nd9gfB+nrmhtYyMINbjI1zHJxYteKR6qSMX56FsmjMcDb3SMcjJg5BiRRgOCC/yBD0g==} - '@types/node@25.2.3': - resolution: {integrity: sha512-m0jEgYlYz+mDJZ2+F4v8D1AyQb+QzsNqRuI7xg1VQX/KlKS0qT9r1Mo16yo5F/MtifXFgaofIFsdFMox2SxIbQ==} - - '@types/prop-types@15.7.15': - resolution: {integrity: sha512-F6bEyamV9jKGAFBEmlQnesRPGOQqS2+Uwi0Em15xenOxHaf2hv6L8YCVn3rPdPJOiJfPiCnLIRyvwVaqMY3MIw==} - - '@types/react-dom@18.3.7': - resolution: {integrity: sha512-MEe3UeoENYVFXzoXEWsvcpg6ZvlrFNlOQ7EOsvhI3CfAXwzPfO8Qwuxd40nepsYKqyyVQnTdEfv68q91yLcKrQ==} + '@types/react-dom@19.2.3': + resolution: {integrity: sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ==} peerDependencies: - '@types/react': ^18.0.0 + '@types/react': ^19.2.0 - '@types/react@18.3.27': - resolution: {integrity: sha512-cisd7gxkzjBKU2GgdYrTdtQx1SORymWyaAFhaxQPK9bYO9ot3Y5OikQRvY0VYQtvwjeQnizCINJAenh/V7MK2w==} + '@types/react@19.2.14': + resolution: {integrity: sha512-ilcTH/UniCkMdtexkoCN0bI7pMcJDvmQFPvuPvmEaYA/NSfFTAgdUSLAoVjaRJm7+6PvcM+q1zYOwS4wTYMF9w==} '@types/use-sync-external-store@0.0.6': resolution: {integrity: sha512-zFDAD+tlpf2r4asuHEj0XH6pY6i0g5NeAHPn+15wk3BV6JA69eERFXC1gyGThDkVa1zCyKr5jox1+2LbV/AMLg==} - '@types/whatwg-mimetype@3.0.2': - resolution: {integrity: sha512-c2AKvDT8ToxLIOUlN51gTiHXflsfIFisS4pO7pDPoKouJCESkhZnEy623gwP9laCy5lnLDAw1vAzu2vM2YLOrA==} - - '@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.59.0': + resolution: {integrity: sha512-HyAZtpdkgZwpq8Sz3FSUvCR4c+ScbuWa9AksK2Jweub7w4M3yTz4O11AqVJzLYjy/B9ZWPyc81I+mOdJU/bDQw==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: - '@typescript-eslint/parser': ^8.56.1 + '@typescript-eslint/parser': ^8.59.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.59.0': + resolution: {integrity: sha512-TI1XGwKbDpo9tRW8UDIXCOeLk55qe9ZFGs8MTKU6/M08HWTw52DD/IYhfQtOEhEdPhLMT26Ka/x7p70nd3dzDg==} 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.59.0': + resolution: {integrity: sha512-Lw5ITrR5s5TbC19YSvlr63ZfLaJoU6vtKTHyB0GQOpX0W7d5/Ir6vUahWi/8Sps/nOukZQ0IB3SmlxZnjaKVnw==} 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.59.0': + resolution: {integrity: sha512-UzR16Ut8IpA3Mc4DbgAShlPPkVm8xXMWafXxB0BocaVRHs8ZGakAxGRskF7FId3sdk9lgGD73GSFaWmWFDE4dg==} 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.59.0': + resolution: {integrity: sha512-91Sbl3s4Kb3SybliIY6muFBmHVv+pYXfybC4Oolp3dvk8BvIE3wOPc+403CWIT7mJNkfQRGtdqghzs2+Z91Tqg==} 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.59.0': + resolution: {integrity: sha512-3TRiZaQSltGqGeNrJzzr1+8YcEobKH9rHnqIp/1psfKFmhRQDNMGP5hBufanYTGznwShzVLs3Mz+gDN7HkWfXg==} 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.2': + resolution: {integrity: sha512-9TukXyATBQf/Jq9AMQXfvurk+G5R2MwfqQGDR2GzGz28HvY/lXNKGhkY+6IOubwcquikWk5cjlgPvD2uAA7htQ==} 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/types@8.59.0': + resolution: {integrity: sha512-nLzdsT1gdOgFxxxwrlNVUBzSNBEEHJ86bblmk4QAS6stfig7rcJzWKqCyxFy3YRRHXDWEkb2NralA1nOYkkm/A==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@typescript-eslint/typescript-estree@8.59.0': + resolution: {integrity: sha512-O9Re9P1BmBLFJyikRbQpLku/QA3/AueZNO9WePLBwQrvkixTmDe8u76B6CYUAITRl/rHawggEqUGn5QIkVRLMw==} 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.59.0': + resolution: {integrity: sha512-I1R/K7V07XsMJ12Oaxg/O9GfrysGTmCRhvZJBv0RE0NcULMzjqVpR5kRRQjHsz3J/bElU7HwCO7zkqL+MSUz+g==} 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.59.0': + resolution: {integrity: sha512-/uejZt4dSere1bx12WLlPfv8GktzcaDtuJ7s42/HEZ5zGj9oxRaD4bj7qwSunXkf+pbAhFt2zjpHYUiT5lHf0Q==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} '@unrs/resolver-binding-android-arm-eabi@1.11.1': @@ -1308,49 +1183,56 @@ packages: '@uppercod/css-to-object@1.1.1': resolution: {integrity: sha512-dCTxxolI6fu28lzNRVwd7CzJV8EbARITFyCbP/JqLHYLfWHY7GJqXHDdk0GbtfXvsZosPCvjOE4dOIMT4XDFZQ==} - '@vitejs/plugin-react-swc@4.2.3': - resolution: {integrity: sha512-QIluDil2prhY1gdA3GGwxZzTAmLdi8cQ2CcuMW4PB/Wu4e/1pzqrwhYWVd09LInCRlDUidQjd0B70QWbjWtLxA==} + '@vitejs/plugin-react@6.0.1': + resolution: {integrity: sha512-l9X/E3cDb+xY3SWzlG1MOGt2usfEHGMNIaegaUGFsLkb3RCn/k8/TOXBcab+OndDI4TBtktT8/9BwwW8Vi9KUQ==} engines: {node: ^20.19.0 || >=22.12.0} peerDependencies: - vite: ^4 || ^5 || ^6 || ^7 + '@rolldown/plugin-babel': ^0.1.7 || ^0.2.0 + babel-plugin-react-compiler: ^1.0.0 + vite: ^8.0.0 + peerDependenciesMeta: + '@rolldown/plugin-babel': + optional: true + babel-plugin-react-compiler: + optional: true - '@vitest/coverage-v8@4.0.18': - resolution: {integrity: sha512-7i+N2i0+ME+2JFZhfuz7Tg/FqKtilHjGyGvoHYQ6iLV0zahbsJ9sljC9OcFcPDbhYKCet+sG8SsVqlyGvPflZg==} + '@vitest/coverage-v8@4.1.4': + resolution: {integrity: sha512-x7FptB5oDruxNPDNY2+S8tCh0pcq7ymCe1gTHcsp733jYjrJl8V1gMUlVysuCD9Kz46Xz9t1akkv08dPcYDs1w==} peerDependencies: - '@vitest/browser': 4.0.18 - vitest: 4.0.18 + '@vitest/browser': 4.1.4 + vitest: 4.1.4 peerDependenciesMeta: '@vitest/browser': optional: true - '@vitest/expect@4.0.18': - resolution: {integrity: sha512-8sCWUyckXXYvx4opfzVY03EOiYVxyNrHS5QxX3DAIi5dpJAAkyJezHCP77VMX4HKA2LDT/Jpfo8i2r5BE3GnQQ==} + '@vitest/expect@4.1.4': + resolution: {integrity: sha512-iPBpra+VDuXmBFI3FMKHSFXp3Gx5HfmSCE8X67Dn+bwephCnQCaB7qWK2ldHa+8ncN8hJU8VTMcxjPpyMkUjww==} - '@vitest/mocker@4.0.18': - resolution: {integrity: sha512-HhVd0MDnzzsgevnOWCBj5Otnzobjy5wLBe4EdeeFGv8luMsGcYqDuFRMcttKWZA5vVO8RFjexVovXvAM4JoJDQ==} + '@vitest/mocker@4.1.4': + resolution: {integrity: sha512-R9HTZBhW6yCSGbGQnDnH3QHfJxokKN4KB+Yvk9Q1le7eQNYwiCyKxmLmurSpFy6BzJanSLuEUDrD+j97Q+ZLPg==} 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.4': + resolution: {integrity: sha512-ddmDHU0gjEUyEVLxtZa7xamrpIefdEETu3nZjWtHeZX4QxqJ7tRxSteHVXJOcr8jhiLoGAhkK4WJ3WqBpjx42A==} - '@vitest/runner@4.0.18': - resolution: {integrity: sha512-rpk9y12PGa22Jg6g5M3UVVnTS7+zycIGk9ZNGN+m6tZHKQb7jrP7/77WfZy13Y/EUDd52NDsLRQhYKtv7XfPQw==} + '@vitest/runner@4.1.4': + resolution: {integrity: sha512-xTp7VZ5aXP5ZJrn15UtJUWlx6qXLnGtF6jNxHepdPHpMfz/aVPx+htHtgcAL2mDXJgKhpoo2e9/hVJsIeFbytQ==} - '@vitest/snapshot@4.0.18': - resolution: {integrity: sha512-PCiV0rcl7jKQjbgYqjtakly6T1uwv/5BQ9SwBLekVg/EaYeQFPiXcgrC2Y7vDMA8dM1SUEAEV82kgSQIlXNMvA==} + '@vitest/snapshot@4.1.4': + resolution: {integrity: sha512-MCjCFgaS8aZz+m5nTcEcgk/xhWv0rEH4Yl53PPlMXOZ1/Ka2VcZU6CJ+MgYCZbcJvzGhQRjVrGQNZqkGPttIKw==} - '@vitest/spy@4.0.18': - resolution: {integrity: sha512-cbQt3PTSD7P2OARdVW3qWER5EGq7PHlvE+QfzSC0lbwO+xnt7+XH06ZzFjFRgzUX//JmpxrCu92VdwvEPlWSNw==} + '@vitest/spy@4.1.4': + resolution: {integrity: sha512-XxNdAsKW7C+FLydqFJLb5KhJtl3PGCMmYwFRfhvIgxJvLSXhhVI1zM8f1qD3Zg7RCjTSzDVyct6sghs9UEgBEQ==} - '@vitest/utils@4.0.18': - resolution: {integrity: sha512-msMRKLMVLWygpK3u2Hybgi4MNjcYJvwTb0Ru09+fOyCXIgT5raYP041DRRdiJiI3k/2U6SEbAETB3YtBrUkCFA==} + '@vitest/utils@4.1.4': + resolution: {integrity: sha512-13QMT+eysM5uVGa1rG4kegGYNp6cnQcsTc67ELFbhNLQO+vgsygtYJx2khvdt4gVQqSSpC/KT5FZZxUpP3Oatw==} accepts@2.0.0: resolution: {integrity: sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==} @@ -1366,10 +1248,6 @@ packages: engines: {node: '>=0.4.0'} hasBin: true - agent-base@7.1.4: - resolution: {integrity: sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==} - engines: {node: '>= 14'} - ajv@6.14.0: resolution: {integrity: sha512-IWrosm/yrn43eiKqkfkHis7QioDleaXQHdDVPKg0FSwwd/DuvyX79TZnFOnYpB7dcsFAMmtFztZuXPDvSePkFw==} @@ -1385,10 +1263,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 +1313,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==} @@ -1463,8 +1331,8 @@ packages: resolution: {integrity: sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==} engines: {node: '>= 0.4'} - axios-cache-interceptor@1.11.4: - resolution: {integrity: sha512-xZ4OZUxdpcFUpZjrqfYlGK0VglpPRKKSoE3vMHrstxolixQNs/MrbMezOAO5uS454hIEcWpnk75RZK26WkPW/g==} + axios-cache-interceptor@1.12.0: + resolution: {integrity: sha512-15XuJkdeJmQo/HY2b0xx3zim8DMx7Nu+G8R4z6OG2VZLtbIDnsfn4qZsLLvkPfK4SVNRzXnoG4jPR7dqdQznRA==} engines: {node: '>=12'} peerDependencies: axios: ^1 @@ -1474,8 +1342,8 @@ packages: peerDependencies: axios: '>= 0.17.0' - axios@1.13.5: - resolution: {integrity: sha512-cz4ur7Vb0xS4/KUN0tPWe44eqxrIu31me+fbang3ijiNscE129POzipJJA6zniq2C/Z6sJCjMimjS8Lc/GAs8Q==} + axios@1.15.2: + resolution: {integrity: sha512-wLrXxPtcrPTsNlJmKjkPnNPK2Ihe0hn0wGSaTEiHRPxwjvJwT3hKmXF4dpqxmPO9SoNb2FsYXj/xEo0gHN+D5A==} balanced-match@1.0.2: resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} @@ -1484,81 +1352,30 @@ 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.20: + resolution: {integrity: sha512-1AaXxEPfXT+GvTBJFuy4yXVHWJBXa4OdbIebGN/wX5DlsIkU0+wzGnd2lOzokSk51d5LUmqjgBLRLlypLUqInQ==} 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.14: + resolution: {integrity: sha512-MWPGfDxnyzKU7rNOW9SP/c50vi3xrmrua/+6hfPbCS2ABNWfx24vPidzvC7krjU/RTo235sV776ymlsMtGKj8g==} - 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'} @@ -1570,52 +1387,33 @@ packages: resolution: {integrity: sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==} engines: {node: '>= 0.4'} - call-bind@1.0.8: - resolution: {integrity: sha512-oKlSFMcMwpUg2ednkhQ454wfWiU/ul3CkJe/PEHcTKuiX6RpbehUiFMXu13HalGZxfUwCQzZG747YXBn1im9ww==} + call-bind@1.0.9: + resolution: {integrity: sha512-a/hy+pNsFUTR+Iz8TCJvXudKVLAnz/DyeSUo10I5yvFDQJBFU2s9uqQpoSrJlroHUKoKqzg+epxyP9lqFdzfBQ==} engines: {node: '>= 0.4'} call-bound@1.0.4: 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.30001788: + resolution: {integrity: sha512-6q8HFp+lOQtcf7wBK+uEenxymVWkGKkjFpCvw5W25cmMwEDU45p1xQFBQv8JDlMMry7eNxyBaR+qxgmTUZkIRQ==} 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==} @@ -1627,24 +1425,15 @@ packages: resolution: {integrity: sha512-H+y0Jo/T1RZ9qPP4Eh1pkcQcLRglraJaSLoyOtHxu6AapkjWVCy2Sit1QQ4x3Dng8qDlSsZEet7g5Pq06MvTgw==} engines: {node: '>=20'} - 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==} + content-disposition@1.1.0: + resolution: {integrity: sha512-5jRCH9Z/+DRP7rkvY83B+yGIGX96OYdJmzngqnw2SBSxqCFPd0w2km3s5iawpGX8krnwSGmF0FW5Nhr0Hfai3g==} engines: {node: '>=18'} content-type@1.0.5: @@ -1662,40 +1451,17 @@ 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==} + css-tree@3.2.1: + resolution: {integrity: sha512-X7sjQzceUhu1u7Y/ylrRZFU2FS6LRiFVp6rKLPg23y3x3c3DOKAwuXGDp+PAGjh6CSnCjYeAul8pcT8bAl+lSA==} engines: {node: ^10 || ^12.20.0 || ^14.13.0 || >=15.0.0} css.escape@1.5.1: resolution: {integrity: sha512-YUifsXXuknHlUsmlgyY0PKzgPOr7/FjCePfHNt0jxm83wHZi44VDMQ7/fGNkjY3/jV1MC+1CmZbaHzugyeRtpg==} - cssstyle@6.1.0: - resolution: {integrity: sha512-Ml4fP2UT2K3CUBQnVlbdV/8aFDdlY69E+YnwJM+3VUWl08S3J8c8aRuJqCkD9Py8DHZ7zNNvsfKl8psocHZEFg==} - engines: {node: '>=20'} - csstype@3.2.3: resolution: {integrity: sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==} @@ -1753,16 +1519,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 +1533,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 +1540,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.340: + resolution: {integrity: sha512-908qahOGocRMinT2nM3ajCEM99H4iPdv84eagPP3FfZy/1ZGeOy2CZYzjhms81ckOPCXPlW7LkY4XpxD8r1DrA==} emoji-name-map@2.0.3: resolution: {integrity: sha512-3KBuQuhYkRtLd9utBKfTtclbWP3IytC1FNcXg+NKARltPSYpkg/MLiklGv4vLwl8A8jMQjdneXNBYx8k0rrg+g==} @@ -1800,20 +1553,20 @@ packages: resolution: {integrity: sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==} engines: {node: '>= 0.8'} - enhanced-resolve@5.19.0: - resolution: {integrity: sha512-phv3E1Xl4tQOShqSte26C7Fl84EwUdZsyOuSSk9qtAGyyQs2s3jJzComh+Abf4g187lUUAvH+H26omrqia2aGg==} + enhanced-resolve@5.20.1: + resolution: {integrity: sha512-Qohcme7V1inbAfvjItgw0EaxVX5q2rdVEZHRBrEQdRZTssLDGsL8Lwrznl8oQ/6kuTJONLaDcGjkNP247XEhcA==} engines: {node: '>=10.13.0'} - entities@6.0.1: - resolution: {integrity: sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g==} - engines: {node: '>=0.12'} + entities@8.0.0: + resolution: {integrity: sha512-zwfzJecQ/Uej6tusMqwAqU/6KL2XaB2VZ2Jg54Je6ahNBGNH6Ek6g3jjNCF0fG9EWQKGZNddNjU5F1ZQn/sBnA==} + engines: {node: '>=20.19.0'} environment@1.1.0: resolution: {integrity: sha512-xUtoPkMggbz0MPyPiIWr1Kp4aeWJjDZ6SMvURhimjdZgsRuDplF5/s9hcgGhyXMhs+6vpnuoiZ2kFiu3FMnS8Q==} engines: {node: '>=18'} - es-abstract@1.24.1: - resolution: {integrity: sha512-zHXBLhP+QehSSbsS9Pt23Gg964240DPd6QCf8WpkqEXxQ7fhdZzYsocOr5u7apWonsS5EjZDmTF+/slGMyasvw==} + es-abstract@1.24.2: + resolution: {integrity: sha512-2FpH9Q5i2RRwyEP1AylXe6nYLR5OhaJTZwmlcP0dL/+JCbgg7yyEo/sEK6HeGZRf3dFpWwThaRHVApXSkW3xeg==} engines: {node: '>= 0.4'} es-define-property@1.0.1: @@ -1824,12 +1577,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.2: + resolution: {integrity: sha512-HVLACW1TppGYjJ8H6/jqH/pqOtKRw6wMlrB23xfExmFWxFquAIWCmwoLsOyN96K4a5KbmOf5At9ZUO3GZbetAw==} 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==} @@ -1847,11 +1600,6 @@ packages: resolution: {integrity: sha512-w+5mJ3GuFL+NjVtJlvydShqE1eN3h3PbI7/5LAsYJP/2qtuMXjfL2LpHSRqo4b4eSF5K/DH1JXKUAHSB2UW50g==} engines: {node: '>= 0.4'} - esbuild@0.27.3: - resolution: {integrity: sha512-8VwMnyGCONIs6cWue2IdpHxHnAjzxnw2Zr7MkVxB2vjmQ2ivqGFb4LEG3SMnv0Gb2F/G/2yA8zUaiL1gywDCCg==} - engines: {node: '>=18'} - hasBin: true - escalade@3.2.0: resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==} engines: {node: '>=6'} @@ -1863,10 +1611,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 +1633,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,17 +1646,17 @@ 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 - eslint-plugin-react-hooks@7.0.1: - resolution: {integrity: sha512-O0d0m04evaNzEPoSW+59Mezf8Qt0InfgGIBJnpC0h3NH/WjUAR7BIKUfysC6todmtiZ/A0oUVS8Gce0WhBrHsA==} + eslint-plugin-react-hooks@7.1.1: + resolution: {integrity: sha512-f2I7Gw6JbvCexzIInuSbZpfdQ44D7iqdWX01FKLvrPgqxoE7oMj8clOfto8U6vYiz4yd5oKu39rRSVOe1zRu0g==} engines: {node: '>=18'} peerDependencies: - eslint: ^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 || ^9.0.0 + eslint: ^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 || ^9.0.0 || ^10.0.0 eslint-plugin-react@7.37.5: resolution: {integrity: sha512-Qteup0SqU15kdocexFNAJMvCJEfa2xUKNV4CC1xsVMrIIqEy3SQ/rqyxCWNzfrd3/ldy6HMlD2e0JDVpDg2qIA==} @@ -1920,25 +1664,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.2.1: + resolution: {integrity: sha512-wiyGaKsDgqXvF40P8mDwiUp/KQjE1FdrIEJsM8PZ3XCiniTMXS3OHWWUe5FI5agoCnr8x4xPrTDZuxsBlNHl+Q==} + engines: {node: ^20.19.0 || ^22.13.0 || >=24} hasBin: true peerDependencies: jiti: '*' @@ -1946,12 +1686,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 +1702,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 +1716,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'} @@ -2004,19 +1730,12 @@ packages: fast-defer@1.1.9: resolution: {integrity: sha512-JP7Xm9HuePSeTT1DI78NeE9eAQvgNb9qNP2jlyQrcx4jiWM189omV6oyd0xaUPWHPlKmvDzz6H1FfPWIDU+xfg==} - fast-glob@3.3.3: - resolution: {integrity: sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==} - engines: {node: '>=8.6.0'} - fast-json-stable-stringify@2.1.0: resolution: {integrity: sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==} fast-levenshtein@2.0.6: resolution: {integrity: sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==} - fastq@1.20.1: - resolution: {integrity: sha512-GGToxJ/w1x32s/D2EKND7kTil4n8OVk/9mycTc4VDza13lOvpUZTGX3mFSCtV9ksdGBVzvsyAVLM6mHFThxXxw==} - fd-package-json@2.0.0: resolution: {integrity: sha512-jKmm9YtsNXN789RS/0mSzOC1NUq9mkVd65vbSSVsKdjGvYXBuE4oWe2QOEoFeRmJg+lPuZxpmrfFclNhoRMneQ==} @@ -2033,10 +1752,6 @@ packages: resolution: {integrity: sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==} engines: {node: '>=16.0.0'} - fill-range@7.1.1: - resolution: {integrity: sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==} - engines: {node: '>=8'} - finalhandler@2.1.1: resolution: {integrity: sha512-S8KoZgRZN+a5rNwqTxlZZePjT/4cnm0ROV70LedRHZ0p8u9fRID0hJUZQpkKLzro8LfmC8sx23bY6tVNxv8pQA==} engines: {node: '>= 18.0.0'} @@ -2049,11 +1764,11 @@ 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==} + follow-redirects@1.16.0: + resolution: {integrity: sha512-y5rN/uOsadFT/JfYwhxRS5R7Qce+g3zG97+JrtFZlC9klX/W5hD7iiLzScI4nZqUS7DNUdhPgw4xI8W2LuXlUw==} engines: {node: '>=4.0'} peerDependencies: debug: '*' @@ -2126,36 +1841,24 @@ 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.14.0: + resolution: {integrity: sha512-yTb+8DXzDREzgvYmh6s9vHsSVCHeC0G3PI5bEXNBHtmshPnO+S5O7qgLEOn0I5QvMy6kpZN8K1NKGyilLb93wA==} github-username-regex@1.0.0: resolution: {integrity: sha512-EqDVkN0/5MQyDPOSDLInVRRXdeISRfcN1UW/1FUqD2knV1HHw8DndMB3UPNn5lO51DvRnjzbLXwWqNNV86PLOw==} - glob-parent@5.1.2: - resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==} - engines: {node: '>= 6'} - glob-parent@6.0.2: 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.5.0: + resolution: {integrity: sha512-qoV+HK2yFl/366t2/Cb3+xxPUo5BuMynomoDmiaZBIdbs+0pYbjfZU+twLhGKp4uCZ/+NbtpVepH5bGCxRyy2g==} 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'} @@ -2163,10 +1866,6 @@ packages: graceful-fs@4.2.11: resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==} - happy-dom@20.6.1: - resolution: {integrity: sha512-+0vhESXXhFwkdjZnJ5DlmJIfUYGgIEEjzIjB+aKJbFuqlvvKyOi+XkI1fYbgYR9QCxG5T08koxsQ6HrQfa5gCQ==} - engines: {node: '>=20.0.0'} - has-bigints@1.1.0: resolution: {integrity: sha512-R3pbpkcIqv2Pm3dUwgjclDRVmWpTJW2DcMzcIhEXEx1oh/CEMObMm3KLmRJOdvhM7o4uQBnwr8pzRK2sJWIqfg==} engines: {node: '>= 0.4'} @@ -2190,19 +1889,8 @@ 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==} + hasown@2.0.3: + resolution: {integrity: sha512-ej4AhfhfL2Q2zpMmLo7U1Uv9+PyhIZpgQLGT1F9miIGmiCJIoCgSmczFdrc97mWT4kVY72KA+WnnhJ5pghSvSg==} engines: {node: '>= 0.4'} hermes-estree@0.25.1: @@ -2211,9 +1899,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} @@ -2228,20 +1913,9 @@ packages: resolution: {integrity: sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==} engines: {node: '>= 0.8'} - http-proxy-agent@7.0.2: - resolution: {integrity: sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==} - engines: {node: '>= 14'} - 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'} - husky@9.1.7: resolution: {integrity: sha512-5gs5ytaNjBrh5Ow3zrvdUUY+0VxIuWVL4i9irt6friV+BqdCfmV11CQTWMiBYWHbXhco+J1kHfTOUkePhCDvMA==} engines: {node: '>=18'} @@ -2251,9 +1925,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 +1936,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 +1955,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 +2018,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'} @@ -2367,10 +2026,6 @@ packages: resolution: {integrity: sha512-lZhclumE1G6VYD8VHe35wFaIif+CTy5SJIi5+3y4psDgWu4wPDoBhF8NxUOinEc7pHgiTsT6MaBb92rKhhD+Xw==} engines: {node: '>= 0.4'} - is-number@7.0.0: - resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==} - engines: {node: '>=0.12.0'} - is-potential-custom-element-name@1.0.1: resolution: {integrity: sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==} @@ -2413,19 +2068,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,13 +2104,13 @@ 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: - resolution: {integrity: sha512-0+MoQNYyr2rBHqO1xilltfDjV9G7ymYGlAUazgcDLQaUf8JDHbuGwsxN6U9qWaElZ4w1B2r7yEGIL3GdeW3Rug==} - engines: {node: ^20.19.0 || ^22.12.0 || >=24.0.0} + jsdom@29.0.2: + resolution: {integrity: sha512-9VnGEBosc/ZpwyOsJBCQ/3I5p7Q5ngOY14a9bf5btenAORmZfDse1ZEheMiWcJ3h81+Fv7HmJFdS0szo/waF2w==} + engines: {node: ^20.19.0 || ^22.13.0 || >=24.0.0} peerDependencies: canvas: ^3.0.0 peerDependenciesMeta: @@ -2495,94 +2143,91 @@ 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.7.0: + resolution: {integrity: sha512-ckL51NDH1YJxnv1kNB0iUdDngB4f/e9Igz8uIqYfmNDoyOFmmk1V0WFv3LQ7/hzC63b2Z9X41gGUE9eOWrZpaA==} + 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==} engines: {node: '>= 0.8.0'} - lightningcss-android-arm64@1.31.1: - resolution: {integrity: sha512-HXJF3x8w9nQ4jbXRiNppBCqeZPIAfUo8zE/kOEGbW5NZvGc/K7nMxbhIr+YlFlHW5mpbg/YFPdbnCh1wAXCKFg==} + lightningcss-android-arm64@1.32.0: + resolution: {integrity: sha512-YK7/ClTt4kAK0vo6w3X+Pnm0D2cf2vPHbhOXdoNti1Ga0al1P4TBZhwjATvjNwLEBCnKvjJc2jQgHXH0NEwlAg==} engines: {node: '>= 12.0.0'} cpu: [arm64] os: [android] - lightningcss-darwin-arm64@1.31.1: - resolution: {integrity: sha512-02uTEqf3vIfNMq3h/z2cJfcOXnQ0GRwQrkmPafhueLb2h7mqEidiCzkE4gBMEH65abHRiQvhdcQ+aP0D0g67sg==} + lightningcss-darwin-arm64@1.32.0: + resolution: {integrity: sha512-RzeG9Ju5bag2Bv1/lwlVJvBE3q6TtXskdZLLCyfg5pt+HLz9BqlICO7LZM7VHNTTn/5PRhHFBSjk5lc4cmscPQ==} engines: {node: '>= 12.0.0'} cpu: [arm64] os: [darwin] - lightningcss-darwin-x64@1.31.1: - resolution: {integrity: sha512-1ObhyoCY+tGxtsz1lSx5NXCj3nirk0Y0kB/g8B8DT+sSx4G9djitg9ejFnjb3gJNWo7qXH4DIy2SUHvpoFwfTA==} + lightningcss-darwin-x64@1.32.0: + resolution: {integrity: sha512-U+QsBp2m/s2wqpUYT/6wnlagdZbtZdndSmut/NJqlCcMLTWp5muCrID+K5UJ6jqD2BFshejCYXniPDbNh73V8w==} engines: {node: '>= 12.0.0'} cpu: [x64] os: [darwin] - lightningcss-freebsd-x64@1.31.1: - resolution: {integrity: sha512-1RINmQKAItO6ISxYgPwszQE1BrsVU5aB45ho6O42mu96UiZBxEXsuQ7cJW4zs4CEodPUioj/QrXW1r9pLUM74A==} + lightningcss-freebsd-x64@1.32.0: + resolution: {integrity: sha512-JCTigedEksZk3tHTTthnMdVfGf61Fky8Ji2E4YjUTEQX14xiy/lTzXnu1vwiZe3bYe0q+SpsSH/CTeDXK6WHig==} engines: {node: '>= 12.0.0'} cpu: [x64] os: [freebsd] - lightningcss-linux-arm-gnueabihf@1.31.1: - resolution: {integrity: sha512-OOCm2//MZJ87CdDK62rZIu+aw9gBv4azMJuA8/KB74wmfS3lnC4yoPHm0uXZ/dvNNHmnZnB8XLAZzObeG0nS1g==} + lightningcss-linux-arm-gnueabihf@1.32.0: + resolution: {integrity: sha512-x6rnnpRa2GL0zQOkt6rts3YDPzduLpWvwAF6EMhXFVZXD4tPrBkEFqzGowzCsIWsPjqSK+tyNEODUBXeeVHSkw==} engines: {node: '>= 12.0.0'} cpu: [arm] os: [linux] - lightningcss-linux-arm64-gnu@1.31.1: - resolution: {integrity: sha512-WKyLWztD71rTnou4xAD5kQT+982wvca7E6QoLpoawZ1gP9JM0GJj4Tp5jMUh9B3AitHbRZ2/H3W5xQmdEOUlLg==} + lightningcss-linux-arm64-gnu@1.32.0: + resolution: {integrity: sha512-0nnMyoyOLRJXfbMOilaSRcLH3Jw5z9HDNGfT/gwCPgaDjnx0i8w7vBzFLFR1f6CMLKF8gVbebmkUN3fa/kQJpQ==} engines: {node: '>= 12.0.0'} cpu: [arm64] os: [linux] libc: [glibc] - lightningcss-linux-arm64-musl@1.31.1: - resolution: {integrity: sha512-mVZ7Pg2zIbe3XlNbZJdjs86YViQFoJSpc41CbVmKBPiGmC4YrfeOyz65ms2qpAobVd7WQsbW4PdsSJEMymyIMg==} + lightningcss-linux-arm64-musl@1.32.0: + resolution: {integrity: sha512-UpQkoenr4UJEzgVIYpI80lDFvRmPVg6oqboNHfoH4CQIfNA+HOrZ7Mo7KZP02dC6LjghPQJeBsvXhJod/wnIBg==} engines: {node: '>= 12.0.0'} cpu: [arm64] os: [linux] libc: [musl] - lightningcss-linux-x64-gnu@1.31.1: - resolution: {integrity: sha512-xGlFWRMl+0KvUhgySdIaReQdB4FNudfUTARn7q0hh/V67PVGCs3ADFjw+6++kG1RNd0zdGRlEKa+T13/tQjPMA==} + lightningcss-linux-x64-gnu@1.32.0: + resolution: {integrity: sha512-V7Qr52IhZmdKPVr+Vtw8o+WLsQJYCTd8loIfpDaMRWGUZfBOYEJeyJIkqGIDMZPwPx24pUMfwSxxI8phr/MbOA==} engines: {node: '>= 12.0.0'} cpu: [x64] os: [linux] libc: [glibc] - lightningcss-linux-x64-musl@1.31.1: - resolution: {integrity: sha512-eowF8PrKHw9LpoZii5tdZwnBcYDxRw2rRCyvAXLi34iyeYfqCQNA9rmUM0ce62NlPhCvof1+9ivRaTY6pSKDaA==} + lightningcss-linux-x64-musl@1.32.0: + resolution: {integrity: sha512-bYcLp+Vb0awsiXg/80uCRezCYHNg1/l3mt0gzHnWV9XP1W5sKa5/TCdGWaR/zBM2PeF/HbsQv/j2URNOiVuxWg==} engines: {node: '>= 12.0.0'} cpu: [x64] os: [linux] libc: [musl] - lightningcss-win32-arm64-msvc@1.31.1: - resolution: {integrity: sha512-aJReEbSEQzx1uBlQizAOBSjcmr9dCdL3XuC/6HLXAxmtErsj2ICo5yYggg1qOODQMtnjNQv2UHb9NpOuFtYe4w==} + lightningcss-win32-arm64-msvc@1.32.0: + resolution: {integrity: sha512-8SbC8BR40pS6baCM8sbtYDSwEVQd4JlFTOlaD3gWGHfThTcABnNDBda6eTZeqbofalIJhFx0qKzgHJmcPTnGdw==} engines: {node: '>= 12.0.0'} cpu: [arm64] os: [win32] - lightningcss-win32-x64-msvc@1.31.1: - resolution: {integrity: sha512-I9aiFrbd7oYHwlnQDqr1Roz+fTz61oDDJX7n9tYF9FJymH1cIN1DtKw3iYt6b8WZgEjoNwVSncwF4wx/ZedMhw==} + lightningcss-win32-x64-msvc@1.32.0: + resolution: {integrity: sha512-Amq9B/SoZYdDi1kFrojnoqPLxYhQ4Wo5XiL8EVJrVsB8ARoC1PWW6VGtT0WKCemjy8aC+louJnjS7U18x3b06Q==} engines: {node: '>= 12.0.0'} cpu: [x64] os: [win32] - lightningcss@1.31.1: - resolution: {integrity: sha512-l51N2r93WmGUye3WuFoN5k10zyvrVs0qfKBhyC5ogUQ6Ew6JUSswh78mbSO+IU3nTWsyOArqPCcShdQSadghBQ==} + lightningcss@1.32.0: + resolution: {integrity: sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ==} 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 +2239,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'} @@ -2605,8 +2247,8 @@ packages: resolution: {integrity: sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==} hasBin: true - lru-cache@11.2.6: - resolution: {integrity: sha512-ESL2CrkS/2wTPfuend7Zhkzo2u0daGJ/A2VucJOgQ/C48S/zB8MMeMHSGKYpXhIjbPxfuezITkaBH1wqv00DDQ==} + lru-cache@11.3.5: + resolution: {integrity: sha512-NxVFwLAnrd9i7KUBxC4DrUhmgjzOs+1Qm50D3oF1/oL+r1NpZ4gA7xvG0/zJ8evR7zIKn4vLf7qTNduWFtCrRw==} engines: {node: 20 || >=22} lru-cache@5.1.1: @@ -2630,11 +2272,8 @@ 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==} + mdn-data@2.27.1: + resolution: {integrity: sha512-9Yubnt3e8A0OKwxYSXyhLymGW4sCufcLG6VdiDdUGVkPhpqLxlvP5vl1983gQjJl3tqbrM731mjaZaP68AgosQ==} media-typer@1.1.0: resolution: {integrity: sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw==} @@ -2644,18 +2283,6 @@ packages: resolution: {integrity: sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g==} engines: {node: '>=18'} - merge2@1.4.1: - resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==} - engines: {node: '>= 8'} - - micromatch@4.0.8: - 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 +2307,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 +2320,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 +2341,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 +2358,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 +2396,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.127.0: + resolution: {integrity: sha512-bkgD4qHlN7WxLdX8bLXdaU54TtQtAIg/ZBAfm0aje/mo3MRDo3P0hZSgr4U7O3xfX+fQmR5AP04JS/TGcZLcFA==} + 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,33 +2415,19 @@ 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==} parse-statements@1.0.11: resolution: {integrity: sha512-HlsyYdMBnbPQ9Jr/VgJ1YF4scnldvJpJxCVx6KgqPL4dxppsWrJHCIIxQXMJrqGnsRkNPATbeMJ8Yxu7JMsYcA==} - parse5@8.0.0: - resolution: {integrity: sha512-9m4m5GSgXjL4AjumKzq1Fgfp3Z8rsvjRNbnkVwfu2ImRqE5D0LnY2QfDen18FSY9C573YU5XxSapdHZTZ2WolA==} + parse5@8.0.1: + resolution: {integrity: sha512-z1e/HMG90obSGeidlli3hj7cbocou0/wa5HacvI3ASx34PecNjNQeaHNo5WIZpWofN9kgkqV1q5YvXe3F0FoPw==} parseurl@1.3.3: 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'} @@ -2843,44 +2439,36 @@ packages: path-parse@1.0.7: resolution: {integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==} - 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'} + path-to-regexp@8.4.2: + resolution: {integrity: sha512-qRcuIdP69NPm4qbACK+aDogI5CBDMi1jKe0ry5rSQJz8JVLsC7jV8XpiJjGRLLol3N+R5ihGYcrPLTno6pAdBA==} 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==} - pg-connection-string@2.11.0: - resolution: {integrity: sha512-kecgoJwhOpxYU21rZjULrmrBJ698U2RxXofKVzOn5UDj61BPj/qMb7diYUR1nLScCDbrztQFl1TaQZT0t1EtzQ==} + pg-connection-string@2.12.0: + resolution: {integrity: sha512-U7qg+bpswf3Cs5xLzRqbXbQl85ng0mfSV/J0nnA31MCLgvEaAo7CIhmeyrmJpOr7o+zm0rXK+hNnT5l9RHkCkQ==} pg-int8@1.0.1: resolution: {integrity: sha512-WCtabS6t3c8SkpDBUlb1kjOs7l66xsGdKpIPZsg4wR+B3+u9UAum2odSsF9tnvxg80h4ZxLWMy4pRjOsFIqQpw==} engines: {node: '>=4.0.0'} - pg-pool@3.11.0: - resolution: {integrity: sha512-MJYfvHwtGp870aeusDh+hg9apvOe2zmpZJpyt+BMtzUWlVqbhFmMK6bOBXLBUPd7iRtIF9fZplDc7KrPN3PN7w==} + pg-pool@3.13.0: + resolution: {integrity: sha512-gB+R+Xud1gLFuRD/QgOIgGOBE2KCQPaPwkzBBGC9oG69pHTkhQeIuejVIk3/cnDyX39av2AxomQiyPT13WKHQA==} peerDependencies: pg: '>=8.0' - pg-protocol@1.11.0: - resolution: {integrity: sha512-pfsxk2M9M3BuGgDOfuy37VNRRX3jmKgMjcvAcWqNDpZSf4cUmv8HSOl5ViRQFsfARFn0KuUQTgLxVMbNq5NW3g==} + pg-protocol@1.13.0: + resolution: {integrity: sha512-zzdvXfS6v89r6v7OcFCHfHlyG/wvry1ALxZo4LqgUoy7W9xhBDMaqOuMiF3qEV45VqsN6rdlcehHrfDtlCPc8w==} pg-types@2.2.0: resolution: {integrity: sha512-qTAAlrEsl8s4OiEQY69wDvcMIdQN6wdz5ojQiOy6YRMuynxenON0O5oCpJI6lshc6scgAY8qvJ2On/p+CXY0GA==} engines: {node: '>=4'} - pg@8.18.0: - resolution: {integrity: sha512-xqrUDL1b9MbkydY/s+VZ6v+xiMUmOUk7SS9d/1kpyQxoJ6U9AO1oIJyUWVZojbfe5Cc/oluutcgFG4L9RDP1iQ==} + pg@8.20.0: + resolution: {integrity: sha512-ldhMxz2r8fl/6QkXnBD3CR9/xg694oT6DZQ2s6c/RI28OjtSOpxnPrUCGOBJ46RCUxcWdx3p6kw/xnDHjKvaRA==} engines: {node: '>= 16.0.0'} peerDependencies: pg-native: '>=3.0.1' @@ -2894,30 +2482,17 @@ packages: picocolors@1.1.1: resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} - picomatch@2.3.1: - resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==} - 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 @@ -2925,8 +2500,8 @@ packages: resolution: {integrity: sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg==} engines: {node: '>= 0.4'} - postcss@8.5.6: - resolution: {integrity: sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==} + postcss@8.5.10: + resolution: {integrity: sha512-pMMHxBOZKFU6HgAZ4eyGnwXF/EvPGGqUr0MnZ5+99485wwW41kW91A4LOGxSHhgugZmSChL5AlElNdwlNgcnLQ==} engines: {node: ^10 || ^12 || >=14} postgres-array@2.0.0: @@ -2949,8 +2524,8 @@ packages: resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==} engines: {node: '>= 0.8.0'} - prettier@3.8.1: - resolution: {integrity: sha512-UOnG6LftzbdaHZcKoPFtOcCKztrQ57WkHDeRD9t/PTQtmT0NHSeWWepj6pS0z/N7+08BHFDQVUrfmfMRcZwbMg==} + prettier@3.8.3: + resolution: {integrity: sha512-7igPTM53cGHMW8xWuVTydi2KO233VFiTNyF5hLJqpilHfmn8C8gPf+PS7dUT64YcXFbiMGZxS9pCSxL/Dxm/Jw==} engines: {node: '>=14'} hasBin: true @@ -2958,13 +2533,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==} @@ -2972,36 +2540,18 @@ packages: resolution: {integrity: sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==} engines: {node: '>= 0.10'} - 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==} + proxy-from-env@2.1.0: + resolution: {integrity: sha512-cJ+oHTW1VAEa8cJslgmUZrc+sjRKgAKl3Zyse6+PV38hZe/V6Z14TbCuXcan9F9ghlz4QrFr2c92TNF82UkYHA==} + engines: {node: '>=10'} punycode@2.3.1: resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==} engines: {node: '>=6'} - qs@6.15.0: - resolution: {integrity: sha512-mAZTtNCeetKMH+pSjrb76NAM8V9a05I9aBZOHztWy/UqcJdQYNsf59vrRKWnojAT9Y+GbIvoTBC++CPHqpDBhQ==} + qs@6.15.1: + resolution: {integrity: sha512-6YHEFRL9mfgcAvql/XhwTvf5jKcOiiupt2FiJxHkiX1z4j7WL8J/jRHYLluORvc1XxB5rV20KoeK00gVJamspg==} 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'} @@ -3010,13 +2560,13 @@ packages: resolution: {integrity: sha512-K5zQjDllxWkf7Z5xJdV0/B0WTNqx6vxG70zJE4N0kBs4LovmEYWJzQGxC9bS9RAKu3bgM40lrd5zoLJ12MQ5BA==} engines: {node: '>= 0.10'} - react-dom@18.3.1: - resolution: {integrity: sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==} + react-dom@19.2.5: + resolution: {integrity: sha512-J5bAZz+DXMMwW/wV3xzKke59Af6CHY7G4uYLN1OvBcKEsWOs4pQExj86BBKamxl/Ik5bx9whOrvBlSDfWzgSag==} peerDependencies: - react: ^18.3.1 + react: ^19.2.5 - react-icons@5.5.0: - resolution: {integrity: sha512-MEFcXdkP3dLo8uumGI5xN3lDFNsRtrjbOEKDLD7yv76v4wpnEq2Lt2qeHaQOr34I/wPN3s3+N08WkQ+CW37Xiw==} + react-icons@5.6.0: + resolution: {integrity: sha512-RH93p5ki6LfOiIt0UtDyNg/cee+HLVR6cHHtW3wALfo+eOHTp8RnU2kRkI6E+H19zMIs03DyxUG/GfZMOGvmiA==} peerDependencies: react: '*' @@ -3049,23 +2599,16 @@ packages: react: ^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 react-dom: ^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 - react-toastify@11.0.5: - resolution: {integrity: sha512-EpqHBGvnSTtHYhCPLxML05NLY2ZX0JURbAdNYa6BUkk+amz4wbKBQvoKQAB0ardvSarUBuY4Q4s1sluAzZwkmA==} + react-toastify@11.1.0: + resolution: {integrity: sha512-e9h23x3phN0wbFeB6yovmWp7lobzV4CaCH0LO8nVP6H7Y+3GbcLpIzMm9dJhcp1RXbpyfvjgpfXqO80QAmn7sg==} peerDependencies: react: ^18 || ^19 react-dom: ^18 || ^19 - react@18.3.1: - resolution: {integrity: sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==} + react@19.2.5: + resolution: {integrity: sha512-llUJLzz1zTUBrskt2pwZgLq59AemifIftw4aB7JxOqf1HY2FDaGDxgwpAPVzHU1kdWabH7FauP4i1oEeer2WCA==} 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 +2640,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'} @@ -3118,39 +2652,22 @@ packages: resolution: {integrity: sha512-oMA2dcrw6u0YfxJQXm342bFKX/E4sG9rbTzO9ptUcR/e8A33cHuvStiYOwH7fszkZlZ1z/ta9AAoPk2F4qIOHA==} engines: {node: '>=18'} - reusify@1.1.0: - resolution: {integrity: sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==} - engines: {iojs: '>=1.0.0', node: '>=0.10.0'} - 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'} + rolldown@1.0.0-rc.17: + resolution: {integrity: sha512-ZrT53oAKrtA4+YtBWPQbtPOxIbVDbxT0orcYERKd63VJTF13zPcgXTvD4843L8pcsI7M6MErt8QtON6lrB9tyA==} + engines: {node: ^20.19.0 || >=22.12.0} hasBin: true router@2.2.0: resolution: {integrity: sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ==} engines: {node: '>= 18'} - run-parallel@1.2.0: - resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==} - - safe-array-concat@1.1.3: - resolution: {integrity: sha512-AURm5f0jYEOydBj7VQlVvDrjeFgthDdEF5H1dP+6mNpoXOMo1quQqJ4wvJDyRZ9+pO3kGWoOdmV08cSv2aJV6Q==} + safe-array-concat@1.1.4: + resolution: {integrity: sha512-wtZlHyOje6OZTGqAoaDKxFkgRtkF9CnHAVnCHKfuj200wAgL+bSJhdsCD2l0Qx/2ekEXjPWcyKkfGb5CPboslg==} 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'} @@ -3169,8 +2686,8 @@ packages: resolution: {integrity: sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA==} engines: {node: '>=v12.22.7'} - scheduler@0.23.2: - resolution: {integrity: sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==} + scheduler@0.27.0: + resolution: {integrity: sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==} semver@6.3.1: resolution: {integrity: sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==} @@ -3201,17 +2718,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'} @@ -3220,8 +2729,8 @@ packages: resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==} engines: {node: '>=8'} - side-channel-list@1.0.0: - resolution: {integrity: sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==} + side-channel-list@1.0.1: + resolution: {integrity: sha512-mjn/0bi/oUURjc5Xl7IaWi/OJJJumuoJFQJfDDyO46+hBWsfaVM65TBHq2eoZBhzl9EchxOijpkbRC8SVBQU0w==} engines: {node: '>= 0.4'} side-channel-map@1.0.1: @@ -3243,29 +2752,22 @@ 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: resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==} engines: {node: '>=0.10.0'} - source-map-support@0.5.21: - resolution: {integrity: sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==} - - source-map@0.6.1: - resolution: {integrity: sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==} - engines: {node: '>=0.10.0'} - spdx-exceptions@2.5.0: resolution: {integrity: sha512-PiU42r+xO4UbUS1buo3LPJkjlO7430Xn5SVAhdpzzsPHsjbYVflnnFdATgabnLude+Cqu25p6N+g2lw/PFsa4w==} @@ -3290,19 +2792,13 @@ packages: resolution: {integrity: sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==} engines: {node: '>= 0.8'} - std-env@3.10.0: - resolution: {integrity: sha512-5GS12FdOZNliM5mAOxFRg7Ir0pWz8MdpYm6AY6VPkGpbA7ZzmbzNcBJQ0GPvvyWgcY7QAhCgf9Uy89I03faLkg==} + std-env@4.1.0: + resolution: {integrity: sha512-Rq7ybcX2RuC55r9oaPVEW7/xu3tj8u4GeBYHBWCychFtzMIr86A7e3PPEBPT37sHStKX3+TiX/Fr/ACmJLVlLQ==} 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 +2830,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'} @@ -3367,52 +2853,35 @@ packages: symbol-tree@3.2.4: resolution: {integrity: sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==} - tailwindcss@4.2.1: - resolution: {integrity: sha512-/tBrSQ36vCleJkAOsy9kbNTgaxvGbyOamC30PRePTQe/o1MFwEKHQk4Cn7BNGaPtjp+PuUrByJehM1hgxfq4sw==} + tailwindcss@4.2.2: + resolution: {integrity: sha512-KWBIxs1Xb6NoLdMVqhbhgwZf2PGBpPEiwOqgI4pFIYbNTfBXiKYyWoTsXgBQ9WFg/OlhnvHaY+AEpW7wSmFo2Q==} - tapable@2.3.0: - resolution: {integrity: sha512-g9ljZiwki/LfxmQADO3dEY1CbpmXT5Hm2fJ+QaGKwSXUylMybePR7/67YW7jOrrvjEgL1Fmz5kzyAjWVWLlucg==} + tapable@2.3.2: + resolution: {integrity: sha512-1MOpMXuhGzGL5TTCZFItxCc0AARf1EZFQkGqMm7ERKj8+Hgr5oLvJOVFcC+lRmR8hCe2S3jC4T5D7Vg/d7/fhA==} engines: {node: '>=6'} - terser@5.44.1: - resolution: {integrity: sha512-t/R3R/n0MSwnnazuPpPNVO60LX0SKL45pyl9YlvxIdkH0Of7D5qM2EVe+yASRIlY5pZ73nclYJfNANGWPwFDZw==} - 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.1.1: + resolution: {integrity: sha512-VKS/ZaQhhkKFMANmAOhhXVoIfBXblQxGX1myCQ2faQrfmobMftXeJPcZGp0gS07ocvGJWDLZGyOZDadDBqYIJg==} engines: {node: '>=18'} - tinyglobby@0.2.15: - resolution: {integrity: sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==} + tinyglobby@0.2.16: + resolution: {integrity: sha512-pn99VhoACYR8nFHhxqix+uvsbXineAasWm5ojXoN8xEwK5Kd3/TrhNn1wByuD52UxWRLy8pu+kRMniEi6Eq9Zg==} 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: - resolution: {integrity: sha512-0g9vrtDQLrNIiCj22HSe9d4mLVG3g5ph5DZ8zCKBr4OtrspmNB6ss7hVyzArAeE88ceZocIEGkyW1Ime7fxPtQ==} + tldts-core@7.0.28: + resolution: {integrity: sha512-7W5Efjhsc3chVdFhqtaU0KtK32J37Zcr9RKtID54nG+tIpcY79CQK/veYPODxtD/LJ4Lue66jvrQzIX2Z2/pUQ==} - tldts@7.0.23: - resolution: {integrity: sha512-ASdhgQIBSay0R/eXggAkQ53G4nTJqTXqC2kbaBbdDwM7SkjyZyO0OaaN1/FH7U/yCeqOHDwFO5j8+Os/IS1dXw==} + tldts@7.0.28: + resolution: {integrity: sha512-+Zg3vWhRUv8B1maGSTFdev9mjoo8Etn2Ayfs4cnjlD3CsGkxXX4QyW3j2WJ0wdjYcYmy7Lx2RDsZMhgCWafKIw==} 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'} - to-valid-identifier@1.0.0: resolution: {integrity: sha512-41wJyvKep3yT2tyPqX/4blcfybknGB4D+oETKLs7Q76UiPqRpUJK3hr1nxelyYO0PHKVzJwlu0aCeEAsGI6rpw==} engines: {node: '>=20'} @@ -3421,8 +2890,8 @@ packages: resolution: {integrity: sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==} engines: {node: '>=0.6'} - tough-cookie@6.0.0: - resolution: {integrity: sha512-kXuRi1mtaKMrsLUxz3sQYvVl37B0Ns6MzfrtV5DvJceE9bPyspOqk9xxv7XbZWcfLWbFmm997vl83qUWVJA64w==} + tough-cookie@6.0.1: + resolution: {integrity: sha512-LktZQb3IeoUWB9lqR5EWTHgW/VTITCXg4D21M+lvybRVdylLrRMnqaIONLVb5mav8vM19m44HIcGq4qASeu2Qw==} engines: {node: '>=16'} tr46@6.0.0: @@ -3432,8 +2901,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 +2910,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.6: + resolution: {integrity: sha512-+v2QJey7ZUeUiuigkU+uFfklvNUyPI2VO2vBpMYJA+a1hKFLFiKtUYlRHdb3P9CrAvMzi0upbjI4WT+zKtqkBg==} + hasBin: true type-check@0.4.0: resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==} @@ -3468,18 +2938,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.59.0: + resolution: {integrity: sha512-BU3ONW9X+v90EcCH9ZS6LMackcVtxRLlI3XrYyqZIwVSHIk7Qf7bFw1z0M9Q0IUxhTMZCf8piY9hTYaNEIASrw==} 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==} + typescript@6.0.3: + resolution: {integrity: sha512-y2TvuxSZPDyQakkFRPZHKFm+KKVqIisdg9/CZwm9ftvKXLP8NRWj38/ODjNbr43SsoXqNuAisEf1GdCxqWcdBw==} engines: {node: '>=14.17'} hasBin: true + unbash@3.0.0: + resolution: {integrity: sha512-FeFPZ/WFT0mbRCuydiZzpPFlrYN8ZUpphQKoq4EeElVIYjYyGzPMxQR/simUwCOJIyVhpFk4RbtyO7RuMpMnHA==} + engines: {node: '>=14'} + unbox-primitive@1.1.0: resolution: {integrity: sha512-nWJ91DjeOkej/TA8pXQ3myruKpKEYgqvpw9lz4OPHj/NWFNluYrjbz9j01CJ8yKQd2g4jFoOkINCTW2I5LEEyw==} engines: {node: '>= 0.4'} @@ -3487,14 +2961,10 @@ packages: undici-types@7.16.0: resolution: {integrity: sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==} - undici@7.22.0: - resolution: {integrity: sha512-RqslV2Us5BrllB+JeiZnK4peryVTndy9Dnqq62S3yYRRTj0tFQCwEniUy2167skdGOy3vqRzEvl1Dm4sV2ReDg==} + undici@7.25.0: + resolution: {integrity: sha512-xXnp4kTyor2Zq+J1FfPI6Eq3ew5h6Vl0F/8d9XU5zZQf1tX9s2Su1/3PiMmUANFULpmksxkClamIZcaUqryHsQ==} 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,46 +2981,29 @@ 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==} + uuid@14.0.0: + resolution: {integrity: sha512-Qo+uWgilfSmAhXCMav1uYFynlQO7fMFiMVZsQqZRMIXp0O7rR7qjkj+cPvBHLgBqi960QCoo/PH2/6ZtVqKvrg==} hasBin: true vary@1.1.2: 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==} + vite@8.0.10: + resolution: {integrity: sha512-rZuUu9j6J5uotLDs+cAA4O5H4K1SfPliUlQwqa6YEwSrWDZzP4rhm00oJR5snMewjxF5V/K3D4kctsUTsIU9Mw==} engines: {node: ^20.19.0 || >=22.12.0} hasBin: true peerDependencies: '@types/node': ^20.19.0 || >=22.12.0 + '@vitejs/devtools': ^0.1.0 + esbuild: ^0.27.0 || ^0.28.0 jiti: '>=1.21.0' less: ^4.0.0 - lightningcss: ^1.21.0 sass: ^1.70.0 sass-embedded: ^1.70.0 stylus: '>=0.54.8' @@ -3561,12 +3014,14 @@ packages: peerDependenciesMeta: '@types/node': optional: true + '@vitejs/devtools': + optional: true + esbuild: + optional: true jiti: optional: true less: optional: true - lightningcss: - optional: true sass: optional: true sass-embedded: @@ -3582,20 +3037,23 @@ packages: yaml: optional: true - vitest@4.0.18: - resolution: {integrity: sha512-hOQuK7h0FGKgBAas7v0mSAsnvrIgAvWmRFjmzpJ7SwFHH3g1k2u37JtYwOwmEKhK6ZO3v9ggDBBm0La1LCK4uQ==} + vitest@4.1.4: + resolution: {integrity: sha512-tFuJqTxKb8AvfyqMfnavXdzfy3h3sWZRWwfluGbkeR7n0HUev+FmNgZ8SDrRBTVrVCjgH5cA21qGbCffMNtWvg==} 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.4 + '@vitest/browser-preview': 4.1.4 + '@vitest/browser-webdriverio': 4.1.4 + '@vitest/coverage-istanbul': 4.1.4 + '@vitest/coverage-v8': 4.1.4 + '@vitest/ui': 4.1.4 happy-dom: '*' jsdom: '*' + vite: ^6.0.0 || ^7.0.0 || ^8.0.0 peerDependenciesMeta: '@edge-runtime/vm': optional: true @@ -3609,6 +3067,10 @@ packages: optional: true '@vitest/browser-webdriverio': optional: true + '@vitest/coverage-istanbul': + optional: true + '@vitest/coverage-v8': + optional: true '@vitest/ui': optional: true happy-dom: @@ -3616,9 +3078,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'} @@ -3631,10 +3090,6 @@ packages: resolution: {integrity: sha512-BMhLD/Sw+GbJC21C/UgyaZX41nPt8bUTg+jWyDeg7e7YN4xOM05YPSIXceACnXVtqyEw/LMClUQMtMZ+PGGpqQ==} engines: {node: '>=20'} - whatwg-mimetype@3.0.0: - resolution: {integrity: sha512-nt+N2dzIutVRxARx1nghPKGv1xHikU7HKdfafKkLNLindmPU/ch3U31NOCGGA/dmPcmb1VlofO0vnKAcsm0o/Q==} - engines: {node: '>=12'} - whatwg-mimetype@5.0.0: resolution: {integrity: sha512-sXcNcHOC51uPGF0P/D4NVtrkjSU2fNsm9iog4ZvZJsL3rjoDAzXZhkm2MWt1y+PUdggKAYVoMAIYcs78wJ51Cw==} engines: {node: '>=20'} @@ -3680,18 +3135,6 @@ packages: wrappy@1.0.2: resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==} - ws@8.19.0: - resolution: {integrity: sha512-blAT2mjOEIi0ZzruJfIhb3nps74PRWTCz1IjglWEEpQl5XS/UNama6u2/rjFkDDouqr4L67ry+1aGIALViWjDg==} - engines: {node: '>=10.0.0'} - peerDependencies: - bufferutil: ^4.0.1 - utf-8-validate: '>=5.0.2' - peerDependenciesMeta: - bufferutil: - optional: true - utf-8-validate: - optional: true - xml-name-validator@5.0.0: resolution: {integrity: sha512-EvGK8EJ3DhaHfbRlETOWAS5pO9MZITeauHKJyb8wyajUfQUenkIg2MvLDTZ4T/TgIcm3HU0TFBgWWboAZ30UHg==} engines: {node: '>=18'} @@ -3706,8 +3149,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 @@ -3726,25 +3169,25 @@ packages: snapshots: - '@acemir/cssom@0.9.31': {} - '@adobe/css-tools@4.4.4': {} - '@asamuzakjp/css-color@5.0.1': + '@asamuzakjp/css-color@5.1.11': dependencies: - '@csstools/css-calc': 3.1.1(@csstools/css-parser-algorithms@4.0.0(@csstools/css-tokenizer@4.0.0))(@csstools/css-tokenizer@4.0.0) - '@csstools/css-color-parser': 4.0.2(@csstools/css-parser-algorithms@4.0.0(@csstools/css-tokenizer@4.0.0))(@csstools/css-tokenizer@4.0.0) + '@asamuzakjp/generational-cache': 1.0.1 + '@csstools/css-calc': 3.2.0(@csstools/css-parser-algorithms@4.0.0(@csstools/css-tokenizer@4.0.0))(@csstools/css-tokenizer@4.0.0) + '@csstools/css-color-parser': 4.1.0(@csstools/css-parser-algorithms@4.0.0(@csstools/css-tokenizer@4.0.0))(@csstools/css-tokenizer@4.0.0) '@csstools/css-parser-algorithms': 4.0.0(@csstools/css-tokenizer@4.0.0) '@csstools/css-tokenizer': 4.0.0 - lru-cache: 11.2.6 - '@asamuzakjp/dom-selector@6.8.1': + '@asamuzakjp/dom-selector@7.1.1': dependencies: + '@asamuzakjp/generational-cache': 1.0.1 '@asamuzakjp/nwsapi': 2.3.9 bidi-js: 1.0.3 - css-tree: 3.1.0 + css-tree: 3.2.1 is-potential-custom-element-name: 1.0.1 - lru-cache: 11.2.6 + + '@asamuzakjp/generational-cache@1.0.1': {} '@asamuzakjp/nwsapi@2.3.9': {} @@ -3762,8 +3205,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 +3221,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 +3231,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,21 +3259,21 @@ 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 - '@babel/runtime@7.28.6': {} + '@babel/runtime@7.29.2': {} '@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 +3281,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 @@ -3854,19 +3297,19 @@ snapshots: '@bramus/specificity@2.4.2': dependencies: - css-tree: 3.1.0 + css-tree: 3.2.1 '@csstools/color-helpers@6.0.2': {} - '@csstools/css-calc@3.1.1(@csstools/css-parser-algorithms@4.0.0(@csstools/css-tokenizer@4.0.0))(@csstools/css-tokenizer@4.0.0)': + '@csstools/css-calc@3.2.0(@csstools/css-parser-algorithms@4.0.0(@csstools/css-tokenizer@4.0.0))(@csstools/css-tokenizer@4.0.0)': dependencies: '@csstools/css-parser-algorithms': 4.0.0(@csstools/css-tokenizer@4.0.0) '@csstools/css-tokenizer': 4.0.0 - '@csstools/css-color-parser@4.0.2(@csstools/css-parser-algorithms@4.0.0(@csstools/css-tokenizer@4.0.0))(@csstools/css-tokenizer@4.0.0)': + '@csstools/css-color-parser@4.1.0(@csstools/css-parser-algorithms@4.0.0(@csstools/css-tokenizer@4.0.0))(@csstools/css-tokenizer@4.0.0)': dependencies: '@csstools/color-helpers': 6.0.2 - '@csstools/css-calc': 3.1.1(@csstools/css-parser-algorithms@4.0.0(@csstools/css-tokenizer@4.0.0))(@csstools/css-tokenizer@4.0.0) + '@csstools/css-calc': 3.2.0(@csstools/css-parser-algorithms@4.0.0(@csstools/css-tokenizer@4.0.0))(@csstools/css-tokenizer@4.0.0) '@csstools/css-parser-algorithms': 4.0.0(@csstools/css-tokenizer@4.0.0) '@csstools/css-tokenizer': 4.0.0 @@ -3874,181 +3317,103 @@ snapshots: dependencies: '@csstools/css-tokenizer': 4.0.0 - '@csstools/css-syntax-patches-for-csstree@1.0.28': {} + '@csstools/css-syntax-patches-for-csstree@1.1.3(css-tree@3.2.1)': + optionalDependencies: + css-tree: 3.2.1 '@csstools/css-tokenizer@4.0.0': {} - '@emnapi/core@1.8.1': + '@emnapi/core@1.10.0': 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/core@1.9.2': + dependencies: + '@emnapi/wasi-threads': 1.2.1 + tslib: 2.8.1 + optional: true + + '@emnapi/runtime@1.10.0': dependencies: tslib: 2.8.1 optional: true - '@emnapi/wasi-threads@1.1.0': + '@emnapi/runtime@1.9.2': dependencies: tslib: 2.8.1 optional: true - '@es-joy/jsdoccomment@0.84.0': + '@emnapi/wasi-threads@1.2.1': + dependencies: + tslib: 2.8.1 + optional: true + + '@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.2 + 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': {} - '@esbuild/aix-ppc64@0.27.3': - optional: true - - '@esbuild/android-arm64@0.27.3': - optional: true - - '@esbuild/android-arm@0.27.3': - optional: true - - '@esbuild/android-x64@0.27.3': - optional: true - - '@esbuild/darwin-arm64@0.27.3': - optional: true - - '@esbuild/darwin-x64@0.27.3': - optional: true - - '@esbuild/freebsd-arm64@0.27.3': - optional: true - - '@esbuild/freebsd-x64@0.27.3': - optional: true - - '@esbuild/linux-arm64@0.27.3': - optional: true - - '@esbuild/linux-arm@0.27.3': - optional: true - - '@esbuild/linux-ia32@0.27.3': - optional: true - - '@esbuild/linux-loong64@0.27.3': - optional: true - - '@esbuild/linux-mips64el@0.27.3': - optional: true - - '@esbuild/linux-ppc64@0.27.3': - optional: true - - '@esbuild/linux-riscv64@0.27.3': - optional: true - - '@esbuild/linux-s390x@0.27.3': - optional: true - - '@esbuild/linux-x64@0.27.3': - optional: true - - '@esbuild/netbsd-arm64@0.27.3': - optional: true - - '@esbuild/netbsd-x64@0.27.3': - optional: true - - '@esbuild/openbsd-arm64@0.27.3': - optional: true - - '@esbuild/openbsd-x64@0.27.3': - optional: true - - '@esbuild/openharmony-arm64@0.27.3': - optional: true - - '@esbuild/sunos-x64@0.27.3': - optional: true - - '@esbuild/win32-arm64@0.27.3': - optional: true - - '@esbuild/win32-ia32@0.27.3': - optional: true - - '@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.2.1(jiti@2.6.1))': dependencies: - eslint: 9.39.2(jiti@2.6.1) + eslint: 10.2.1(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.5(eslint@10.2.1(jiti@2.6.1))': dependencies: - '@eslint/core': 1.1.0 + '@eslint/core': 1.2.1 optionalDependencies: - eslint: 9.39.2(jiti@2.6.1) + eslint: 10.2.1(jiti@2.6.1) - '@eslint/config-array@0.21.1': + '@eslint/config-array@0.23.5': dependencies: - '@eslint/object-schema': 2.1.7 + '@eslint/object-schema': 3.0.5 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.5': dependencies: - '@eslint/core': 0.17.0 + '@eslint/core': 1.2.1 - '@eslint/core@0.17.0': + '@eslint/core@1.2.1': dependencies: '@types/json-schema': 7.0.15 - '@eslint/core@1.1.0': + '@eslint/js@10.0.1(eslint@10.2.1(jiti@2.6.1))': + optionalDependencies: + eslint: 10.2.1(jiti@2.6.1) + + '@eslint/object-schema@3.0.5': {} + + '@eslint/plugin-kit@0.7.1': 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/object-schema@2.1.7': {} - - '@eslint/plugin-kit@0.4.1': - dependencies: - '@eslint/core': 0.17.0 + '@eslint/core': 1.2.1 levn: 0.4.1 - '@exodus/bytes@1.14.1': {} + '@exodus/bytes@1.15.0': {} - '@humanfs/core@0.19.1': {} - - '@humanfs/node@0.16.7': + '@humanfs/core@0.19.2': dependencies: - '@humanfs/core': 0.19.1 + '@humanfs/types': 0.15.0 + + '@humanfs/node@0.16.8': + dependencies: + '@humanfs/core': 0.19.2 + '@humanfs/types': 0.15.0 '@humanwhocodes/retry': 0.4.3 + '@humanfs/types@0.15.0': {} + '@humanwhocodes/module-importer@1.0.1': {} '@humanwhocodes/retry@0.4.3': {} @@ -4065,12 +3430,6 @@ snapshots: '@jridgewell/resolve-uri@3.1.2': {} - '@jridgewell/source-map@0.3.11': - dependencies: - '@jridgewell/gen-mapping': 0.3.13 - '@jridgewell/trace-mapping': 0.3.31 - optional: true - '@jridgewell/sourcemap-codec@1.5.5': {} '@jridgewell/trace-mapping@0.3.31': @@ -4080,97 +3439,163 @@ snapshots: '@napi-rs/wasm-runtime@0.2.12': dependencies: - '@emnapi/core': 1.8.1 - '@emnapi/runtime': 1.8.1 + '@emnapi/core': 1.10.0 + '@emnapi/runtime': 1.10.0 '@tybys/wasm-util': 0.10.1 optional: true - '@napi-rs/wasm-runtime@1.1.1': + '@napi-rs/wasm-runtime@1.1.4(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0)': dependencies: - '@emnapi/core': 1.8.1 - '@emnapi/runtime': 1.8.1 + '@emnapi/core': 1.10.0 + '@emnapi/runtime': 1.10.0 '@tybys/wasm-util': 0.10.1 optional: true - '@nodelib/fs.scandir@2.1.5': + '@napi-rs/wasm-runtime@1.1.4(@emnapi/core@1.9.2)(@emnapi/runtime@1.9.2)': dependencies: - '@nodelib/fs.stat': 2.0.5 - run-parallel: 1.2.0 + '@emnapi/core': 1.9.2 + '@emnapi/runtime': 1.9.2 + '@tybys/wasm-util': 0.10.1 + optional: true - '@nodelib/fs.stat@2.0.5': {} + '@oxc-parser/binding-android-arm-eabi@0.127.0': + optional: true - '@nodelib/fs.walk@1.2.8': + '@oxc-parser/binding-android-arm64@0.127.0': + optional: true + + '@oxc-parser/binding-darwin-arm64@0.127.0': + optional: true + + '@oxc-parser/binding-darwin-x64@0.127.0': + optional: true + + '@oxc-parser/binding-freebsd-x64@0.127.0': + optional: true + + '@oxc-parser/binding-linux-arm-gnueabihf@0.127.0': + optional: true + + '@oxc-parser/binding-linux-arm-musleabihf@0.127.0': + optional: true + + '@oxc-parser/binding-linux-arm64-gnu@0.127.0': + optional: true + + '@oxc-parser/binding-linux-arm64-musl@0.127.0': + optional: true + + '@oxc-parser/binding-linux-ppc64-gnu@0.127.0': + optional: true + + '@oxc-parser/binding-linux-riscv64-gnu@0.127.0': + optional: true + + '@oxc-parser/binding-linux-riscv64-musl@0.127.0': + optional: true + + '@oxc-parser/binding-linux-s390x-gnu@0.127.0': + optional: true + + '@oxc-parser/binding-linux-x64-gnu@0.127.0': + optional: true + + '@oxc-parser/binding-linux-x64-musl@0.127.0': + optional: true + + '@oxc-parser/binding-openharmony-arm64@0.127.0': + optional: true + + '@oxc-parser/binding-wasm32-wasi@0.127.0': dependencies: - '@nodelib/fs.scandir': 2.1.5 - fastq: 1.20.1 - - '@oxc-resolver/binding-android-arm-eabi@11.19.0': + '@emnapi/core': 1.9.2 + '@emnapi/runtime': 1.9.2 + '@napi-rs/wasm-runtime': 1.1.4(@emnapi/core@1.9.2)(@emnapi/runtime@1.9.2) optional: true - '@oxc-resolver/binding-android-arm64@11.19.0': + '@oxc-parser/binding-win32-arm64-msvc@0.127.0': optional: true - '@oxc-resolver/binding-darwin-arm64@11.19.0': + '@oxc-parser/binding-win32-ia32-msvc@0.127.0': optional: true - '@oxc-resolver/binding-darwin-x64@11.19.0': + '@oxc-parser/binding-win32-x64-msvc@0.127.0': optional: true - '@oxc-resolver/binding-freebsd-x64@11.19.0': + '@oxc-project/types@0.127.0': {} + + '@oxc-resolver/binding-android-arm-eabi@11.19.1': optional: true - '@oxc-resolver/binding-linux-arm-gnueabihf@11.19.0': + '@oxc-resolver/binding-android-arm64@11.19.1': optional: true - '@oxc-resolver/binding-linux-arm-musleabihf@11.19.0': + '@oxc-resolver/binding-darwin-arm64@11.19.1': optional: true - '@oxc-resolver/binding-linux-arm64-gnu@11.19.0': + '@oxc-resolver/binding-darwin-x64@11.19.1': optional: true - '@oxc-resolver/binding-linux-arm64-musl@11.19.0': + '@oxc-resolver/binding-freebsd-x64@11.19.1': optional: true - '@oxc-resolver/binding-linux-ppc64-gnu@11.19.0': + '@oxc-resolver/binding-linux-arm-gnueabihf@11.19.1': optional: true - '@oxc-resolver/binding-linux-riscv64-gnu@11.19.0': + '@oxc-resolver/binding-linux-arm-musleabihf@11.19.1': optional: true - '@oxc-resolver/binding-linux-riscv64-musl@11.19.0': + '@oxc-resolver/binding-linux-arm64-gnu@11.19.1': optional: true - '@oxc-resolver/binding-linux-s390x-gnu@11.19.0': + '@oxc-resolver/binding-linux-arm64-musl@11.19.1': optional: true - '@oxc-resolver/binding-linux-x64-gnu@11.19.0': + '@oxc-resolver/binding-linux-ppc64-gnu@11.19.1': optional: true - '@oxc-resolver/binding-linux-x64-musl@11.19.0': + '@oxc-resolver/binding-linux-riscv64-gnu@11.19.1': optional: true - '@oxc-resolver/binding-openharmony-arm64@11.19.0': + '@oxc-resolver/binding-linux-riscv64-musl@11.19.1': optional: true - '@oxc-resolver/binding-wasm32-wasi@11.19.0': + '@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.10.0)(@emnapi/runtime@1.10.0)': dependencies: - '@napi-rs/wasm-runtime': 1.1.1 + '@napi-rs/wasm-runtime': 1.1.4(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0) + transitivePeerDependencies: + - '@emnapi/core' + - '@emnapi/runtime' optional: true - '@oxc-resolver/binding-win32-arm64-msvc@11.19.0': + '@oxc-resolver/binding-win32-arm64-msvc@11.19.1': optional: true - '@oxc-resolver/binding-win32-ia32-msvc@11.19.0': + '@oxc-resolver/binding-win32-ia32-msvc@11.19.1': optional: true - '@oxc-resolver/binding-win32-x64-msvc@11.19.0': + '@oxc-resolver/binding-win32-x64-msvc@11.19.1': optional: true - '@playwright/test@1.58.2': + '@package-json/types@0.0.12': {} + + '@playwright/test@1.59.1': dependencies: - playwright: 1.58.2 + 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)': + '@reduxjs/toolkit@2.11.2(react-redux@9.2.0(@types/react@19.2.14)(react@19.2.5)(redux@5.0.1))(react@19.2.5)': dependencies: '@standard-schema/spec': 1.1.0 '@standard-schema/utils': 0.3.0 @@ -4179,234 +3604,140 @@ snapshots: redux-thunk: 3.1.0(redux@5.0.1) reselect: 5.1.1 optionalDependencies: - react: 18.3.1 - react-redux: 9.2.0(@types/react@18.3.27)(react@18.3.1)(redux@5.0.1) + react: 19.2.5 + react-redux: 9.2.0(@types/react@19.2.14)(react@19.2.5)(redux@5.0.1) - '@rolldown/pluginutils@1.0.0-rc.2': {} + '@rolldown/binding-android-arm64@1.0.0-rc.17': + optional: true - '@rollup/plugin-inject@5.0.5(rollup@4.59.0)': + '@rolldown/binding-darwin-arm64@1.0.0-rc.17': + optional: true + + '@rolldown/binding-darwin-x64@1.0.0-rc.17': + optional: true + + '@rolldown/binding-freebsd-x64@1.0.0-rc.17': + optional: true + + '@rolldown/binding-linux-arm-gnueabihf@1.0.0-rc.17': + optional: true + + '@rolldown/binding-linux-arm64-gnu@1.0.0-rc.17': + optional: true + + '@rolldown/binding-linux-arm64-musl@1.0.0-rc.17': + optional: true + + '@rolldown/binding-linux-ppc64-gnu@1.0.0-rc.17': + optional: true + + '@rolldown/binding-linux-s390x-gnu@1.0.0-rc.17': + optional: true + + '@rolldown/binding-linux-x64-gnu@1.0.0-rc.17': + optional: true + + '@rolldown/binding-linux-x64-musl@1.0.0-rc.17': + optional: true + + '@rolldown/binding-openharmony-arm64@1.0.0-rc.17': + optional: true + + '@rolldown/binding-wasm32-wasi@1.0.0-rc.17': 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': + '@emnapi/core': 1.10.0 + '@emnapi/runtime': 1.10.0 + '@napi-rs/wasm-runtime': 1.1.4(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0) optional: true - '@rollup/rollup-android-arm64@4.59.0': + '@rolldown/binding-win32-arm64-msvc@1.0.0-rc.17': optional: true - '@rollup/rollup-darwin-arm64@4.59.0': + '@rolldown/binding-win32-x64-msvc@1.0.0-rc.17': optional: true - '@rollup/rollup-darwin-x64@4.59.0': - optional: true + '@rolldown/pluginutils@1.0.0-rc.17': {} - '@rollup/rollup-freebsd-arm64@4.59.0': - optional: true - - '@rollup/rollup-freebsd-x64@4.59.0': - optional: true - - '@rollup/rollup-linux-arm-gnueabihf@4.59.0': - optional: true - - '@rollup/rollup-linux-arm-musleabihf@4.59.0': - optional: true - - '@rollup/rollup-linux-arm64-gnu@4.59.0': - optional: true - - '@rollup/rollup-linux-arm64-musl@4.59.0': - optional: true - - '@rollup/rollup-linux-loong64-gnu@4.59.0': - optional: true - - '@rollup/rollup-linux-loong64-musl@4.59.0': - optional: true - - '@rollup/rollup-linux-ppc64-gnu@4.59.0': - optional: true - - '@rollup/rollup-linux-ppc64-musl@4.59.0': - optional: true - - '@rollup/rollup-linux-riscv64-gnu@4.59.0': - optional: true - - '@rollup/rollup-linux-riscv64-musl@4.59.0': - optional: true - - '@rollup/rollup-linux-s390x-gnu@4.59.0': - optional: true - - '@rollup/rollup-linux-x64-gnu@4.59.0': - optional: true - - '@rollup/rollup-linux-x64-musl@4.59.0': - optional: true - - '@rollup/rollup-openbsd-x64@4.59.0': - optional: true - - '@rollup/rollup-openharmony-arm64@4.59.0': - optional: true - - '@rollup/rollup-win32-arm64-msvc@4.59.0': - optional: true - - '@rollup/rollup-win32-ia32-msvc@4.59.0': - optional: true - - '@rollup/rollup-win32-x64-gnu@4.59.0': - optional: true - - '@rollup/rollup-win32-x64-msvc@4.59.0': - optional: true + '@rolldown/pluginutils@1.0.0-rc.7': {} '@sindresorhus/base62@1.0.0': {} - '@sindresorhus/merge-streams@2.3.0': {} - '@standard-schema/spec@1.1.0': {} '@standard-schema/utils@0.3.0': {} - '@swc/core-darwin-arm64@1.15.13': - optional: true - - '@swc/core-darwin-x64@1.15.13': - optional: true - - '@swc/core-linux-arm-gnueabihf@1.15.13': - optional: true - - '@swc/core-linux-arm64-gnu@1.15.13': - optional: true - - '@swc/core-linux-arm64-musl@1.15.13': - optional: true - - '@swc/core-linux-x64-gnu@1.15.13': - optional: true - - '@swc/core-linux-x64-musl@1.15.13': - optional: true - - '@swc/core-win32-arm64-msvc@1.15.13': - optional: true - - '@swc/core-win32-ia32-msvc@1.15.13': - optional: true - - '@swc/core-win32-x64-msvc@1.15.13': - optional: true - - '@swc/core@1.15.13': - dependencies: - '@swc/counter': 0.1.3 - '@swc/types': 0.1.25 - optionalDependencies: - '@swc/core-darwin-arm64': 1.15.13 - '@swc/core-darwin-x64': 1.15.13 - '@swc/core-linux-arm-gnueabihf': 1.15.13 - '@swc/core-linux-arm64-gnu': 1.15.13 - '@swc/core-linux-arm64-musl': 1.15.13 - '@swc/core-linux-x64-gnu': 1.15.13 - '@swc/core-linux-x64-musl': 1.15.13 - '@swc/core-win32-arm64-msvc': 1.15.13 - '@swc/core-win32-ia32-msvc': 1.15.13 - '@swc/core-win32-x64-msvc': 1.15.13 - - '@swc/counter@0.1.3': {} - - '@swc/types@0.1.25': - dependencies: - '@swc/counter': 0.1.3 - - '@tailwindcss/node@4.2.1': + '@tailwindcss/node@4.2.2': dependencies: '@jridgewell/remapping': 2.3.5 - enhanced-resolve: 5.19.0 + enhanced-resolve: 5.20.1 jiti: 2.6.1 - lightningcss: 1.31.1 + lightningcss: 1.32.0 magic-string: 0.30.21 source-map-js: 1.2.1 - tailwindcss: 4.2.1 + tailwindcss: 4.2.2 - '@tailwindcss/oxide-android-arm64@4.2.1': + '@tailwindcss/oxide-android-arm64@4.2.2': optional: true - '@tailwindcss/oxide-darwin-arm64@4.2.1': + '@tailwindcss/oxide-darwin-arm64@4.2.2': optional: true - '@tailwindcss/oxide-darwin-x64@4.2.1': + '@tailwindcss/oxide-darwin-x64@4.2.2': optional: true - '@tailwindcss/oxide-freebsd-x64@4.2.1': + '@tailwindcss/oxide-freebsd-x64@4.2.2': optional: true - '@tailwindcss/oxide-linux-arm-gnueabihf@4.2.1': + '@tailwindcss/oxide-linux-arm-gnueabihf@4.2.2': optional: true - '@tailwindcss/oxide-linux-arm64-gnu@4.2.1': + '@tailwindcss/oxide-linux-arm64-gnu@4.2.2': optional: true - '@tailwindcss/oxide-linux-arm64-musl@4.2.1': + '@tailwindcss/oxide-linux-arm64-musl@4.2.2': optional: true - '@tailwindcss/oxide-linux-x64-gnu@4.2.1': + '@tailwindcss/oxide-linux-x64-gnu@4.2.2': optional: true - '@tailwindcss/oxide-linux-x64-musl@4.2.1': + '@tailwindcss/oxide-linux-x64-musl@4.2.2': optional: true - '@tailwindcss/oxide-wasm32-wasi@4.2.1': + '@tailwindcss/oxide-wasm32-wasi@4.2.2': optional: true - '@tailwindcss/oxide-win32-arm64-msvc@4.2.1': + '@tailwindcss/oxide-win32-arm64-msvc@4.2.2': optional: true - '@tailwindcss/oxide-win32-x64-msvc@4.2.1': + '@tailwindcss/oxide-win32-x64-msvc@4.2.2': optional: true - '@tailwindcss/oxide@4.2.1': + '@tailwindcss/oxide@4.2.2': optionalDependencies: - '@tailwindcss/oxide-android-arm64': 4.2.1 - '@tailwindcss/oxide-darwin-arm64': 4.2.1 - '@tailwindcss/oxide-darwin-x64': 4.2.1 - '@tailwindcss/oxide-freebsd-x64': 4.2.1 - '@tailwindcss/oxide-linux-arm-gnueabihf': 4.2.1 - '@tailwindcss/oxide-linux-arm64-gnu': 4.2.1 - '@tailwindcss/oxide-linux-arm64-musl': 4.2.1 - '@tailwindcss/oxide-linux-x64-gnu': 4.2.1 - '@tailwindcss/oxide-linux-x64-musl': 4.2.1 - '@tailwindcss/oxide-wasm32-wasi': 4.2.1 - '@tailwindcss/oxide-win32-arm64-msvc': 4.2.1 - '@tailwindcss/oxide-win32-x64-msvc': 4.2.1 + '@tailwindcss/oxide-android-arm64': 4.2.2 + '@tailwindcss/oxide-darwin-arm64': 4.2.2 + '@tailwindcss/oxide-darwin-x64': 4.2.2 + '@tailwindcss/oxide-freebsd-x64': 4.2.2 + '@tailwindcss/oxide-linux-arm-gnueabihf': 4.2.2 + '@tailwindcss/oxide-linux-arm64-gnu': 4.2.2 + '@tailwindcss/oxide-linux-arm64-musl': 4.2.2 + '@tailwindcss/oxide-linux-x64-gnu': 4.2.2 + '@tailwindcss/oxide-linux-x64-musl': 4.2.2 + '@tailwindcss/oxide-wasm32-wasi': 4.2.2 + '@tailwindcss/oxide-win32-arm64-msvc': 4.2.2 + '@tailwindcss/oxide-win32-x64-msvc': 4.2.2 - '@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.2(vite@8.0.10(@types/node@24.12.2)(jiti@2.6.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) + '@tailwindcss/node': 4.2.2 + '@tailwindcss/oxide': 4.2.2 + tailwindcss: 4.2.2 + vite: 8.0.10(@types/node@24.12.2)(jiti@2.6.1)(yaml@2.8.3) '@testing-library/dom@10.4.1': dependencies: '@babel/code-frame': 7.29.0 - '@babel/runtime': 7.28.6 + '@babel/runtime': 7.29.2 '@types/aria-query': 5.0.4 aria-query: 5.3.0 dom-accessibility-api: 0.5.16 @@ -4423,6 +3754,24 @@ snapshots: picocolors: 1.1.1 redent: 3.0.0 + '@turbo/darwin-64@2.9.6': + optional: true + + '@turbo/darwin-arm64@2.9.6': + optional: true + + '@turbo/linux-64@2.9.6': + optional: true + + '@turbo/linux-arm64@2.9.6': + optional: true + + '@turbo/windows-64@2.9.6': + optional: true + + '@turbo/windows-arm64@2.9.6': + optional: true + '@tybys/wasm-util@0.10.1': dependencies: tslib: 2.8.1 @@ -4437,129 +3786,117 @@ 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.2': dependencies: undici-types: 7.16.0 - '@types/node@25.2.3': + '@types/react-dom@19.2.3(@types/react@19.2.14)': dependencies: - undici-types: 7.16.0 - optional: true + '@types/react': 19.2.14 - '@types/prop-types@15.7.15': {} - - '@types/react-dom@18.3.7(@types/react@18.3.27)': + '@types/react@19.2.14': dependencies: - '@types/react': 18.3.27 - - '@types/react@18.3.27': - dependencies: - '@types/prop-types': 15.7.15 csstype: 3.2.3 '@types/use-sync-external-store@0.0.6': {} - '@types/whatwg-mimetype@3.0.2': - optional: true - - '@types/ws@8.18.1': - dependencies: - '@types/node': 24.10.13 - 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.59.0(@typescript-eslint/parser@8.59.0(eslint@10.2.1(jiti@2.6.1))(typescript@6.0.3))(eslint@10.2.1(jiti@2.6.1))(typescript@6.0.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.59.0(eslint@10.2.1(jiti@2.6.1))(typescript@6.0.3) + '@typescript-eslint/scope-manager': 8.59.0 + '@typescript-eslint/type-utils': 8.59.0(eslint@10.2.1(jiti@2.6.1))(typescript@6.0.3) + '@typescript-eslint/utils': 8.59.0(eslint@10.2.1(jiti@2.6.1))(typescript@6.0.3) + '@typescript-eslint/visitor-keys': 8.59.0 + eslint: 10.2.1(jiti@2.6.1) ignore: 7.0.5 natural-compare: 1.4.0 - ts-api-utils: 2.4.0(typescript@5.9.3) - typescript: 5.9.3 + ts-api-utils: 2.5.0(typescript@6.0.3) + typescript: 6.0.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.59.0(eslint@10.2.1(jiti@2.6.1))(typescript@6.0.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.59.0 + '@typescript-eslint/types': 8.59.0 + '@typescript-eslint/typescript-estree': 8.59.0(typescript@6.0.3) + '@typescript-eslint/visitor-keys': 8.59.0 debug: 4.4.3 - eslint: 9.39.2(jiti@2.6.1) - typescript: 5.9.3 + eslint: 10.2.1(jiti@2.6.1) + typescript: 6.0.3 transitivePeerDependencies: - supports-color - '@typescript-eslint/project-service@8.56.1(typescript@5.9.3)': + '@typescript-eslint/project-service@8.59.0(typescript@6.0.3)': dependencies: - '@typescript-eslint/tsconfig-utils': 8.56.1(typescript@5.9.3) - '@typescript-eslint/types': 8.56.1 + '@typescript-eslint/tsconfig-utils': 8.59.0(typescript@6.0.3) + '@typescript-eslint/types': 8.59.0 debug: 4.4.3 - typescript: 5.9.3 + typescript: 6.0.3 transitivePeerDependencies: - supports-color - '@typescript-eslint/scope-manager@8.56.1': + '@typescript-eslint/scope-manager@8.59.0': dependencies: - '@typescript-eslint/types': 8.56.1 - '@typescript-eslint/visitor-keys': 8.56.1 + '@typescript-eslint/types': 8.59.0 + '@typescript-eslint/visitor-keys': 8.59.0 - '@typescript-eslint/tsconfig-utils@8.56.1(typescript@5.9.3)': + '@typescript-eslint/tsconfig-utils@8.59.0(typescript@6.0.3)': dependencies: - typescript: 5.9.3 + typescript: 6.0.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.59.0(eslint@10.2.1(jiti@2.6.1))(typescript@6.0.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.59.0 + '@typescript-eslint/typescript-estree': 8.59.0(typescript@6.0.3) + '@typescript-eslint/utils': 8.59.0(eslint@10.2.1(jiti@2.6.1))(typescript@6.0.3) debug: 4.4.3 - eslint: 9.39.2(jiti@2.6.1) - ts-api-utils: 2.4.0(typescript@5.9.3) - typescript: 5.9.3 + eslint: 10.2.1(jiti@2.6.1) + ts-api-utils: 2.5.0(typescript@6.0.3) + typescript: 6.0.3 transitivePeerDependencies: - supports-color - '@typescript-eslint/types@8.56.1': {} + '@typescript-eslint/types@8.58.2': {} - '@typescript-eslint/typescript-estree@8.56.1(typescript@5.9.3)': + '@typescript-eslint/types@8.59.0': {} + + '@typescript-eslint/typescript-estree@8.59.0(typescript@6.0.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.59.0(typescript@6.0.3) + '@typescript-eslint/tsconfig-utils': 8.59.0(typescript@6.0.3) + '@typescript-eslint/types': 8.59.0 + '@typescript-eslint/visitor-keys': 8.59.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) - typescript: 5.9.3 + tinyglobby: 0.2.16 + ts-api-utils: 2.5.0(typescript@6.0.3) + typescript: 6.0.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.59.0(eslint@10.2.1(jiti@2.6.1))(typescript@6.0.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) - typescript: 5.9.3 + '@eslint-community/eslint-utils': 4.9.1(eslint@10.2.1(jiti@2.6.1)) + '@typescript-eslint/scope-manager': 8.59.0 + '@typescript-eslint/types': 8.59.0 + '@typescript-eslint/typescript-estree': 8.59.0(typescript@6.0.3) + eslint: 10.2.1(jiti@2.6.1) + typescript: 6.0.3 transitivePeerDependencies: - supports-color - '@typescript-eslint/visitor-keys@8.56.1': + '@typescript-eslint/visitor-keys@8.59.0': dependencies: - '@typescript-eslint/types': 8.56.1 + '@typescript-eslint/types': 8.59.0 eslint-visitor-keys: 5.0.1 '@unrs/resolver-binding-android-arm-eabi@1.11.1': @@ -4623,66 +3960,65 @@ 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@6.0.1(vite@8.0.10(@types/node@24.12.2)(jiti@2.6.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) - transitivePeerDependencies: - - '@swc/helpers' + '@rolldown/pluginutils': 1.0.0-rc.7 + vite: 8.0.10(@types/node@24.12.2)(jiti@2.6.1)(yaml@2.8.3) - '@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.4(vitest@4.1.4)': dependencies: '@bcoe/v8-coverage': 1.0.2 - '@vitest/utils': 4.0.18 - ast-v8-to-istanbul: 0.3.11 + '@vitest/utils': 4.1.4 + 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.1.0 + tinyrainbow: 3.1.0 + vitest: 4.1.4(@types/node@24.12.2)(@vitest/coverage-v8@4.1.4)(jsdom@29.0.2)(vite@8.0.10(@types/node@24.12.2)(jiti@2.6.1)(yaml@2.8.3)) - '@vitest/expect@4.0.18': + '@vitest/expect@4.1.4': 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.4 + '@vitest/utils': 4.1.4 chai: 6.2.2 - tinyrainbow: 3.0.3 + tinyrainbow: 3.1.0 - '@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.4(vite@8.0.10(@types/node@24.12.2)(jiti@2.6.1)(yaml@2.8.3))': dependencies: - '@vitest/spy': 4.0.18 + '@vitest/spy': 4.1.4 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: 8.0.10(@types/node@24.12.2)(jiti@2.6.1)(yaml@2.8.3) - '@vitest/pretty-format@4.0.18': + '@vitest/pretty-format@4.1.4': dependencies: - tinyrainbow: 3.0.3 + tinyrainbow: 3.1.0 - '@vitest/runner@4.0.18': + '@vitest/runner@4.1.4': dependencies: - '@vitest/utils': 4.0.18 + '@vitest/utils': 4.1.4 pathe: 2.0.3 - '@vitest/snapshot@4.0.18': + '@vitest/snapshot@4.1.4': dependencies: - '@vitest/pretty-format': 4.0.18 + '@vitest/pretty-format': 4.1.4 + '@vitest/utils': 4.1.4 magic-string: 0.30.21 pathe: 2.0.3 - '@vitest/spy@4.0.18': {} + '@vitest/spy@4.1.4': {} - '@vitest/utils@4.0.18': + '@vitest/utils@4.1.4': dependencies: - '@vitest/pretty-format': 4.0.18 - tinyrainbow: 3.0.3 + '@vitest/pretty-format': 4.1.4 + convert-source-map: 2.0.0 + tinyrainbow: 3.1.0 accepts@2.0.0: dependencies: @@ -4695,8 +4031,6 @@ snapshots: acorn@8.16.0: {} - agent-base@7.1.4: {} - ajv@6.14.0: dependencies: fast-deep-equal: 3.1.3 @@ -4712,10 +4046,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: {} @@ -4737,10 +4067,10 @@ snapshots: array-includes@3.1.9: dependencies: - call-bind: 1.0.8 + call-bind: 1.0.9 call-bound: 1.0.4 define-properties: 1.2.1 - es-abstract: 1.24.1 + es-abstract: 1.24.2 es-object-atoms: 1.1.1 get-intrinsic: 1.3.0 is-string: 1.1.1 @@ -4748,62 +4078,48 @@ snapshots: array.prototype.findlast@1.2.5: dependencies: - call-bind: 1.0.8 + call-bind: 1.0.9 define-properties: 1.2.1 - es-abstract: 1.24.1 + es-abstract: 1.24.2 es-errors: 1.3.0 es-object-atoms: 1.1.1 es-shim-unscopables: 1.1.0 array.prototype.flat@1.3.3: dependencies: - call-bind: 1.0.8 + call-bind: 1.0.9 define-properties: 1.2.1 - es-abstract: 1.24.1 + es-abstract: 1.24.2 es-shim-unscopables: 1.1.0 array.prototype.flatmap@1.3.3: dependencies: - call-bind: 1.0.8 + call-bind: 1.0.9 define-properties: 1.2.1 - es-abstract: 1.24.1 + es-abstract: 1.24.2 es-shim-unscopables: 1.1.0 array.prototype.tosorted@1.1.4: dependencies: - call-bind: 1.0.8 + call-bind: 1.0.9 define-properties: 1.2.1 - es-abstract: 1.24.1 + es-abstract: 1.24.2 es-errors: 1.3.0 es-shim-unscopables: 1.1.0 arraybuffer.prototype.slice@1.0.4: dependencies: array-buffer-byte-length: 1.0.2 - call-bind: 1.0.8 + call-bind: 1.0.9 define-properties: 1.2.1 - es-abstract: 1.24.1 + es-abstract: 1.24.2 es-errors: 1.3.0 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 @@ -4817,26 +4133,26 @@ snapshots: dependencies: possible-typed-array-names: 1.1.0 - axios-cache-interceptor@1.11.4(axios@1.13.5): + axios-cache-interceptor@1.12.0(axios@1.15.2): dependencies: - axios: 1.13.5 + axios: 1.15.2 cache-parser: 1.2.6 fast-defer: 1.1.9 http-vary: 1.0.3 object-code: 2.0.0 try: 1.0.3 - axios-mock-adapter@2.1.0(axios@1.13.5): + axios-mock-adapter@2.1.0(axios@1.15.2): dependencies: - axios: 1.13.5 + axios: 1.15.2 fast-deep-equal: 3.1.3 is-buffer: 2.0.5 - axios@1.13.5: + axios@1.15.2: dependencies: - follow-redirects: 1.15.11 + follow-redirects: 1.16.0 form-data: 4.0.5 - proxy-from-env: 1.1.0 + proxy-from-env: 2.1.0 transitivePeerDependencies: - debug @@ -4844,18 +4160,12 @@ snapshots: balanced-match@4.0.4: {} - base64-js@1.5.1: {} - - baseline-browser-mapping@2.10.0: {} + baseline-browser-mapping@2.10.20: {} 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 @@ -4864,94 +4174,28 @@ snapshots: http-errors: 2.0.1 iconv-lite: 0.7.2 on-finished: 2.4.1 - qs: 6.15.0 + qs: 6.15.1 raw-body: 3.0.2 type-is: 2.0.1 transitivePeerDependencies: - supports-color - brace-expansion@1.1.12: + brace-expansion@1.1.14: 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 - braces@3.0.3: + browserslist@4.28.2: 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: - 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) - - 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: {} + baseline-browser-mapping: 2.10.20 + caniuse-lite: 1.0.30001788 + electron-to-chromium: 1.5.340 + node-releases: 2.0.37 + update-browserslist-db: 1.2.3(browserslist@4.28.2) bytes@3.1.2: {} @@ -4962,7 +4206,7 @@ snapshots: es-errors: 1.3.0 function-bind: 1.1.2 - call-bind@1.0.8: + call-bind@1.0.9: dependencies: call-bind-apply-helpers: 1.0.2 es-define-property: 1.0.1 @@ -4974,40 +4218,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.30001788: {} 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: @@ -5016,18 +4241,11 @@ snapshots: commander@14.0.3: {} - 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-disposition@1.1.0: {} content-type@1.0.5: {} @@ -5037,67 +4255,19 @@ 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: + css-tree@3.2.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 + mdn-data: 2.27.1 source-map-js: 1.2.1 css.escape@1.5.1: {} - cssstyle@6.1.0: - dependencies: - '@asamuzakjp/css-color': 5.0.1 - '@csstools/css-syntax-patches-for-csstree': 1.0.28 - css-tree: 3.1.0 - lru-cache: 11.2.6 - csstype@3.2.3: {} daisyui@5.5.19: {} @@ -5153,19 +4323,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 +4333,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 +4341,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.340: {} emoji-name-map@2.0.3: {} @@ -5202,21 +4349,21 @@ snapshots: encodeurl@2.0.0: {} - enhanced-resolve@5.19.0: + enhanced-resolve@5.20.1: dependencies: graceful-fs: 4.2.11 - tapable: 2.3.0 + tapable: 2.3.2 - entities@6.0.1: {} + entities@8.0.0: {} environment@1.1.0: {} - es-abstract@1.24.1: + es-abstract@1.24.2: dependencies: array-buffer-byte-length: 1.0.2 arraybuffer.prototype.slice: 1.0.4 available-typed-arrays: 1.0.7 - call-bind: 1.0.8 + call-bind: 1.0.9 call-bound: 1.0.4 data-view-buffer: 1.0.2 data-view-byte-length: 1.0.2 @@ -5235,7 +4382,7 @@ snapshots: has-property-descriptors: 1.0.2 has-proto: 1.2.0 has-symbols: 1.1.0 - hasown: 2.0.2 + hasown: 2.0.3 internal-slot: 1.1.0 is-array-buffer: 3.0.5 is-callable: 1.2.7 @@ -5253,7 +4400,7 @@ snapshots: object.assign: 4.1.7 own-keys: 1.0.1 regexp.prototype.flags: 1.5.4 - safe-array-concat: 1.1.3 + safe-array-concat: 1.1.4 safe-push-apply: 1.0.0 safe-regex-test: 1.1.0 set-proto: 1.0.0 @@ -5272,12 +4419,12 @@ snapshots: es-errors@1.3.0: {} - es-iterator-helpers@1.2.2: + es-iterator-helpers@1.3.2: dependencies: - call-bind: 1.0.8 + call-bind: 1.0.9 call-bound: 1.0.4 define-properties: 1.2.1 - es-abstract: 1.24.1 + es-abstract: 1.24.2 es-errors: 1.3.0 es-set-tostringtag: 2.1.0 function-bind: 1.1.2 @@ -5289,9 +4436,9 @@ snapshots: has-symbols: 1.1.0 internal-slot: 1.1.0 iterator.prototype: 1.1.5 - safe-array-concat: 1.1.3 + math-intrinsics: 1.1.0 - es-module-lexer@1.7.0: {} + es-module-lexer@2.0.0: {} es-object-atoms@1.1.1: dependencies: @@ -5302,11 +4449,11 @@ snapshots: es-errors: 1.3.0 get-intrinsic: 1.3.0 has-tostringtag: 1.0.2 - hasown: 2.0.2 + hasown: 2.0.3 es-shim-unscopables@1.1.0: dependencies: - hasown: 2.0.2 + hasown: 2.0.3 es-to-primitive@1.3.0: dependencies: @@ -5314,92 +4461,62 @@ snapshots: is-date-object: 1.1.0 is-symbol: 1.1.1 - esbuild@0.27.3: - optionalDependencies: - '@esbuild/aix-ppc64': 0.27.3 - '@esbuild/android-arm': 0.27.3 - '@esbuild/android-arm64': 0.27.3 - '@esbuild/android-x64': 0.27.3 - '@esbuild/darwin-arm64': 0.27.3 - '@esbuild/darwin-x64': 0.27.3 - '@esbuild/freebsd-arm64': 0.27.3 - '@esbuild/freebsd-x64': 0.27.3 - '@esbuild/linux-arm': 0.27.3 - '@esbuild/linux-arm64': 0.27.3 - '@esbuild/linux-ia32': 0.27.3 - '@esbuild/linux-loong64': 0.27.3 - '@esbuild/linux-mips64el': 0.27.3 - '@esbuild/linux-ppc64': 0.27.3 - '@esbuild/linux-riscv64': 0.27.3 - '@esbuild/linux-s390x': 0.27.3 - '@esbuild/linux-x64': 0.27.3 - '@esbuild/netbsd-arm64': 0.27.3 - '@esbuild/netbsd-x64': 0.27.3 - '@esbuild/openbsd-arm64': 0.27.3 - '@esbuild/openbsd-x64': 0.27.3 - '@esbuild/openharmony-arm64': 0.27.3 - '@esbuild/sunos-x64': 0.27.3 - '@esbuild/win32-arm64': 0.27.3 - '@esbuild/win32-ia32': 0.27.3 - '@esbuild/win32-x64': 0.27.3 - escalade@3.2.0: {} escape-html@1.0.3: {} 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.14.0 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.59.0(eslint@10.2.1(jiti@2.6.1))(typescript@6.0.3))(eslint@10.2.1(jiti@2.6.1)))(eslint@10.2.1(jiti@2.6.1)): dependencies: debug: 4.4.3 - eslint: 9.39.2(jiti@2.6.1) + eslint: 10.2.1(jiti@2.6.1) eslint-import-context: 0.1.9(unrs-resolver@1.11.1) - get-tsconfig: 4.13.6 + get-tsconfig: 4.14.0 is-bun-module: 2.0.0 stable-hash-x: 0.2.0 - tinyglobby: 0.2.15 + tinyglobby: 0.2.16 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.59.0(eslint@10.2.1(jiti@2.6.1))(typescript@6.0.3))(eslint@10.2.1(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.59.0(eslint@10.2.1(jiti@2.6.1))(typescript@6.0.3))(eslint@10.2.1(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.2 + comment-parser: 1.4.6 debug: 4.4.3 - eslint: 9.39.2(jiti@2.6.1) + eslint: 10.2.1(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.59.0(eslint@10.2.1(jiti@2.6.1))(typescript@6.0.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.2.1(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.2.1(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 +4527,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.1.1(eslint@10.2.1(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.2.1(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.2.1(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.2 + eslint: 10.2.1(jiti@2.6.1) estraverse: 5.3.0 - hasown: 2.0.2 + hasown: 2.0.3 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 +4560,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.2.1(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.2.1(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 - '@humanfs/node': 0.16.7 + '@eslint/config-array': 0.23.5 + '@eslint/config-helpers': 0.5.5 + '@eslint/core': 1.2.1 + '@eslint/plugin-kit': 0.7.1 + '@humanfs/node': 0.16.8 '@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 +4600,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 +4608,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 +4624,6 @@ snapshots: estraverse@5.3.0: {} - estree-walker@2.0.2: {} - estree-walker@3.0.3: dependencies: '@types/estree': 1.0.8 @@ -5529,20 +4634,13 @@ 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: dependencies: accepts: 2.0.0 body-parser: 2.2.2 - content-disposition: 1.0.1 + content-disposition: 1.1.0 content-type: 1.0.5 cookie: 0.7.2 cookie-signature: 1.2.2 @@ -5560,7 +4658,7 @@ snapshots: once: 1.4.0 parseurl: 1.3.3 proxy-addr: 2.0.7 - qs: 6.15.0 + qs: 6.15.1 range-parser: 1.2.1 router: 2.2.0 send: 1.2.1 @@ -5575,38 +4673,22 @@ snapshots: fast-defer@1.1.9: {} - fast-glob@3.3.3: - dependencies: - '@nodelib/fs.stat': 2.0.5 - '@nodelib/fs.walk': 1.2.8 - glob-parent: 5.1.2 - merge2: 1.4.1 - micromatch: 4.0.8 - fast-json-stable-stringify@2.1.0: {} fast-levenshtein@2.0.6: {} - fastq@1.20.1: - dependencies: - reusify: 1.1.0 - fd-package-json@2.0.0: 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: flat-cache: 4.0.1 - fill-range@7.1.1: - dependencies: - to-regex-range: 5.0.1 - finalhandler@2.1.1: dependencies: debug: 4.4.3 @@ -5625,12 +4707,12 @@ 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: {} + follow-redirects@1.16.0: {} for-each@0.3.5: dependencies: @@ -5641,7 +4723,7 @@ snapshots: asynckit: 0.4.0 combined-stream: 1.0.8 es-set-tostringtag: 2.1.0 - hasown: 2.0.2 + hasown: 2.0.3 mime-types: 2.1.35 formatly@0.3.0: @@ -5662,11 +4744,11 @@ snapshots: function.prototype.name@1.1.8: dependencies: - call-bind: 1.0.8 + call-bind: 1.0.9 call-bound: 1.0.4 define-properties: 1.2.1 functions-have-names: 1.2.3 - hasown: 2.0.2 + hasown: 2.0.3 is-callable: 1.2.7 functions-have-names@1.2.3: {} @@ -5687,7 +4769,7 @@ snapshots: get-proto: 1.0.1 gopd: 1.2.0 has-symbols: 1.1.0 - hasown: 2.0.2 + hasown: 2.0.3 math-intrinsics: 1.1.0 get-proto@1.0.1: @@ -5701,55 +4783,27 @@ snapshots: es-errors: 1.3.0 get-intrinsic: 1.3.0 - get-tsconfig@4.13.6: + get-tsconfig@4.14.0: dependencies: resolve-pkg-maps: 1.0.0 github-username-regex@1.0.0: {} - glob-parent@5.1.2: - dependencies: - is-glob: 4.0.3 - glob-parent@6.0.2: dependencies: is-glob: 4.0.3 - globals@14.0.0: {} - - globals@17.3.0: {} + globals@17.5.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/whatwg-mimetype': 3.0.2 - '@types/ws': 8.18.1 - entities: 6.0.1 - whatwg-mimetype: 3.0.0 - ws: 8.19.0 - transitivePeerDependencies: - - bufferutil - - utf-8-validate - optional: true - has-bigints@1.1.0: {} has-flag@4.0.0: {} @@ -5768,24 +4822,7 @@ 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: + hasown@2.0.3: dependencies: function-bind: 1.1.2 @@ -5795,15 +4832,9 @@ 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 + '@exodus/bytes': 1.15.0 transitivePeerDependencies: - '@noble/hashes' @@ -5819,43 +4850,20 @@ snapshots: statuses: 2.0.2 toidentifier: 1.0.1 - http-proxy-agent@7.0.2: - dependencies: - agent-base: 7.1.4 - debug: 4.4.3 - transitivePeerDependencies: - - supports-color - http-vary@1.0.3: {} - https-browserify@1.0.0: {} - - https-proxy-agent@7.0.6: - dependencies: - agent-base: 7.1.4 - debug: 4.4.3 - transitivePeerDependencies: - - supports-color - husky@9.1.7: {} iconv-lite@0.7.2: 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: {} @@ -5865,19 +4873,14 @@ snapshots: internal-slot@1.1.0: dependencies: es-errors: 1.3.0 - hasown: 2.0.2 + hasown: 2.0.3 side-channel: 1.1.0 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 + call-bind: 1.0.9 call-bound: 1.0.4 get-intrinsic: 1.3.0 @@ -5908,7 +4911,7 @@ snapshots: is-core-module@2.16.1: dependencies: - hasown: 2.0.2 + hasown: 2.0.3 is-data-view@1.0.2: dependencies: @@ -5945,11 +4948,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: @@ -5957,8 +4955,6 @@ snapshots: call-bound: 1.0.4 has-tostringtag: 1.0.2 - is-number@7.0.0: {} - is-potential-custom-element-name@1.0.1: {} is-promise@4.0.0: {} @@ -5968,7 +4964,7 @@ snapshots: call-bound: 1.0.4 gopd: 1.2.0 has-tostringtag: 1.0.2 - hasown: 2.0.2 + hasown: 2.0.3 is-set@2.0.3: {} @@ -6002,14 +4998,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,26 +5034,26 @@ snapshots: dependencies: argparse: 2.0.1 - jsdoc-type-pratt-parser@7.1.1: {} + jsdoc-type-pratt-parser@7.2.0: {} - jsdom@28.1.0: + jsdom@29.0.2: dependencies: - '@acemir/cssom': 0.9.31 - '@asamuzakjp/dom-selector': 6.8.1 + '@asamuzakjp/css-color': 5.1.11 + '@asamuzakjp/dom-selector': 7.1.1 '@bramus/specificity': 2.4.2 - '@exodus/bytes': 1.14.1 - cssstyle: 6.1.0 + '@csstools/css-syntax-patches-for-csstree': 1.1.3(css-tree@3.2.1) + '@exodus/bytes': 1.15.0 + css-tree: 3.2.1 data-urls: 7.0.0 decimal.js: 10.6.0 html-encoding-sniffer: 6.0.0 - http-proxy-agent: 7.0.2 - https-proxy-agent: 7.0.6 is-potential-custom-element-name: 1.0.1 - parse5: 8.0.0 + lru-cache: 11.3.5 + parse5: 8.0.1 saxes: 6.0.0 symbol-tree: 3.2.4 - tough-cookie: 6.0.0 - undici: 7.22.0 + tough-cookie: 6.0.1 + undici: 7.25.0 w3c-xmlserializer: 5.0.0 webidl-conversions: 8.0.1 whatwg-mimetype: 5.0.0 @@ -6069,7 +5061,6 @@ snapshots: xml-name-validator: 5.0.0 transitivePeerDependencies: - '@noble/hashes' - - supports-color jsesc@3.1.0: {} @@ -6092,90 +5083,92 @@ snapshots: dependencies: json-buffer: 3.0.1 - knip@5.85.0(@types/node@24.10.13)(typescript@5.9.3): + knip@6.7.0(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0): dependencies: - '@nodelib/fs.walk': 1.2.8 - '@types/node': 24.10.13 - fast-glob: 3.3.3 + fdir: 6.5.0(picomatch@4.0.4) formatly: 0.3.0 + get-tsconfig: 4.14.0 jiti: 2.6.1 - js-yaml: 4.1.1 minimist: 1.2.8 - oxc-resolver: 11.19.0 - picocolors: 1.1.1 - picomatch: 4.0.3 - smol-toml: 1.6.0 + oxc-parser: 0.127.0 + oxc-resolver: 11.19.1(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0) + picomatch: 4.0.4 + smol-toml: 1.6.1 strip-json-comments: 5.0.3 - typescript: 5.9.3 + tinyglobby: 0.2.16 + unbash: 3.0.0 + yaml: 2.8.3 zod: 4.3.6 + transitivePeerDependencies: + - '@emnapi/core' + - '@emnapi/runtime' levn@0.4.1: dependencies: prelude-ls: 1.2.1 type-check: 0.4.0 - lightningcss-android-arm64@1.31.1: + lightningcss-android-arm64@1.32.0: optional: true - lightningcss-darwin-arm64@1.31.1: + lightningcss-darwin-arm64@1.32.0: optional: true - lightningcss-darwin-x64@1.31.1: + lightningcss-darwin-x64@1.32.0: optional: true - lightningcss-freebsd-x64@1.31.1: + lightningcss-freebsd-x64@1.32.0: optional: true - lightningcss-linux-arm-gnueabihf@1.31.1: + lightningcss-linux-arm-gnueabihf@1.32.0: optional: true - lightningcss-linux-arm64-gnu@1.31.1: + lightningcss-linux-arm64-gnu@1.32.0: optional: true - lightningcss-linux-arm64-musl@1.31.1: + lightningcss-linux-arm64-musl@1.32.0: optional: true - lightningcss-linux-x64-gnu@1.31.1: + lightningcss-linux-x64-gnu@1.32.0: optional: true - lightningcss-linux-x64-musl@1.31.1: + lightningcss-linux-x64-musl@1.32.0: optional: true - lightningcss-win32-arm64-msvc@1.31.1: + lightningcss-win32-arm64-msvc@1.32.0: optional: true - lightningcss-win32-x64-msvc@1.31.1: + lightningcss-win32-x64-msvc@1.32.0: optional: true - lightningcss@1.31.1: + lightningcss@1.32.0: dependencies: detect-libc: 2.1.2 optionalDependencies: - lightningcss-android-arm64: 1.31.1 - lightningcss-darwin-arm64: 1.31.1 - lightningcss-darwin-x64: 1.31.1 - lightningcss-freebsd-x64: 1.31.1 - lightningcss-linux-arm-gnueabihf: 1.31.1 - lightningcss-linux-arm64-gnu: 1.31.1 - lightningcss-linux-arm64-musl: 1.31.1 - lightningcss-linux-x64-gnu: 1.31.1 - lightningcss-linux-x64-musl: 1.31.1 - lightningcss-win32-arm64-msvc: 1.31.1 - lightningcss-win32-x64-msvc: 1.31.1 + lightningcss-android-arm64: 1.32.0 + lightningcss-darwin-arm64: 1.32.0 + lightningcss-darwin-x64: 1.32.0 + lightningcss-freebsd-x64: 1.32.0 + lightningcss-linux-arm-gnueabihf: 1.32.0 + lightningcss-linux-arm64-gnu: 1.32.0 + lightningcss-linux-arm64-musl: 1.32.0 + lightningcss-linux-x64-gnu: 1.32.0 + lightningcss-linux-x64-musl: 1.32.0 + lightningcss-win32-arm64-msvc: 1.32.0 + lightningcss-win32-x64-msvc: 1.32.0 - 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.1.1 + 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,21 +5179,19 @@ 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: dependencies: js-tokens: 4.0.0 - lru-cache@11.2.6: {} + lru-cache@11.3.5: {} lru-cache@5.1.1: dependencies: @@ -6214,7 +5205,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,30 +5215,12 @@ 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: {} + mdn-data@2.27.1: {} media-typer@1.1.0: {} merge-descriptors@2.0.0: {} - merge2@1.4.1: {} - - 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 - mime-db@1.52.0: {} mime-db@1.54.0: {} @@ -6264,24 +5237,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.14 minimist@1.2.8: {} ms@2.1.3: {} - nano-spawn@2.0.0: {} - nanoid@3.3.11: {} napi-postinstall@0.3.4: {} @@ -6297,37 +5264,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,16 +5274,11 @@ 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: dependencies: - call-bind: 1.0.8 + call-bind: 1.0.9 call-bound: 1.0.4 define-properties: 1.2.1 es-object-atoms: 1.1.1 @@ -6355,21 +5287,21 @@ snapshots: object.entries@1.1.9: dependencies: - call-bind: 1.0.8 + call-bind: 1.0.9 call-bound: 1.0.4 define-properties: 1.2.1 es-object-atoms: 1.1.1 object.fromentries@2.0.8: dependencies: - call-bind: 1.0.8 + call-bind: 1.0.9 define-properties: 1.2.1 - es-abstract: 1.24.1 + es-abstract: 1.24.2 es-object-atoms: 1.1.1 object.values@1.2.1: dependencies: - call-bind: 1.0.8 + call-bind: 1.0.9 call-bound: 1.0.4 define-properties: 1.2.1 es-object-atoms: 1.1.1 @@ -6397,36 +5329,62 @@ 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.127.0: + dependencies: + '@oxc-project/types': 0.127.0 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-parser/binding-android-arm-eabi': 0.127.0 + '@oxc-parser/binding-android-arm64': 0.127.0 + '@oxc-parser/binding-darwin-arm64': 0.127.0 + '@oxc-parser/binding-darwin-x64': 0.127.0 + '@oxc-parser/binding-freebsd-x64': 0.127.0 + '@oxc-parser/binding-linux-arm-gnueabihf': 0.127.0 + '@oxc-parser/binding-linux-arm-musleabihf': 0.127.0 + '@oxc-parser/binding-linux-arm64-gnu': 0.127.0 + '@oxc-parser/binding-linux-arm64-musl': 0.127.0 + '@oxc-parser/binding-linux-ppc64-gnu': 0.127.0 + '@oxc-parser/binding-linux-riscv64-gnu': 0.127.0 + '@oxc-parser/binding-linux-riscv64-musl': 0.127.0 + '@oxc-parser/binding-linux-s390x-gnu': 0.127.0 + '@oxc-parser/binding-linux-x64-gnu': 0.127.0 + '@oxc-parser/binding-linux-x64-musl': 0.127.0 + '@oxc-parser/binding-openharmony-arm64': 0.127.0 + '@oxc-parser/binding-wasm32-wasi': 0.127.0 + '@oxc-parser/binding-win32-arm64-msvc': 0.127.0 + '@oxc-parser/binding-win32-ia32-msvc': 0.127.0 + '@oxc-parser/binding-win32-x64-msvc': 0.127.0 + + oxc-resolver@11.19.1(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0): + optionalDependencies: + '@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.10.0)(@emnapi/runtime@1.10.0) + '@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,67 +5394,40 @@ 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 parse-statements@1.0.11: {} - parse5@8.0.0: + parse5@8.0.1: dependencies: - entities: 6.0.1 + entities: 8.0.0 parseurl@1.3.3: {} - path-browserify@1.0.1: {} - path-exists@4.0.0: {} path-key@3.1.1: {} path-parse@1.0.7: {} - path-to-regexp@8.3.0: {} - - path-type@6.0.0: {} + path-to-regexp@8.4.2: {} 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 - pg-connection-string@2.11.0: {} + pg-connection-string@2.12.0: {} pg-int8@1.0.1: {} - pg-pool@3.11.0(pg@8.18.0): + pg-pool@3.13.0(pg@8.20.0): dependencies: - pg: 8.18.0 + pg: 8.20.0 - pg-protocol@1.11.0: {} + pg-protocol@1.13.0: {} pg-types@2.2.0: dependencies: @@ -6506,11 +5437,11 @@ snapshots: postgres-date: 1.0.7 postgres-interval: 1.2.0 - pg@8.18.0: + pg@8.20.0: dependencies: - pg-connection-string: 2.11.0 - pg-pool: 3.11.0(pg@8.18.0) - pg-protocol: 1.11.0 + pg-connection-string: 2.12.0 + pg-pool: 3.13.0(pg@8.20.0) + pg-protocol: 1.13.0 pg-types: 2.2.0 pgpass: 1.0.5 optionalDependencies: @@ -6522,27 +5453,19 @@ snapshots: picocolors@1.1.1: {} - picomatch@2.3.1: {} + picomatch@4.0.4: {} - picomatch@4.0.3: {} + playwright-core@1.59.1: {} - pidtree@0.6.0: {} - - pkg-dir@5.0.0: + playwright@1.59.1: dependencies: - find-up: 5.0.0 - - playwright-core@1.58.2: {} - - playwright@1.58.2: - dependencies: - playwright-core: 1.58.2 + playwright-core: 1.59.1 optionalDependencies: fsevents: 2.3.2 possible-typed-array-names@1.1.0: {} - postcss@8.5.6: + postcss@8.5.10: dependencies: nanoid: 3.3.11 picocolors: 1.1.1 @@ -6560,7 +5483,7 @@ snapshots: prelude-ls@1.2.1: {} - prettier@3.8.1: {} + prettier@3.8.3: {} pretty-format@27.5.1: dependencies: @@ -6568,10 +5491,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 @@ -6583,38 +5502,14 @@ snapshots: forwarded: 0.2.0 ipaddr.js: 1.9.1 - 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: {} + proxy-from-env@2.1.0: {} punycode@2.3.1: {} - qs@6.15.0: + qs@6.15.1: 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: @@ -6624,63 +5519,44 @@ snapshots: iconv-lite: 0.7.2 unpipe: 1.0.0 - react-dom@18.3.1(react@18.3.1): + react-dom@19.2.5(react@19.2.5): dependencies: - loose-envify: 1.4.0 - react: 18.3.1 - scheduler: 0.23.2 + react: 19.2.5 + scheduler: 0.27.0 - react-icons@5.5.0(react@18.3.1): + react-icons@5.6.0(react@19.2.5): dependencies: - react: 18.3.1 + react: 19.2.5 react-is@16.13.1: {} react-is@17.0.2: {} - react-loading-skeleton@3.5.0(react@18.3.1): + react-loading-skeleton@3.5.0(react@19.2.5): dependencies: - react: 18.3.1 + react: 19.2.5 - react-redux@9.2.0(@types/react@18.3.27)(react@18.3.1)(redux@5.0.1): + react-redux@9.2.0(@types/react@19.2.14)(react@19.2.5)(redux@5.0.1): dependencies: '@types/use-sync-external-store': 0.0.6 - react: 18.3.1 - use-sync-external-store: 1.6.0(react@18.3.1) + react: 19.2.5 + use-sync-external-store: 1.6.0(react@19.2.5) optionalDependencies: - '@types/react': 18.3.27 + '@types/react': 19.2.14 redux: 5.0.1 - react-spinners@0.17.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1): + react-spinners@0.17.0(react-dom@19.2.5(react@19.2.5))(react@19.2.5): dependencies: - react: 18.3.1 - react-dom: 18.3.1(react@18.3.1) + react: 19.2.5 + react-dom: 19.2.5(react@19.2.5) - react-toastify@11.0.5(react-dom@18.3.1(react@18.3.1))(react@18.3.1): + react-toastify@11.1.0(react-dom@19.2.5(react@19.2.5))(react@19.2.5): dependencies: clsx: 2.1.1 - react: 18.3.1 - react-dom: 18.3.1(react@18.3.1) + react: 19.2.5 + react-dom: 19.2.5(react@19.2.5) - react@18.3.1: - 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 + react@19.2.5: {} redent@3.0.0: dependencies: @@ -6695,9 +5571,9 @@ snapshots: reflect.getprototypeof@1.0.10: dependencies: - call-bind: 1.0.8 + call-bind: 1.0.9 define-properties: 1.2.1 - es-abstract: 1.24.1 + es-abstract: 1.24.2 es-errors: 1.3.0 es-object-atoms: 1.1.1 get-intrinsic: 1.3.0 @@ -6706,7 +5582,7 @@ snapshots: regexp.prototype.flags@1.5.4: dependencies: - call-bind: 1.0.8 + call-bind: 1.0.9 define-properties: 1.2.1 es-errors: 1.3.0 get-proto: 1.0.1 @@ -6719,16 +5595,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 @@ -6743,45 +5611,28 @@ snapshots: onetime: 7.0.0 signal-exit: 4.1.0 - reusify@1.1.0: {} - rfdc@1.4.1: {} - ripemd160@2.0.3: + rolldown@1.0.0-rc.17: dependencies: - hash-base: 3.1.2 - inherits: 2.0.4 - - rollup@4.59.0: - dependencies: - '@types/estree': 1.0.8 + '@oxc-project/types': 0.127.0 + '@rolldown/pluginutils': 1.0.0-rc.17 optionalDependencies: - '@rollup/rollup-android-arm-eabi': 4.59.0 - '@rollup/rollup-android-arm64': 4.59.0 - '@rollup/rollup-darwin-arm64': 4.59.0 - '@rollup/rollup-darwin-x64': 4.59.0 - '@rollup/rollup-freebsd-arm64': 4.59.0 - '@rollup/rollup-freebsd-x64': 4.59.0 - '@rollup/rollup-linux-arm-gnueabihf': 4.59.0 - '@rollup/rollup-linux-arm-musleabihf': 4.59.0 - '@rollup/rollup-linux-arm64-gnu': 4.59.0 - '@rollup/rollup-linux-arm64-musl': 4.59.0 - '@rollup/rollup-linux-loong64-gnu': 4.59.0 - '@rollup/rollup-linux-loong64-musl': 4.59.0 - '@rollup/rollup-linux-ppc64-gnu': 4.59.0 - '@rollup/rollup-linux-ppc64-musl': 4.59.0 - '@rollup/rollup-linux-riscv64-gnu': 4.59.0 - '@rollup/rollup-linux-riscv64-musl': 4.59.0 - '@rollup/rollup-linux-s390x-gnu': 4.59.0 - '@rollup/rollup-linux-x64-gnu': 4.59.0 - '@rollup/rollup-linux-x64-musl': 4.59.0 - '@rollup/rollup-openbsd-x64': 4.59.0 - '@rollup/rollup-openharmony-arm64': 4.59.0 - '@rollup/rollup-win32-arm64-msvc': 4.59.0 - '@rollup/rollup-win32-ia32-msvc': 4.59.0 - '@rollup/rollup-win32-x64-gnu': 4.59.0 - '@rollup/rollup-win32-x64-msvc': 4.59.0 - fsevents: 2.3.3 + '@rolldown/binding-android-arm64': 1.0.0-rc.17 + '@rolldown/binding-darwin-arm64': 1.0.0-rc.17 + '@rolldown/binding-darwin-x64': 1.0.0-rc.17 + '@rolldown/binding-freebsd-x64': 1.0.0-rc.17 + '@rolldown/binding-linux-arm-gnueabihf': 1.0.0-rc.17 + '@rolldown/binding-linux-arm64-gnu': 1.0.0-rc.17 + '@rolldown/binding-linux-arm64-musl': 1.0.0-rc.17 + '@rolldown/binding-linux-ppc64-gnu': 1.0.0-rc.17 + '@rolldown/binding-linux-s390x-gnu': 1.0.0-rc.17 + '@rolldown/binding-linux-x64-gnu': 1.0.0-rc.17 + '@rolldown/binding-linux-x64-musl': 1.0.0-rc.17 + '@rolldown/binding-openharmony-arm64': 1.0.0-rc.17 + '@rolldown/binding-wasm32-wasi': 1.0.0-rc.17 + '@rolldown/binding-win32-arm64-msvc': 1.0.0-rc.17 + '@rolldown/binding-win32-x64-msvc': 1.0.0-rc.17 router@2.2.0: dependencies: @@ -6789,26 +5640,18 @@ snapshots: depd: 2.0.0 is-promise: 4.0.0 parseurl: 1.3.3 - path-to-regexp: 8.3.0 + path-to-regexp: 8.4.2 transitivePeerDependencies: - supports-color - run-parallel@1.2.0: + safe-array-concat@1.1.4: dependencies: - queue-microtask: 1.2.3 - - safe-array-concat@1.1.3: - dependencies: - call-bind: 1.0.8 + call-bind: 1.0.9 call-bound: 1.0.4 get-intrinsic: 1.3.0 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 @@ -6828,9 +5671,7 @@ snapshots: dependencies: xmlchars: 2.2.0 - scheduler@0.23.2: - dependencies: - loose-envify: 1.4.0 + scheduler@0.27.0: {} semver@6.3.1: {} @@ -6883,23 +5724,15 @@ 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 shebang-regex@3.0.0: {} - side-channel-list@1.0.0: + side-channel-list@1.0.1: dependencies: es-errors: 1.3.0 object-inspect: 1.13.4 @@ -6923,7 +5756,7 @@ snapshots: dependencies: es-errors: 1.3.0 object-inspect: 1.13.4 - side-channel-list: 1.0.0 + side-channel-list: 1.0.1 side-channel-map: 1.0.1 side-channel-weakmap: 1.0.2 @@ -6931,26 +5764,20 @@ 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: {} - source-map-support@0.5.21: - dependencies: - buffer-from: 1.1.2 - source-map: 0.6.1 - optional: true - - source-map@0.6.1: - optional: true - spdx-exceptions@2.5.0: {} spdx-expression-parse@4.0.0: @@ -6968,44 +5795,32 @@ snapshots: statuses@2.0.2: {} - std-env@3.10.0: {} + std-env@4.1.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: - call-bind: 1.0.8 + call-bind: 1.0.9 call-bound: 1.0.4 define-properties: 1.2.1 - es-abstract: 1.24.1 + es-abstract: 1.24.2 es-errors: 1.3.0 es-object-atoms: 1.1.1 get-intrinsic: 1.3.0 @@ -7019,40 +5834,32 @@ snapshots: string.prototype.repeat@1.0.0: dependencies: define-properties: 1.2.1 - es-abstract: 1.24.1 + es-abstract: 1.24.2 string.prototype.trim@1.2.10: dependencies: - call-bind: 1.0.8 + call-bind: 1.0.9 call-bound: 1.0.4 define-data-property: 1.1.4 define-properties: 1.2.1 - es-abstract: 1.24.1 + es-abstract: 1.24.2 es-object-atoms: 1.1.1 has-property-descriptors: 1.0.2 string.prototype.trimend@1.0.9: dependencies: - call-bind: 1.0.8 + call-bind: 1.0.9 call-bound: 1.0.4 define-properties: 1.2.1 es-object-atoms: 1.1.1 string.prototype.trimstart@1.0.8: dependencies: - call-bind: 1.0.8 + call-bind: 1.0.9 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 +5867,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: @@ -7072,48 +5877,26 @@ snapshots: symbol-tree@3.2.4: {} - tailwindcss@4.2.1: {} + tailwindcss@4.2.2: {} - tapable@2.3.0: {} - - terser@5.44.1: - dependencies: - '@jridgewell/source-map': 0.3.11 - acorn: 8.16.0 - commander: 2.20.3 - source-map-support: 0.5.21 - optional: true - - timers-browserify@2.0.12: - dependencies: - setimmediate: 1.0.5 + tapable@2.3.2: {} tinybench@2.9.0: {} - tinyexec@1.0.2: {} + tinyexec@1.1.1: {} - tinyglobby@0.2.15: + tinyglobby@0.2.16: 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: {} + tldts-core@7.0.28: {} - tldts@7.0.23: + tldts@7.0.28: 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 + tldts-core: 7.0.28 to-valid-identifier@1.0.0: dependencies: @@ -7122,9 +5905,9 @@ snapshots: toidentifier@1.0.1: {} - tough-cookie@6.0.0: + tough-cookie@6.0.1: dependencies: - tldts: 7.0.23 + tldts: 7.0.28 tr46@6.0.0: dependencies: @@ -7132,14 +5915,21 @@ snapshots: try@1.0.3: {} - ts-api-utils@2.4.0(typescript@5.9.3): + ts-api-utils@2.5.0(typescript@6.0.3): dependencies: - typescript: 5.9.3 + typescript: 6.0.3 tslib@2.8.1: optional: true - tty-browserify@0.0.1: {} + turbo@2.9.6: + optionalDependencies: + '@turbo/darwin-64': 2.9.6 + '@turbo/darwin-arm64': 2.9.6 + '@turbo/linux-64': 2.9.6 + '@turbo/linux-arm64': 2.9.6 + '@turbo/windows-64': 2.9.6 + '@turbo/windows-arm64': 2.9.6 type-check@0.4.0: dependencies: @@ -7159,7 +5949,7 @@ snapshots: typed-array-byte-length@1.0.3: dependencies: - call-bind: 1.0.8 + call-bind: 1.0.9 for-each: 0.3.5 gopd: 1.2.0 has-proto: 1.2.0 @@ -7168,7 +5958,7 @@ snapshots: typed-array-byte-offset@1.0.4: dependencies: available-typed-arrays: 1.0.7 - call-bind: 1.0.8 + call-bind: 1.0.9 for-each: 0.3.5 gopd: 1.2.0 has-proto: 1.2.0 @@ -7177,25 +5967,27 @@ snapshots: typed-array-length@1.0.7: dependencies: - call-bind: 1.0.8 + call-bind: 1.0.9 for-each: 0.3.5 gopd: 1.2.0 is-typed-array: 1.1.15 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.59.0(eslint@10.2.1(jiti@2.6.1))(typescript@6.0.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: 5.9.3 + '@typescript-eslint/eslint-plugin': 8.59.0(@typescript-eslint/parser@8.59.0(eslint@10.2.1(jiti@2.6.1))(typescript@6.0.3))(eslint@10.2.1(jiti@2.6.1))(typescript@6.0.3) + '@typescript-eslint/parser': 8.59.0(eslint@10.2.1(jiti@2.6.1))(typescript@6.0.3) + '@typescript-eslint/typescript-estree': 8.59.0(typescript@6.0.3) + '@typescript-eslint/utils': 8.59.0(eslint@10.2.1(jiti@2.6.1))(typescript@6.0.3) + eslint: 10.2.1(jiti@2.6.1) + typescript: 6.0.3 transitivePeerDependencies: - supports-color - typescript@5.9.3: {} + typescript@6.0.3: {} + + unbash@3.0.0: {} unbox-primitive@1.1.0: dependencies: @@ -7206,9 +5998,7 @@ snapshots: undici-types@7.16.0: {} - undici@7.22.0: {} - - unicorn-magic@0.3.0: {} + undici@7.25.0: {} unpipe@1.0.0: {} @@ -7236,9 +6026,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 +6036,55 @@ snapshots: dependencies: punycode: 2.3.1 - url@0.11.4: + use-sync-external-store@1.6.0(react@19.2.5): dependencies: - punycode: 1.4.1 - qs: 6.15.0 + react: 19.2.5 - 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: {} + uuid@14.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)): + vite@8.0.10(@types/node@24.12.2)(jiti@2.6.1)(yaml@2.8.3): 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: - dependencies: - escape-string-regexp: 5.0.0 - globby: 14.1.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: - esbuild: 0.27.3 - fdir: 6.5.0(picomatch@4.0.3) - picomatch: 4.0.3 - postcss: 8.5.6 - rollup: 4.59.0 - tinyglobby: 0.2.15 + lightningcss: 1.32.0 + picomatch: 4.0.4 + postcss: 8.5.10 + rolldown: 1.0.0-rc.17 + tinyglobby: 0.2.16 optionalDependencies: - '@types/node': 25.2.3 + '@types/node': 24.12.2 fsevents: 2.3.3 jiti: 2.6.1 - lightningcss: 1.31.1 - terser: 5.44.1 - yaml: 2.8.2 + yaml: 2.8.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): + vitest@4.1.4(@types/node@24.12.2)(@vitest/coverage-v8@4.1.4)(jsdom@29.0.2)(vite@8.0.10(@types/node@24.12.2)(jiti@2.6.1)(yaml@2.8.3)): 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 + '@vitest/expect': 4.1.4 + '@vitest/mocker': 4.1.4(vite@8.0.10(@types/node@24.12.2)(jiti@2.6.1)(yaml@2.8.3)) + '@vitest/pretty-format': 4.1.4 + '@vitest/runner': 4.1.4 + '@vitest/snapshot': 4.1.4 + '@vitest/spy': 4.1.4 + '@vitest/utils': 4.1.4 + 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.1.0 tinybench: 2.9.0 - tinyexec: 1.0.2 - 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) + tinyexec: 1.1.1 + tinyglobby: 0.2.16 + tinyrainbow: 3.1.0 + vite: 8.0.10(@types/node@24.12.2)(jiti@2.6.1)(yaml@2.8.3) why-is-node-running: 2.3.0 optionalDependencies: - '@types/node': 25.2.3 - happy-dom: 20.6.1 - jsdom: 28.1.0 + '@types/node': 24.12.2 + '@vitest/coverage-v8': 4.1.4(vitest@4.1.4) + jsdom: 29.0.2 transitivePeerDependencies: - - jiti - - less - - lightningcss - msw - - sass - - sass-embedded - - stylus - - sugarss - - terser - - tsx - - yaml - - vm-browserify@1.1.2: {} w3c-xmlserializer@5.0.0: dependencies: @@ -7347,14 +6094,11 @@ snapshots: webidl-conversions@8.0.1: {} - whatwg-mimetype@3.0.0: - optional: true - whatwg-mimetype@5.0.0: {} whatwg-url@16.0.1: dependencies: - '@exodus/bytes': 1.14.1 + '@exodus/bytes': 1.15.0 tr46: 6.0.0 webidl-conversions: 8.0.1 transitivePeerDependencies: @@ -7394,7 +6138,7 @@ snapshots: which-typed-array@1.1.20: dependencies: available-typed-arrays: 1.0.7 - call-bind: 1.0.8 + call-bind: 1.0.9 call-bound: 1.0.4 for-each: 0.3.5 get-proto: 1.0.1 @@ -7416,13 +6160,10 @@ 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: - optional: true - xml-name-validator@5.0.0: {} xmlchars@2.2.0: {} @@ -7431,7 +6172,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 6e443191..7a6da682 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -1,10 +1,11 @@ packages: - apps/* + - packages/* catalog: - "@vitest/coverage-v8": 4.0.18 - vite: 7.3.1 - vitest: 4.0.18 + "@vitest/coverage-v8": 4.1.4 + vite: 8.0.10 + vitest: 4.1.4 minimumReleaseAge: 10080 diff --git a/tsconfig.base.json b/tsconfig.base.json index 39e84b3a..00912579 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 2a537f3a..3dcd0840 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 00000000..48832af8 --- /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 35ebfe01..410356e2 100755 --- a/vercel-preparation.sh +++ b/vercel-preparation.sh @@ -12,5 +12,8 @@ cp -RP apps/backend/. apps/backend-copy/ # `shopt` includes dot-files in the `mv` operation (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/ +rm -rf apps/deployment +pnpm install +pnpm build:frontend +mkdir -p apps/backend/.vercel/output/static/frontend/ +cp -RP apps/frontend/build/. apps/backend/.vercel/output/static/frontend/ diff --git a/vitest.config.coverage.ts b/vitest.config.coverage.ts new file mode 100644 index 00000000..22fc386c --- /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 00000000..c3c14e5b --- /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", + ], + }, +});