From 91181f21709a70f689f33c5e5d3d166fa6c1721e Mon Sep 17 00:00:00 2001 From: Davide Grilli Date: Fri, 11 Sep 2026 11:41:52 +0200 Subject: [PATCH] Add unit, integration and e2e test suites MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit tests/{unit,integration,e2e}/, at the repo root (not under frontend/, since e2e exercises the frontend and PocketBase together) — all against real code rather than mocks: - unit/: Vitest (nuxt environment) — pure functions in server/utils and shared/utils, plus components/composables via @nuxt/test-utils/runtime's mountSuspended. - integration/: Vitest (node environment) — @nuxt/test-utils/e2e's setup() builds and runs the real Nitro server against a real ephemeral PocketBase instance, exercising the actual /content/* and /admin routes end to end. - e2e/: Playwright, a real browser against the built app and another ephemeral PocketBase, covering the user-facing flows (home, blog archive, article detail incl. JSON-LD, category filter, 404s, the /admin redirect). The Vitest/Playwright tooling (and node_modules) stays in frontend/, the repo's only npm project; its configs just point at ../tests/. Playwright resolves packages from the node_modules nearest each spec file, so `npm run test:e2e` first symlinks tests/node_modules to frontend/node_modules (pretest:e2e, idempotent, gitignored). tests/support/pocketbase.ts is the shared piece behind integration and e2e: it downloads the same pinned PocketBase binary pocketbase/Dockerfile uses, starts it against the real pocketbase/pb_migrations/ (not a copy), and seeds it from tests/support/fixtures.ts — so these tests run against the exact schema and API rules that ship to production, catching the class of bug that only shows up when PocketBase actually enforces them (e.g. a wrong filter/fields query string silently returning nothing, or too much). CLAUDE.md and docs/frontend.md document the new npm scripts and the single-test commands for each layer. --- .gitignore | 18 +- CLAUDE.md | 27 +- docs/frontend.md | 26 + frontend/package-lock.json | 1104 +++++++++++++++++++++++-- frontend/package.json | 13 +- frontend/playwright.config.ts | 35 + frontend/vitest.integration.config.ts | 13 + frontend/vitest.unit.config.ts | 8 + tests/e2e/admin-redirect.spec.ts | 11 + tests/e2e/article-detail.spec.ts | 24 + tests/e2e/blog.spec.ts | 9 + tests/e2e/category.spec.ts | 14 + tests/e2e/global-setup.ts | 18 + tests/e2e/home.spec.ts | 11 + tests/integration/api.test.ts | 91 ++ tests/package.json | 3 + tests/support/fixtures.ts | 47 ++ tests/support/pocketbase.ts | 165 ++++ tests/unit/ArticleCard.test.ts | 45 + tests/unit/format.utils.test.ts | 37 + tests/unit/pocketbase.utils.test.ts | 66 ++ tests/unit/queries.utils.test.ts | 36 + tests/unit/useMediaUrl.test.ts | 21 + 23 files changed, 1767 insertions(+), 75 deletions(-) create mode 100644 frontend/playwright.config.ts create mode 100644 frontend/vitest.integration.config.ts create mode 100644 frontend/vitest.unit.config.ts create mode 100644 tests/e2e/admin-redirect.spec.ts create mode 100644 tests/e2e/article-detail.spec.ts create mode 100644 tests/e2e/blog.spec.ts create mode 100644 tests/e2e/category.spec.ts create mode 100644 tests/e2e/global-setup.ts create mode 100644 tests/e2e/home.spec.ts create mode 100644 tests/integration/api.test.ts create mode 100644 tests/package.json create mode 100644 tests/support/fixtures.ts create mode 100644 tests/support/pocketbase.ts create mode 100644 tests/unit/ArticleCard.test.ts create mode 100644 tests/unit/format.utils.test.ts create mode 100644 tests/unit/pocketbase.utils.test.ts create mode 100644 tests/unit/queries.utils.test.ts create mode 100644 tests/unit/useMediaUrl.test.ts diff --git a/.gitignore b/.gitignore index 8e68202..e32f940 100644 --- a/.gitignore +++ b/.gitignore @@ -2,6 +2,10 @@ node_modules/ .pnp/ .pnp.js +# symlink to frontend/node_modules (created by `npm run pretest:e2e`); the +# trailing-slash pattern above doesn't match a symlink even when it points to +# a directory +tests/node_modules # env / secrets .env @@ -21,14 +25,12 @@ frontend/dist/ .nitro/ .cache/ -# strapi -cms/.strapi/ -cms/dist/ -cms/build/ -cms/.tmp/ -cms/public/uploads/* -!cms/public/uploads/.gitkeep -cms/.strapi-updater.json +# playwright +frontend/test-results/ +frontend/playwright-report/ + +# nuxt module-setup marker (regenerated, not meaningful project config) +frontend/.nuxtrc # caddy runtime caddy/data/ diff --git a/CLAUDE.md b/CLAUDE.md index dce63e9..898d275 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -10,8 +10,8 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co ## Stato -Struttura, content type, pagine blog, Docker e Caddy sono in piedi. Restano da fare: -sitemap e `robots.txt` dinamici, ricerca, e i test (nessun framework ancora configurato). +Struttura, content type, pagine blog, Docker, Caddy e i test (unit/integration/e2e) sono in +piedi. Restano da fare: sitemap e `robots.txt` dinamici, ricerca. ## Architettura @@ -67,6 +67,16 @@ npm run build # build produzione npm run typecheck # nuxi typecheck — obbligatorio prima di dichiarare fatto npm run lint +npm run test:unit # Vitest, ambiente Nuxt: funzioni pure + componenti/composable +npm run test:integration # Vitest, server Nitro reale + PocketBase effimero reale +npm run test:e2e # Playwright, browser reale contro build + PocketBase effimero reale +npm run test # i tre, in sequenza + +# singolo test: +npx vitest run --config vitest.unit.config.ts tests/unit/queries.utils.test.ts +npx vitest run --config vitest.integration.config.ts -t "lists only published articles" +npx playwright test tests/e2e/home.spec.ts + # pocketbase/ (nessun npm script: binario singolo, le migration si applicano da sole all'avvio) docker compose -f docker-compose.yml -f docker-compose.dev.yml up -d --build pocketbase frontend @@ -81,8 +91,17 @@ docker compose -f docker-compose.yml -f docker-compose.dev.yml up -d --build poc `docker-compose.dev.yml` va passato **sempre esplicitamente**: non è un `override.yml` proprio per non finire per sbaglio in produzione esponendo le porte. -Nessun test framework è ancora configurato. Se ne aggiungi uno, documenta qui il comando per -lanciare **un singolo test**. +`tests/` sta nella root del repo (non sotto `frontend/`), perché l'e2e esercita frontend e +PocketBase insieme — gli strumenti (config Vitest/Playwright, `node_modules`) restano comunque in +`frontend/`, l'unico progetto npm del repo, e puntano a `../tests/`. Playwright risolve i pacchetti +dal `node_modules` più vicino al file di test: per questo `npm run test:e2e` crea prima (script +`pretest:e2e`, idempotente) il symlink `tests/node_modules → ../frontend/node_modules`. + +I test di integration/e2e avviano un vero binario PocketBase effimero (scaricato una volta in +`.cache/pocketbase/` nella root, versione allineata a `pocketbase/Dockerfile`) contro le vere +migration in `pocketbase/pb_migrations/` — niente PocketBase mockato. `npm run test:e2e` richiede +Chromium installato una tantum: `npx playwright install chromium` (senza `--with-deps`, che +richiede `sudo`; se mancano librerie di sistema per il browser, installarle a parte). ## Modelli di contenuto diff --git a/docs/frontend.md b/docs/frontend.md index 1d05b88..52b36f0 100644 --- a/docs/frontend.md +++ b/docs/frontend.md @@ -87,3 +87,29 @@ JSON-LD `BlogPosting` structured data, with the article body already present in HTML (no client-only content). Accessibility requirements (focus visibility, labeled inputs, meaningful alt text, descriptive links, full keyboard navigation) apply across all pages/components — see `CLAUDE.md`. + +## Tests (`tests/`, repo root) + +Kept at the repo root, not under `frontend/`, since e2e exercises the frontend *and* PocketBase +together — but the tooling (Vitest/Playwright configs, `node_modules`) still lives in `frontend/`, +the only npm project in the repo; the configs there just point `include`/`testDir` at `../tests/`. + +Three layers, all against real code (no mocked PocketBase): + +- `tests/unit/` — Vitest, `environment: 'nuxt'` (`frontend/vitest.unit.config.ts`). Pure functions + in `frontend/server/utils/*` and `frontend/shared/utils/*` (imported directly, not via + auto-import) plus components and composables via `@nuxt/test-utils/runtime`'s `mountSuspended`. +- `tests/integration/` — Vitest, node environment (`frontend/vitest.integration.config.ts`). + `@nuxt/test-utils/e2e`'s `setup()` builds and runs the real Nitro server, pointed (via + `nuxtConfig.runtimeConfig` overrides) at a real ephemeral PocketBase instance from + `tests/support/pocketbase.ts`. Exercises the actual `/content/*` and `/admin` routes. +- `tests/e2e/` — Playwright (`frontend/playwright.config.ts`), a real browser against the built + app (`node .output/server/index.mjs`) and another ephemeral PocketBase on a fixed port (needed + so the Nuxt server's env and Playwright's `globalSetup` can reference each other without an + async hand-off — see the comments in `playwright.config.ts`/`tests/e2e/global-setup.ts`). + +`tests/support/pocketbase.ts` is the shared piece: it downloads the same pinned PocketBase binary +`pocketbase/Dockerfile` uses (cached in `.cache/pocketbase/` at the repo root, gitignored), starts +it against the real `pocketbase/pb_migrations/`, and seeds it from `tests/support/fixtures.ts` — +so integration and e2e tests run against the exact schema/rules that ship to production, not a +hand-maintained approximation of them. diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 0651bea..c0321fc 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -7,8 +7,6 @@ "name": "frontend", "hasInstallScript": true, "dependencies": { - "@nuxt/eslint": "^1.17.0", - "eslint": "^10.10.0", "marked": "^18.0.11", "nuxt": "^4.5.2", "simple-icons": "^16.28.0", @@ -16,7 +14,15 @@ "vue-router": "^5.2.0" }, "devDependencies": { + "@nuxt/eslint": "^1.17.0", + "@nuxt/test-utils": "^4.3.2", + "@playwright/test": "^1.63.0", + "@vitest/coverage-v8": "^5.0.0", + "@vue/test-utils": "^2.5.0", + "eslint": "^10.10.0", + "happy-dom": "^20.14.3", "typescript": "^5.9.3", + "vitest": "^5.0.0", "vue-tsc": "^3.3.11" } }, @@ -24,6 +30,7 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/@antfu/install-pkg/-/install-pkg-2.0.1.tgz", "integrity": "sha512-iCKVQcIC0e3oDxEfs3SHQGW+ovhBMZmS1TE+bTk50rVyMCBmCfClv7Qi3HQKlumYwvjb/iIMeWCW2i67q6kFfQ==", + "dev": true, "license": "MIT", "dependencies": { "package-manager-detector": "^1.7.0", @@ -37,6 +44,7 @@ "version": "14.2.1", "resolved": "https://registry.npmjs.org/@apidevtools/json-schema-ref-parser/-/json-schema-ref-parser-14.2.1.tgz", "integrity": "sha512-HmdFw9CDYqM6B25pqGBpNeLCKvGPlIx1EbLrVL0zPvj50CJQUHyBNBw45Muk0kEIkogo1VZvOKHajdMuAzSxRg==", + "dev": true, "license": "MIT", "dependencies": { "js-yaml": "^4.1.0" @@ -454,6 +462,16 @@ "node": ">=6.9.0" } }, + "node_modules/@bcoe/v8-coverage": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-1.0.2.tgz", + "integrity": "sha512-6zABk/ECA/QYSCQ1NGiVwwbQerUCZ+TQbp64Q3AgmfNvurHH0j8TtXa1qbShXA6qqkpAj4V5W8pP6mLe1mcMqA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, "node_modules/@bomb.sh/tab": { "version": "0.0.19", "resolved": "https://registry.npmjs.org/@bomb.sh/tab/-/tab-0.0.19.tgz", @@ -483,6 +501,7 @@ "version": "2.2.0", "resolved": "https://registry.npmjs.org/@cacheable/memory/-/memory-2.2.0.tgz", "integrity": "sha512-CTLKqLItRCEixEAewD3/j9DB3/o96gpTPD4eJ1v+DGOlxZRZncRQkGYqqnAGCscYd6RNeXfGeiuCphsPtqyIfQ==", + "devOptional": true, "license": "MIT", "dependencies": { "@cacheable/utils": "^2.5.0", @@ -495,6 +514,7 @@ "version": "2.5.0", "resolved": "https://registry.npmjs.org/@cacheable/utils/-/utils-2.5.0.tgz", "integrity": "sha512-buipgOVDkkPXNR5+xBpDw7Zk2n1EvU7qBJCNUcL7rhQ//kfpOXPAvQ511Os0vpLYJ1pZnvudNytkQt2hst3wqA==", + "devOptional": true, "license": "MIT", "dependencies": { "hashery": "^1.5.1", @@ -571,6 +591,7 @@ "version": "1.10.0", "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.10.0.tgz", "integrity": "sha512-yq6OkJ4p82CAfPl0u9mQebQHKPJkY7WrIuk205cTYnYe+k2Z8YBh11FrbRG/H6ihirqcacOgl2BIO8oyMQLeXw==", + "dev": true, "license": "MIT", "optional": true, "dependencies": { @@ -582,6 +603,7 @@ "version": "1.10.0", "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.10.0.tgz", "integrity": "sha512-ewvYlk86xUoGI0zQRNq/mC+16R1QeDlKQy21Ki3oSYXNgLb45GV1P6A0M+/s6nyCuNDqe5VpaY84BzXGwVbwFA==", + "dev": true, "license": "MIT", "optional": true, "dependencies": { @@ -592,6 +614,7 @@ "version": "1.2.1", "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.2.1.tgz", "integrity": "sha512-uTII7OYF+/Mes/MrcIOYp5yOtSMLBWSIoLPpcgwipoiKbli6k322tcoFsxoIIxPDqW01SQGAgko4EzZi2BNv2w==", + "dev": true, "license": "MIT", "optional": true, "dependencies": { @@ -602,6 +625,7 @@ "version": "0.91.0", "resolved": "https://registry.npmjs.org/@es-joy/jsdoccomment/-/jsdoccomment-0.91.0.tgz", "integrity": "sha512-vgqlMGNNhZxwDYbUNIHj3Hskb4R28iqdXx90ufHyt/NeuTQkeqjTDslAs9I0/GCAfbxP5BpH5WsL1R1fht5Lxg==", + "dev": true, "license": "MIT", "dependencies": { "@types/estree": "^1.0.9", @@ -618,6 +642,7 @@ "version": "1.4.7", "resolved": "https://registry.npmjs.org/comment-parser/-/comment-parser-1.4.7.tgz", "integrity": "sha512-0h+uSNtQGW3D98eQt3jJ8L06Fves8hncB4V/PKdw/Qb8Hnk19VaKuTr55UNRYiSoVa7WwrFls+rh3ux9agmkeQ==", + "dev": true, "license": "MIT", "engines": { "node": ">= 12.0.0" @@ -627,6 +652,7 @@ "version": "1.2.0", "resolved": "https://registry.npmjs.org/@es-joy/resolve.exports/-/resolve.exports-1.2.0.tgz", "integrity": "sha512-Q9hjxWI5xBM+qW2enxfe8wDKdFWMfd0Z29k5ZJnuBqD/CasY5Zryj09aCA6owbGATWz+39p5uIdaHXpopOcG8g==", + "dev": true, "license": "MIT", "engines": { "node": ">=10" @@ -1052,6 +1078,7 @@ "version": "4.10.1", "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.10.1.tgz", "integrity": "sha512-cuadcxVFE8sDK6iWJbs8Sn0av2Nrh2QSGQhVlBW9AaAHqHwjWsZHT8LJ4hFGPh7ASBV2deFdM7H/DPjulmh8rg==", + "devOptional": true, "license": "MIT", "dependencies": { "eslint-visitor-keys": "^3.4.3" @@ -1070,6 +1097,7 @@ "version": "3.4.3", "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", + "devOptional": true, "license": "Apache-2.0", "engines": { "node": "^12.22.0 || ^14.17.0 || >=16.0.0" @@ -1082,6 +1110,7 @@ "version": "4.12.2", "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.2.tgz", "integrity": "sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==", + "devOptional": true, "license": "MIT", "engines": { "node": "^12.0.0 || ^14.0.0 || >=16.0.0" @@ -1091,6 +1120,7 @@ "version": "2.1.1", "resolved": "https://registry.npmjs.org/@eslint/compat/-/compat-2.1.1.tgz", "integrity": "sha512-rMcy8GSrwNzcISX/BlTDY/GLB4eCopEuy9woIls3To+15OLxykZrxxq+WUcylCPCQ6F4MujjBM1DX5V1aqI3Vw==", + "dev": true, "license": "Apache-2.0", "dependencies": { "@eslint/core": "^1.2.1" @@ -1111,6 +1141,7 @@ "version": "0.23.5", "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.23.5.tgz", "integrity": "sha512-Y3kKLvC1dvTOT+oGlqNQ1XLqK6D1HU2YXPc52NmAlJZbMMWDzGYXMiPRJ8TYD39muD/OTjlZmNJ4ib7dvSrMBA==", + "devOptional": true, "license": "Apache-2.0", "dependencies": { "@eslint/object-schema": "^3.0.5", @@ -1125,6 +1156,7 @@ "version": "0.7.0", "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.7.0.tgz", "integrity": "sha512-DObd/KKUsU+FaFv4PLxSRenpXfQWmPXXP3pPZ6/K1PCrMu2vQpMDMuQe/BqYeoLcz8ro0bVDF1RxOJgfVEdhUw==", + "devOptional": true, "license": "Apache-2.0", "dependencies": { "@eslint/core": "^1.2.1" @@ -1137,6 +1169,7 @@ "version": "3.4.1", "resolved": "https://registry.npmjs.org/@eslint/config-inspector/-/config-inspector-3.4.1.tgz", "integrity": "sha512-E5pAF/gZR2yLIMG0N5EVhhFlhJ1K9AALgz+mnaRRm91ACvG1M0Zz6FOAWBylLhTtqpCLrDZqMfZYoFp04bHwJQ==", + "dev": true, "license": "Apache-2.0", "dependencies": { "ansis": "^4.3.1", @@ -1164,6 +1197,7 @@ "version": "7.0.0", "resolved": "https://registry.npmjs.org/cac/-/cac-7.0.0.tgz", "integrity": "sha512-tixWYgm5ZoOD+3g6UTea91eow5z6AAHaho3g0V9CNSNb45gM8SmflpAc+GRd1InC4AqN/07Unrgp56Y94N9hJQ==", + "dev": true, "license": "MIT", "engines": { "node": ">=20.19.0" @@ -1173,6 +1207,7 @@ "version": "0.4.12", "resolved": "https://registry.npmjs.org/crossws/-/crossws-0.4.12.tgz", "integrity": "sha512-aypfsr6t0uNvkqaZc6zvBfXzC6pLI0/sIulpkV6RwCVtZqG5ebBzv4weImKK0VNCj91Wl9F5j7p5WU4MNrybng==", + "dev": true, "license": "MIT", "peerDependencies": { "srvx": ">=0.11.5" @@ -1187,6 +1222,7 @@ "version": "0.9.18", "resolved": "https://registry.npmjs.org/devframe/-/devframe-0.9.18.tgz", "integrity": "sha512-SjvL9/MiqaG3m2bpfAb8fMsX6ALcyqFNIZ3VYjhZ/BZxSqkTknt3U6YcQTEAZOpFvGv+MVk6OeWkRW0IhzVxAg==", + "dev": true, "license": "MIT", "dependencies": { "@modelcontextprotocol/server": "^2.0.0", @@ -1219,6 +1255,7 @@ "version": "2.0.1-rc.31", "resolved": "https://registry.npmjs.org/h3/-/h3-2.0.1-rc.31.tgz", "integrity": "sha512-AG7qZzF99a0BSYoXT3evJgIhwSIUKow7R+hwkLRZS06WJ9nbSb7QXUDgs1ByBjp6svTvl++N/GKA7I9YPz9cQQ==", + "dev": true, "license": "MIT", "dependencies": { "rou3": "^0.9.2", @@ -1247,6 +1284,7 @@ "version": "1.0.4", "resolved": "https://registry.npmjs.org/srvx/-/srvx-1.0.4.tgz", "integrity": "sha512-eZmYaxUfZSo7/8m8UdsHRJNmTLSCTPPom9d7a60vMmuVeZvwhbvflRR+00/CxTCjMOFa5+H8tgBeNDVZ+w7AAQ==", + "dev": true, "license": "MIT", "bin": { "srvx": "bin/srvx.mjs" @@ -1259,6 +1297,7 @@ "version": "1.2.1", "resolved": "https://registry.npmjs.org/@eslint/core/-/core-1.2.1.tgz", "integrity": "sha512-MwcE1P+AZ4C6DWlpin/OmOA54mmIZ/+xZuJiQd4SyB29oAJjN30UW9wkKNptW2ctp4cEsvhlLY/CsQ1uoHDloQ==", + "devOptional": true, "license": "Apache-2.0", "dependencies": { "@types/json-schema": "^7.0.15" @@ -1271,6 +1310,7 @@ "version": "4.1.0", "resolved": "https://registry.npmjs.org/@eslint/css-tree/-/css-tree-4.1.0.tgz", "integrity": "sha512-cg0ohyrAG3swyGqt8t1K/OK97DqBw/ftDvlvyY1fmEst5B40UOmsimwLENq74z2dyw5CDM+3zJIW+CV2nFNDdA==", + "dev": true, "license": "MIT", "dependencies": { "mdn-data": "2.34.0", @@ -1284,12 +1324,14 @@ "version": "2.34.0", "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.34.0.tgz", "integrity": "sha512-OgIlLv0NxJKVW4GTSAoEgpRGd4F2XCqGinK0MsMlBCCS/Zcm2/LsbercNWNA7PeMMcjl75NnI97eqyo7zkdxWA==", + "dev": true, "license": "CC0-1.0" }, "node_modules/@eslint/js": { "version": "10.0.1", "resolved": "https://registry.npmjs.org/@eslint/js/-/js-10.0.1.tgz", "integrity": "sha512-zeR9k5pd4gxjZ0abRoIaxdc7I3nDktoXZk2qOv9gCNWx3mVwEn32VRhyLaRsDiJjTs0xq/T8mfPtyuXu7GWBcA==", + "dev": true, "license": "MIT", "engines": { "node": "^20.19.0 || ^22.13.0 || >=24" @@ -1310,6 +1352,7 @@ "version": "3.0.5", "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-3.0.5.tgz", "integrity": "sha512-vqTaUEgxzm+YDSdElad6PiRoX4t8VGDjCtt05zn4nU810UIx/uNEV7/lZJ6KwFThKZOzOxzXy48da+No7HZaMw==", + "devOptional": true, "license": "Apache-2.0", "engines": { "node": "^20.19.0 || ^22.13.0 || >=24" @@ -1319,6 +1362,7 @@ "version": "0.7.3", "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.7.3.tgz", "integrity": "sha512-IkO+/KEUvwbVpiURZg+P7zF74z5Jxe0UgJxVni+RtoHQ6IZieXaO02kmadomap/q+l6bc/jdPGGqTjhuZnuz1Q==", + "devOptional": true, "license": "Apache-2.0", "dependencies": { "@eslint/core": "^1.2.1", @@ -1332,6 +1376,7 @@ "version": "0.19.2", "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.2.tgz", "integrity": "sha512-UhXNm+CFMWcbChXywFwkmhqjs3PRCmcSa/hfBgLIb7oQ5HNb1wS0icWsGtSAUNgefHeI+eBrA8I1fxmbHsGdvA==", + "devOptional": true, "license": "Apache-2.0", "dependencies": { "@humanfs/types": "^0.15.0" @@ -1344,6 +1389,7 @@ "version": "0.16.8", "resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.8.tgz", "integrity": "sha512-gE1eQNZ3R++kTzFUpdGlpmy8kDZD/MLyHqDwqjkVQI0JMdI1D51sy1H958PNXYkM2rAac7e5/CnIKZrHtPh3BQ==", + "devOptional": true, "license": "Apache-2.0", "dependencies": { "@humanfs/core": "^0.19.2", @@ -1358,6 +1404,7 @@ "version": "0.15.0", "resolved": "https://registry.npmjs.org/@humanfs/types/-/types-0.15.0.tgz", "integrity": "sha512-ZZ1w0aoQkwuUuC7Yf+7sdeaNfqQiiLcSRbfI08oAxqLtpXQr9AIVX7Ay7HLDuiLYAaFPu8oBYNq/QIi9URHJ3Q==", + "devOptional": true, "license": "Apache-2.0", "engines": { "node": ">=18.18.0" @@ -1367,6 +1414,7 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", + "devOptional": true, "license": "Apache-2.0", "engines": { "node": ">=12.22" @@ -1380,6 +1428,7 @@ "version": "0.4.3", "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.3.tgz", "integrity": "sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==", + "devOptional": true, "license": "Apache-2.0", "engines": { "node": ">=18.18" @@ -1504,9 +1553,9 @@ } }, "node_modules/@jridgewell/sourcemap-codec": { - "version": "1.5.5", - "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", - "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.6.0.tgz", + "integrity": "sha512-T7jf+5zgsZHwNJ4lvQ7/aezbyk0nNX+zJVWpmHA7VYsEx7a7qr5Rg5IbtJFqkgze5Y2sruq1RUY8Q837Od7iFw==", "license": "MIT" }, "node_modules/@jridgewell/trace-mapping": { @@ -1523,6 +1572,7 @@ "version": "1.3.1", "resolved": "https://registry.npmjs.org/@keyv/bigmap/-/bigmap-1.3.1.tgz", "integrity": "sha512-WbzE9sdmQtKy8vrNPa9BRnwZh5UF4s1KTmSK0KUVLo3eff5BlQNNWDnFOouNpKfPKDnms9xynJjsMYjMaT/aFQ==", + "devOptional": true, "license": "MIT", "dependencies": { "hashery": "^1.4.0", @@ -1539,6 +1589,7 @@ "version": "1.1.1", "resolved": "https://registry.npmjs.org/@keyv/serialize/-/serialize-1.1.1.tgz", "integrity": "sha512-dXn3FZhPv0US+7dtJsIi2R+c7qWYiReoEh5zUntWCf4oSpMNib8FDhSoed6m3QyZdx5hK7iLFkYk3rNxwt8vTA==", + "devOptional": true, "license": "MIT" }, "node_modules/@kwsites/file-exists": { @@ -1581,6 +1632,7 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/@modelcontextprotocol/core/-/core-2.0.0.tgz", "integrity": "sha512-pJCEwGG7Lfr/+PQp9ZTwKXNeO5wzbfKL7H3MYpCorM4oFBoQrdjnBgEoqG+RjhsvS1FKrDbKux+M1HhlnGWqcA==", + "dev": true, "license": "MIT", "dependencies": { "zod": "^4.2.0" @@ -1593,6 +1645,7 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/@modelcontextprotocol/server/-/server-2.0.0.tgz", "integrity": "sha512-YhHWdHfpFMQfd0prsEnxKeS3Qz3ytIGmsS0sth4KDjnacIT7hxk6hXHkJ9KysxlkvTM+WZAtQbbcUhdoP4Hvtw==", + "dev": true, "license": "MIT", "dependencies": { "@modelcontextprotocol/core": "2.0.0", @@ -1625,6 +1678,7 @@ "version": "1.2.4", "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-1.2.4.tgz", "integrity": "sha512-AJxoUD2/15ESHbvpcyjU274nsAPLuOtPHCk0vKJM5pj//Fg/B1FXNWjPnXTT9PymCYYiHo4zPj0ZomXBKhoy7g==", + "dev": true, "license": "MIT", "optional": true, "dependencies": { @@ -1825,6 +1879,7 @@ "version": "1.17.0", "resolved": "https://registry.npmjs.org/@nuxt/eslint/-/eslint-1.17.0.tgz", "integrity": "sha512-54aD2xJvI2QJnHvQwN22hEgE/izF5ez+diPE7yLCJskKQ0tqPrNJCcRjBtuFQmQMQjTfHESsfuFdLfwMGUz8bw==", + "dev": true, "license": "MIT", "dependencies": { "@eslint/config-inspector": "^3.2.0", @@ -1859,6 +1914,7 @@ "version": "1.17.0", "resolved": "https://registry.npmjs.org/@nuxt/eslint-config/-/eslint-config-1.17.0.tgz", "integrity": "sha512-5lHecnGvi7RxGsR3Amvnl47bxb3F4wFD9KQgbbmhd6prz3OAAbgQio9EK88tPyNHt7+dxIFFOnCH6TJNl/QbqQ==", + "dev": true, "license": "MIT", "dependencies": { "@antfu/install-pkg": "^2.0.1", @@ -1897,6 +1953,7 @@ "version": "1.17.0", "resolved": "https://registry.npmjs.org/@nuxt/eslint-plugin/-/eslint-plugin-1.17.0.tgz", "integrity": "sha512-h/fn4K09tA5tvdLj9Zlu5mb3TOVO2zLAetCsfJ1/LLVm2XWtgDVhfSB+Sm93cF1rCGxahmHZd4RJz2nAsWTDvg==", + "dev": true, "license": "MIT", "dependencies": { "@typescript-eslint/types": "^8.66.0", @@ -2039,6 +2096,199 @@ "integrity": "sha512-zpYTCs2byOuft65vI3z43Dd6iSdFbOZZLb9/d21aCpx2rGastVU9dOCv0lu4ykc1Ur1anAYjDi3SUvR0vq50JA==", "license": "MIT" }, + "node_modules/@nuxt/test-utils": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/@nuxt/test-utils/-/test-utils-4.3.2.tgz", + "integrity": "sha512-RgBgYN/CyJYWdHzGA8+bznK7bfqpdCUTkA6WGylYuSdt51l6w/jbz/2PyGuGqFkta4H7KQifWNIeFVjW5x66tg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@clack/prompts": "1.7.0", + "@nuxt/devtools-kit": "^2.7.0", + "@nuxt/kit": "^3.21.11", + "c12": "^3.3.4", + "consola": "^3.4.2", + "defu": "^6.1.7", + "destr": "^2.0.5", + "estree-walker": "^3.0.3", + "exsolve": "^1.1.1", + "fake-indexeddb": "^6.2.5", + "get-port-please": "^3.2.0", + "h3": "^1.15.11", + "local-pkg": "^1.2.1", + "magic-string": "^1.2.3", + "node-fetch-native": "^1.6.7", + "node-mock-http": "^1.0.5", + "nypm": "^0.6.9", + "ofetch": "^1.5.1", + "pathe": "^2.0.3", + "perfect-debounce": "^2.1.0", + "radix3": "^1.1.2", + "scule": "^1.3.0", + "std-env": "^4.2.0", + "tinyexec": "^1.3.1", + "ufo": "^1.6.4", + "unplugin": "^3.3.0", + "vitest-environment-nuxt": "2.0.0", + "vue": "^3.5.42" + }, + "engines": { + "node": "^20.19.0 || ^22.12.0 || >=24.0.0" + }, + "peerDependencies": { + "@cucumber/cucumber": ">=11.0.0", + "@jest/globals": ">=30.0.0", + "@playwright/test": "^1.43.1", + "@testing-library/vue": "^8.0.1", + "@vue/test-utils": "^2.4.2", + "h3-next": ">=2.0.1-rc.22", + "happy-dom": ">=20.0.11", + "jsdom": ">=27.4.0", + "playwright-core": "^1.43.1", + "vitest": "^4.0.2 || ^5.0.0" + }, + "peerDependenciesMeta": { + "@cucumber/cucumber": { + "optional": true + }, + "@jest/globals": { + "optional": true + }, + "@playwright/test": { + "optional": true + }, + "@testing-library/vue": { + "optional": true + }, + "@vitest/ui": { + "optional": true + }, + "@vue/test-utils": { + "optional": true + }, + "h3-next": { + "optional": true + }, + "happy-dom": { + "optional": true + }, + "jsdom": { + "optional": true + }, + "playwright-core": { + "optional": true + }, + "vitest": { + "optional": true + } + } + }, + "node_modules/@nuxt/test-utils/node_modules/@nuxt/devtools-kit": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/@nuxt/devtools-kit/-/devtools-kit-2.7.0.tgz", + "integrity": "sha512-MIJdah6CF6YOW2GhfKnb8Sivu6HpcQheqdjOlZqShBr+1DyjtKQbAKSCAyKPaoIzZP4QOo2SmTFV6aN8jBeEIQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nuxt/kit": "^3.19.3", + "execa": "^8.0.1" + }, + "peerDependencies": { + "vite": ">=6.0" + } + }, + "node_modules/@nuxt/test-utils/node_modules/@nuxt/kit": { + "version": "3.21.11", + "resolved": "https://registry.npmjs.org/@nuxt/kit/-/kit-3.21.11.tgz", + "integrity": "sha512-0Xi3tgwN77w43Q8GCPIrvWmF1J7Peehkts44E0uKNIml9lB8WoUn8YxyUjxBv47XtVR86NWoWALAT+/IEMHJEA==", + "dev": true, + "license": "MIT", + "dependencies": { + "c12": "^3.3.4", + "consola": "^3.4.2", + "defu": "^6.1.7", + "destr": "^2.0.5", + "errx": "^0.1.2", + "exsolve": "^1.1.1", + "ignore": "^7.0.6", + "jiti": "^2.7.0", + "klona": "^2.0.6", + "knitwork": "^1.3.0", + "mlly": "^1.8.2", + "ohash": "^2.0.11", + "pathe": "^2.0.3", + "pkg-types": "^2.3.1", + "rc9": "^3.0.1", + "scule": "^1.3.0", + "semver": "^7.8.5", + "tinyglobby": "^0.2.17", + "ufo": "^1.6.4", + "unctx": "^2.5.0", + "untyped": "^2.0.0" + }, + "engines": { + "node": ">=18.12.0" + } + }, + "node_modules/@nuxt/test-utils/node_modules/estree-walker": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz", + "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0" + } + }, + "node_modules/@nuxt/test-utils/node_modules/tinyexec": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-1.3.1.tgz", + "integrity": "sha512-GCvB3aoys96IuDFBMcTB46JOR6mdMtAToqwiW8JlWhsoh1mhHi/xn9ss/Dg7N555GiJyEt2qzoG/NHCwM6h1EA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/@nuxt/test-utils/node_modules/unctx": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/unctx/-/unctx-2.5.0.tgz", + "integrity": "sha512-p+Rz9x0R7X+CYDkT+Xg8/GhpcShTlU8n+cf9OtOEf7zEQsNcCZO1dPKNRDqvUTaq+P32PMMkxWHwfrxkqfqAYg==", + "dev": true, + "license": "MIT", + "dependencies": { + "acorn": "^8.15.0", + "estree-walker": "^3.0.3", + "magic-string": "^0.30.21", + "unplugin": "^2.3.11" + } + }, + "node_modules/@nuxt/test-utils/node_modules/unctx/node_modules/magic-string": { + "version": "0.30.21", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz", + "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.5" + } + }, + "node_modules/@nuxt/test-utils/node_modules/unctx/node_modules/unplugin": { + "version": "2.3.11", + "resolved": "https://registry.npmjs.org/unplugin/-/unplugin-2.3.11.tgz", + "integrity": "sha512-5uKD0nqiYVzlmCRs01Fhs2BdkEgBS3SAVP6ndrBsuK42iC2+JHyxM05Rm9G8+5mkmRtzMZGY8Ct5+mliZxU/Ww==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/remapping": "^2.3.5", + "acorn": "^8.15.0", + "picomatch": "^4.0.3", + "webpack-virtual-modules": "^0.6.2" + }, + "engines": { + "node": ">=18.12.0" + } + }, "node_modules/@nuxt/vite-builder": { "version": "4.5.2", "resolved": "https://registry.npmjs.org/@nuxt/vite-builder/-/vite-builder-4.5.2.tgz", @@ -2101,6 +2351,13 @@ } } }, + "node_modules/@one-ini/wasm": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/@one-ini/wasm/-/wasm-0.2.1.tgz", + "integrity": "sha512-TUqERXGNTifZ9y2g3wPxQrw3HpHv/02DsW3D90T9x0hhonrL1ZqpSmNrU2XkoIq0fP1N6gZfVQzy2Fw1ZvGBNg==", + "dev": true, + "license": "MIT" + }, "node_modules/@oxc-project/types": { "version": "0.146.0", "resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.146.0.tgz", @@ -2146,6 +2403,22 @@ "node": ">=14" } }, + "node_modules/@playwright/test": { + "version": "1.63.0", + "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.63.0.tgz", + "integrity": "sha512-oxMK4vllB9RK5NQ2l1pq1IfOf2AvnEuj/vYGDj0H2nMtmtZpKtCwt/l00GEO6xjGfpBNAvjovvYdCm50dRQkpQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "playwright": "1.63.0" + }, + "bin": { + "playwright": "cli.js" + }, + "engines": { + "node": ">=20" + } + }, "node_modules/@polka/url": { "version": "1.0.0-next.29", "resolved": "https://registry.npmjs.org/@polka/url/-/url-1.0.0-next.29.tgz", @@ -3026,6 +3299,7 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/@sindresorhus/base62/-/base62-1.0.0.tgz", "integrity": "sha512-TeheYy0ILzBEI/CO55CP6zJCSdSWeRtGnHy8U8dWSUH4I68iqTsy7HkMktR4xakThc9jotkPQUXT4ITdbV7cHA==", + "dev": true, "license": "MIT", "engines": { "node": ">=18" @@ -3068,12 +3342,14 @@ "version": "1.1.0", "resolved": "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.1.0.tgz", "integrity": "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==", + "dev": true, "license": "MIT" }, "node_modules/@stylistic/eslint-plugin": { "version": "5.10.0", "resolved": "https://registry.npmjs.org/@stylistic/eslint-plugin/-/eslint-plugin-5.10.0.tgz", "integrity": "sha512-nPK52ZHvot8Ju/0A4ucSX1dcPV2/1clx0kLcH5wDmrE4naKso7TUC/voUyU1O9OTKTrR6MYip6LP0ogEMQ9jPQ==", + "dev": true, "license": "MIT", "dependencies": { "@eslint-community/eslint-utils": "^4.9.1", @@ -3094,16 +3370,36 @@ "version": "0.10.3", "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.3.tgz", "integrity": "sha512-F3fo1MYrRJYL3zER0OUOmkutjr1Vp23m7OsSgp7nq4SP6OqX6C/56XFIPAl5bt3zaBRjmW7SGz3u/6LwFpYcOg==", + "dev": true, "license": "MIT", "optional": true, "dependencies": { "tslib": "^2.4.0" } }, + "node_modules/@types/chai": { + "version": "5.2.3", + "resolved": "https://registry.npmjs.org/@types/chai/-/chai-5.2.3.tgz", + "integrity": "sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/deep-eql": "*", + "assertion-error": "^2.0.1" + } + }, + "node_modules/@types/deep-eql": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@types/deep-eql/-/deep-eql-4.0.2.tgz", + "integrity": "sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/esrecurse": { "version": "4.3.1", "resolved": "https://registry.npmjs.org/@types/esrecurse/-/esrecurse-4.3.1.tgz", "integrity": "sha512-xJBAbDifo5hpffDBuHl0Y8ywswbiAp/Wi7Y/GtAgSlZyIABppyurxVueOPE8LUQOxdlgi6Zqce7uoEpqNTeiUw==", + "devOptional": true, "license": "MIT" }, "node_modules/@types/estree": { @@ -3122,12 +3418,14 @@ "version": "7.0.15", "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", + "devOptional": true, "license": "MIT" }, "node_modules/@types/node": { "version": "26.5.1", "resolved": "https://registry.npmjs.org/@types/node/-/node-26.5.1.tgz", "integrity": "sha512-CzNm2FezW4VR/LjG6yUdiEgLE/rAQ9Slj5gCu/C2VrdcW7I0ahNZ8DRbHT7zOZ6r3ONgd/bsQIeSaoDGrd1C6g==", + "devOptional": true, "license": "MIT", "dependencies": { "undici-types": "~8.9.0" @@ -3139,10 +3437,28 @@ "integrity": "sha512-60BCwRFOZCQhDncwQdxxeOEEkbc5dIMccYLwbxsS4TUNeVECQ/pBJ0j09mrHOl/JJvpRPGwO9SvE4nR2Nb/a4Q==", "license": "MIT" }, + "node_modules/@types/whatwg-mimetype": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@types/whatwg-mimetype/-/whatwg-mimetype-3.0.2.tgz", + "integrity": "sha512-c2AKvDT8ToxLIOUlN51gTiHXflsfIFisS4pO7pDPoKouJCESkhZnEy623gwP9laCy5lnLDAw1vAzu2vM2YLOrA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/ws": { + "version": "8.18.1", + "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.18.1.tgz", + "integrity": "sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, "node_modules/@typescript-eslint/eslint-plugin": { "version": "8.70.0", "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.70.0.tgz", "integrity": "sha512-/v8HZt6RlyIZxB3ntehELOcUcfxKPVGWXnQdJuHRmzrqgF8nQypcC/oxGW+Ot4VGKDq81XugPKxx0n5PBtf9PA==", + "dev": true, "license": "MIT", "dependencies": { "@eslint-community/regexpp": "^4.12.2", @@ -3171,6 +3487,7 @@ "version": "8.70.0", "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.70.0.tgz", "integrity": "sha512-zYvrmj9Yxd63UGaXw+kdt6A0F0s0qveJyuatIM77bYC2DE4pgmg7a50u8LR7PRtXd0x+h+Tl3eXabGm06SWd3Q==", + "dev": true, "license": "MIT", "dependencies": { "@typescript-eslint/scope-manager": "8.70.0", @@ -3195,6 +3512,7 @@ "version": "8.70.0", "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.70.0.tgz", "integrity": "sha512-hFHbTNqhU9G+2eKFXCBVb1tjFT/LceiJ4+HfLO4pTpDI0KHi6iajpcFFkaSQ9gXmCh7n82A0PthaayEdN6mspQ==", + "dev": true, "license": "MIT", "dependencies": { "@typescript-eslint/tsconfig-utils": "^8.70.0", @@ -3216,6 +3534,7 @@ "version": "8.70.0", "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.70.0.tgz", "integrity": "sha512-8nP3Kwh5hlgZ4FicGvmznAmJe8UL4sdU8tLukrPaMuQmDuk4Y8xYfzu/aYZW4xT2JCgc7H/TpDI5cGlxcWJSqQ==", + "dev": true, "license": "MIT", "dependencies": { "@typescript-eslint/types": "8.70.0", @@ -3233,6 +3552,7 @@ "version": "8.70.0", "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.70.0.tgz", "integrity": "sha512-adnkeeNq9Sq1sUf4+FRVc0KdgYghzsgFpZSQVZVvY0LCuUuN0FnQgyGzCJeC4fW1cdXseBAjU2EOqUIjbNcZUw==", + "dev": true, "license": "MIT", "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -3249,6 +3569,7 @@ "version": "8.70.0", "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.70.0.tgz", "integrity": "sha512-NUMKIhYVaVIVLnRL9CRt+VVcuLgSHUCpXn4/+K8wql+vdInUzvx8BjUO1oJ7cG9shjFJKtF8F8Hh2kCh3/KBVw==", + "dev": true, "license": "MIT", "dependencies": { "@typescript-eslint/types": "8.70.0", @@ -3273,6 +3594,7 @@ "version": "8.70.0", "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.70.0.tgz", "integrity": "sha512-asTOIYhDg4zdzOScCyaytrsV3cR6B4ecPQlXw/dJIm7J/MZTtCtfVII9JD8Geh4jTCrK/Xe6cg5UevoleMcoJQ==", + "dev": true, "license": "MIT", "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -3286,6 +3608,7 @@ "version": "8.70.0", "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.70.0.tgz", "integrity": "sha512-d9NmHMPEKQ7QCLLm1jI3zmoQBwT5KwFYjXBJ9ymZfKCUU+5rmTRykKAFvH5Qn/ZCds3CEAFS9OC9M/jkl0X2bA==", + "dev": true, "license": "MIT", "dependencies": { "@typescript-eslint/project-service": "8.70.0", @@ -3313,6 +3636,7 @@ "version": "8.70.0", "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.70.0.tgz", "integrity": "sha512-oZmtKJz/4fufZ2p3+Cn3ijEojcdfR+1zYDH2xKYrEly0dR/Q/1xUPRCOlKGxod78nWlU2UnDe09GZ3TaknBFGA==", + "dev": true, "license": "MIT", "dependencies": { "@eslint-community/eslint-utils": "^4.9.1", @@ -3336,6 +3660,7 @@ "version": "8.70.0", "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.70.0.tgz", "integrity": "sha512-BoC8PiO4Hkdo0TVJh9Ntxr5MxPDI7/oFsrygN5ADelFSeXG/qgNuucIGA+L5Z6JpPTE/uRfcTWtscjbUaufepQ==", + "dev": true, "license": "MIT", "dependencies": { "@typescript-eslint/types": "8.70.0", @@ -3353,6 +3678,7 @@ "version": "5.0.1", "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-5.0.1.tgz", "integrity": "sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA==", + "dev": true, "license": "Apache-2.0", "engines": { "node": "^20.19.0 || ^22.13.0 || >=24" @@ -3447,6 +3773,7 @@ "cpu": [ "arm" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -3460,6 +3787,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -3473,6 +3801,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -3486,6 +3815,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -3499,6 +3829,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -3512,6 +3843,7 @@ "cpu": [ "arm" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -3525,6 +3857,7 @@ "cpu": [ "arm" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -3538,6 +3871,7 @@ "cpu": [ "arm64" ], + "dev": true, "libc": [ "glibc" ], @@ -3554,6 +3888,7 @@ "cpu": [ "arm64" ], + "dev": true, "libc": [ "musl" ], @@ -3570,6 +3905,7 @@ "cpu": [ "loong64" ], + "dev": true, "libc": [ "glibc" ], @@ -3586,6 +3922,7 @@ "cpu": [ "loong64" ], + "dev": true, "libc": [ "musl" ], @@ -3602,6 +3939,7 @@ "cpu": [ "ppc64" ], + "dev": true, "libc": [ "glibc" ], @@ -3618,6 +3956,7 @@ "cpu": [ "riscv64" ], + "dev": true, "libc": [ "glibc" ], @@ -3634,6 +3973,7 @@ "cpu": [ "riscv64" ], + "dev": true, "libc": [ "musl" ], @@ -3650,6 +3990,7 @@ "cpu": [ "s390x" ], + "dev": true, "libc": [ "glibc" ], @@ -3666,6 +4007,7 @@ "cpu": [ "x64" ], + "dev": true, "libc": [ "glibc" ], @@ -3682,6 +4024,7 @@ "cpu": [ "x64" ], + "dev": true, "libc": [ "musl" ], @@ -3698,6 +4041,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -3711,6 +4055,7 @@ "cpu": [ "wasm32" ], + "dev": true, "license": "MIT", "optional": true, "dependencies": { @@ -3729,6 +4074,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -3742,6 +4088,7 @@ "cpu": [ "ia32" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -3755,6 +4102,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -3823,6 +4171,106 @@ "vue": "^3.0.0" } }, + "node_modules/@vitest/coverage-v8": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/@vitest/coverage-v8/-/coverage-v8-5.0.0.tgz", + "integrity": "sha512-toMg6PZGCIa/lQNCDoASrfb1ly4hsUKXFtFYC9kD4t78o5Y6LyNJU7AENt8eHPr3quYdxaxK7hj2mnbFfUk9NA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@bcoe/v8-coverage": "^1.0.2", + "@vitest/istanbul-lib-coverage": "^1.0.0", + "@vitest/istanbul-lib-report": "^1.0.0", + "ast-v8-to-istanbul": "^1.0.5", + "magicast": "^0.5.4", + "obug": "^2.1.4", + "std-env": "^4.2.0", + "tinyrainbow": "^3.1.1" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "@vitest/browser": "5.0.0", + "vitest": "5.0.0" + }, + "peerDependenciesMeta": { + "@vitest/browser": { + "optional": true + } + } + }, + "node_modules/@vitest/istanbul-lib-coverage": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@vitest/istanbul-lib-coverage/-/istanbul-lib-coverage-1.0.1.tgz", + "integrity": "sha512-k3DJZ8LhMBK9NS4SclF1ASD3OgXEWDorbIcPTRDK0/Zae6fRvu+fJRxtFdLfHsa9Y24beCdPnoNZ4LviTNstfA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=22" + } + }, + "node_modules/@vitest/istanbul-lib-report": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@vitest/istanbul-lib-report/-/istanbul-lib-report-1.0.1.tgz", + "integrity": "sha512-1EOLRfsTMnyAr3+kEAsP4o9dhaDlGPpD7H5iLBBeq//YpNB1VIahkPhB+eRp9N2Dkfw8oySROjE3yf9XDeaIkQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/istanbul-lib-coverage": "1.0.1" + }, + "engines": { + "node": ">=22" + } + }, + "node_modules/@vitest/mocker": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-5.0.0.tgz", + "integrity": "sha512-66PGTMIiVJP3t4a5yxU9qPtf7MdTBs8jmToMvy+HVflB3Yy13WJZTtPePdvU+wjRV02SKK5doLbSA6o9pwOmiA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/trace-mapping": "0.3.31", + "@vitest/spy": "5.0.0", + "estree-walker": "^3.0.3", + "magic-string": "^1.2.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "msw": "^2.4.9", + "vite": "^6.0.0 || ^7.0.0 || ^8.0.0" + }, + "peerDependenciesMeta": { + "msw": { + "optional": true + }, + "vite": { + "optional": true + } + } + }, + "node_modules/@vitest/mocker/node_modules/estree-walker": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz", + "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0" + } + }, + "node_modules/@vitest/spy": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-5.0.0.tgz", + "integrity": "sha512-uy+luWBAPw9XfthoHi5AkfHUnuPYEESjl0p/r+meoBnU8bxg5GDQ3Ey8MjcJ6sqahkL4PFyrvfMJJBw7LbU06g==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://opencollective.com/vitest" + } + }, "node_modules/@volar/language-core": { "version": "2.4.28", "resolved": "https://registry.npmjs.org/@volar/language-core/-/language-core-2.4.28.tgz", @@ -3930,39 +4378,39 @@ } }, "node_modules/@vue/compiler-core": { - "version": "3.5.41", - "resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.5.41.tgz", - "integrity": "sha512-q0Xtv/F9w2YO/7htQhtiL+Ev2WCJbe5N2hc+XfgyKkEKqWpSxknmT8QOuGdEKNdjPq0c3F7rNpFkTo3Kfrm7pg==", + "version": "3.5.42", + "resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.5.42.tgz", + "integrity": "sha512-2Ye1ilMtKXxl8qZUrQ5j0CdgenFp/HFQmta6rfRyfEsTG69L6Wk+tWuNoHYHMx9E8tF2Slvdg1FuwDvAXdy1LQ==", "license": "MIT", "dependencies": { "@babel/parser": "^7.29.8", - "@vue/shared": "3.5.41", + "@vue/shared": "3.5.42", "entities": "^7.0.1", "estree-walker": "^2.0.2", "source-map-js": "^1.2.1" } }, "node_modules/@vue/compiler-dom": { - "version": "3.5.41", - "resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.5.41.tgz", - "integrity": "sha512-oKacVfNglLvGjnS6BXOlGL7EyG2h8X03pqXCjzotRZUaXGjbrTJUnVAQjrCqUnS+lyu31nwQjZY/d817GmCnfw==", + "version": "3.5.42", + "resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.5.42.tgz", + "integrity": "sha512-qbhQZEFmycr+ni/qyuccS4sucNN7VAbDfbkvNxWOX2VfgFm90MNs3/UhRNKoPMEIVn0F8gdlYjLPvqxHwHeQOA==", "license": "MIT", "dependencies": { - "@vue/compiler-core": "3.5.41", - "@vue/shared": "3.5.41" + "@vue/compiler-core": "3.5.42", + "@vue/shared": "3.5.42" } }, "node_modules/@vue/compiler-sfc": { - "version": "3.5.41", - "resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.5.41.tgz", - "integrity": "sha512-XJhip7R2wy6vX3knCxdZN4KracFaZUef58s1KYewqluedHIJaPIVfXoYT7MF1F8nCvv6k8bWWxDC8opMkg1VTQ==", + "version": "3.5.42", + "resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.5.42.tgz", + "integrity": "sha512-fkCAFB4okcAANGMThboWnScp/gzWjU0ZSkVnjTIiplmMDq2uq0tIB3j+xVu4rhv5rvOgBySCysudmbMd6xRRqw==", "license": "MIT", "dependencies": { "@babel/parser": "^7.29.8", - "@vue/compiler-core": "3.5.41", - "@vue/compiler-dom": "3.5.41", - "@vue/compiler-ssr": "3.5.41", - "@vue/shared": "3.5.41", + "@vue/compiler-core": "3.5.42", + "@vue/compiler-dom": "3.5.42", + "@vue/compiler-ssr": "3.5.42", + "@vue/shared": "3.5.42", "estree-walker": "^2.0.2", "magic-string": "^0.30.21", "postcss": "^8.5.19", @@ -3979,13 +4427,13 @@ } }, "node_modules/@vue/compiler-ssr": { - "version": "3.5.41", - "resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.5.41.tgz", - "integrity": "sha512-U3v5OejKEGqOI0Wy0+Sz7hGuIFZHA4LSXzrNM3IMIeDyJEBBfTpX26n3SDgToRpP2bLc9FfI2j/kSgcJ8Emq5A==", + "version": "3.5.42", + "resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.5.42.tgz", + "integrity": "sha512-xmLk3wLkbizPAiLyomjgFFosf2ys9b5Ghb+oh/k2tnvipNz8OFrQOiTcWCzyK7MpBp9KkyGtfvgfLUivbmuGYA==", "license": "MIT", "dependencies": { - "@vue/compiler-dom": "3.5.41", - "@vue/shared": "3.5.41" + "@vue/compiler-dom": "3.5.42", + "@vue/shared": "3.5.42" } }, "node_modules/@vue/devtools-api": { @@ -4060,53 +4508,74 @@ } }, "node_modules/@vue/reactivity": { - "version": "3.5.41", - "resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.5.41.tgz", - "integrity": "sha512-rznsqKM0np0x18EjzF8x88MpEhdNsffbvFbckLL5+oUKz1BxAImEmO7J1ArRYSyo6aQaVoBDp7jEkT91OOxydA==", + "version": "3.5.42", + "resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.5.42.tgz", + "integrity": "sha512-TzNNfKpb7hDxbQltwAut8VDQA5YP+BuRlxntHUuRjyKwlMvmAPbs3unhCvieijifY6vFfVBwsS7wG/C7uq+bEQ==", "license": "MIT", "dependencies": { - "@vue/shared": "3.5.41" + "@vue/shared": "3.5.42" } }, "node_modules/@vue/runtime-core": { - "version": "3.5.41", - "resolved": "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.5.41.tgz", - "integrity": "sha512-Vcry58hiAKwGen9Z1jUZE0feFsNArPCMOImYI8el48A9Idf6DuQYD0U05zZIF2Iad1hGhPSvcbBbAOhNr55fhg==", + "version": "3.5.42", + "resolved": "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.5.42.tgz", + "integrity": "sha512-9uACtuHs7vJGkm5Bp3xu4xRDLFTIYy5DgxpToVjqGIAhAEKwQfsaLvKINhM6nFVp6bZPRFGdDqd1g52MqKsotA==", "license": "MIT", "dependencies": { - "@vue/reactivity": "3.5.41", - "@vue/shared": "3.5.41" + "@vue/reactivity": "3.5.42", + "@vue/shared": "3.5.42" } }, "node_modules/@vue/runtime-dom": { - "version": "3.5.41", - "resolved": "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.5.41.tgz", - "integrity": "sha512-3vVBahVBS9+U6cmXBLyb8nE6/yYo4J/CGI9eVFs3KiMc0YHuudwKyShTD65jtJy/L9PUUxNAFu4cj4LiJ0UFbw==", + "version": "3.5.42", + "resolved": "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.5.42.tgz", + "integrity": "sha512-rsCmhiWLaRxGltLwhlCWyYkFn7WAbKRh0q17eZ1A6Dq6eqc2ACQ61IIryxz0LrsvCzHSilLA9JHovVwM8CNE2g==", "license": "MIT", "dependencies": { - "@vue/reactivity": "3.5.41", - "@vue/runtime-core": "3.5.41", - "@vue/shared": "3.5.41", + "@vue/reactivity": "3.5.42", + "@vue/runtime-core": "3.5.42", + "@vue/shared": "3.5.42", "csstype": "^3.2.3" } }, "node_modules/@vue/server-renderer": { - "version": "3.5.41", - "resolved": "https://registry.npmjs.org/@vue/server-renderer/-/server-renderer-3.5.41.tgz", - "integrity": "sha512-n6hx/pNFfbD6SuyeuMVkvqox8bwf/ET9JlA/kAz/imw8sw++wkqKe2mHX5KutjPpbKE4Z56yTHszoOjGMI9igQ==", + "version": "3.5.42", + "resolved": "https://registry.npmjs.org/@vue/server-renderer/-/server-renderer-3.5.42.tgz", + "integrity": "sha512-2++5dUyYS4gvo7xQXSECUDhB7TS0aOl5SeVfC5qSq1Jgfhjvegw1zqhwTIR3imZ+QYPJQw9gfcFvXGAjGZ7ajQ==", "license": "MIT", "dependencies": { - "@vue/compiler-ssr": "3.5.41", - "@vue/runtime-dom": "3.5.41", - "@vue/shared": "3.5.41" + "@vue/compiler-ssr": "3.5.42", + "@vue/runtime-dom": "3.5.42", + "@vue/shared": "3.5.42" } }, "node_modules/@vue/shared": { - "version": "3.5.41", - "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.5.41.tgz", - "integrity": "sha512-IOnwSCma8j+9xJT6b8H0dEYidC80NsYmNMlZxRsukYcSoGaDBohog5hDxzeUXdFeGWFA++vWvxqOmrr96VlqMA==", + "version": "3.5.42", + "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.5.42.tgz", + "integrity": "sha512-2rPxex1jQf4jvl9MOHl6YaXCPcrNqz/FstMOEh3QWY+/OME9nQTvl9WYeCwhW7AFjaR0SnngZGlp/wkR6rkI6g==", "license": "MIT" }, + "node_modules/@vue/test-utils": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/@vue/test-utils/-/test-utils-2.5.0.tgz", + "integrity": "sha512-6Clu5EKR/r6cDPYrKsu+8wenciWJJ3rhS9OEGsfDlZeZIhlJeEPGIZQHxE4lHRJCzPSq3EWMsFxQUqCvrbHQuQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "js-beautify": "^2.0.0", + "vue-component-type-helpers": "^3.0.0" + }, + "peerDependencies": { + "@vue/compiler-dom": "3.x", + "@vue/server-renderer": "3.x", + "vue": "3.x" + }, + "peerDependenciesMeta": { + "@vue/server-renderer": { + "optional": true + } + } + }, "node_modules/abbrev": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-3.0.1.tgz", @@ -4153,6 +4622,7 @@ "version": "5.3.2", "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "devOptional": true, "license": "MIT", "peerDependencies": { "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" @@ -4171,6 +4641,7 @@ "version": "6.15.0", "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.15.0.tgz", "integrity": "sha512-fgFx7Hfoq60ytK2c7DhnF8jIvzYgOMxfugjLOSMHjLIPgenqa7S7oaagATUq99mV6IYvN2tRmC0wnTYX6iPbMw==", + "devOptional": true, "license": "MIT", "dependencies": { "fast-deep-equal": "^3.1.1", @@ -4373,6 +4844,7 @@ "version": "0.0.2", "resolved": "https://registry.npmjs.org/are-docs-informative/-/are-docs-informative-0.0.2.tgz", "integrity": "sha512-ixiS0nLNNG5jNQzgZJNoUpBKdo9yTYZMGJ+QgT2jmjR7G7+QHRCc4v6LQ3NgE7EBJq+o0ams3waJwkrlBom8Ig==", + "dev": true, "license": "MIT", "engines": { "node": ">=14" @@ -4382,8 +4854,19 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true, "license": "Python-2.0" }, + "node_modules/assertion-error": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-2.0.1.tgz", + "integrity": "sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + } + }, "node_modules/ast-kit": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/ast-kit/-/ast-kit-2.2.0.tgz", @@ -4400,6 +4883,28 @@ "url": "https://github.com/sponsors/sxzz" } }, + "node_modules/ast-v8-to-istanbul": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/ast-v8-to-istanbul/-/ast-v8-to-istanbul-1.0.6.tgz", + "integrity": "sha512-fvpl29helSO2w/z7utIbrkNXILdrLwDwAMH2I/zPKlGf5244+gf+B4cyS1sANcrPY2h+hWCGSgC8N61s/+AF9A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/trace-mapping": "^0.3.31", + "estree-walker": "^3.0.3", + "js-tokens": "^10.0.0" + } + }, + "node_modules/ast-v8-to-istanbul/node_modules/estree-walker": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz", + "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0" + } + }, "node_modules/ast-walker-scope": { "version": "0.9.0", "resolved": "https://registry.npmjs.org/ast-walker-scope/-/ast-walker-scope-0.9.0.tgz", @@ -4720,10 +5225,24 @@ "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", "license": "MIT" }, + "node_modules/buffer-image-size": { + "version": "0.6.4", + "resolved": "https://registry.npmjs.org/buffer-image-size/-/buffer-image-size-0.6.4.tgz", + "integrity": "sha512-nEh+kZOPY1w+gcCMobZ6ETUp9WfibndnosbpwB1iJk/8Gt5ZF2bhS6+B6bPYz424KtwsR6Rflc3tCz1/ghX2dQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + }, + "engines": { + "node": ">=4.0" + } + }, "node_modules/builtin-modules": { "version": "5.3.0", "resolved": "https://registry.npmjs.org/builtin-modules/-/builtin-modules-5.3.0.tgz", "integrity": "sha512-hMQUl2bUFG339QygPM97E+mc8OY1IAchORZxm4a/frcYwKzozMzRVDBwHW0NjOqGElLm2O37AVQE8ikxlZHrMQ==", + "dev": true, "license": "MIT", "engines": { "node": ">=18.20" @@ -4779,6 +5298,7 @@ "version": "2.5.0", "resolved": "https://registry.npmjs.org/cacheable/-/cacheable-2.5.0.tgz", "integrity": "sha512-60cyAOytib/OzBw1JNSoSV/boK1AtHryDIjvVBk7XbN4ugfkM3+Sry7fEjNgPMGgOjuaZPAp8ruZ0Cxafwyq9g==", + "devOptional": true, "license": "MIT", "dependencies": { "@cacheable/memory": "^2.2.0", @@ -4818,10 +5338,21 @@ ], "license": "CC-BY-4.0" }, + "node_modules/chai": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/chai/-/chai-6.2.2.tgz", + "integrity": "sha512-NUPRluOfOiTKBKvWPtSD4PhFvWCqOi0BGStNWs57X9js7XGTprSmFoz5F0tWhR4WPjNeR9jXqdC7/UpSJTnlRg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, "node_modules/change-case": { "version": "5.4.4", "resolved": "https://registry.npmjs.org/change-case/-/change-case-5.4.4.tgz", "integrity": "sha512-HRQyTk2/YPEkt9TnUPbOpr64Uw3KOicFWPVBb+xiHvd6eBx/qPr9xqfBFDT8P2vWsvvz4jbEkfDe71W3VyNu2w==", + "dev": true, "license": "MIT" }, "node_modules/chokidar": { @@ -4852,6 +5383,7 @@ "version": "4.4.0", "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-4.4.0.tgz", "integrity": "sha512-77PSwercCZU2Fc4sX94eF8k8Pxte6JAwL4/ICZLFjJLqegs7kCuAsqqj/70NQF6TvDpgFjkubQB2FW2ZZddvQg==", + "dev": true, "funding": [ { "type": "github", @@ -4933,10 +5465,21 @@ "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", "license": "MIT" }, + "node_modules/commander": { + "version": "14.0.3", + "resolved": "https://registry.npmjs.org/commander/-/commander-14.0.3.tgz", + "integrity": "sha512-H+y0Jo/T1RZ9qPP4Eh1pkcQcLRglraJaSLoyOtHxu6AapkjWVCy2Sit1QQ4x3Dng8qDlSsZEet7g5Pq06MvTgw==", + "devOptional": true, + "license": "MIT", + "engines": { + "node": ">=20" + } + }, "node_modules/comment-parser": { "version": "1.4.9", "resolved": "https://registry.npmjs.org/comment-parser/-/comment-parser-1.4.9.tgz", "integrity": "sha512-+2AvZKjJaNq9GQljm3tr0utwrig5BL+jgJGvz5rDpGKNIp+ojcRXWUwBbh/sGIi1QCprR6PsKFakub+w1v+4hQ==", + "dev": true, "license": "MIT", "engines": { "node": ">= 12.0.0" @@ -4988,6 +5531,24 @@ "integrity": "sha512-ysOGlgTFbN2/Y6Cg3Iye8YKulHw+R2fNXHrgSmXISQdMnomY6eNDprVdW9R5xBguEqI954+S6709UyiO7B+6OQ==", "license": "MIT" }, + "node_modules/config-chain": { + "version": "1.1.13", + "resolved": "https://registry.npmjs.org/config-chain/-/config-chain-1.1.13.tgz", + "integrity": "sha512-qj+f8APARXHrM0hraqXYb2/bOVSV4PvJQlNZ/DVj0QrmNM2q2euizkeuVckQ57J+W0mRH6Hvi+k50M4Jul2VRQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ini": "^1.3.4", + "proto-list": "~1.2.1" + } + }, + "node_modules/config-chain/node_modules/ini": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", + "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==", + "dev": true, + "license": "ISC" + }, "node_modules/consola": { "version": "3.4.2", "resolved": "https://registry.npmjs.org/consola/-/consola-3.4.2.tgz", @@ -5001,6 +5562,7 @@ "version": "5.0.0", "resolved": "https://registry.npmjs.org/convert-hrtime/-/convert-hrtime-5.0.0.tgz", "integrity": "sha512-lOETlkIeYSJWcbbcvjRKGxVMXJR+8+OQb/mTPbA4ObPMytYIsUbuOE0Jzy60hjARYszq1id0j8KgVhC+WGZVTg==", + "dev": true, "license": "MIT", "engines": { "node": ">=12" @@ -5025,6 +5587,7 @@ "version": "3.50.0", "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.50.0.tgz", "integrity": "sha512-XGpFGbMLHwSt74YLTKho7Ib242qi6O8MSX+sRokV4oz7iKXvQWGYZthjIhjRGMxjzVkAubBO512dKGYcefmX3Q==", + "dev": true, "license": "MIT", "dependencies": { "browserslist": "^4.28.7" @@ -5353,6 +5916,7 @@ "version": "0.1.4", "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", + "devOptional": true, "license": "MIT" }, "node_modules/deepmerge": { @@ -5438,6 +6002,7 @@ "version": "7.0.2", "resolved": "https://registry.npmjs.org/detect-indent/-/detect-indent-7.0.2.tgz", "integrity": "sha512-y+8xyqdGLL+6sh0tVeHcfP/QDd8gUgbasolJJpY7NgeQGSZ739bDtSiaiDgtoicy+mtYB81dKLxO9xRhCyIB3A==", + "dev": true, "license": "MIT", "engines": { "node": ">=12.20" @@ -5576,6 +6141,25 @@ "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", "license": "MIT" }, + "node_modules/editorconfig": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/editorconfig/-/editorconfig-3.0.2.tgz", + "integrity": "sha512-T0ix8GhtxyKVfUFEcvdNDt3YGqlwkFHbD4/5bgFUDgFmxhI/cSRAeJ87/Sz//Cq8Eam6JX/e23RkoFO71P7aAA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@one-ini/wasm": "0.2.1", + "commander": "^14.0.3", + "minimatch": "~10.2.4", + "semver": "^7.7.4" + }, + "bin": { + "editorconfig": "bin/editorconfig" + }, + "engines": { + "node": ">=20" + } + }, "node_modules/ee-first": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", @@ -5717,6 +6301,7 @@ "version": "10.10.0", "resolved": "https://registry.npmjs.org/eslint/-/eslint-10.10.0.tgz", "integrity": "sha512-NPXn6r5zl4uET1DAVPaOwzX3rut4c0wcmw3dWJAfOsTM5+TogXo0DDjz8pwm/hL8cyVNpHqeK4JpN0NjnyFFNw==", + "devOptional": true, "license": "MIT", "workspaces": [ "packages/*" @@ -5775,6 +6360,7 @@ "version": "2.4.0", "resolved": "https://registry.npmjs.org/eslint-config-flat-gitignore/-/eslint-config-flat-gitignore-2.4.0.tgz", "integrity": "sha512-JhYC+V7qEDiCDsbJcVP8fxNc62fk4+i91YLP/YvmVHlkaQ8Xo99olYN8MO5COdxl7onGlsrfZVONe5NYsZP1UA==", + "dev": true, "license": "MIT", "dependencies": { "@eslint/compat": "^2.1.0" @@ -5790,6 +6376,7 @@ "version": "3.2.0", "resolved": "https://registry.npmjs.org/eslint-flat-config-utils/-/eslint-flat-config-utils-3.2.0.tgz", "integrity": "sha512-PHgo1X5uqIorJONLVD9BIaOSdoYFD3z/AeJljdqDPlWVRpeCYkDbK9k0AXoYVqqNJr6FEYIEr5Rm2TSktLQcHw==", + "dev": true, "license": "MIT", "dependencies": { "@eslint/config-helpers": "^0.5.5", @@ -5803,6 +6390,7 @@ "version": "0.5.5", "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.5.5.tgz", "integrity": "sha512-eIJYKTCECbP/nsKaaruF6LW967mtbQbsw4JTtSVkUQc9MneSkbrgPJAbKl9nWr0ZeowV8BfsarBmPpBzGelA2w==", + "dev": true, "license": "Apache-2.0", "dependencies": { "@eslint/core": "^1.2.1" @@ -5815,6 +6403,7 @@ "version": "0.1.9", "resolved": "https://registry.npmjs.org/eslint-import-context/-/eslint-import-context-0.1.9.tgz", "integrity": "sha512-K9Hb+yRaGAGUbwjhFNHvSmmkZs9+zbuoe3kFQ4V1wYjrepUFYM2dZAfNtjbbj3qsPfUfsA68Bx/ICWQMi+C8Eg==", + "dev": true, "license": "MIT", "dependencies": { "get-tsconfig": "^4.10.1", @@ -5839,6 +6428,7 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/eslint-merge-processors/-/eslint-merge-processors-2.0.0.tgz", "integrity": "sha512-sUuhSf3IrJdGooquEUB5TNpGNpBoQccbnaLHsb1XkBLUPPqCNivCpY05ZcpCOiV9uHwO2yxXEWVczVclzMxYlA==", + "dev": true, "license": "MIT", "funding": { "url": "https://github.com/sponsors/antfu" @@ -5851,6 +6441,7 @@ "version": "0.6.0", "resolved": "https://registry.npmjs.org/eslint-plugin-import-lite/-/eslint-plugin-import-lite-0.6.0.tgz", "integrity": "sha512-80vevx2A7i3H7n1/6pqDO8cc5wRz6OwLDvIyVl9UflBV1N1f46e9Ihzi65IOLYoSxM6YykK2fTw1xm0Ixx6aTQ==", + "dev": true, "license": "MIT", "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -5863,6 +6454,7 @@ "version": "4.17.1", "resolved": "https://registry.npmjs.org/eslint-plugin-import-x/-/eslint-plugin-import-x-4.17.1.tgz", "integrity": "sha512-4cdstYkKCyjumM2Q9NSI03K8D2a9F4Ssz33K2lv2hQa4KmR9jPLwk3uWGtNvclfqBrPGfGuMBwsGMbe6dMRbfg==", + "dev": true, "license": "MIT", "dependencies": { "@typescript-eslint/types": "^8.56.0", @@ -5899,6 +6491,7 @@ "version": "63.3.3", "resolved": "https://registry.npmjs.org/eslint-plugin-jsdoc/-/eslint-plugin-jsdoc-63.3.3.tgz", "integrity": "sha512-xI4IeVRzRFA2DGHrPLIxF3U+oJHU3FE+P9Zb27fVs5dPHgfcpoAs0PyCbznVhK7pwR+9BPUztFeXSgpw/CL4Yg==", + "dev": true, "license": "BSD-3-Clause", "dependencies": { "@es-joy/jsdoccomment": "~0.91.0", @@ -5927,6 +6520,7 @@ "version": "1.4.7", "resolved": "https://registry.npmjs.org/comment-parser/-/comment-parser-1.4.7.tgz", "integrity": "sha512-0h+uSNtQGW3D98eQt3jJ8L06Fves8hncB4V/PKdw/Qb8Hnk19VaKuTr55UNRYiSoVa7WwrFls+rh3ux9agmkeQ==", + "dev": true, "license": "MIT", "engines": { "node": ">= 12.0.0" @@ -5936,6 +6530,7 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true, "license": "MIT", "engines": { "node": ">=10" @@ -5948,6 +6543,7 @@ "version": "5.0.1", "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-5.0.1.tgz", "integrity": "sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA==", + "dev": true, "license": "Apache-2.0", "engines": { "node": "^20.19.0 || ^22.13.0 || >=24" @@ -5960,6 +6556,7 @@ "version": "11.2.0", "resolved": "https://registry.npmjs.org/espree/-/espree-11.2.0.tgz", "integrity": "sha512-7p3DrVEIopW1B1avAGLuCSh1jubc01H2JHc8B4qqGblmg5gI9yumBgACjWo4JlIc04ufug4xJ3SQI8HkS/Rgzw==", + "dev": true, "license": "BSD-2-Clause", "dependencies": { "acorn": "^8.16.0", @@ -5977,6 +6574,7 @@ "version": "3.3.0", "resolved": "https://registry.npmjs.org/eslint-plugin-regexp/-/eslint-plugin-regexp-3.3.0.tgz", "integrity": "sha512-TT0JTQbW7CRKohntpGAsMdQ1K3MsmJK4gdAhKJtIwlJ0JvuYxauymFgJdvqxIY4lqNj0EuRZWVrymQtEdJYEFg==", + "dev": true, "license": "MIT", "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", @@ -5998,6 +6596,7 @@ "version": "9.2.1", "resolved": "https://registry.npmjs.org/jsdoc-type-pratt-parser/-/jsdoc-type-pratt-parser-9.2.1.tgz", "integrity": "sha512-V4Ww4EHnTcTLSOMoB0FsF72JhQvcAsriCm/LWnxJeGWoxIjEL2l9na11abQok5SYShq8m0Gl02el/xAbTCulvQ==", + "dev": true, "license": "MIT", "dependencies": { "@types/estree": "^1.0.9", @@ -6011,6 +6610,7 @@ "version": "73.0.0", "resolved": "https://registry.npmjs.org/eslint-plugin-unicorn/-/eslint-plugin-unicorn-73.0.0.tgz", "integrity": "sha512-V0YatLe9nkGhXEXKe2Qljb1EY0sJHwDV0HUF1NKFwtsHh/fU7qGHDgv+6fchzZcgU2/7noHo2gdjnmo0P2uDPw==", + "dev": true, "license": "MIT", "dependencies": { "@eslint-community/eslint-utils": "^4.9.1", @@ -6048,6 +6648,7 @@ "version": "4.5.0", "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", + "dev": true, "license": "BSD-2-Clause", "engines": { "node": ">=0.12" @@ -6060,6 +6661,7 @@ "version": "10.11.0", "resolved": "https://registry.npmjs.org/eslint-plugin-vue/-/eslint-plugin-vue-10.11.0.tgz", "integrity": "sha512-fGt38SsaPCZ2FmA3bX/3vOW2qdsPNx0/9x+7Aab6NMX++PhynMtK4LWC4vzXgfQBCqpUAler18H38GA+NBAd0Q==", + "dev": true, "license": "MIT", "dependencies": { "@eslint-community/eslint-utils": "^4.9.1", @@ -6091,6 +6693,7 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/eslint-processor-vue-blocks/-/eslint-processor-vue-blocks-2.0.0.tgz", "integrity": "sha512-u4W0CJwGoWY3bjXAuFpc/b6eK3NQEI8MoeW7ritKj3G3z/WtHrKjkqf+wk8mPEy5rlMGS+k6AZYOw2XBoN/02Q==", + "dev": true, "license": "MIT", "funding": { "url": "https://github.com/sponsors/antfu" @@ -6104,6 +6707,7 @@ "version": "9.1.2", "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-9.1.2.tgz", "integrity": "sha512-xS90H51cKw0jltxmvmHy2Iai1LIqrfbw57b79w/J7MfvDfkIkFZ+kj6zC3BjtUwh150HsSSdxXZcsuv72miDFQ==", + "devOptional": true, "license": "BSD-2-Clause", "dependencies": { "@types/esrecurse": "^4.3.1", @@ -6122,6 +6726,7 @@ "version": "2.3.1", "resolved": "https://registry.npmjs.org/eslint-typegen/-/eslint-typegen-2.3.1.tgz", "integrity": "sha512-zVdh8rThBvv2o5T/K524Fr5iy1Jo0q09rHL7y7FbOhgMB177T2gw+shxfC4ChCEqdq6/y6LJA4j8Fbr/Xls9aw==", + "dev": true, "license": "MIT", "dependencies": { "json-schema-to-typescript-lite": "^15.0.0", @@ -6138,6 +6743,7 @@ "version": "4.2.1", "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz", "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==", + "dev": true, "license": "Apache-2.0", "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -6150,6 +6756,7 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "devOptional": true, "license": "MIT", "engines": { "node": ">=10" @@ -6162,6 +6769,7 @@ "version": "5.0.1", "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-5.0.1.tgz", "integrity": "sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA==", + "devOptional": true, "license": "Apache-2.0", "engines": { "node": "^20.19.0 || ^22.13.0 || >=24" @@ -6174,6 +6782,7 @@ "version": "11.2.0", "resolved": "https://registry.npmjs.org/espree/-/espree-11.2.0.tgz", "integrity": "sha512-7p3DrVEIopW1B1avAGLuCSh1jubc01H2JHc8B4qqGblmg5gI9yumBgACjWo4JlIc04ufug4xJ3SQI8HkS/Rgzw==", + "devOptional": true, "license": "BSD-2-Clause", "dependencies": { "acorn": "^8.16.0", @@ -6191,6 +6800,7 @@ "version": "5.0.0", "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "devOptional": true, "license": "MIT", "dependencies": { "locate-path": "^6.0.0", @@ -6207,6 +6817,7 @@ "version": "6.0.2", "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "devOptional": true, "license": "ISC", "dependencies": { "is-glob": "^4.0.3" @@ -6219,6 +6830,7 @@ "version": "5.3.2", "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", + "devOptional": true, "license": "MIT", "engines": { "node": ">= 4" @@ -6228,6 +6840,7 @@ "version": "6.0.0", "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "devOptional": true, "license": "MIT", "dependencies": { "p-locate": "^5.0.0" @@ -6243,6 +6856,7 @@ "version": "3.1.0", "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "devOptional": true, "license": "MIT", "dependencies": { "yocto-queue": "^0.1.0" @@ -6258,6 +6872,7 @@ "version": "5.0.0", "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "devOptional": true, "license": "MIT", "dependencies": { "p-limit": "^3.0.2" @@ -6273,6 +6888,7 @@ "version": "0.1.0", "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "devOptional": true, "license": "MIT", "engines": { "node": ">=10" @@ -6285,6 +6901,7 @@ "version": "10.4.0", "resolved": "https://registry.npmjs.org/espree/-/espree-10.4.0.tgz", "integrity": "sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==", + "dev": true, "license": "BSD-2-Clause", "dependencies": { "acorn": "^8.15.0", @@ -6302,6 +6919,7 @@ "version": "1.7.0", "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.7.0.tgz", "integrity": "sha512-Ap6G0WQwcU/LHsvLwON1fAQX9Zp0A2Y6Y/cJBl9r/JbW90Zyg4/zbG6zzKa2OTALELarYHmKu0GhpM5EO+7T0g==", + "devOptional": true, "license": "BSD-3-Clause", "dependencies": { "estraverse": "^5.1.0" @@ -6314,6 +6932,7 @@ "version": "4.3.0", "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "devOptional": true, "license": "BSD-2-Clause", "dependencies": { "estraverse": "^5.2.0" @@ -6326,6 +6945,7 @@ "version": "5.3.0", "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "devOptional": true, "license": "BSD-2-Clause", "engines": { "node": ">=4.0" @@ -6341,6 +6961,7 @@ "version": "2.0.3", "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "devOptional": true, "license": "BSD-2-Clause", "engines": { "node": ">=0.10.0" @@ -6405,16 +7026,37 @@ "url": "https://github.com/sindresorhus/execa?sponsor=1" } }, + "node_modules/expect-type": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/expect-type/-/expect-type-1.4.0.tgz", + "integrity": "sha512-KfYbmpRm0VbLjEvVa9yGwCi9GI34xvi7A/HXYWQO65CSD2u3MczUJSuwXKFIxlGsgBQizV9q5J9NHj4VG0n+pA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=12.0.0" + } + }, "node_modules/exsolve": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/exsolve/-/exsolve-1.1.1.tgz", "integrity": "sha512-9U/jZUgjnSGyntRr6y5Muu1MJcwFl6kPu7k8qLF0IMNfLqvw0NZ4nnVDq0RVoZ0RvCyumib4Ez3KYrVfilrw+g==", "license": "MIT" }, + "node_modules/fake-indexeddb": { + "version": "6.2.5", + "resolved": "https://registry.npmjs.org/fake-indexeddb/-/fake-indexeddb-6.2.5.tgz", + "integrity": "sha512-CGnyrvbhPlWYMngksqrSSUT1BAVP49dZocrHuK0SvtR0D5TMs5wP0o3j7jexDJW01KSadjBp1M/71o/KR3nD1w==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18" + } + }, "node_modules/fast-deep-equal": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "devOptional": true, "license": "MIT" }, "node_modules/fast-fifo": { @@ -6443,12 +7085,14 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "devOptional": true, "license": "MIT" }, "node_modules/fast-levenshtein": { "version": "2.0.6", "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", + "devOptional": true, "license": "MIT" }, "node_modules/fast-npm-meta": { @@ -6529,6 +7173,7 @@ "version": "11.1.5", "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-11.1.5.tgz", "integrity": "sha512-+PFTHITI08JIGhnNpGNI8T8inUpgZfk3GNEqfT9R2zZV2iFXg3CvqzSl/uEhs7TSGujYRELEANyDvS8Fj7+S7Q==", + "devOptional": true, "license": "MIT", "dependencies": { "flat-cache": "^6.1.23" @@ -6556,6 +7201,7 @@ "version": "8.0.0", "resolved": "https://registry.npmjs.org/find-up/-/find-up-8.0.0.tgz", "integrity": "sha512-JGG8pvDi2C+JxidYdIwQDyS/CgcrIdh18cvgxcBge3wSHRQOrooMD3GlFBcmMJAN9M42SAZjDp5zv1dglJjwww==", + "dev": true, "license": "MIT", "dependencies": { "locate-path": "^8.0.0", @@ -6572,6 +7218,7 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/find-up-simple/-/find-up-simple-1.0.1.tgz", "integrity": "sha512-afd4O7zpqHeRyg4PfDQsXmlDe2PfdHtJt6Akt8jOWaApLOZk5JXs6VMR29lz03pRe9mpykrRCYIYxaJYcfpncQ==", + "dev": true, "license": "MIT", "engines": { "node": ">=18" @@ -6584,6 +7231,7 @@ "version": "0.3.0", "resolved": "https://registry.npmjs.org/unicorn-magic/-/unicorn-magic-0.3.0.tgz", "integrity": "sha512-+QBBXBCvifc56fsbuxZQ6Sic3wqqc3WWaqxs58gvJrcOuN83HGTCwz3oS5phzU9LthRNE9VrJCFCLUgHeeFnfA==", + "dev": true, "license": "MIT", "engines": { "node": ">=18" @@ -6596,6 +7244,7 @@ "version": "6.1.23", "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-6.1.23.tgz", "integrity": "sha512-f++BY9pTk+983xK1FLzlLpmM0i0z+jHmx3QESGkURMXujQZz1k5wzwX6hjnQ8goaD0B+sYnDK1yZ6MTyZfUaqA==", + "devOptional": true, "license": "MIT", "dependencies": { "cacheable": "^2.5.0", @@ -6607,6 +7256,7 @@ "version": "3.4.4", "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.4.4.tgz", "integrity": "sha512-5+ybhBZANEJxaH3X5evAFatUxLfEHSr7n6kYJ+1Qd0mUqr4eu9gIf6GDbWHf8RJijHrjjO8G+la14SlL2SeS1Q==", + "devOptional": true, "license": "ISC" }, "node_modules/fnv1a-64": { @@ -6680,6 +7330,7 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/function-timeout/-/function-timeout-1.0.2.tgz", "integrity": "sha512-939eZS4gJ3htTHAldmyyuzlrD58P03fHG49v2JfFXbV6OhvZKRC9j2yAtdHw/zrp2zXHuv05zMIy40F0ge7spA==", + "dev": true, "license": "MIT", "engines": { "node": ">=18" @@ -6768,6 +7419,7 @@ "version": "4.14.3", "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.14.3.tgz", "integrity": "sha512-++QEw4DIY7WGoukz+/+A/8dGYPT9l9yIadnmSgZ8Rjr3YVSVDipQSO9CdnJo9ePqFqUUqh+wk9uIaoiAwsiPkA==", + "dev": true, "license": "MIT", "dependencies": { "resolve-pkg-maps": "^1.0.0" @@ -6833,6 +7485,7 @@ "version": "17.12.0", "resolved": "https://registry.npmjs.org/globals/-/globals-17.12.0.tgz", "integrity": "sha512-cezEd/DTyyht9cvSSURyygXPfy04GtWO/5e6ZPvH7fCtjKz9PYOmuawphw1Ctd1f6C+5JypXfGD7ahNMXvevBA==", + "dev": true, "license": "MIT", "engines": { "node": ">=18" @@ -6906,10 +7559,30 @@ "integrity": "sha512-lXVyvUvrNXblMqzIRrxHb57UUVmqsSWlxqt3XIjCkUP0wDAf6uicO6KMbEgYrMNtEvWgWHwe42CKxPu9MYAnWw==", "license": "MIT" }, + "node_modules/happy-dom": { + "version": "20.14.3", + "resolved": "https://registry.npmjs.org/happy-dom/-/happy-dom-20.14.3.tgz", + "integrity": "sha512-0KMb/Eh8rsd+aMNkOk5h8LkAjo9Na1aksnEGmsuZf+GXl6UkAdIi0VbUHU7d59lXhNmawODgwwuCMiZpCxwwCw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": ">=20.0.0", + "@types/whatwg-mimetype": "^3.0.2", + "@types/ws": "^8.18.1", + "buffer-image-size": "^0.6.4", + "entities": "^7.0.1", + "whatwg-mimetype": "^3.0.0", + "ws": "^8.21.0" + }, + "engines": { + "node": ">=20.0.0" + } + }, "node_modules/hashery": { "version": "1.5.1", "resolved": "https://registry.npmjs.org/hashery/-/hashery-1.5.1.tgz", "integrity": "sha512-iZyKG96/JwPz1N55vj2Ie2vXbhu440zfUfJvSwEqEbeLluk7NnapfGqa7LH0mOsnDxTF85Mx8/dyR6HfqcbmbQ==", + "devOptional": true, "license": "MIT", "dependencies": { "hookified": "^1.15.0" @@ -6940,12 +7613,14 @@ "version": "1.15.1", "resolved": "https://registry.npmjs.org/hookified/-/hookified-1.15.1.tgz", "integrity": "sha512-MvG/clsADq1GPM2KGo2nyfaWVyn9naPiXrqIe4jYjXNZQt238kWyOGrsyc/DmRAQ+Re6yeo6yX/yoNCG5KAEVg==", + "devOptional": true, "license": "MIT" }, "node_modules/html-entities": { "version": "2.6.0", "resolved": "https://registry.npmjs.org/html-entities/-/html-entities-2.6.0.tgz", "integrity": "sha512-kig+rMn/QOVRvr7c86gQ8lWXq+Hkv6CbAH1hLu+RG338StTpE8Z0b44SDVaqVu7HGKf27frdmUYEs9hTUX/cLQ==", + "dev": true, "funding": [ { "type": "github", @@ -7020,6 +7695,7 @@ "version": "1.1.0", "resolved": "https://registry.npmjs.org/identifier-regex/-/identifier-regex-1.1.0.tgz", "integrity": "sha512-SLX4H/vtcYlYnL7XqnuJKHU7Z8517TgsW9nmQiGOgMCjQ8V/deLYu6bEmbGoXe7WMMhc9+EUGyFFneHja8KabA==", + "dev": true, "license": "MIT", "dependencies": { "reserved-identifiers": "^1.0.0" @@ -7083,6 +7759,7 @@ "version": "0.1.4", "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "devOptional": true, "license": "MIT", "engines": { "node": ">=0.8.19" @@ -7092,6 +7769,7 @@ "version": "5.0.0", "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-5.0.0.tgz", "integrity": "sha512-m6FAo/spmsW2Ab2fU35JTYwtOKa2yAwXSwgjSv1TJzh4Mh7mC3lzAOVLBprb72XsTrgkEIsl7YrFNAiDiRhIGg==", + "dev": true, "license": "MIT", "engines": { "node": ">=12" @@ -7150,6 +7828,7 @@ "version": "5.0.0", "resolved": "https://registry.npmjs.org/is-builtin-module/-/is-builtin-module-5.0.0.tgz", "integrity": "sha512-f4RqJKBUe5rQkJ2eJEJBXSticB3hGbN9j0yxxMQFqIW89Jp9WYFtzfTcRlstDKVUTRzSOTLKRfO9vIztenwtxA==", + "dev": true, "license": "MIT", "dependencies": { "builtin-modules": "^5.0.0" @@ -7225,6 +7904,7 @@ "version": "1.1.0", "resolved": "https://registry.npmjs.org/is-identifier/-/is-identifier-1.1.0.tgz", "integrity": "sha512-NhOds0mDx9lJu+1lBRO0xbwFo5nobA7GCk/0e5xjr6+6XugX985+0OyGX35BNrTkPAsdLcIKg02HUQJOK8D8kw==", + "dev": true, "license": "MIT", "dependencies": { "identifier-regex": "^1.1.0", @@ -7385,6 +8065,61 @@ "jiti": "lib/jiti-cli.mjs" } }, + "node_modules/js-beautify": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/js-beautify/-/js-beautify-2.0.3.tgz", + "integrity": "sha512-cyFbh3tkPhknnTD/0bLf0T0yy2ZIbqL05mttzbt4y1Zfr7NxqXQZ62dkBLKs3oHH/lpjmDRAnciJiSUyOy8XwQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "config-chain": "^1.1.13", + "editorconfig": "^3.0.2", + "glob": "^13.0.6", + "js-cookie": "^3.0.8", + "nopt": "^10.0.1" + }, + "bin": { + "css-beautify": "js/bin/css-beautify.js", + "html-beautify": "js/bin/html-beautify.js", + "js-beautify": "js/bin/js-beautify.js" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/js-beautify/node_modules/abbrev": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-5.0.0.tgz", + "integrity": "sha512-/XrFJgzQQQHpti1raDJC6m4ws6aNktmjBlhk8Fdlk7LwCEuDoieEJJY9OFHjfiFJFFRM2tK+Ky/IsfbbmlMu1w==", + "dev": true, + "license": "ISC", + "engines": { + "node": "^22.22.2 || ^24.15.0 || >=26.0.0" + } + }, + "node_modules/js-beautify/node_modules/nopt": { + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/nopt/-/nopt-10.0.1.tgz", + "integrity": "sha512-df3sBr/6ax9hSGuC3CspvLlbnX8cP5L5nZwXF8cGN8l0zSWR6BvzmQ6jPUKjvo6+/xdpkNvEcucBNUdBeeV13g==", + "dev": true, + "license": "ISC", + "dependencies": { + "abbrev": "^5.0.0" + }, + "bin": { + "nopt": "bin/nopt.js" + }, + "engines": { + "node": "^22.22.2 || ^24.15.0 || >=26.0.0" + } + }, + "node_modules/js-cookie": { + "version": "3.0.8", + "resolved": "https://registry.npmjs.org/js-cookie/-/js-cookie-3.0.8.tgz", + "integrity": "sha512-yeJd4aNAdYZQjaon2bpD/Gb0B/omw7HQOsynXXcOiWVCacbBcPlgn8S/d1X6blFSaHao7ozqtW7NZW19xpCtIw==", + "dev": true, + "license": "MIT" + }, "node_modules/js-tokens": { "version": "10.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-10.0.0.tgz", @@ -7395,6 +8130,7 @@ "version": "4.3.2", "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.3.2.tgz", "integrity": "sha512-SFNOvSJ+Dgf/9An904Yx+CgSlIPCkIpao4qo51lpee25TIRejdH3rhR4EZMGoNx3/TP3O+wzWuiTFl4sqbltzA==", + "dev": true, "funding": [ { "type": "github", @@ -7417,6 +8153,7 @@ "version": "8.0.0", "resolved": "https://registry.npmjs.org/jsdoc-type-pratt-parser/-/jsdoc-type-pratt-parser-8.0.0.tgz", "integrity": "sha512-uQu/fXVqVaMg6gM8/E5G5+eygVcZ1NV0Z51CvqhNa2bDWxvHMl484ETr6vph4oPyC+KUcbP/w2W2pewfCiR9aQ==", + "dev": true, "license": "MIT", "engines": { "node": ">=20.0.0" @@ -7438,6 +8175,7 @@ "version": "15.0.0", "resolved": "https://registry.npmjs.org/json-schema-to-typescript-lite/-/json-schema-to-typescript-lite-15.0.0.tgz", "integrity": "sha512-5mMORSQm9oTLyjM4mWnyNBi2T042Fhg1/0gCIB6X8U/LVpM2A+Nmj2yEyArqVouDmFThDxpEXcnTgSrjkGJRFA==", + "dev": true, "license": "MIT", "dependencies": { "@apidevtools/json-schema-ref-parser": "^14.1.1", @@ -7448,12 +8186,14 @@ "version": "0.4.1", "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "devOptional": true, "license": "MIT" }, "node_modules/json-stable-stringify-without-jsonify": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", + "devOptional": true, "license": "MIT" }, "node_modules/json5": { @@ -7472,6 +8212,7 @@ "version": "5.6.0", "resolved": "https://registry.npmjs.org/keyv/-/keyv-5.6.0.tgz", "integrity": "sha512-CYDD3SOtsHtyXeEORYRx2qBtpDJFjRTGXUtmNEMGyzYOKj1TE3tycdlho7kA1Ufx9OYWZzg52QFBGALTirzDSw==", + "devOptional": true, "license": "MIT", "dependencies": { "@keyv/serialize": "^1.1.1" @@ -7557,6 +8298,7 @@ "version": "0.4.1", "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", + "devOptional": true, "license": "MIT", "dependencies": { "prelude-ls": "^1.2.1", @@ -7919,6 +8661,7 @@ "version": "8.0.0", "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-8.0.0.tgz", "integrity": "sha512-XT9ewWAC43tiAV7xDAPflMkG0qOPn2QjHqlgX8FOqmWa/rxnyYDulF9T0F7tRy1u+TVTmK/M//6VIOye+2zDXg==", + "dev": true, "license": "MIT", "dependencies": { "p-locate": "^6.0.0" @@ -7946,12 +8689,12 @@ } }, "node_modules/magic-string": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-1.2.2.tgz", - "integrity": "sha512-veT/+7iXrXzT39XnEN4lOxtNl72dMgJ8Lp+5Bd6YcMSWpb0n0MjBM8Uuooi6jgJr8dhUW2swQgBmoZVMni5SVg==", + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-1.3.1.tgz", + "integrity": "sha512-rm91zr2Ou+XueDTohjQQjdQEcYM6zVi8KVUCG8Ec3vHwUEKrhSdCNyfuIywkA6hcCAteIn0ZOtAHA6eGpiX+Pg==", "license": "MIT", "dependencies": { - "@jridgewell/sourcemap-codec": "^1.5.5" + "@jridgewell/sourcemap-codec": "^1.6.0" } }, "node_modules/magic-string-ast": { @@ -7993,6 +8736,7 @@ "version": "1.1.0", "resolved": "https://registry.npmjs.org/make-asynchronous/-/make-asynchronous-1.1.0.tgz", "integrity": "sha512-ayF7iT+44LXdxJLTrTd3TLQpFDDvPCBxXxbv+pMUSuHA5Q8zyAfwkRP6aHHwNVFBUFWtxAHqwNJxF8vMZLAbVg==", + "dev": true, "license": "MIT", "dependencies": { "p-event": "^6.0.0", @@ -8010,6 +8754,7 @@ "version": "4.41.0", "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-4.41.0.tgz", "integrity": "sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA==", + "dev": true, "license": "(MIT OR CC0-1.0)", "engines": { "node": ">=16" @@ -8248,6 +8993,7 @@ "version": "0.3.4", "resolved": "https://registry.npmjs.org/napi-postinstall/-/napi-postinstall-0.3.4.tgz", "integrity": "sha512-PHI5f1O0EP5xJ9gQmFGMS6IZcrVvTjpXjz7Na41gTE7eE2hK11lg04CECCYEEjdc17EV4DO+fkGEtt7TpTaTiQ==", + "dev": true, "license": "MIT", "bin": { "napi-postinstall": "lib/cli.js" @@ -8263,6 +9009,7 @@ "version": "1.4.0", "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", + "devOptional": true, "license": "MIT" }, "node_modules/nitropack": { @@ -8653,6 +9400,7 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/object-deep-merge/-/object-deep-merge-2.0.1.tgz", "integrity": "sha512-aKttDKcU3pyZqKcCkDhsMn70WmZFG2JGDQLP9EcLyTSIFQRCPWLAmBZRLJnrVUrhPG1jETEEbfdgbNtJf1LyMg==", + "dev": true, "license": "MIT" }, "node_modules/object-identity": { @@ -8754,6 +9502,7 @@ "version": "0.9.4", "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==", + "devOptional": true, "license": "MIT", "dependencies": { "deep-is": "^0.1.3", @@ -8793,6 +9542,7 @@ "version": "6.0.1", "resolved": "https://registry.npmjs.org/p-event/-/p-event-6.0.1.tgz", "integrity": "sha512-Q6Bekk5wpzW5qIyUP4gdMEujObYstZl6DMMOSenwBvV0BlE5LkDwkjs5yHbZmdCEq2o4RJx4tE1vwxFVf2FG1w==", + "dev": true, "license": "MIT", "dependencies": { "p-timeout": "^6.1.2" @@ -8808,6 +9558,7 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-4.0.0.tgz", "integrity": "sha512-5b0R4txpzjPWVw/cXXUResoD4hb6U/x9BH08L7nw+GN1sezDzPdxeRvpc9c433fZhBan/wusjbCsqwqm4EIBIQ==", + "dev": true, "license": "MIT", "dependencies": { "yocto-queue": "^1.0.0" @@ -8823,6 +9574,7 @@ "version": "6.0.0", "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-6.0.0.tgz", "integrity": "sha512-wPrq66Llhl7/4AGC6I+cqxT07LhXvWL08LNXz1fENOw0Ap4sRZZ/gZpTTJ5jpurzzzfS2W/Ge9BY3LgLjCShcw==", + "dev": true, "license": "MIT", "dependencies": { "p-limit": "^4.0.0" @@ -8838,6 +9590,7 @@ "version": "6.1.4", "resolved": "https://registry.npmjs.org/p-timeout/-/p-timeout-6.1.4.tgz", "integrity": "sha512-MyIV3ZA/PmyBN/ud8vV9XzwTrNtR4jFrObymZYnZqMmW0zA8Z17vnT0rBgFE/TlohB+YCHqXMgZzb3Csp49vqg==", + "dev": true, "license": "MIT", "engines": { "node": ">=14.16" @@ -8856,12 +9609,14 @@ "version": "1.8.0", "resolved": "https://registry.npmjs.org/package-manager-detector/-/package-manager-detector-1.8.0.tgz", "integrity": "sha512-yQA4H19AmPEoMUeavPMDIe1higySl/gH/yaQrkT/s07Qp+7pp2hYz30N3z2l5BkjVkF9Ow6o0wjJamm2y7Sn0A==", + "dev": true, "license": "MIT" }, "node_modules/parse-imports-exports": { "version": "0.2.4", "resolved": "https://registry.npmjs.org/parse-imports-exports/-/parse-imports-exports-0.2.4.tgz", "integrity": "sha512-4s6vd6dx1AotCx/RCI2m7t7GCh5bDRUtGNvRfHSP2wbBQdMi67pPe7mtzmgwcaQ8VKK/6IB7Glfyu3qdZJPybQ==", + "dev": true, "license": "MIT", "dependencies": { "parse-statements": "1.0.11" @@ -8871,6 +9626,7 @@ "version": "1.0.11", "resolved": "https://registry.npmjs.org/parse-statements/-/parse-statements-1.0.11.tgz", "integrity": "sha512-HlsyYdMBnbPQ9Jr/VgJ1YF4scnldvJpJxCVx6KgqPL4dxppsWrJHCIIxQXMJrqGnsRkNPATbeMJ8Yxu7JMsYcA==", + "dev": true, "license": "MIT" }, "node_modules/parseurl": { @@ -8893,6 +9649,7 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "devOptional": true, "license": "MIT", "engines": { "node": ">=8" @@ -8979,10 +9736,40 @@ "pathe": "^2.0.3" } }, + "node_modules/playwright": { + "version": "1.63.0", + "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.63.0.tgz", + "integrity": "sha512-+7ziBLidS4NaNCdt57SUDT+wYmmd5fmiQejUic/kb+YsYSCPyOOE9sebzMjNmQrsnNpDJqd4WHvV/8lfKfUDUg==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "playwright-core": "1.63.0" + }, + "bin": { + "playwright": "cli.js" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/playwright-core": { + "version": "1.63.0", + "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.63.0.tgz", + "integrity": "sha512-rYCsBF/M5HjUch52bbtVONEFjv6Xu8sm8h72dNlR5bzIE1fvC/bxgspzkjSfU+MweEMmPM8KJebG6nnyxo5mCg==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "playwright-core": "cli.js" + }, + "engines": { + "node": ">=20" + } + }, "node_modules/pluralize": { "version": "8.0.0", "resolved": "https://registry.npmjs.org/pluralize/-/pluralize-8.0.0.tgz", "integrity": "sha512-Nc3IT5yHzflTfbjgqWcCPpo7DaKy4FnpB0l/zCAW0Tc7jxAiuqSxHasntB3D7887LSrA93kDJ9IXovxJYxyLCA==", + "dev": true, "license": "MIT", "engines": { "node": ">=4" @@ -9464,6 +10251,7 @@ "version": "1.2.1", "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", + "devOptional": true, "license": "MIT", "engines": { "node": ">= 0.8.0" @@ -9513,10 +10301,18 @@ "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", "license": "ISC" }, + "node_modules/proto-list": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/proto-list/-/proto-list-1.2.4.tgz", + "integrity": "sha512-vtK/94akxsTMhe0/cbfpR+syPuszcuwhqVjJq26CuNDgFGj682oRBXOP5MJpv2r7JtE8MsiepGIqvvOTBwn2vA==", + "dev": true, + "license": "ISC" + }, "node_modules/punycode": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", + "devOptional": true, "license": "MIT", "engines": { "node": ">=6" @@ -9526,6 +10322,7 @@ "version": "0.10.1", "resolved": "https://registry.npmjs.org/qified/-/qified-0.10.1.tgz", "integrity": "sha512-+Owyggi9IxT1ePKGafcI87ubSmxol6smwJ+RAHDQlx9+9cPwFWDiKFFCPuWhr9ignlGpZ9vDQLw67N4dcTVFEA==", + "devOptional": true, "license": "MIT", "dependencies": { "hookified": "^2.1.1" @@ -9538,6 +10335,7 @@ "version": "2.2.0", "resolved": "https://registry.npmjs.org/hookified/-/hookified-2.2.0.tgz", "integrity": "sha512-p/LgFzRN5FeoD3DLS6bkUapeye6E4SI6yJs6KetENd18S+FBthqYq2amJUWpt5z0EQwwHemidjY5OqJGEKm5uA==", + "devOptional": true, "license": "MIT" }, "node_modules/quansync": { @@ -9580,6 +10378,7 @@ "version": "0.1.0", "resolved": "https://registry.npmjs.org/quote-js-string/-/quote-js-string-0.1.0.tgz", "integrity": "sha512-Y3NoRtprEEZQD8RfxMCfS0ZTqc4e+i18OrXEXAvpM6TfC/3y+0L5rNbZiSnbBBEkDfFzbpd8o+cE8q3/anjMGA==", + "dev": true, "license": "MIT", "engines": { "node": ">=22" @@ -9707,6 +10506,7 @@ "version": "0.12.1", "resolved": "https://registry.npmjs.org/refa/-/refa-0.12.1.tgz", "integrity": "sha512-J8rn6v4DBb2nnFqkqwy6/NnTYMcgLA+sLr0iIO41qpv0n+ngb7ksag2tMRl0inb1bbO/esUwzW1vbJi7K0sI0g==", + "dev": true, "license": "MIT", "dependencies": { "@eslint-community/regexpp": "^4.8.0" @@ -9719,6 +10519,7 @@ "version": "0.7.1", "resolved": "https://registry.npmjs.org/regexp-ast-analysis/-/regexp-ast-analysis-0.7.1.tgz", "integrity": "sha512-sZuz1dYW/ZsfG17WSAG7eS85r5a0dDsvg+7BiiYR5o6lKCAtUrEwdmRmaGF6rwVj3LcmAeYkOWKEPlbPzN3Y3A==", + "dev": true, "license": "MIT", "dependencies": { "@eslint-community/regexpp": "^4.8.0", @@ -9732,6 +10533,7 @@ "version": "0.13.2", "resolved": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.13.2.tgz", "integrity": "sha512-NgRBy2Nx/bE+9F27nVHnqcN5HjyLmecqsqx2PJHu3/IEtADD4WuxuXIVExD5PoSDFVrl78dOonfcOe5O+5nbzQ==", + "dev": true, "license": "BSD-2-Clause", "dependencies": { "jsesc": "~3.1.0" @@ -9744,6 +10546,7 @@ "version": "1.2.0", "resolved": "https://registry.npmjs.org/reserved-identifiers/-/reserved-identifiers-1.2.0.tgz", "integrity": "sha512-yE7KUfFvaBFzGPs5H3Ops1RevfUEsDc5Iz65rOwWg4lE8HJSYtle77uul3+573457oHvBKuHYDl/xqUkKpEEdw==", + "dev": true, "license": "MIT", "engines": { "node": ">=18" @@ -9786,6 +10589,7 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz", "integrity": "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==", + "dev": true, "license": "MIT", "funding": { "url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1" @@ -10024,6 +10828,7 @@ "version": "0.3.0", "resolved": "https://registry.npmjs.org/scslre/-/scslre-0.3.0.tgz", "integrity": "sha512-3A6sD0WYP7+QrjbfNA2FN3FsOaGGFoekCVgTyypy53gPxhbkCIjtO6YWgdrfM+n/8sI8JeXZOIxsHjMTNxQ4nQ==", + "dev": true, "license": "MIT", "dependencies": { "@eslint-community/regexpp": "^4.8.0", @@ -10163,6 +10968,13 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/siginfo": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/siginfo/-/siginfo-2.0.0.tgz", + "integrity": "sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==", + "dev": true, + "license": "ISC" + }, "node_modules/signal-exit": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", @@ -10293,12 +11105,14 @@ "version": "2.5.0", "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.5.0.tgz", "integrity": "sha512-PiU42r+xO4UbUS1buo3LPJkjlO7430Xn5SVAhdpzzsPHsjbYVflnnFdATgabnLude+Cqu25p6N+g2lw/PFsa4w==", + "dev": true, "license": "CC-BY-3.0" }, "node_modules/spdx-expression-parse": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-5.0.0.tgz", "integrity": "sha512-vngmw3Rgn+o2arXNbnZaj5UtOEBuWBfvaI+Wc8GFfykIhA5/vdK9/Sp/XkLv63dykz2rxKDvKEHupF5P0FORcQ==", + "dev": true, "license": "MIT", "dependencies": { "spdx-exceptions": "^2.1.0", @@ -10309,6 +11123,7 @@ "version": "3.0.23", "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.23.tgz", "integrity": "sha512-CWLcCCH7VLu13TgOH+r8p1O/Znwhqv/dbb6lqWy67G+pT1kHmeD/+V36AVb/vq8QMIQwVShJ6Ssl5FPh0fuSdw==", + "dev": true, "license": "CC0-1.0" }, "node_modules/srvx": { @@ -10327,11 +11142,19 @@ "version": "0.2.0", "resolved": "https://registry.npmjs.org/stable-hash-x/-/stable-hash-x-0.2.0.tgz", "integrity": "sha512-o3yWv49B/o4QZk5ZcsALc6t0+eCelPc44zZsLtCQnZPDwFpDYSWcDnrv2TtMmMbQ7uKo3J0HTURCqckw23czNQ==", + "dev": true, "license": "MIT", "engines": { "node": ">=12.0.0" } }, + "node_modules/stackback": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/stackback/-/stackback-0.0.2.tgz", + "integrity": "sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==", + "dev": true, + "license": "MIT" + }, "node_modules/standard-as-callback": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/standard-as-callback/-/standard-as-callback-2.1.0.tgz", @@ -10478,6 +11301,7 @@ "version": "4.1.1", "resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-4.1.1.tgz", "integrity": "sha512-SlyRoSkdh1dYP0PzclLE7r0M9sgbFKKMFXpFRUMNuKhQSbC6VQIGzq3E0qsfvGJaUFJPGv6Ws1NZ/haTAjfbMA==", + "dev": true, "license": "MIT", "engines": { "node": ">=12" @@ -10524,6 +11348,7 @@ "version": "1.1.0", "resolved": "https://registry.npmjs.org/super-regex/-/super-regex-1.1.0.tgz", "integrity": "sha512-WHkws2ZflZe41zj6AolvvmaTrWds/VuyeYr9iPVv/oQeaIoVxMKaushfFWpOGDT+GuBrM/sVqF8KUCYQlSSTdQ==", + "dev": true, "license": "MIT", "dependencies": { "function-timeout": "^1.0.1", @@ -10690,6 +11515,7 @@ "version": "5.1.0", "resolved": "https://registry.npmjs.org/time-span/-/time-span-5.1.0.tgz", "integrity": "sha512-75voc/9G4rDIJleOo4jPvN4/YC4GRZrY8yy1uU4lwrB3XEQbWve8zXoO5No4eFrGcTAMYyoY67p8jRQdtA1HbA==", + "dev": true, "license": "MIT", "dependencies": { "convert-hrtime": "^5.0.0" @@ -10707,6 +11533,16 @@ "integrity": "sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg==", "license": "MIT" }, + "node_modules/tinybench": { + "version": "6.1.4", + "resolved": "https://registry.npmjs.org/tinybench/-/tinybench-6.1.4.tgz", + "integrity": "sha512-9APumHG7r4yOk4X4WlkmE71aZcv1gvin1czO3OQ1U9iJcFA5Ja/ygyb0vPOVHTthFozUYs8CLoLUlM8grb2lTQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=20.0.0" + } + }, "node_modules/tinyclip": { "version": "0.1.15", "resolved": "https://registry.npmjs.org/tinyclip/-/tinyclip-0.1.15.tgz", @@ -10741,6 +11577,16 @@ "url": "https://github.com/sponsors/SuperchupuDev" } }, + "node_modules/tinyrainbow": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/tinyrainbow/-/tinyrainbow-3.1.1.tgz", + "integrity": "sha512-yau8yJdTt989Mm0Bd/236QnzEiPf2xLLTqUZRUJOo/3CB078LSwzei343DgtJVmfJKJE3TMINY1u42SQsP6mXw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, "node_modules/to-regex-range": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", @@ -10757,6 +11603,7 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/to-valid-identifier/-/to-valid-identifier-1.0.0.tgz", "integrity": "sha512-41wJyvKep3yT2tyPqX/4blcfybknGB4D+oETKLs7Q76UiPqRpUJK3hr1nxelyYO0PHKVzJwlu0aCeEAsGI6rpw==", + "dev": true, "license": "MIT", "dependencies": { "@sindresorhus/base62": "^1.0.0", @@ -10797,6 +11644,7 @@ "version": "2.5.0", "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.5.0.tgz", "integrity": "sha512-OJ/ibxhPlqrMM0UiNHJ/0CKQkoKF243/AEmplt3qpRgkW8VG7IfOS41h7V8TjITqdByHzrjcS/2si+y4lIh8NA==", + "dev": true, "license": "MIT", "engines": { "node": ">=18.12" @@ -10809,6 +11657,7 @@ "version": "2.8.1", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "dev": true, "license": "0BSD", "optional": true }, @@ -10816,6 +11665,7 @@ "version": "0.4.0", "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", + "devOptional": true, "license": "MIT", "dependencies": { "prelude-ls": "^1.2.1" @@ -10843,6 +11693,7 @@ "version": "5.9.3", "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "devOptional": true, "license": "Apache-2.0", "bin": { "tsc": "bin/tsc", @@ -10909,6 +11760,7 @@ "version": "8.9.0", "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-8.9.0.tgz", "integrity": "sha512-KTDyRTYX8sWmKXAikPHHSyc63CRPETMctyjKFupcC6OBLXT3xsN0e9aF7m+mIXutFWpUXuedtowG7iLOzp0kQg==", + "devOptional": true, "license": "MIT" }, "node_modules/unenv": { @@ -11083,6 +11935,7 @@ "version": "1.12.2", "resolved": "https://registry.npmjs.org/unrs-resolver/-/unrs-resolver-1.12.2.tgz", "integrity": "sha512-dmlRxBJJayXjqTwC+JtF1HhJmgf3ftQ3YejFcZrf4+KKtJv0qDsK1pjqaaVjG7wJ5NJ6UVP1OqRMQ71Z4C3rxQ==", + "dev": true, "hasInstallScript": true, "license": "MIT", "dependencies": { @@ -11318,6 +12171,7 @@ "version": "4.4.1", "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "devOptional": true, "license": "BSD-2-Clause", "dependencies": { "punycode": "^2.1.0" @@ -11641,6 +12495,99 @@ "@types/estree": "^1.0.0" } }, + "node_modules/vitest": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/vitest/-/vitest-5.0.0.tgz", + "integrity": "sha512-gpsMNoRhMjMktVxPtstOH4/PJuPyovVaMDr4oDilXaGH1EcqM2OE96SoHT2VIQ6fTGtTjqmHDrEu2X9RQiXf8Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/chai": "^5.2.2", + "@vitest/mocker": "5.0.0", + "chai": "^6.2.2", + "es-module-lexer": "^2.3.2", + "expect-type": "^1.4.0", + "magic-string": "^1.2.3", + "obug": "^2.1.4", + "picomatch": "^4.0.7", + "std-env": "^4.2.0", + "tinybench": "6.1.4", + "tinyexec": "1.3.0", + "tinyglobby": "^0.2.17", + "why-is-node-running": "^2.3.0" + }, + "bin": { + "vitest": "vitest.mjs" + }, + "engines": { + "node": "^22.12.0 || ^24.0.0 || >=26.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "@edge-runtime/vm": "*", + "@opentelemetry/api": "^1.9.0", + "@types/node": "^22.0.0 || >=24.0.0", + "@vitest/browser-playwright": "5.0.0", + "@vitest/browser-preview": "5.0.0", + "@vitest/browser-webdriverio": "^5.0.0-beta.5 || >=5.0.0", + "@vitest/coverage-istanbul": "5.0.0", + "@vitest/coverage-v8": "5.0.0", + "@vitest/ui": "5.0.0", + "happy-dom": "*", + "jsdom": "*", + "vite": "^6.4.0 || ^7.0.0 || ^8.0.0" + }, + "peerDependenciesMeta": { + "@edge-runtime/vm": { + "optional": true + }, + "@opentelemetry/api": { + "optional": true + }, + "@types/node": { + "optional": true + }, + "@vitest/browser-playwright": { + "optional": true + }, + "@vitest/browser-preview": { + "optional": true + }, + "@vitest/browser-webdriverio": { + "optional": true + }, + "@vitest/coverage-istanbul": { + "optional": true + }, + "@vitest/coverage-v8": { + "optional": true + }, + "@vitest/ui": { + "optional": true + }, + "happy-dom": { + "optional": true + }, + "jsdom": { + "optional": true + }, + "vite": { + "optional": false + } + } + }, + "node_modules/vitest-environment-nuxt": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/vitest-environment-nuxt/-/vitest-environment-nuxt-2.0.0.tgz", + "integrity": "sha512-zEGFRiCAaRR3fHnqISHKMNTRvCzkQEI1XyFeqNgR2IBD0oYkfZ1rUHwi7C+h3Cns3KPykfB0av1B3MtLEbChDw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nuxt/test-utils": "^4.0.0" + } + }, "node_modules/vscode-uri": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/vscode-uri/-/vscode-uri-3.2.0.tgz", @@ -11649,16 +12596,16 @@ "license": "MIT" }, "node_modules/vue": { - "version": "3.5.41", - "resolved": "https://registry.npmjs.org/vue/-/vue-3.5.41.tgz", - "integrity": "sha512-2laE0p+aK+/AOPG/XL/WepOs/GlK755LJ1XECi9kDUrz1FKNw8rb2Xzlw9JS1rqEV55nb0ttsKxVlTCcd+R5cg==", + "version": "3.5.42", + "resolved": "https://registry.npmjs.org/vue/-/vue-3.5.42.tgz", + "integrity": "sha512-4RyHQTbQvOPs3MfvUO1Sg0YRrKNnA0mAVtvpd12Tg1fKDN7OHBUl1IqSn8zGJjK9nI3NkNp8cgTpVrSZC5TTcA==", "license": "MIT", "dependencies": { - "@vue/compiler-dom": "3.5.41", - "@vue/compiler-sfc": "3.5.41", - "@vue/runtime-dom": "3.5.41", - "@vue/server-renderer": "3.5.41", - "@vue/shared": "3.5.41" + "@vue/compiler-dom": "3.5.42", + "@vue/compiler-sfc": "3.5.42", + "@vue/runtime-dom": "3.5.42", + "@vue/server-renderer": "3.5.42", + "@vue/shared": "3.5.42" }, "peerDependencies": { "typescript": "*" @@ -11694,6 +12641,7 @@ "version": "10.4.1", "resolved": "https://registry.npmjs.org/vue-eslint-parser/-/vue-eslint-parser-10.4.1.tgz", "integrity": "sha512-Gk6gRDj0n/fkRa3C3l0bBheoBckUq/Rs0F/TvMWIS6nzzx67amAViMe9CkNgsP2tXyQONvGiHQESHwFtZ3aYDA==", + "dev": true, "license": "MIT", "dependencies": { "debug": "^4.4.0", @@ -11856,6 +12804,7 @@ "version": "1.5.0", "resolved": "https://registry.npmjs.org/web-worker/-/web-worker-1.5.0.tgz", "integrity": "sha512-RiMReJrTAiA+mBjGONMnjVDP2u3p9R1vkcGz6gDIrOMT3oGuYwX2WRMYI9ipkphSuE5XKEhydbhNEJh4NY9mlw==", + "dev": true, "license": "Apache-2.0" }, "node_modules/webidl-conversions": { @@ -11870,6 +12819,16 @@ "integrity": "sha512-66/V2i5hQanC51vBQKPH4aI8NMAcBW59FVBs+rC7eGHupMyfn34q7rZIE+ETlJ+XTevqfUhVVBgSUNSW2flEUQ==", "license": "MIT" }, + "node_modules/whatwg-mimetype": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-3.0.0.tgz", + "integrity": "sha512-nt+N2dzIutVRxARx1nghPKGv1xHikU7HKdfafKkLNLindmPU/ch3U31NOCGGA/dmPcmb1VlofO0vnKAcsm0o/Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + } + }, "node_modules/whatwg-url": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", @@ -11895,10 +12854,28 @@ "node": "^20.17.0 || >=22.9.0" } }, + "node_modules/why-is-node-running": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/why-is-node-running/-/why-is-node-running-2.3.0.tgz", + "integrity": "sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==", + "dev": true, + "license": "MIT", + "dependencies": { + "siginfo": "^2.0.0", + "stackback": "0.0.2" + }, + "bin": { + "why-is-node-running": "cli.js" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/word-wrap": { "version": "1.2.5", "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==", + "devOptional": true, "license": "MIT", "engines": { "node": ">=0.10.0" @@ -12065,6 +13042,7 @@ "version": "5.0.0", "resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-5.0.0.tgz", "integrity": "sha512-EvGK8EJ3DhaHfbRlETOWAS5pO9MZITeauHKJyb8wyajUfQUenkIg2MvLDTZ4T/TgIcm3HU0TFBgWWboAZ30UHg==", + "dev": true, "license": "Apache-2.0", "engines": { "node": ">=18" @@ -12130,6 +13108,7 @@ "version": "1.2.2", "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-1.2.2.tgz", "integrity": "sha512-4LCcse/U2MHZ63HAJVE+v71o7yOdIe4cZ70Wpf8D/IyjDKYQLV5GD46B+hSTjJsvV5PztjvHoU580EftxjDZFQ==", + "dev": true, "license": "MIT", "engines": { "node": ">=12.20" @@ -12188,6 +13167,7 @@ "version": "4.6.2", "resolved": "https://registry.npmjs.org/zod/-/zod-4.6.2.tgz", "integrity": "sha512-lh5RCAGFa1Cm2hjtNwLQhSs/AsqdWnTQaBER9fEwN/88pSh7KOtJavtBx/0VlkN/uFd61SwYmljLMDAsHlvzBQ==", + "dev": true, "license": "MIT", "funding": { "url": "https://github.com/sponsors/colinhacks" diff --git a/frontend/package.json b/frontend/package.json index f311b96..fb2903c 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -9,7 +9,12 @@ "preview": "nuxt preview", "postinstall": "nuxt prepare", "typecheck": "nuxt typecheck", - "lint": "eslint ." + "lint": "eslint .", + "test:unit": "vitest run --config vitest.unit.config.ts", + "test:integration": "vitest run --config vitest.integration.config.ts", + "pretest:e2e": "ln -sfn ../frontend/node_modules ../tests/node_modules", + "test:e2e": "playwright test", + "test": "npm run test:unit && npm run test:integration && npm run test:e2e" }, "dependencies": { "marked": "^18.0.11", @@ -20,8 +25,14 @@ }, "devDependencies": { "@nuxt/eslint": "^1.17.0", + "@nuxt/test-utils": "^4.3.2", + "@playwright/test": "^1.63.0", + "@vitest/coverage-v8": "^5.0.0", + "@vue/test-utils": "^2.5.0", "eslint": "^10.10.0", + "happy-dom": "^20.14.3", "typescript": "^5.9.3", + "vitest": "^5.0.0", "vue-tsc": "^3.3.11" } } diff --git a/frontend/playwright.config.ts b/frontend/playwright.config.ts new file mode 100644 index 0000000..31f5958 --- /dev/null +++ b/frontend/playwright.config.ts @@ -0,0 +1,35 @@ +import { fileURLToPath } from 'node:url' +import { defineConfig, devices } from '@playwright/test' + +// Fixed ports so the PocketBase instance (started in global-setup.ts) and the +// built Nuxt server (started by Playwright's webServer below) can reference +// each other statically — see tests/e2e/global-setup.ts for why. +export const E2E_POCKETBASE_PORT = 8095 +const APP_PORT = 3100 +const BASE_URL = `http://127.0.0.1:${APP_PORT}` +const POCKETBASE_URL = `http://127.0.0.1:${E2E_POCKETBASE_PORT}` + +export default defineConfig({ + testDir: '../tests/e2e', + fullyParallel: false, + retries: 0, + reporter: [['list']], + use: { + baseURL: BASE_URL, + }, + projects: [{ name: 'chromium', use: { ...devices['Desktop Chrome'] } }], + globalSetup: fileURLToPath(new URL('../tests/e2e/global-setup.ts', import.meta.url)), + webServer: { + command: 'npm run build && node .output/server/index.mjs', + url: BASE_URL, + reuseExistingServer: false, + timeout: 120000, + env: { + HOST: '127.0.0.1', + PORT: String(APP_PORT), + NUXT_POCKETBASE_URL: POCKETBASE_URL, + NUXT_PUBLIC_POCKETBASE_URL: POCKETBASE_URL, + NUXT_PUBLIC_SITE_URL: BASE_URL, + }, + }, +}) diff --git a/frontend/vitest.integration.config.ts b/frontend/vitest.integration.config.ts new file mode 100644 index 0000000..e33529d --- /dev/null +++ b/frontend/vitest.integration.config.ts @@ -0,0 +1,13 @@ +import { defineConfig } from 'vitest/config' + +export default defineConfig({ + test: { + environment: 'node', + include: ['../tests/integration/**/*.test.ts'], + testTimeout: 30000, + hookTimeout: 60000, + // Integration tests boot a real Nuxt server + PocketBase instance; running + // several of those concurrently is wasteful, not parallel-safe by design. + fileParallelism: false, + }, +}) diff --git a/frontend/vitest.unit.config.ts b/frontend/vitest.unit.config.ts new file mode 100644 index 0000000..504ccaf --- /dev/null +++ b/frontend/vitest.unit.config.ts @@ -0,0 +1,8 @@ +import { defineVitestConfig } from '@nuxt/test-utils/config' + +export default defineVitestConfig({ + test: { + environment: 'nuxt', + include: ['../tests/unit/**/*.test.ts'], + }, +}) diff --git a/tests/e2e/admin-redirect.spec.ts b/tests/e2e/admin-redirect.spec.ts new file mode 100644 index 0000000..728ad29 --- /dev/null +++ b/tests/e2e/admin-redirect.spec.ts @@ -0,0 +1,11 @@ +import { expect, test } from '@playwright/test' + +test('/admin redirects to the real PocketBase dashboard', async ({ page }) => { + await page.goto('/admin') + + await expect(page).toHaveURL(/\/_\/(#.*)?$/) + // The static is "PocketBase"; the dashboard SPA then renders its + // login view and retitles the page — asserting the live title also proves + // the dashboard's own JS bundle loaded and ran correctly through the proxy. + await expect(page).toHaveTitle('Superuser login') +}) diff --git a/tests/e2e/article-detail.spec.ts b/tests/e2e/article-detail.spec.ts new file mode 100644 index 0000000..2cb8167 --- /dev/null +++ b/tests/e2e/article-detail.spec.ts @@ -0,0 +1,24 @@ +import { expect, test } from '@playwright/test' + +test('article detail renders body, breadcrumb and structured data', async ({ page }) => { + await page.goto('/blog/articolo-pubblicato') + + await expect(page.getByRole('heading', { level: 1, name: 'Articolo pubblicato' })).toBeVisible() + await expect(page.getByRole('link', { name: 'Economia' })).toHaveAttribute( + 'href', + '/category/economia' + ) + await expect(page.locator('.prose strong')).toHaveText('markdown') + + const jsonLd = await page.locator('script[type="application/ld+json"]').textContent() + const data = JSON.parse(jsonLd ?? '{}') + expect(data['@type']).toBe('BlogPosting') + expect(data.headline).toBe('Articolo pubblicato') +}) + +test('unknown article slug renders the 404 page', async ({ page }) => { + const response = await page.goto('/blog/non-esiste') + + expect(response?.status()).toBe(404) + await expect(page.getByRole('heading', { name: 'Pagina non trovata' })).toBeVisible() +}) diff --git a/tests/e2e/blog.spec.ts b/tests/e2e/blog.spec.ts new file mode 100644 index 0000000..8633293 --- /dev/null +++ b/tests/e2e/blog.spec.ts @@ -0,0 +1,9 @@ +import { expect, test } from '@playwright/test' + +test('blog archive lists only published articles', async ({ page }) => { + await page.goto('/blog') + + await expect(page.getByRole('link', { name: 'Secondo articolo pubblicato' })).toBeVisible() + await expect(page.getByRole('link', { name: 'Articolo pubblicato', exact: true })).toBeVisible() + await expect(page.getByText('Articolo in bozza')).toHaveCount(0) +}) diff --git a/tests/e2e/category.spec.ts b/tests/e2e/category.spec.ts new file mode 100644 index 0000000..c53e78e --- /dev/null +++ b/tests/e2e/category.spec.ts @@ -0,0 +1,14 @@ +import { expect, test } from '@playwright/test' + +test('category page filters articles to that category only', async ({ page }) => { + await page.goto('/category/attualita') + + await expect(page.getByRole('link', { name: 'Secondo articolo pubblicato' })).toBeVisible() + await expect(page.getByRole('link', { name: 'Articolo pubblicato', exact: true })).toHaveCount(0) +}) + +test('unknown category slug 404s', async ({ page }) => { + const response = await page.goto('/category/non-esiste') + + expect(response?.status()).toBe(404) +}) diff --git a/tests/e2e/global-setup.ts b/tests/e2e/global-setup.ts new file mode 100644 index 0000000..fb9374d --- /dev/null +++ b/tests/e2e/global-setup.ts @@ -0,0 +1,18 @@ +import { startTestPocketbase } from '../support/pocketbase' +import { E2E_POCKETBASE_PORT } from '../../frontend/playwright.config' + +/** + * Starts one PocketBase instance, shared by the whole e2e run, on the fixed + * port `playwright.config.ts` also bakes into the Nuxt server's env — so both + * sides can reference it without needing an async hand-off between them. + * + * Playwright keeps this module's closure alive and calls the returned + * function as teardown once all tests finish (no separate globalTeardown + * file needed). + */ +export default async function globalSetup() { + const pb = await startTestPocketbase({ port: E2E_POCKETBASE_PORT }) + return async () => { + await pb.stop() + } +} diff --git a/tests/e2e/home.spec.ts b/tests/e2e/home.spec.ts new file mode 100644 index 0000000..173ecee --- /dev/null +++ b/tests/e2e/home.spec.ts @@ -0,0 +1,11 @@ +import { expect, test } from '@playwright/test' + +test('home page shows the latest published article as featured', async ({ page }) => { + await page.goto('/') + + await expect(page.getByRole('heading', { name: 'Secondo articolo pubblicato' })).toBeVisible() + await expect(page.getByRole('link', { name: "Leggi l'ultimo articolo" })).toHaveAttribute( + 'href', + '/blog/secondo-articolo' + ) +}) diff --git a/tests/integration/api.test.ts b/tests/integration/api.test.ts new file mode 100644 index 0000000..4d43065 --- /dev/null +++ b/tests/integration/api.test.ts @@ -0,0 +1,91 @@ +import { fileURLToPath } from 'node:url' +import { afterAll, describe, expect, it } from 'vitest' +import { $fetch, fetch, setup } from '@nuxt/test-utils/e2e' +import { startTestPocketbase } from '../support/pocketbase' +import type { Article, ArticleSummary, Category, Paginated } from '../../frontend/shared/types/blog' + +// setup() registers its own beforeAll/afterAll hooks, so — like startTestPocketbase() +// below — it must run at module scope (top-level await), not nested inside another +// hook: Vitest only picks up hooks registered during the initial collection phase. +const pb = await startTestPocketbase() + +await setup({ + rootDir: fileURLToPath(new URL('../../frontend', import.meta.url)), + server: true, + nuxtConfig: { + runtimeConfig: { + pocketbaseUrl: pb.url, + public: { pocketbaseUrl: pb.url }, + }, + }, +}) + +async function expectStatus(promise: Promise<unknown>, statusCode: number) { + const error = await promise.catch((caught) => caught) + expect(error).toBeDefined() + expect(error?.statusCode ?? error?.response?.status).toBe(statusCode) +} + +afterAll(async () => { + await pb.stop() +}) + +describe('GET /content/articles', () => { + it('lists only published articles, newest first', async () => { + const response = await $fetch<Paginated<ArticleSummary>>('/content/articles') + expect(response.items.map((a) => a.slug)).toEqual(['secondo-articolo', 'articolo-pubblicato']) + expect(response.total).toBe(2) + }) + + it('filters by category slug', async () => { + const response = await $fetch<Paginated<ArticleSummary>>('/content/articles', { + query: { category: 'attualita' }, + }) + expect(response.items.map((a) => a.slug)).toEqual(['secondo-articolo']) + }) +}) + +describe('GET /content/articles/:slug', () => { + it('returns the full article with rendered html, summary and author', async () => { + const article = await $fetch<Article>('/content/articles/articolo-pubblicato') + expect(article.title).toBe('Articolo pubblicato') + expect(article.html).toContain('<strong>markdown</strong>') + expect(article.summary.length).toBeGreaterThan(0) + expect(article.author).toBe('Redazione') + expect(article.category?.slug).toBe('economia') + }) + + it('404s for an unknown slug', async () => { + await expectStatus($fetch('/content/articles/does-not-exist'), 404) + }) + + it('404s for a draft (unpublished) slug', async () => { + await expectStatus($fetch('/content/articles/articolo-bozza'), 404) + }) +}) + +describe('GET /content/categories', () => { + it('lists categories sorted by name', async () => { + const categories = await $fetch<Category[]>('/content/categories') + expect(categories.map((c) => c.name)).toEqual(['Attualità', 'Economia']) + }) +}) + +describe('GET /content/categories/:slug', () => { + it('returns one category by slug', async () => { + const category = await $fetch<Category>('/content/categories/economia') + expect(category.name).toBe('Economia') + }) + + it('404s for an unknown slug', async () => { + await expectStatus($fetch('/content/categories/does-not-exist'), 404) + }) +}) + +describe('GET /admin', () => { + it('redirects to the PocketBase dashboard', async () => { + const response = await fetch('/admin', { redirect: 'manual' }) + expect(response.status).toBe(302) + expect(response.headers.get('location')).toBe(`${pb.url}/_/`) + }) +}) diff --git a/tests/package.json b/tests/package.json new file mode 100644 index 0000000..3dbc1ca --- /dev/null +++ b/tests/package.json @@ -0,0 +1,3 @@ +{ + "type": "module" +} diff --git a/tests/support/fixtures.ts b/tests/support/fixtures.ts new file mode 100644 index 0000000..59f8a1a --- /dev/null +++ b/tests/support/fixtures.ts @@ -0,0 +1,47 @@ +/** Shared seed data for integration and e2e tests against a real ephemeral PocketBase. */ + +export interface FixtureCategory { + name: string + slug: string +} + +export interface FixtureArticle { + title: string + slug: string + content: string + category: string + /** `null` = draft (never returned by the public API). */ + publishedAt: string | null + authorName?: string +} + +export const FIXTURE_CATEGORIES: FixtureCategory[] = [ + { name: 'Economia', slug: 'economia' }, + { name: 'Attualità', slug: 'attualita' }, +] + +export const FIXTURE_ARTICLES: FixtureArticle[] = [ + { + title: 'Articolo pubblicato', + slug: 'articolo-pubblicato', + content: '# Titolo\n\nCorpo **markdown** di prova con [un link](https://example.com).', + category: 'economia', + publishedAt: '2025-01-15 10:00:00', + authorName: 'Redazione', + }, + { + title: 'Articolo in bozza', + slug: 'articolo-bozza', + content: 'Contenuto non ancora pubblicato.', + category: 'economia', + publishedAt: null, + }, + { + title: 'Secondo articolo pubblicato', + slug: 'secondo-articolo', + content: 'Un altro articolo pubblicato, in un\'altra categoria.', + category: 'attualita', + publishedAt: '2025-02-01 08:00:00', + authorName: 'Redazione', + }, +] diff --git a/tests/support/pocketbase.ts b/tests/support/pocketbase.ts new file mode 100644 index 0000000..088a947 --- /dev/null +++ b/tests/support/pocketbase.ts @@ -0,0 +1,165 @@ +import { execFileSync, spawn } from 'node:child_process' +import { existsSync, mkdirSync, mkdtempSync, rmSync, writeFileSync } from 'node:fs' +import { createServer } from 'node:net' +import { tmpdir } from 'node:os' +import { join } from 'node:path' +import { fileURLToPath } from 'node:url' +import { FIXTURE_ARTICLES, FIXTURE_CATEGORIES } from './fixtures' + +// Keep in sync with pocketbase/Dockerfile's PB_VERSION build arg. +const PB_VERSION = '0.40.3' + +const REPO_ROOT = fileURLToPath(new URL('../../', import.meta.url)) +const MIGRATIONS_DIR = join(REPO_ROOT, 'pocketbase', 'pb_migrations') +const BIN_DIR = join(REPO_ROOT, '.cache', 'pocketbase') +const BIN_PATH = join(BIN_DIR, 'pocketbase') + +const TEST_ADMIN_EMAIL = 'test-admin@example.com' +const TEST_ADMIN_PASSWORD = 'test-password-not-for-production' + +async function ensureBinary(): Promise<string> { + if (existsSync(BIN_PATH)) return BIN_PATH + + mkdirSync(BIN_DIR, { recursive: true }) + const platform = process.platform === 'darwin' ? 'darwin' : 'linux' + const arch = process.arch === 'arm64' ? 'arm64' : 'amd64' + const downloadUrl = `https://github.com/pocketbase/pocketbase/releases/download/v${PB_VERSION}/pocketbase_${PB_VERSION}_${platform}_${arch}.zip` + + const response = await fetch(downloadUrl) + if (!response.ok) { + throw new Error(`Failed to download PocketBase test binary: ${response.status} ${downloadUrl}`) + } + + const zipPath = join(BIN_DIR, 'pocketbase.zip') + writeFileSync(zipPath, Buffer.from(await response.arrayBuffer())) + execFileSync('unzip', ['-o', zipPath, 'pocketbase', '-d', BIN_DIR]) + rmSync(zipPath) + execFileSync('chmod', ['+x', BIN_PATH]) + + return BIN_PATH +} + +function findFreePort(): Promise<number> { + return new Promise((resolve, reject) => { + const server = createServer() + server.unref() + server.on('error', reject) + server.listen(0, '127.0.0.1', () => { + const address = server.address() + if (address && typeof address === 'object') { + server.close(() => resolve(address.port)) + } else { + reject(new Error('Could not determine a free port')) + } + }) + }) +} + +async function waitForHealth(url: string, timeoutMs = 15000): Promise<void> { + const deadline = Date.now() + timeoutMs + while (Date.now() < deadline) { + try { + const response = await fetch(`${url}/api/health`) + if (response.ok) return + } catch { + // Not listening yet. + } + await new Promise((resolve) => setTimeout(resolve, 200)) + } + throw new Error(`PocketBase did not become healthy within ${timeoutMs}ms`) +} + +export interface TestArticle { + id: string + slug: string + publishedAt: string +} + +export interface TestCategory { + id: string + name: string + slug: string +} + +export interface TestPocketbase { + url: string + categories: TestCategory[] + articles: TestArticle[] + stop(): Promise<void> +} + +export interface StartOptions { + /** Fixed port, for setups (e2e) that need it known ahead of time. Random by default. */ + port?: number +} + +/** + * Spawns a real, disposable PocketBase instance — using `pocketbase/pb_migrations/` + * directly, so tests run against the exact schema/rules that ship to production — + * then seeds it with FIXTURE_CATEGORIES/FIXTURE_ARTICLES. + */ +export async function startTestPocketbase(options: StartOptions = {}): Promise<TestPocketbase> { + const bin = await ensureBinary() + const dataDir = mkdtempSync(join(tmpdir(), 'pb-test-')) + const port = options.port ?? (await findFreePort()) + const url = `http://127.0.0.1:${port}` + + const proc = spawn( + bin, + ['serve', `--http=127.0.0.1:${port}`, `--dir=${dataDir}`, `--migrationsDir=${MIGRATIONS_DIR}`], + { stdio: 'ignore' } + ) + + const crashed = new Promise<never>((_, reject) => { + proc.on('exit', (code) => reject(new Error(`PocketBase exited early with code ${code}`))) + }) + + await Promise.race([waitForHealth(url), crashed]) + + execFileSync(bin, ['superuser', 'upsert', TEST_ADMIN_EMAIL, TEST_ADMIN_PASSWORD, `--dir=${dataDir}`]) + + const authResponse = await fetch(`${url}/api/collections/_superusers/auth-with-password`, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ identity: TEST_ADMIN_EMAIL, password: TEST_ADMIN_PASSWORD }), + }) + const { token } = (await authResponse.json()) as { token: string } + + const categories: TestCategory[] = [] + for (const category of FIXTURE_CATEGORIES) { + const response = await fetch(`${url}/api/collections/categories/records`, { + method: 'POST', + headers: { Authorization: token, 'Content-Type': 'application/json' }, + body: JSON.stringify(category), + }) + categories.push((await response.json()) as TestCategory) + } + + const articles: TestArticle[] = [] + for (const article of FIXTURE_ARTICLES) { + const category = categories.find((c) => c.slug === article.category) + const response = await fetch(`${url}/api/collections/articles/records`, { + method: 'POST', + headers: { Authorization: token, 'Content-Type': 'application/json' }, + body: JSON.stringify({ + title: article.title, + slug: article.slug, + content: article.content, + category: category?.id, + publishedAt: article.publishedAt ?? '', + authorName: article.authorName ?? '', + }), + }) + articles.push((await response.json()) as TestArticle) + } + + return { + url, + categories, + articles, + async stop() { + proc.kill() + rmSync(dataDir, { recursive: true, force: true }) + }, + } +} diff --git a/tests/unit/ArticleCard.test.ts b/tests/unit/ArticleCard.test.ts new file mode 100644 index 0000000..7cf7f48 --- /dev/null +++ b/tests/unit/ArticleCard.test.ts @@ -0,0 +1,45 @@ +import { describe, expect, it } from 'vitest' +import { mountSuspended } from '@nuxt/test-utils/runtime' +import ArticleCard from '~/components/ArticleCard.vue' +import type { ArticleSummary } from '#shared/types/blog' + +const baseArticle: ArticleSummary = { + title: 'Titolo di prova', + slug: 'titolo-di-prova', + publishedAt: '2025-01-15T10:00:00.000Z', + cover: null, + category: { name: 'Economia', slug: 'economia' }, +} + +describe('ArticleCard', () => { + it('renders title, category and date, with no cover link when cover is null', async () => { + const wrapper = await mountSuspended(ArticleCard, { props: { article: baseArticle } }) + expect(wrapper.text()).toContain('Titolo di prova') + expect(wrapper.text()).toContain('Economia') + expect(wrapper.find('.cover-link').exists()).toBe(false) + }) + + it('renders the cover image with alt text and no width/height attributes', async () => { + const article: ArticleSummary = { + ...baseArticle, + cover: { url: '/api/files/articles/abc/cover.jpg', alt: 'Testo alternativo' }, + } + const wrapper = await mountSuspended(ArticleCard, { props: { article } }) + const img = wrapper.find('img.cover') + + expect(img.exists()).toBe(true) + expect(img.attributes('alt')).toBe('Testo alternativo') + expect(img.attributes('src')).toContain('/api/files/articles/abc/cover.jpg') + expect(img.attributes('width')).toBeUndefined() + expect(img.attributes('height')).toBeUndefined() + }) + + it('falls back to an empty alt when the cover has none', async () => { + const article: ArticleSummary = { + ...baseArticle, + cover: { url: '/api/files/articles/abc/cover.jpg', alt: null }, + } + const wrapper = await mountSuspended(ArticleCard, { props: { article } }) + expect(wrapper.find('img.cover').attributes('alt')).toBe('') + }) +}) diff --git a/tests/unit/format.utils.test.ts b/tests/unit/format.utils.test.ts new file mode 100644 index 0000000..755a492 --- /dev/null +++ b/tests/unit/format.utils.test.ts @@ -0,0 +1,37 @@ +import { describe, expect, it } from 'vitest' +import { formatDate, formatDateTime, isoDate } from '../../frontend/shared/utils/format' + +const SAMPLE = '2025-01-15T10:30:00.000Z' + +describe('formatDate', () => { + it('formats a date in long it-IT style', () => { + expect(formatDate(SAMPLE)).toBe('15 gennaio 2025') + }) + + it('returns an empty string when unset', () => { + expect(formatDate(null)).toBe('') + expect(formatDate(undefined)).toBe('') + }) +}) + +describe('formatDateTime', () => { + it('includes both date and time', () => { + const result = formatDateTime(SAMPLE) + expect(result).toContain('15 gennaio 2025') + }) + + it('returns an empty string when unset', () => { + expect(formatDateTime(null)).toBe('') + }) +}) + +describe('isoDate', () => { + it('returns an ISO 8601 string', () => { + expect(isoDate(SAMPLE)).toBe(new Date(SAMPLE).toISOString()) + }) + + it('returns an empty string when unset', () => { + expect(isoDate(null)).toBe('') + expect(isoDate(undefined)).toBe('') + }) +}) diff --git a/tests/unit/pocketbase.utils.test.ts b/tests/unit/pocketbase.utils.test.ts new file mode 100644 index 0000000..6fc84c0 --- /dev/null +++ b/tests/unit/pocketbase.utils.test.ts @@ -0,0 +1,66 @@ +import { describe, expect, it } from 'vitest' +import { renderMarkdown, summarise, toMediaImage } from '../../frontend/server/utils/pocketbase' + +describe('renderMarkdown', () => { + it('returns an empty string for null/undefined/empty input', () => { + expect(renderMarkdown(null)).toBe('') + expect(renderMarkdown(undefined)).toBe('') + expect(renderMarkdown('')).toBe('') + }) + + it('converts Markdown to HTML', () => { + const html = renderMarkdown('# Titolo\n\nCorpo **grassetto**.') + expect(html).toContain('<h1>Titolo</h1>') + expect(html).toContain('<strong>grassetto</strong>') + }) +}) + +describe('summarise', () => { + it('returns short text unchanged, stripped of Markdown syntax', () => { + expect(summarise('# Titolo\n\nTesto breve.')).toBe('Titolo Testo breve.') + }) + + it('strips code fences, images and unwraps links', () => { + const source = '```js\nconst x = 1\n```\n![alt](img.png) [testo](https://example.com) fine.' + expect(summarise(source)).toBe('testo fine.') + }) + + it('clips long text on a word boundary with an ellipsis', () => { + const source = 'parola '.repeat(40).trim() + const result = summarise(source, 20) + expect(result.length).toBeLessThanOrEqual(21) + expect(result.endsWith('…')).toBe(true) + expect(result).not.toContain(' …') + }) + + it('returns an empty string for null/undefined input', () => { + expect(summarise(null)).toBe('') + expect(summarise(undefined)).toBe('') + }) +}) + +describe('toMediaImage', () => { + it('returns null when there is no filename', () => { + expect(toMediaImage('articles', 'abc123', '', 'alt')).toBeNull() + expect(toMediaImage('articles', 'abc123', null, 'alt')).toBeNull() + expect(toMediaImage('articles', 'abc123', undefined, 'alt')).toBeNull() + }) + + it('builds the PocketBase file URL from collection, id and filename', () => { + expect(toMediaImage('articles', 'abc123', 'cover.jpg', 'Testo alternativo')).toEqual({ + url: '/api/files/articles/abc123/cover.jpg', + alt: 'Testo alternativo', + }) + }) + + it('normalises a missing/empty alt to null', () => { + expect(toMediaImage('articles', 'abc123', 'cover.jpg', null)).toEqual({ + url: '/api/files/articles/abc123/cover.jpg', + alt: null, + }) + expect(toMediaImage('articles', 'abc123', 'cover.jpg', '')).toEqual({ + url: '/api/files/articles/abc123/cover.jpg', + alt: null, + }) + }) +}) diff --git a/tests/unit/queries.utils.test.ts b/tests/unit/queries.utils.test.ts new file mode 100644 index 0000000..cb0e05f --- /dev/null +++ b/tests/unit/queries.utils.test.ts @@ -0,0 +1,36 @@ +import { describe, expect, it } from 'vitest' +import { PAGE_SIZE, pageParam, pagination, quote } from '../../frontend/server/utils/queries' + +describe('pagination', () => { + it('defaults to PAGE_SIZE', () => { + expect(pagination(2)).toBe(`page=2&perPage=${PAGE_SIZE}`) + }) + + it('accepts a custom page size', () => { + expect(pagination(1, 5)).toBe('page=1&perPage=5') + }) +}) + +describe('pageParam', () => { + it('accepts a positive integer', () => { + expect(pageParam('3')).toBe(3) + expect(pageParam(3)).toBe(3) + }) + + it.each([undefined, null, '', 'abc', '0', '-1', '1.5', NaN])( + 'falls back to 1 for invalid input %p', + (value) => { + expect(pageParam(value)).toBe(1) + } + ) +}) + +describe('quote', () => { + it('wraps a plain value in double quotes', () => { + expect(quote('economia')).toBe('"economia"') + }) + + it('escapes embedded double quotes', () => { + expect(quote('la "crisi"')).toBe('"la \\"crisi\\""') + }) +}) diff --git a/tests/unit/useMediaUrl.test.ts b/tests/unit/useMediaUrl.test.ts new file mode 100644 index 0000000..d0e116b --- /dev/null +++ b/tests/unit/useMediaUrl.test.ts @@ -0,0 +1,21 @@ +import { describe, expect, it } from 'vitest' + +describe('useMediaUrl', () => { + it('returns null for a null or missing image', () => { + const mediaUrl = useMediaUrl() + expect(mediaUrl(null)).toBeNull() + expect(mediaUrl(undefined)).toBeNull() + }) + + it('prefixes a relative url with the configured PocketBase origin', () => { + const mediaUrl = useMediaUrl() + const result = mediaUrl({ url: '/api/files/articles/abc/cover.jpg', alt: null }) + expect(result).toBe('http://localhost:8090/api/files/articles/abc/cover.jpg') + }) + + it('leaves an already-absolute url unchanged', () => { + const mediaUrl = useMediaUrl() + const result = mediaUrl({ url: 'https://cdn.example.com/cover.jpg', alt: null }) + expect(result).toBe('https://cdn.example.com/cover.jpg') + }) +})