diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index c514e094..21b2c855 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -120,6 +120,9 @@ jobs: - name: Run tests (content-translator) run: cd content-translator && pnpm test + - name: Run tests (pages) + run: cd pages && pnpm test:unit + - name: Run tests (vercel-deployments) run: cd vercel-deployments && pnpm test diff --git a/CLAUDE.md b/CLAUDE.md index 46281563..19205410 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -56,7 +56,7 @@ Every test must justify its existence by verifying meaningful behavior. A test e Default to integration tests against real Payload / real React rendering. Reach for unit tests only for pure logic with non-trivial branches. Only stub external boundaries: network, filesystem, time, randomness, LLM providers, third-party SDKs. If a test requires mocking the module under test's close collaborators, test at a higher level instead. -Name each test by the behavior it protects in one sentence (*"rejects a confirmation when the tool call id is unknown"*), not by the method it calls (*"calls handleConfirm with false"*). +Name each test by the behavior it protects in one sentence (_"rejects a confirmation when the tool call id is unknown"_), not by the method it calls (_"calls handleConfirm with false"_). ## Changelog diff --git a/alt-text/CHANGELOG.md b/alt-text/CHANGELOG.md index 51cf2f00..31472fea 100644 --- a/alt-text/CHANGELOG.md +++ b/alt-text/CHANGELOG.md @@ -1,5 +1,10 @@ # Changelog +## Unreleased + +- fix: respect a user-customized `routes.api` in the generate and bulk-generate buttons (the fetch previously hardcoded `/api/alt-text-plugin/...`) +- refactor: use Payload's `formatAdminURL` helper when linking from the health widget to collection lists + ## 0.4.4 - style: standardize icons to use Geist icon set (16x16 filled) diff --git a/alt-text/eslint.config.js b/alt-text/eslint.config.js index 71a819a6..4bc172b3 100644 --- a/alt-text/eslint.config.js +++ b/alt-text/eslint.config.js @@ -30,6 +30,15 @@ export default [ 'no-console': 'off', // TODO: remove this rule and use the Payload logger instead }, }, + { + // Test files use `vi.mock` to stub hooks like `useConfig`, `useLocale`, etc. + // The mock arrow functions share names with real hooks, so the rule flags them + // as "useless custom hooks" — but they are intentionally plain mocks. + files: ['src/**/*.test.ts', 'src/**/*.test.tsx'], + rules: { + '@eslint-react/hooks-extra/no-useless-custom-hooks': 'off', + }, + }, { ignores: defaultESLintIgnores, }, diff --git a/alt-text/package.json b/alt-text/package.json index ff0125ac..380cfe3e 100644 --- a/alt-text/package.json +++ b/alt-text/package.json @@ -18,15 +18,16 @@ "types": "./src/index.ts", "scripts": { "build": "pnpm copyfiles && pnpm build:types && pnpm build:swc", - "build:swc": "swc ./src -d ./dist --config-file .swcrc --strip-leading-paths", - "build:types": "tsc --outDir dist --rootDir ./src", + "build:swc": "swc ./src -d ./dist --config-file .swcrc --strip-leading-paths --ignore '**/*.test.ts,**/*.test.tsx'", + "build:types": "tsc --project tsconfig.build.json --outDir dist --rootDir ./src", "copyfiles": "copyfiles -u 1 \"src/**/*.{html,css,scss,ttf,woff,woff2,eot,svg,jpg,png,json}\" dist/", "clean": "rimraf --glob {dist,*.tsbuildinfo}", "dev": "tsc -w", "format": "prettier --write src \"*.{json,md,js,mjs,cjs}\"", "lint": "eslint src", "lint:fix": "eslint src --fix", - "test": "node --experimental-strip-types --test test/*.test.ts", + "test": "pnpm test:health && pnpm test:components", + "test:components": "vitest run", "test:health": "node --experimental-strip-types --test test/altTextHealth.test.ts", "prepublishOnly": "pnpm clean && pnpm build" }, @@ -48,13 +49,16 @@ "@payloadcms/translations": "^3.83.0", "@swc/cli": "^0.8.1", "@swc/core": "^1.15.26", + "@testing-library/react": "^16.3.2", "@types/react": "19.2.14", "@types/react-dom": "19.2.3", "copyfiles": "2.4.1", "eslint": "^9.0.0", + "jsdom": "^29.0.2", "prettier": "^3.8.3", "rimraf": "6.1.3", - "typescript": "5.9.3" + "typescript": "5.9.3", + "vitest": "^4.1.4" }, "files": [ "dist" diff --git a/alt-text/pnpm-lock.yaml b/alt-text/pnpm-lock.yaml index a84ef4f2..9b571de3 100644 --- a/alt-text/pnpm-lock.yaml +++ b/alt-text/pnpm-lock.yaml @@ -35,7 +35,7 @@ importers: devDependencies: '@payloadcms/eslint-config': specifier: ^3.28.0 - version: 3.28.0(@typescript-eslint/eslint-plugin@8.26.1(@typescript-eslint/parser@8.26.1(eslint@9.22.0)(typescript@5.9.3))(eslint@9.22.0)(typescript@5.9.3))(ts-api-utils@2.5.0(typescript@5.9.3)) + version: 3.28.0(@testing-library/dom@10.4.1)(@typescript-eslint/eslint-plugin@8.26.1(@typescript-eslint/parser@8.26.1(eslint@9.22.0)(typescript@5.9.3))(eslint@9.22.0)(typescript@5.9.3))(ts-api-utils@2.5.0(typescript@5.9.3)) '@payloadcms/translations': specifier: ^3.83.0 version: 3.83.0 @@ -45,6 +45,9 @@ importers: '@swc/core': specifier: ^1.15.26 version: 1.15.26 + '@testing-library/react': + specifier: ^16.3.2 + version: 16.3.2(@testing-library/dom@10.4.1)(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.5))(react@19.2.5) '@types/react': specifier: 19.2.14 version: 19.2.14 @@ -57,6 +60,9 @@ importers: eslint: specifier: ^9.0.0 version: 9.22.0 + jsdom: + specifier: ^29.0.2 + version: 29.0.2 prettier: specifier: ^3.8.3 version: 3.8.3 @@ -66,6 +72,9 @@ importers: typescript: specifier: 5.9.3 version: 5.9.3 + vitest: + specifier: ^4.1.4 + version: 4.1.4(@types/node@25.0.3)(jsdom@29.0.2)(vite@8.0.8(@types/node@25.0.3)(esbuild@0.27.3)(sass@1.77.4)(tsx@4.21.0)) dev: dependencies: @@ -156,6 +165,21 @@ packages: resolution: {integrity: sha512-60vepv88RwcJtSHrD6MjIL6Ta3SOYbgfnkHb+ppAVK+o9mXprRtulx7VlRl3lN3bbvysAfCS7WMVfhUYemB0IQ==} engines: {node: '>= 16'} + '@asamuzakjp/css-color@5.1.11': + resolution: {integrity: sha512-KVw6qIiCTUQhByfTd78h2yD1/00waTmm9uy/R7Ck/ctUyAPj+AEDLkQIdJW0T8+qGgj3j5bpNKK7Q3G+LedJWg==} + engines: {node: ^20.19.0 || ^22.12.0 || >=24.0.0} + + '@asamuzakjp/dom-selector@7.0.10': + resolution: {integrity: sha512-KyOb19eytNSELkmdqzZZUXWCU25byIlOld5qVFg0RYdS0T3tt7jeDByxk9hIAC73frclD8GKrHttr0SUjKCCdQ==} + engines: {node: ^20.19.0 || ^22.12.0 || >=24.0.0} + + '@asamuzakjp/generational-cache@1.0.1': + resolution: {integrity: sha512-wajfB8KqzMCN2KGNFdLkReeHncd0AslUSrvHVvvYWuU8ghncRJoA50kT3zP9MVL0+9g4/67H+cdvBskj9THPzg==} + engines: {node: ^20.19.0 || ^22.12.0 || >=24.0.0} + + '@asamuzakjp/nwsapi@2.3.9': + resolution: {integrity: sha512-n8GuYSrI9bF7FFZ/SjhwevlHc8xaVlb/7HmHelnc/PZXBD2ZR49NnN9sMMuDdEGPeeRQ5d0hqlSlEpgCX3Wl0Q==} + '@babel/code-frame@7.27.1': resolution: {integrity: sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==} engines: {node: '>=6.9.0'} @@ -204,6 +228,46 @@ packages: '@borewit/text-codec@0.2.2': resolution: {integrity: sha512-DDaRehssg1aNrH4+2hnj1B7vnUGEjU6OIlyRdkMd0aUdIUvKXrJfXsy8LVtXAy7DRvYVluWbMspsRhz2lcW0mQ==} + '@bramus/specificity@2.4.2': + resolution: {integrity: sha512-ctxtJ/eA+t+6q2++vj5j7FYX3nRu311q1wfYH3xjlLOsczhlhxAg2FWNUXhpGvAw3BWo1xBcvOV6/YLc2r5FJw==} + hasBin: true + + '@csstools/color-helpers@6.0.2': + resolution: {integrity: sha512-LMGQLS9EuADloEFkcTBR3BwV/CGHV7zyDxVRtVDTwdI2Ca4it0CCVTT9wCkxSgokjE5Ho41hEPgb8OEUwoXr6Q==} + engines: {node: '>=20.19.0'} + + '@csstools/css-calc@3.2.0': + resolution: {integrity: sha512-bR9e6o2BDB12jzN/gIbjHa5wLJ4UjD1CB9pM7ehlc0ddk6EBz+yYS1EV2MF55/HUxrHcB/hehAyt5vhsA3hx7w==} + engines: {node: '>=20.19.0'} + peerDependencies: + '@csstools/css-parser-algorithms': ^4.0.0 + '@csstools/css-tokenizer': ^4.0.0 + + '@csstools/css-color-parser@4.1.0': + resolution: {integrity: sha512-U0KhLYmy2GVj6q4T3WaAe6NPuFYCPQoE3b0dRGxejWDgcPp8TP7S5rVdM5ZrFaqu4N67X8YaPBw14dQSYx3IyQ==} + engines: {node: '>=20.19.0'} + peerDependencies: + '@csstools/css-parser-algorithms': ^4.0.0 + '@csstools/css-tokenizer': ^4.0.0 + + '@csstools/css-parser-algorithms@4.0.0': + resolution: {integrity: sha512-+B87qS7fIG3L5h3qwJ/IFbjoVoOe/bpOdh9hAjXbvx0o8ImEmUsGXN0inFOnk2ChCFgqkkGFQ+TpM5rbhkKe4w==} + engines: {node: '>=20.19.0'} + peerDependencies: + '@csstools/css-tokenizer': ^4.0.0 + + '@csstools/css-syntax-patches-for-csstree@1.1.3': + resolution: {integrity: sha512-SH60bMfrRCJF3morcdk57WklujF4Jr/EsQUzqkarfHXEFcAR1gg7fS/chAE922Sehgzc1/+Tz5H3Ypa1HiEKrg==} + peerDependencies: + css-tree: ^3.2.1 + peerDependenciesMeta: + css-tree: + optional: true + + '@csstools/css-tokenizer@4.0.0': + resolution: {integrity: sha512-QxULHAm7cNu72w97JUNCBFODFaXpbDg+dP8b/oWFAZ2MTRppA3U00Y2L1HqaS4J6yBqxwa/Y3nMBaxVKbB/NsA==} + engines: {node: '>=20.19.0'} + '@date-fns/tz@1.2.0': resolution: {integrity: sha512-LBrd7MiJZ9McsOgxqWX7AaxrDjcFVjWH/tIKJd7pnR7McaslGYOP1QmmiBXdJH/H/yLCT+rcQ7FaPBUxRGUtrg==} @@ -235,9 +299,18 @@ packages: peerDependencies: react: '>=16.8.0' + '@emnapi/core@1.9.2': + resolution: {integrity: sha512-UC+ZhH3XtczQYfOlu3lNEkdW/p4dsJ1r/bP7H8+rhao3TTTMO1ATq/4DdIi23XuGoFY+Cz0JmCbdVl0hz9jZcA==} + '@emnapi/runtime@1.7.1': resolution: {integrity: sha512-PVtJr5CmLwYAU9PZDMITZoR5iAOShYREoR45EyyLrbntV50mdePTgUn4AmOw90Ifcj+x2kRjdzr1HP3RrNiHGA==} + '@emnapi/runtime@1.9.2': + resolution: {integrity: sha512-3U4+MIWHImeyu1wnmVygh5WlgfYDtyf0k8AbLhMFxOipihf6nrWC4syIm/SwEeec0mNSafiiNnMJwbza/Is6Lw==} + + '@emnapi/wasi-threads@1.2.1': + resolution: {integrity: sha512-uTII7OYF+/Mes/MrcIOYp5yOtSMLBWSIoLPpcgwipoiKbli6k322tcoFsxoIIxPDqW01SQGAgko4EzZi2BNv2w==} + '@emotion/babel-plugin@11.13.5': resolution: {integrity: sha512-pxHCpT2ex+0q+HH91/zsdHkw/lXd468DIN2zvfvLtPKLLMo6gQj7oLObq8PhkrxOZb/gGCq03S3Z7PDhS8pduQ==} @@ -514,6 +587,15 @@ packages: resolution: {integrity: sha512-ZAoA40rNMPwSm+AeHpCq8STiNAwzWLJuP8Xv4CHIc9wv/PSuExjMrmjfYNj682vW0OOiZ1HKxzvjQr9XZIisQA==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + '@exodus/bytes@1.15.0': + resolution: {integrity: sha512-UY0nlA+feH81UGSHv92sLEPLCeZFjXOuHhrIo0HQydScuQc8s0A7kL/UdgwgDq8g8ilksmuoF35YVTNphV2aBQ==} + engines: {node: ^20.19.0 || ^22.12.0 || >=24.0.0} + peerDependencies: + '@noble/hashes': ^1.8.0 || ^2.0.0 + peerDependenciesMeta: + '@noble/hashes': + optional: true + '@faceless-ui/modal@3.0.0': resolution: {integrity: sha512-o3oEFsot99EQ8RJc1kL3s/nNMHX+y+WMXVzSSmca9L0l2MR6ez2QM1z1yIelJX93jqkLXQ9tW+R9tmsYa+O4Qg==} peerDependencies: @@ -867,6 +949,12 @@ packages: resolution: {integrity: sha512-xJIPs+bYuc9ASBl+cvGsKbGrJmS6fAKaSZCnT0lhahT5rhA2VVy9/EcIgd2JhtEuFOJNx7UHNn/qiTPTY4nrQw==} engines: {node: '>= 10'} + '@napi-rs/wasm-runtime@1.1.4': + resolution: {integrity: sha512-3NQNNgA1YSlJb/kMH1ildASP9HW7/7kYnRI2szWJaofaS1hWmbGI4H+d3+22aGzXXN9IJ+n+GiFVcGipJP18ow==} + peerDependencies: + '@emnapi/core': ^1.7.1 + '@emnapi/runtime': ^1.7.1 + '@next/env@15.4.11': resolution: {integrity: sha512-mIYp/091eYfPFezKX7ZPTWqrmSXq+ih6+LcUyKvLmeLQGhlPtot33kuEOd4U+xAA7sFfj21+OtCpIZx0g5SpvQ==} @@ -937,6 +1025,9 @@ packages: resolution: {integrity: sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==} engines: {node: '>= 8'} + '@oxc-project/types@0.124.0': + resolution: {integrity: sha512-VBFWMTBvHxS11Z5Lvlr3IWgrwhMTXV+Md+EQF0Xf60+wAdsGFTBx7X7K/hP4pi8N7dcm1RvcHwDxZ16Qx8keUg==} + '@payloadcms/db-mongodb@3.83.0': resolution: {integrity: sha512-/fDDg1x4H4d5v2JTPIqNBSSBQf3/ueWDfO6/Yp++/E7OP+3SRplWHe1Vg6ycuga7OREeXn4Dxyn9NJEC4qe+eg==} peerDependencies: @@ -978,6 +1069,104 @@ packages: '@pinojs/redact@0.4.0': resolution: {integrity: sha512-k2ENnmBugE/rzQfEcdWHcCY+/FM3VLzH9cYEsbdsoqrvzAKRhUZeRNhAZvB8OitQJ1TBed3yqWtdjzS6wJKBwg==} + '@rolldown/binding-android-arm64@1.0.0-rc.15': + resolution: {integrity: sha512-YYe6aWruPZDtHNpwu7+qAHEMbQ/yRl6atqb/AhznLTnD3UY99Q1jE7ihLSahNWkF4EqRPVC4SiR4O0UkLK02tA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [android] + + '@rolldown/binding-darwin-arm64@1.0.0-rc.15': + resolution: {integrity: sha512-oArR/ig8wNTPYsXL+Mzhs0oxhxfuHRfG7Ikw7jXsw8mYOtk71W0OkF2VEVh699pdmzjPQsTjlD1JIOoHkLP1Fg==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [darwin] + + '@rolldown/binding-darwin-x64@1.0.0-rc.15': + resolution: {integrity: sha512-YzeVqOqjPYvUbJSWJ4EDL8ahbmsIXQpgL3JVipmN+MX0XnXMeWomLN3Fb+nwCmP/jfyqte5I3XRSm7OfQrbyxw==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [darwin] + + '@rolldown/binding-freebsd-x64@1.0.0-rc.15': + resolution: {integrity: sha512-9Erhx956jeQ0nNTyif1+QWAXDRD38ZNjr//bSHrt6wDwB+QkAfl2q6Mn1k6OBPerznjRmbM10lgRb1Pli4xZPw==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [freebsd] + + '@rolldown/binding-linux-arm-gnueabihf@1.0.0-rc.15': + resolution: {integrity: sha512-cVwk0w8QbZJGTnP/AHQBs5yNwmpgGYStL88t4UIaqcvYJWBfS0s3oqVLZPwsPU6M0zlW4GqjP0Zq5MnAGwFeGA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm] + os: [linux] + + '@rolldown/binding-linux-arm64-gnu@1.0.0-rc.15': + resolution: {integrity: sha512-eBZ/u8iAK9SoHGanqe/jrPnY0JvBN6iXbVOsbO38mbz+ZJsaobExAm1Iu+rxa4S1l2FjG0qEZn4Rc6X8n+9M+w==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [linux] + libc: [glibc] + + '@rolldown/binding-linux-arm64-musl@1.0.0-rc.15': + resolution: {integrity: sha512-ZvRYMGrAklV9PEkgt4LQM6MjQX2P58HPAuecwYObY2DhS2t35R0I810bKi0wmaYORt6m/2Sm+Z+nFgb0WhXNcQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [linux] + libc: [musl] + + '@rolldown/binding-linux-ppc64-gnu@1.0.0-rc.15': + resolution: {integrity: sha512-VDpgGBzgfg5hLg+uBpCLoFG5kVvEyafmfxGUV0UHLcL5irxAK7PKNeC2MwClgk6ZAiNhmo9FLhRYgvMmedLtnQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [ppc64] + os: [linux] + libc: [glibc] + + '@rolldown/binding-linux-s390x-gnu@1.0.0-rc.15': + resolution: {integrity: sha512-y1uXY3qQWCzcPgRJATPSOUP4tCemh4uBdY7e3EZbVwCJTY3gLJWnQABgeUetvED+bt1FQ01OeZwvhLS2bpNrAQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [s390x] + os: [linux] + libc: [glibc] + + '@rolldown/binding-linux-x64-gnu@1.0.0-rc.15': + resolution: {integrity: sha512-023bTPBod7J3Y/4fzAN6QtpkSABR0rigtrwaP+qSEabUh5zf6ELr9Nc7GujaROuPY3uwdSIXWrvhn1KxOvurWA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [linux] + libc: [glibc] + + '@rolldown/binding-linux-x64-musl@1.0.0-rc.15': + resolution: {integrity: sha512-witB2O0/hU4CgfOOKUoeFgQ4GktPi1eEbAhaLAIpgD6+ZnhcPkUtPsoKKHRzmOoWPZue46IThdSgdo4XneOLYw==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [linux] + libc: [musl] + + '@rolldown/binding-openharmony-arm64@1.0.0-rc.15': + resolution: {integrity: sha512-UCL68NJ0Ud5zRipXZE9dF5PmirzJE4E4BCIOOssEnM7wLDsxjc6Qb0sGDxTNRTP53I6MZpygyCpY8Aa8sPfKPg==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [openharmony] + + '@rolldown/binding-wasm32-wasi@1.0.0-rc.15': + resolution: {integrity: sha512-ApLruZq/ig+nhaE7OJm4lDjayUnOHVUa77zGeqnqZ9pn0ovdVbbNPerVibLXDmWeUZXjIYIT8V3xkT58Rm9u5Q==} + engines: {node: '>=14.0.0'} + cpu: [wasm32] + + '@rolldown/binding-win32-arm64-msvc@1.0.0-rc.15': + resolution: {integrity: sha512-KmoUoU7HnN+Si5YWJigfTws1jz1bKBYDQKdbLspz0UaqjjFkddHsqorgiW1mxcAj88lYUE6NC/zJNwT+SloqtA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [win32] + + '@rolldown/binding-win32-x64-msvc@1.0.0-rc.15': + resolution: {integrity: sha512-3P2A8L+x75qavWLe/Dll3EYBJLQmtkJN8rfh+U/eR3MqMgL/h98PhYI+JFfXuDPgPeCB7iZAKiqii5vqOvnA0g==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [win32] + + '@rolldown/pluginutils@1.0.0-rc.15': + resolution: {integrity: sha512-UromN0peaE53IaBRe9W7CjrZgXl90fqGpK+mIZbA3qSTeYqg3pqpROBdIPvOG3F5ereDHNwoHBI2e50n1BDr1g==} + '@sec-ant/readable-stream@0.4.1': resolution: {integrity: sha512-831qok9r2t8AlxLko40y2ebgSDhenenCatLVeW/uBtnHPyhHOvG0C7TvfgecV+wHzIm5KUICgzmVpWS+IMEAeg==} @@ -989,6 +1178,9 @@ packages: resolution: {integrity: sha512-tlqY9xq5ukxTUZBmoOp+m61cqwQD5pHJtFY3Mn8CA8ps6yghLH/Hw8UPdqg4OLmFW3IFlcXnQNmo/dh8HzXYIQ==} engines: {node: '>=18'} + '@standard-schema/spec@1.1.0': + resolution: {integrity: sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==} + '@swc/cli@0.8.1': resolution: {integrity: sha512-L+ACCGHCiS0VqHVep/INLVnvRvJ2XooQFLZq4L8snhxw1jsqz+XRcY313UsyPVturPPE1shW3jic7rt3qEQTSQ==} engines: {node: '>= 20.19.0'} @@ -1096,6 +1288,25 @@ packages: '@swc/types@0.1.26': resolution: {integrity: sha512-lyMwd7WGgG79RS7EERZV3T8wMdmPq3xwyg+1nmAM64kIhx5yl+juO2PYIHb7vTiPgPCj8LYjsNV2T5wiQHUEaw==} + '@testing-library/dom@10.4.1': + resolution: {integrity: sha512-o4PXJQidqJl82ckFaXUeoAW+XysPLauYI43Abki5hABd853iMhitooc6znOnczgbTYmEP6U6/y1ZyKAIsvMKGg==} + engines: {node: '>=18'} + + '@testing-library/react@16.3.2': + resolution: {integrity: sha512-XU5/SytQM+ykqMnAnvB2umaJNIOsLF3PVv//1Ew4CTcpz0/BRyy/af40qqrt7SjKpDdT1saBMc42CUok5gaw+g==} + engines: {node: '>=18'} + peerDependencies: + '@testing-library/dom': ^10.0.0 + '@types/react': ^18.0.0 || ^19.0.0 + '@types/react-dom': ^18.0.0 || ^19.0.0 + react: ^18.0.0 || ^19.0.0 + react-dom: ^18.0.0 || ^19.0.0 + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + '@tokenizer/inflate@0.4.1': resolution: {integrity: sha512-2mAv+8pkG6GIZiF1kNg1jAjh27IDxEPKwdGul3snfztFerfPGI1LjDezZp3i7BElXompqEtPmoPx6c2wgtWsOA==} engines: {node: '>=18'} @@ -1103,9 +1314,21 @@ packages: '@tokenizer/token@0.3.0': resolution: {integrity: sha512-OvjF+z51L3ov0OyAU0duzsYuvO01PH7x4t6DJx+guahgTnBHkhJdG7soQeTSFLWN3efnHyibZ4Z8l2EuWwJN3A==} + '@tybys/wasm-util@0.10.1': + resolution: {integrity: sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg==} + + '@types/aria-query@5.0.4': + resolution: {integrity: sha512-rfT93uj5s0PRL7EzccGMs3brplhcrghnDoV26NqKhCAS1hVo+WdNsPvE/yb6ilfr5hi2MEk6d5EWJTKdxg8jVw==} + '@types/busboy@1.5.4': resolution: {integrity: sha512-kG7WrUuAKK0NoyxfQHsVE6j1m01s6kMma64E+OZenQABMQyTJop1DumUWcLwAQ2JzpefU7PDYoRDKl8uZosFjw==} + '@types/chai@5.2.3': + resolution: {integrity: sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA==} + + '@types/deep-eql@4.0.2': + resolution: {integrity: sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==} + '@types/doctrine@0.0.9': resolution: {integrity: sha512-eOIHzCUSH7SMfonMG1LsC2f8vxBFtho6NGBznK41R84YzPuvSBzrhEps33IsQiOW9+VL6NQ9DbjQJznk/S4uRA==} @@ -1243,6 +1466,35 @@ packages: resolution: {integrity: sha512-f1WO2Lx8a9t8DARmcWAUPJbu0G20bJlj8L4z72K00TMeJAoyLr/tHhI/pzYBLrR4dXWkcxO1cWYZEOX8DKHTqA==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + '@vitest/expect@4.1.4': + resolution: {integrity: sha512-iPBpra+VDuXmBFI3FMKHSFXp3Gx5HfmSCE8X67Dn+bwephCnQCaB7qWK2ldHa+8ncN8hJU8VTMcxjPpyMkUjww==} + + '@vitest/mocker@4.1.4': + resolution: {integrity: sha512-R9HTZBhW6yCSGbGQnDnH3QHfJxokKN4KB+Yvk9Q1le7eQNYwiCyKxmLmurSpFy6BzJanSLuEUDrD+j97Q+ZLPg==} + peerDependencies: + msw: ^2.4.9 + vite: ^6.0.0 || ^7.0.0 || ^8.0.0 + peerDependenciesMeta: + msw: + optional: true + vite: + optional: true + + '@vitest/pretty-format@4.1.4': + resolution: {integrity: sha512-ddmDHU0gjEUyEVLxtZa7xamrpIefdEETu3nZjWtHeZX4QxqJ7tRxSteHVXJOcr8jhiLoGAhkK4WJ3WqBpjx42A==} + + '@vitest/runner@4.1.4': + resolution: {integrity: sha512-xTp7VZ5aXP5ZJrn15UtJUWlx6qXLnGtF6jNxHepdPHpMfz/aVPx+htHtgcAL2mDXJgKhpoo2e9/hVJsIeFbytQ==} + + '@vitest/snapshot@4.1.4': + resolution: {integrity: sha512-MCjCFgaS8aZz+m5nTcEcgk/xhWv0rEH4Yl53PPlMXOZ1/Ka2VcZU6CJ+MgYCZbcJvzGhQRjVrGQNZqkGPttIKw==} + + '@vitest/spy@4.1.4': + resolution: {integrity: sha512-XxNdAsKW7C+FLydqFJLb5KhJtl3PGCMmYwFRfhvIgxJvLSXhhVI1zM8f1qD3Zg7RCjTSzDVyct6sghs9UEgBEQ==} + + '@vitest/utils@4.1.4': + resolution: {integrity: sha512-13QMT+eysM5uVGa1rG4kegGYNp6cnQcsTc67ELFbhNLQO+vgsygtYJx2khvdt4gVQqSSpC/KT5FZZxUpP3Oatw==} + '@xhmikosr/archive-type@8.0.1': resolution: {integrity: sha512-toXuiWChyfOpEiCPsIw6HGHaNji5LVkvB6EREL548vGWr+hGaehwxG4LzN20vm9aGFXwnA/Jty8yW2/SmV+1zQ==} engines: {node: '>=20'} @@ -1307,6 +1559,10 @@ packages: resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==} engines: {node: '>=8'} + ansi-styles@5.2.0: + resolution: {integrity: sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==} + engines: {node: '>=10'} + anymatch@3.1.3: resolution: {integrity: sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==} engines: {node: '>= 8'} @@ -1317,6 +1573,9 @@ packages: argparse@2.0.1: resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==} + aria-query@5.3.0: + resolution: {integrity: sha512-b0P0sZPKtyu8HkeRAfCq0IfURZK+SuwMjY1UXGBU27wpAiTwQAIlq56IbIO+ytk/JjS1fMR14ee5WBBfKi5J6A==} + aria-query@5.3.2: resolution: {integrity: sha512-COROpnaoap1E2F000S62r6A60uHZnmlvomhfyT2DlTcrY1OrBKn2UhH7qn5wTC9zMvD0AY7csdPSNwKP+7WiQw==} engines: {node: '>= 0.4'} @@ -1341,6 +1600,10 @@ packages: resolution: {integrity: sha512-BNoCY6SXXPQ7gF2opIP4GBE+Xw7U+pHMYKuzjgCN3GwiaIR09UUeKfheyIry77QtrCBlC0KK0q5/TER/tYh3PQ==} engines: {node: '>= 0.4'} + assertion-error@2.0.1: + resolution: {integrity: sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==} + engines: {node: '>=12'} + ast-types-flow@0.0.8: resolution: {integrity: sha512-OH/2E5Fg20h2aPrbe+QL8JZQFko0YZaF+j4mnQ7BGhfavO7OpSLa8a0y9sBwomHdSbkhTS8TQNayBfnW5DwbvQ==} @@ -1394,6 +1657,9 @@ packages: base64-js@1.5.1: resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==} + bidi-js@1.0.3: + resolution: {integrity: sha512-RKshQI1R3YQ+n9YJz2QQ147P66ELpa1FQEg20Dk8oW9t2KgLbpDLLp9aGZ7y8WHSshDknG0bknqGw5/tyCs5tw==} + binary-extensions@2.3.0: resolution: {integrity: sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==} engines: {node: '>=8'} @@ -1474,6 +1740,10 @@ packages: caniuse-lite@1.0.30001760: resolution: {integrity: sha512-7AAMPcueWELt1p3mi13HR/LHH0TJLT11cnwDJEs3xA4+CK/PLKeO9Kl1oru24htkyUKtkGCvAx4ohB0Ttry8Dw==} + chai@6.2.2: + resolution: {integrity: sha512-NUPRluOfOiTKBKvWPtSD4PhFvWCqOi0BGStNWs57X9js7XGTprSmFoz5F0tWhR4WPjNeR9jXqdC7/UpSJTnlRg==} + engines: {node: '>=18'} + chalk@4.1.2: resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==} engines: {node: '>=10'} @@ -1544,6 +1814,9 @@ packages: convert-source-map@1.9.0: resolution: {integrity: sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==} + convert-source-map@2.0.0: + resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==} + copyfiles@2.4.1: resolution: {integrity: sha512-fereAvAvxDrQDOXybk3Qu3dPbOoKoysFMWtkY3mv5BsL8//OSZVL5DCLYqgRfY5cWirgRzlC+WSrxp6Bo3eNZg==} hasBin: true @@ -1571,6 +1844,10 @@ packages: crypt@0.0.2: resolution: {integrity: sha512-mCxBlsHFYh9C+HVpiEacem8FEBnMXgU9gy4zmNC+SXAZNB/1idgp/aulFJ4FgCi7GPEVbfyng092GqL2k2rmow==} + css-tree@3.2.1: + resolution: {integrity: sha512-X7sjQzceUhu1u7Y/ylrRZFU2FS6LRiFVp6rKLPg23y3x3c3DOKAwuXGDp+PAGjh6CSnCjYeAul8pcT8bAl+lSA==} + engines: {node: ^10 || ^12.20.0 || ^14.13.0 || >=15.0.0} + cssfilter@0.0.10: resolution: {integrity: sha512-FAaLDaplstoRsDR8XGYH51znUN0UY7nMc6Z9/fvE8EXGwvJE9hu7W2vHwx1+bd6gCYnln9nLbzxFTrcO9YQDZw==} @@ -1580,6 +1857,10 @@ packages: damerau-levenshtein@1.0.8: resolution: {integrity: sha512-sdQSFB7+llfUcQHUQO3+B8ERRj0Oa4w9POWMI/puGtuf7gFywGmkaLCElnudfTiKZV+NvHqL0ifzdrI8Ro7ESA==} + data-urls@7.0.0: + resolution: {integrity: sha512-23XHcCF+coGYevirZceTVD7NdJOqVn+49IHyxgszm+JIiHLoB2TkmPtsYkNWT1pvRSGkc35L6NHs0yHkN2SumA==} + engines: {node: ^20.19.0 || ^22.12.0 || >=24.0.0} + data-view-buffer@1.0.2: resolution: {integrity: sha512-EmKO5V3OLXh1rtK2wgXRansaK1/mtVdTUEiEI0W8RkvgT05kfxaH29PliLnpLP73yYO6142Q72QNa8Wx/A5CqQ==} engines: {node: '>= 0.4'} @@ -1621,6 +1902,9 @@ packages: supports-color: optional: true + decimal.js@10.6.0: + resolution: {integrity: sha512-YpgQiITW3JXGntzdUmyUR1V812Hn8T1YVXhCu+wO3OpS4eU9l4YdD3qjyiKdV6mvV29zapkMeD390UVEf2lkUg==} + decompress-response@10.0.0: resolution: {integrity: sha512-oj7KWToJuuxlPr7VV0vabvxEIiqNMo+q0NueIiL3XhtwC6FVOX7Hr1c0C4eD0bmf7Zr+S/dSf2xvkH3Ad6sU3Q==} engines: {node: '>=20'} @@ -1652,6 +1936,9 @@ packages: resolution: {integrity: sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==} engines: {node: '>=6.0.0'} + dom-accessibility-api@0.5.16: + resolution: {integrity: sha512-X7BJ2yElsnOJ30pZF4uIIDfBEVgF4XEBxL9Bxhy6dnrm5hkzqmsWHGTiHqRiITNhMyFLyAiWndIJP7Z1NTteDg==} + dom-helpers@5.2.1: resolution: {integrity: sha512-nRCa7CK3VTrM2NmGkIy4cbK7IZlgBE/PYMn55rrXefr5xXDP0LdtfPnblFDoVdcAfslJ7or6iqAUnx0CCGIWQA==} @@ -1679,6 +1966,10 @@ packages: resolution: {integrity: sha512-Qohcme7V1inbAfvjItgw0EaxVX5q2rdVEZHRBrEQdRZTssLDGsL8Lwrznl8oQ/6kuTJONLaDcGjkNP247XEhcA==} engines: {node: '>=10.13.0'} + entities@6.0.1: + resolution: {integrity: sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g==} + engines: {node: '>=0.12'} + error-ex@1.3.4: resolution: {integrity: sha512-sqQamAnR14VgCr1A618A3sGrygcpK+HEbenA/HiEAkkUwcZIIB/tgWqHFxWgOyDh4nB4JCRimh79dR5Ywc9MDQ==} @@ -1694,6 +1985,9 @@ packages: resolution: {integrity: sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==} engines: {node: '>= 0.4'} + es-module-lexer@2.0.0: + resolution: {integrity: sha512-5POEcUuZybH7IdmGsD8wlf0AI55wMecM9rVBTI/qEAy2c1kTOm3DjFYjrBdI2K3BaJjJYfYFeRtM0t9ssnRuxw==} + es-object-atoms@1.1.1: resolution: {integrity: sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==} engines: {node: '>= 0.4'} @@ -1903,6 +2197,9 @@ packages: resolution: {integrity: sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==} engines: {node: '>=4.0'} + estree-walker@3.0.3: + resolution: {integrity: sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==} + esutils@2.0.3: resolution: {integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==} engines: {node: '>=0.10.0'} @@ -1918,6 +2215,10 @@ packages: resolution: {integrity: sha512-9Be3ZoN4LmYR90tUoVu2te2BsbzHfhJyfEiAVfz7N5/zv+jduIfLrV2xdQXOHbaD6KgpGdO9PRPM1Y4Q9QkPkA==} engines: {node: ^18.19.0 || >=20.5.0} + expect-type@1.3.0: + resolution: {integrity: sha512-knvyeauYhqjOYvQ66MznSMs83wmHrCycNEN6Ao+2AeYEfxUIkuiVxdEa1qlGEPK+We3n0THiDciYSsCcgW/DoA==} + engines: {node: '>=12.0.0'} + ext-list@2.2.2: resolution: {integrity: sha512-u+SQgsubraE6zItfVA0tBuCBhfU9ogSRnsvygI7wht9TS510oLkBRXBsqopeUG/GBOIQyKZO9wjTqIu/sf5zFA==} engines: {node: '>=0.10.0'} @@ -2166,6 +2467,10 @@ packages: hoist-non-react-statics@3.3.2: resolution: {integrity: sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==} + html-encoding-sniffer@6.0.0: + resolution: {integrity: sha512-CV9TW3Y3f8/wT0BRFc1/KAVQ3TUHiXmaAb6VW9vtiMFf7SLoMd1PdAc4W3KFOFETBJUb90KatHqlsZMWV+R9Gg==} + engines: {node: ^20.19.0 || ^22.12.0 || >=24.0.0} + http-cache-semantics@4.2.0: resolution: {integrity: sha512-dTxcvPXqPvXBQpq5dUr6mEMJX4oIEFv6bwom3FDwKRDsuIjjJGANqhBuoAn9c1RQJIdAKav33ED65E2ys+87QQ==} @@ -2312,6 +2617,9 @@ packages: resolution: {integrity: sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg==} engines: {node: '>=12'} + is-potential-custom-element-name@1.0.1: + resolution: {integrity: sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==} + is-regex@1.2.1: resolution: {integrity: sha512-MjYsKHO5O7mCsmRGxWcLWheFqN9DJ/2TmngvjKXihe6efViPqc274+Fx/4fYj/r03+ESvBdTXK0V6tA3rgez1g==} engines: {node: '>= 0.4'} @@ -2394,6 +2702,15 @@ packages: resolution: {integrity: sha512-iZ8Bdb84lWRuGHamRXFyML07r21pcwBrLkHEuHgEY5UbCouBwv7ECknDRKzsQIXMiqpPymqtIf8TC/shYKB5rw==} engines: {node: '>=12.0.0'} + jsdom@29.0.2: + resolution: {integrity: sha512-9VnGEBosc/ZpwyOsJBCQ/3I5p7Q5ngOY14a9bf5btenAORmZfDse1ZEheMiWcJ3h81+Fv7HmJFdS0szo/waF2w==} + engines: {node: ^20.19.0 || ^22.13.0 || >=24.0.0} + peerDependencies: + canvas: ^3.0.0 + peerDependenciesMeta: + canvas: + optional: true + jsesc@3.1.0: resolution: {integrity: sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==} engines: {node: '>=6'} @@ -2452,6 +2769,80 @@ packages: resolution: {integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==} engines: {node: '>= 0.8.0'} + lightningcss-android-arm64@1.32.0: + resolution: {integrity: sha512-YK7/ClTt4kAK0vo6w3X+Pnm0D2cf2vPHbhOXdoNti1Ga0al1P4TBZhwjATvjNwLEBCnKvjJc2jQgHXH0NEwlAg==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [android] + + lightningcss-darwin-arm64@1.32.0: + resolution: {integrity: sha512-RzeG9Ju5bag2Bv1/lwlVJvBE3q6TtXskdZLLCyfg5pt+HLz9BqlICO7LZM7VHNTTn/5PRhHFBSjk5lc4cmscPQ==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [darwin] + + lightningcss-darwin-x64@1.32.0: + resolution: {integrity: sha512-U+QsBp2m/s2wqpUYT/6wnlagdZbtZdndSmut/NJqlCcMLTWp5muCrID+K5UJ6jqD2BFshejCYXniPDbNh73V8w==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [darwin] + + lightningcss-freebsd-x64@1.32.0: + resolution: {integrity: sha512-JCTigedEksZk3tHTTthnMdVfGf61Fky8Ji2E4YjUTEQX14xiy/lTzXnu1vwiZe3bYe0q+SpsSH/CTeDXK6WHig==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [freebsd] + + lightningcss-linux-arm-gnueabihf@1.32.0: + resolution: {integrity: sha512-x6rnnpRa2GL0zQOkt6rts3YDPzduLpWvwAF6EMhXFVZXD4tPrBkEFqzGowzCsIWsPjqSK+tyNEODUBXeeVHSkw==} + engines: {node: '>= 12.0.0'} + cpu: [arm] + os: [linux] + + lightningcss-linux-arm64-gnu@1.32.0: + resolution: {integrity: sha512-0nnMyoyOLRJXfbMOilaSRcLH3Jw5z9HDNGfT/gwCPgaDjnx0i8w7vBzFLFR1f6CMLKF8gVbebmkUN3fa/kQJpQ==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [linux] + libc: [glibc] + + lightningcss-linux-arm64-musl@1.32.0: + resolution: {integrity: sha512-UpQkoenr4UJEzgVIYpI80lDFvRmPVg6oqboNHfoH4CQIfNA+HOrZ7Mo7KZP02dC6LjghPQJeBsvXhJod/wnIBg==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [linux] + libc: [musl] + + lightningcss-linux-x64-gnu@1.32.0: + resolution: {integrity: sha512-V7Qr52IhZmdKPVr+Vtw8o+WLsQJYCTd8loIfpDaMRWGUZfBOYEJeyJIkqGIDMZPwPx24pUMfwSxxI8phr/MbOA==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [linux] + libc: [glibc] + + lightningcss-linux-x64-musl@1.32.0: + resolution: {integrity: sha512-bYcLp+Vb0awsiXg/80uCRezCYHNg1/l3mt0gzHnWV9XP1W5sKa5/TCdGWaR/zBM2PeF/HbsQv/j2URNOiVuxWg==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [linux] + libc: [musl] + + lightningcss-win32-arm64-msvc@1.32.0: + resolution: {integrity: sha512-8SbC8BR40pS6baCM8sbtYDSwEVQd4JlFTOlaD3gWGHfThTcABnNDBda6eTZeqbofalIJhFx0qKzgHJmcPTnGdw==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [win32] + + lightningcss-win32-x64-msvc@1.32.0: + resolution: {integrity: sha512-Amq9B/SoZYdDi1kFrojnoqPLxYhQ4Wo5XiL8EVJrVsB8ARoC1PWW6VGtT0WKCemjy8aC+louJnjS7U18x3b06Q==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [win32] + + lightningcss@1.32.0: + resolution: {integrity: sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ==} + engines: {node: '>= 12.0.0'} + lines-and-columns@1.2.4: resolution: {integrity: sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==} @@ -2477,6 +2868,13 @@ packages: resolution: {integrity: sha512-NxVFwLAnrd9i7KUBxC4DrUhmgjzOs+1Qm50D3oF1/oL+r1NpZ4gA7xvG0/zJ8evR7zIKn4vLf7qTNduWFtCrRw==} engines: {node: 20 || >=22} + lz-string@1.5.0: + resolution: {integrity: sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ==} + hasBin: true + + magic-string@0.30.21: + resolution: {integrity: sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==} + make-asynchronous@1.1.0: resolution: {integrity: sha512-ayF7iT+44LXdxJLTrTd3TLQpFDDvPCBxXxbv+pMUSuHA5Q8zyAfwkRP6aHHwNVFBUFWtxAHqwNJxF8vMZLAbVg==} engines: {node: '>=18'} @@ -2493,6 +2891,9 @@ packages: md5@2.3.0: resolution: {integrity: sha512-T1GITYmFaKuO91vxyoQMFETst+O71VUPEU3ze5GNzDm0OWdP8v1ziTaAEPUr/3kLsY3Sftgz242A1SetQiDL7g==} + mdn-data@2.27.1: + resolution: {integrity: sha512-9Yubnt3e8A0OKwxYSXyhLymGW4sCufcLG6VdiDdUGVkPhpqLxlvP5vl1983gQjJl3tqbrM731mjaZaP68AgosQ==} + memoize-one@6.0.0: resolution: {integrity: sha512-rkpe71W0N0c0Xz6QD0eJETuWAJGnJ9afsl1srmwPrI+yBCkge5EycXXbYRyvL29zZVUWQCY7InPRCv3GDXuZNw==} @@ -2693,6 +3094,9 @@ packages: resolution: {integrity: sha512-gXah6aZrcUxjWg2zR2MwouP2eHlCBzdV4pygudehaKXSGW4v2AsRQUK+lwwXhii6KFZcunEnmSUoYp5CXibxtA==} engines: {node: '>= 0.4'} + obug@2.1.1: + resolution: {integrity: sha512-uTqF9MuPraAQ+IsnPf366RG4cP9RtUi7MLO1N3KEc+wb0a6yKpeL0lmk2IB1jY5KHPAlTc6T/JRdC/YqxHNwkQ==} + on-exit-leak-free@2.1.2: resolution: {integrity: sha512-0eJJY6hXLGf1udHwfNftBqH+g73EU4B504nZeKpz1sYRKafAghwxEJunB2O7rDZkL4PGfsMVnTXZ2EjibbqcsA==} engines: {node: '>=14.0.0'} @@ -2763,6 +3167,9 @@ packages: resolution: {integrity: sha512-TXfryirbmq34y8QBwgqCVLi+8oA3oWx2eAnSn62ITyEhEYaWRlVZ2DvMM9eZbMs/RfxPu/PK/aBLyGj4IrqMHw==} engines: {node: '>=18'} + parse5@8.0.0: + resolution: {integrity: sha512-9m4m5GSgXjL4AjumKzq1Fgfp3Z8rsvjRNbnkVwfu2ImRqE5D0LnY2QfDen18FSY9C573YU5XxSapdHZTZ2WolA==} + path-exists@4.0.0: resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==} engines: {node: '>=8'} @@ -2793,6 +3200,9 @@ packages: resolution: {integrity: sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==} engines: {node: '>=8'} + pathe@2.0.3: + resolution: {integrity: sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==} + payload@3.83.0: resolution: {integrity: sha512-3DMcqYVn2pg6b1tqfFtkpuDKgHiqDXLKEDYg3kGAYucqa0AYo2E+EPA1QGNNQ2AIduLf9vbn43nmSMAEq/QC/g==} engines: {node: ^18.20.2 || >=20.9.0} @@ -2843,6 +3253,10 @@ packages: resolution: {integrity: sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ==} engines: {node: ^10 || ^12 || >=14} + postcss@8.5.10: + resolution: {integrity: sha512-pMMHxBOZKFU6HgAZ4eyGnwXF/EvPGGqUr0MnZ5+99485wwW41kW91A4LOGxSHhgugZmSChL5AlElNdwlNgcnLQ==} + engines: {node: ^10 || ^12 || >=14} + prelude-ls@1.2.1: resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==} engines: {node: '>= 0.8.0'} @@ -2852,6 +3266,10 @@ packages: engines: {node: '>=14'} hasBin: true + pretty-format@27.5.1: + resolution: {integrity: sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ==} + engines: {node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0} + pretty-ms@9.3.0: resolution: {integrity: sha512-gjVS5hOP+M3wMm5nmNOucbIrqudzs9v/57bWRHQWLYklXqoXKrVfYW2W9+glfGsqtPgpiz5WwyEEB+ksXIx3gQ==} engines: {node: '>=18'} @@ -2913,6 +3331,9 @@ packages: react-is@16.13.1: resolution: {integrity: sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==} + react-is@17.0.2: + resolution: {integrity: sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==} + react-select@5.9.0: resolution: {integrity: sha512-nwRKGanVHGjdccsnzhFte/PULziueZxGD8LL2WojON78Mvnq7LdAMEtu2frrwld1fr3geixg3iiMBIc/LLAZpw==} peerDependencies: @@ -3004,6 +3425,11 @@ packages: engines: {node: 20 || >=22} hasBin: true + rolldown@1.0.0-rc.15: + resolution: {integrity: sha512-Ff31guA5zT6WjnGp0SXw76X6hzGRk/OQq2hE+1lcDe+lJdHSgnSX6nK3erbONHyCbpSj9a9E+uX/OvytZoWp2g==} + engines: {node: ^20.19.0 || >=22.12.0} + hasBin: true + run-parallel@1.2.0: resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==} @@ -3034,6 +3460,10 @@ packages: engines: {node: '>=14.0.0'} hasBin: true + saxes@6.0.0: + resolution: {integrity: sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA==} + engines: {node: '>=v12.22.7'} + scheduler@0.25.0: resolution: {integrity: sha512-xFVuu11jh+xcO7JOAGJNOXld8/TcEHK/4CituBUeUb5hqxJLj9YuemAEuvm9gQ/+pgXYfbQuqAkiYu+u7YEsNA==} @@ -3111,6 +3541,9 @@ packages: sift@17.1.3: resolution: {integrity: sha512-Rtlj66/b0ICeFzYTuNvX/EF1igRbbnGSvEyT79McoZa/DeGhMyC5pWKOEsZKnpkqtSeovd5FL/bjHWC3CIIvCQ==} + siginfo@2.0.0: + resolution: {integrity: sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==} + signal-exit@4.1.0: resolution: {integrity: sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==} engines: {node: '>=14'} @@ -3164,9 +3597,15 @@ packages: stable-hash@0.0.4: resolution: {integrity: sha512-LjdcbuBeLcdETCrPn9i8AYAZ1eCtu4ECAWtP7UleOiZ9LzVxRzzUZEoZ8zB24nhkQnDWyET0I+3sWokSDS3E7g==} + stackback@0.0.2: + resolution: {integrity: sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==} + state-local@1.0.7: resolution: {integrity: sha512-HTEHMNieakEnoe33shBYcZ7NX83ACUjCu8c40iOGEZsngj9zRnkqS9j1pqQPXwobB0ZcVTk27REb7COQ0UR59w==} + std-env@4.1.0: + resolution: {integrity: sha512-Rq7ybcX2RuC55r9oaPVEW7/xu3tj8u4GeBYHBWCychFtzMIr86A7e3PPEBPT37sHStKX3+TiX/Fr/ACmJLVlLQ==} + stop-iteration-iterator@1.1.0: resolution: {integrity: sha512-eLoXW/DHyl62zxY4SCaIgnRhuMr6ri4juEYARS8E6sCEqzKpOiE521Ucofdx+KnDZl5xmvGYaaKCk5FEOxJCoQ==} engines: {node: '>= 0.4'} @@ -3262,6 +3701,9 @@ packages: resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==} engines: {node: '>= 0.4'} + symbol-tree@3.2.4: + resolution: {integrity: sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==} + tabbable@6.3.0: resolution: {integrity: sha512-EIHvdY5bPLuWForiR/AN2Bxngzpuwn1is4asboytXtpTgsArc+WmSJKVLlhdh71u7jFcryDqB2A8lQvj78MkyQ==} @@ -3288,10 +3730,28 @@ packages: resolution: {integrity: sha512-75voc/9G4rDIJleOo4jPvN4/YC4GRZrY8yy1uU4lwrB3XEQbWve8zXoO5No4eFrGcTAMYyoY67p8jRQdtA1HbA==} engines: {node: '>=12'} + tinybench@2.9.0: + resolution: {integrity: sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==} + + tinyexec@1.1.1: + resolution: {integrity: sha512-VKS/ZaQhhkKFMANmAOhhXVoIfBXblQxGX1myCQ2faQrfmobMftXeJPcZGp0gS07ocvGJWDLZGyOZDadDBqYIJg==} + engines: {node: '>=18'} + tinyglobby@0.2.16: resolution: {integrity: sha512-pn99VhoACYR8nFHhxqix+uvsbXineAasWm5ojXoN8xEwK5Kd3/TrhNn1wByuD52UxWRLy8pu+kRMniEi6Eq9Zg==} engines: {node: '>=12.0.0'} + tinyrainbow@3.1.0: + resolution: {integrity: sha512-Bf+ILmBgretUrdJxzXM0SgXLZ3XfiaUuOj/IKQHuTXip+05Xn+uyEYdVg0kYDipTBcLrCVyUzAPz7QmArb0mmw==} + engines: {node: '>=14.0.0'} + + tldts-core@7.0.28: + resolution: {integrity: sha512-7W5Efjhsc3chVdFhqtaU0KtK32J37Zcr9RKtID54nG+tIpcY79CQK/veYPODxtD/LJ4Lue66jvrQzIX2Z2/pUQ==} + + tldts@7.0.28: + resolution: {integrity: sha512-+Zg3vWhRUv8B1maGSTFdev9mjoo8Etn2Ayfs4cnjlD3CsGkxXX4QyW3j2WJ0wdjYcYmy7Lx2RDsZMhgCWafKIw==} + hasBin: true + to-regex-range@5.0.1: resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==} engines: {node: '>=8.0'} @@ -3300,10 +3760,18 @@ packages: resolution: {integrity: sha512-dRXchy+C0IgK8WPC6xvCHFRIWYUbqqdEIKPaKo/AcTUNzwLTK6AH7RjdLWsEZcAN/TBdtfUw3PYEgPr5VPr6ww==} engines: {node: '>=14.16'} + tough-cookie@6.0.1: + resolution: {integrity: sha512-LktZQb3IeoUWB9lqR5EWTHgW/VTITCXg4D21M+lvybRVdylLrRMnqaIONLVb5mav8vM19m44HIcGq4qASeu2Qw==} + engines: {node: '>=16'} + tr46@5.1.1: resolution: {integrity: sha512-hdF5ZgjTqgAntKkklYw0R03MG2x/bSzTtkxmIRw/sTNV8YXsCJ1tfLAX23lhxhHJlEf3CRCOCGGWw3vI3GaSPw==} engines: {node: '>=18'} + tr46@6.0.0: + resolution: {integrity: sha512-bLVMLPtstlZ4iMQHpFHTR7GAGj2jxi8Dg0s2h2MafAE4uSWF98FC/3MomU51iQAMf8/qDUbKWf5GxuvvVcXEhw==} + engines: {node: '>=20'} + truncate-utf8-bytes@1.0.2: resolution: {integrity: sha512-95Pu1QXQvruGEhv62XCMO3Mm90GscOCClvrIUwCM0PYOXK3kaF3l3sIHxx71ThJfcbM2O5Au6SO3AWCSEfW4mQ==} @@ -3391,6 +3859,10 @@ packages: resolution: {integrity: sha512-BM/JzwwaRXxrLdElV2Uo6cTLEjhSb3WXboncJamZ15NgUURmvlXvxa6xkwIOILIjPNo9i8ku136ZvWV0Uly8+w==} engines: {node: '>=20.18.1'} + undici@7.25.0: + resolution: {integrity: sha512-xXnp4kTyor2Zq+J1FfPI6Eq3ew5h6Vl0F/8d9XU5zZQf1tX9s2Su1/3PiMmUANFULpmksxkClamIZcaUqryHsQ==} + engines: {node: '>=20.18.1'} + unicorn-magic@0.3.0: resolution: {integrity: sha512-+QBBXBCvifc56fsbuxZQ6Sic3wqqc3WWaqxs58gvJrcOuN83HGTCwz3oS5phzU9LthRNE9VrJCFCLUgHeeFnfA==} engines: {node: '>=18'} @@ -3427,6 +3899,94 @@ packages: resolution: {integrity: sha512-0/A9rDy9P7cJ+8w1c9WD9V//9Wj15Ce2MPz8Ri6032usz+NfePxx5AcN3bN+r6ZL6jEo066/yNYB3tn4pQEx+A==} hasBin: true + vite@8.0.8: + resolution: {integrity: sha512-dbU7/iLVa8KZALJyLOBOQ88nOXtNG8vxKuOT4I2mD+Ya70KPceF4IAmDsmU0h1Qsn5bPrvsY9HJstCRh3hG6Uw==} + engines: {node: ^20.19.0 || >=22.12.0} + hasBin: true + peerDependencies: + '@types/node': ^20.19.0 || >=22.12.0 + '@vitejs/devtools': ^0.1.0 + esbuild: ^0.27.0 || ^0.28.0 + jiti: '>=1.21.0' + less: ^4.0.0 + sass: ^1.70.0 + sass-embedded: ^1.70.0 + stylus: '>=0.54.8' + sugarss: ^5.0.0 + terser: ^5.16.0 + tsx: ^4.8.1 + yaml: ^2.4.2 + peerDependenciesMeta: + '@types/node': + optional: true + '@vitejs/devtools': + optional: true + esbuild: + optional: true + jiti: + optional: true + less: + optional: true + sass: + optional: true + sass-embedded: + optional: true + stylus: + optional: true + sugarss: + optional: true + terser: + optional: true + tsx: + optional: true + yaml: + optional: true + + vitest@4.1.4: + resolution: {integrity: sha512-tFuJqTxKb8AvfyqMfnavXdzfy3h3sWZRWwfluGbkeR7n0HUev+FmNgZ8SDrRBTVrVCjgH5cA21qGbCffMNtWvg==} + engines: {node: ^20.0.0 || ^22.0.0 || >=24.0.0} + hasBin: true + peerDependencies: + '@edge-runtime/vm': '*' + '@opentelemetry/api': ^1.9.0 + '@types/node': ^20.0.0 || ^22.0.0 || >=24.0.0 + '@vitest/browser-playwright': 4.1.4 + '@vitest/browser-preview': 4.1.4 + '@vitest/browser-webdriverio': 4.1.4 + '@vitest/coverage-istanbul': 4.1.4 + '@vitest/coverage-v8': 4.1.4 + '@vitest/ui': 4.1.4 + happy-dom: '*' + jsdom: '*' + vite: ^6.0.0 || ^7.0.0 || ^8.0.0 + peerDependenciesMeta: + '@edge-runtime/vm': + optional: true + '@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 + + w3c-xmlserializer@5.0.0: + resolution: {integrity: sha512-o8qghlI8NZHU1lLPrpi2+Uq7abh4GGPpYANlalzWxyWteJOCsr/P+oPBA49TOLu5FTZO4d3F9MnWJfiMo4BkmA==} + engines: {node: '>=18'} + web-worker@1.5.0: resolution: {integrity: sha512-RiMReJrTAiA+mBjGONMnjVDP2u3p9R1vkcGz6gDIrOMT3oGuYwX2WRMYI9ipkphSuE5XKEhydbhNEJh4NY9mlw==} @@ -3434,10 +3994,22 @@ packages: resolution: {integrity: sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==} engines: {node: '>=12'} + webidl-conversions@8.0.1: + resolution: {integrity: sha512-BMhLD/Sw+GbJC21C/UgyaZX41nPt8bUTg+jWyDeg7e7YN4xOM05YPSIXceACnXVtqyEw/LMClUQMtMZ+PGGpqQ==} + engines: {node: '>=20'} + + whatwg-mimetype@5.0.0: + resolution: {integrity: sha512-sXcNcHOC51uPGF0P/D4NVtrkjSU2fNsm9iog4ZvZJsL3rjoDAzXZhkm2MWt1y+PUdggKAYVoMAIYcs78wJ51Cw==} + engines: {node: '>=20'} + whatwg-url@14.2.0: resolution: {integrity: sha512-De72GdQZzNTUBBChsXueQUnPKDkg/5A5zp7pFDuQAj5UFoENpiACU0wlCvzpAGnTkj++ihpKwKyYewn/XNUbKw==} engines: {node: '>=18'} + whatwg-url@16.0.1: + resolution: {integrity: sha512-1to4zXBxmXHV3IiSSEInrreIlu02vUOvrhxJJH5vcxYTBDAx51cqZiKdyTxlecdKNSjj8EcxGBxNf6Vg+945gw==} + engines: {node: ^20.19.0 || ^22.12.0 || >=24.0.0} + which-boxed-primitive@1.1.1: resolution: {integrity: sha512-TbX3mj8n0odCBFVlY8AxkqcHASw3L60jIuF8jFP78az3C2YhmGvqbHBpAjTRH2/xqYunrJ9g1jSyjCjpoWzIAA==} engines: {node: '>= 0.4'} @@ -3459,6 +4031,11 @@ packages: engines: {node: '>= 8'} hasBin: true + why-is-node-running@2.3.0: + resolution: {integrity: sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==} + engines: {node: '>=8'} + hasBin: true + word-wrap@1.2.5: resolution: {integrity: sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==} engines: {node: '>=0.10.0'} @@ -3482,6 +4059,13 @@ packages: utf-8-validate: optional: true + xml-name-validator@5.0.0: + resolution: {integrity: sha512-EvGK8EJ3DhaHfbRlETOWAS5pO9MZITeauHKJyb8wyajUfQUenkIg2MvLDTZ4T/TgIcm3HU0TFBgWWboAZ30UHg==} + engines: {node: '>=18'} + + xmlchars@2.2.0: + resolution: {integrity: sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==} + xss@1.0.15: resolution: {integrity: sha512-FVdlVVC67WOIPvfOwhoMETV72f6GbW7aOabBC3WxN/oUdoEMDyLz4OgRv5/gck2ZeNqEQu+Tb0kloovXOfpYVg==} engines: {node: '>= 0.10.0'} @@ -3530,6 +4114,26 @@ snapshots: '@types/json-schema': 7.0.15 js-yaml: 4.1.1 + '@asamuzakjp/css-color@5.1.11': + dependencies: + '@asamuzakjp/generational-cache': 1.0.1 + '@csstools/css-calc': 3.2.0(@csstools/css-parser-algorithms@4.0.0(@csstools/css-tokenizer@4.0.0))(@csstools/css-tokenizer@4.0.0) + '@csstools/css-color-parser': 4.1.0(@csstools/css-parser-algorithms@4.0.0(@csstools/css-tokenizer@4.0.0))(@csstools/css-tokenizer@4.0.0) + '@csstools/css-parser-algorithms': 4.0.0(@csstools/css-tokenizer@4.0.0) + '@csstools/css-tokenizer': 4.0.0 + + '@asamuzakjp/dom-selector@7.0.10': + dependencies: + '@asamuzakjp/generational-cache': 1.0.1 + '@asamuzakjp/nwsapi': 2.3.9 + bidi-js: 1.0.3 + css-tree: 3.2.1 + is-potential-custom-element-name: 1.0.1 + + '@asamuzakjp/generational-cache@1.0.1': {} + + '@asamuzakjp/nwsapi@2.3.9': {} + '@babel/code-frame@7.27.1': dependencies: '@babel/helper-validator-identifier': 7.28.5 @@ -3588,6 +4192,34 @@ snapshots: '@borewit/text-codec@0.2.2': {} + '@bramus/specificity@2.4.2': + dependencies: + css-tree: 3.2.1 + + '@csstools/color-helpers@6.0.2': {} + + '@csstools/css-calc@3.2.0(@csstools/css-parser-algorithms@4.0.0(@csstools/css-tokenizer@4.0.0))(@csstools/css-tokenizer@4.0.0)': + dependencies: + '@csstools/css-parser-algorithms': 4.0.0(@csstools/css-tokenizer@4.0.0) + '@csstools/css-tokenizer': 4.0.0 + + '@csstools/css-color-parser@4.1.0(@csstools/css-parser-algorithms@4.0.0(@csstools/css-tokenizer@4.0.0))(@csstools/css-tokenizer@4.0.0)': + dependencies: + '@csstools/color-helpers': 6.0.2 + '@csstools/css-calc': 3.2.0(@csstools/css-parser-algorithms@4.0.0(@csstools/css-tokenizer@4.0.0))(@csstools/css-tokenizer@4.0.0) + '@csstools/css-parser-algorithms': 4.0.0(@csstools/css-tokenizer@4.0.0) + '@csstools/css-tokenizer': 4.0.0 + + '@csstools/css-parser-algorithms@4.0.0(@csstools/css-tokenizer@4.0.0)': + dependencies: + '@csstools/css-tokenizer': 4.0.0 + + '@csstools/css-syntax-patches-for-csstree@1.1.3(css-tree@3.2.1)': + optionalDependencies: + css-tree: 3.2.1 + + '@csstools/css-tokenizer@4.0.0': {} + '@date-fns/tz@1.2.0': {} '@dnd-kit/accessibility@3.1.1(react@19.2.5)': @@ -3622,11 +4254,27 @@ snapshots: react: 19.2.5 tslib: 2.8.1 + '@emnapi/core@1.9.2': + dependencies: + '@emnapi/wasi-threads': 1.2.1 + tslib: 2.8.1 + optional: true + '@emnapi/runtime@1.7.1': dependencies: tslib: 2.8.1 optional: true + '@emnapi/runtime@1.9.2': + dependencies: + tslib: 2.8.1 + optional: true + + '@emnapi/wasi-threads@1.2.1': + dependencies: + tslib: 2.8.1 + optional: true + '@emotion/babel-plugin@11.13.5': dependencies: '@babel/helper-module-imports': 7.27.1 @@ -3912,6 +4560,8 @@ snapshots: '@eslint/core': 0.13.0 levn: 0.4.1 + '@exodus/bytes@1.15.0': {} + '@faceless-ui/modal@3.0.0(react-dom@19.2.5(react@19.2.5))(react@19.2.5)': dependencies: body-scroll-lock: 4.0.0-beta.0 @@ -4168,6 +4818,13 @@ snapshots: '@napi-rs/nice-win32-x64-msvc': 1.1.1 optional: true + '@napi-rs/wasm-runtime@1.1.4(@emnapi/core@1.9.2)(@emnapi/runtime@1.9.2)': + dependencies: + '@emnapi/core': 1.9.2 + '@emnapi/runtime': 1.9.2 + '@tybys/wasm-util': 0.10.1 + optional: true + '@next/env@15.4.11': {} '@next/env@15.5.9': {} @@ -4208,6 +4865,8 @@ snapshots: '@nodelib/fs.scandir': 2.1.5 fastq: 1.20.1 + '@oxc-project/types@0.124.0': {} + '@payloadcms/db-mongodb@3.83.0(payload@3.83.0(graphql@16.12.0)(typescript@5.9.3))': dependencies: mongoose: 8.15.1 @@ -4225,18 +4884,18 @@ snapshots: - socks - supports-color - '@payloadcms/eslint-config@3.28.0(@typescript-eslint/eslint-plugin@8.26.1(@typescript-eslint/parser@8.26.1(eslint@9.22.0)(typescript@5.9.3))(eslint@9.22.0)(typescript@5.9.3))(ts-api-utils@2.5.0(typescript@5.9.3))': + '@payloadcms/eslint-config@3.28.0(@testing-library/dom@10.4.1)(@typescript-eslint/eslint-plugin@8.26.1(@typescript-eslint/parser@8.26.1(eslint@9.22.0)(typescript@5.9.3))(eslint@9.22.0)(typescript@5.9.3))(ts-api-utils@2.5.0(typescript@5.9.3))': dependencies: '@eslint-react/eslint-plugin': 1.31.0(eslint@9.22.0)(ts-api-utils@2.5.0(typescript@5.9.3))(typescript@5.7.3) '@eslint/js': 9.22.0 - '@payloadcms/eslint-plugin': 3.28.0(@typescript-eslint/eslint-plugin@8.26.1(@typescript-eslint/parser@8.26.1(eslint@9.22.0)(typescript@5.9.3))(eslint@9.22.0)(typescript@5.9.3))(ts-api-utils@2.5.0(typescript@5.9.3)) + '@payloadcms/eslint-plugin': 3.28.0(@testing-library/dom@10.4.1)(@typescript-eslint/eslint-plugin@8.26.1(@typescript-eslint/parser@8.26.1(eslint@9.22.0)(typescript@5.9.3))(eslint@9.22.0)(typescript@5.9.3))(ts-api-utils@2.5.0(typescript@5.9.3)) '@types/eslint': 9.6.1 '@typescript-eslint/parser': 8.26.1(eslint@9.22.0)(typescript@5.7.3) eslint: 9.22.0 eslint-config-prettier: 10.1.1(eslint@9.22.0) eslint-plugin-import-x: 4.6.1(eslint@9.22.0)(typescript@5.7.3) eslint-plugin-jest: 28.11.0(@typescript-eslint/eslint-plugin@8.26.1(@typescript-eslint/parser@8.26.1(eslint@9.22.0)(typescript@5.9.3))(eslint@9.22.0)(typescript@5.9.3))(eslint@9.22.0)(typescript@5.7.3) - eslint-plugin-jest-dom: 5.5.0(eslint@9.22.0) + eslint-plugin-jest-dom: 5.5.0(@testing-library/dom@10.4.1)(eslint@9.22.0) eslint-plugin-jsx-a11y: 6.10.2(eslint@9.22.0) eslint-plugin-perfectionist: 3.9.1(eslint@9.22.0)(typescript@5.7.3) eslint-plugin-react-hooks: 0.0.0-experimental-d331ba04-20250307(eslint@9.22.0) @@ -4256,7 +4915,7 @@ snapshots: - ts-api-utils - vue-eslint-parser - '@payloadcms/eslint-plugin@3.28.0(@typescript-eslint/eslint-plugin@8.26.1(@typescript-eslint/parser@8.26.1(eslint@9.22.0)(typescript@5.9.3))(eslint@9.22.0)(typescript@5.9.3))(ts-api-utils@2.5.0(typescript@5.9.3))': + '@payloadcms/eslint-plugin@3.28.0(@testing-library/dom@10.4.1)(@typescript-eslint/eslint-plugin@8.26.1(@typescript-eslint/parser@8.26.1(eslint@9.22.0)(typescript@5.9.3))(eslint@9.22.0)(typescript@5.9.3))(ts-api-utils@2.5.0(typescript@5.9.3))': dependencies: '@eslint-react/eslint-plugin': 1.31.0(eslint@9.22.0)(ts-api-utils@2.5.0(typescript@5.9.3))(typescript@5.7.3) '@eslint/js': 9.22.0 @@ -4266,7 +4925,7 @@ snapshots: eslint-config-prettier: 10.1.1(eslint@9.22.0) eslint-plugin-import-x: 4.6.1(eslint@9.22.0)(typescript@5.7.3) eslint-plugin-jest: 28.11.0(@typescript-eslint/eslint-plugin@8.26.1(@typescript-eslint/parser@8.26.1(eslint@9.22.0)(typescript@5.9.3))(eslint@9.22.0)(typescript@5.9.3))(eslint@9.22.0)(typescript@5.7.3) - eslint-plugin-jest-dom: 5.5.0(eslint@9.22.0) + eslint-plugin-jest-dom: 5.5.0(@testing-library/dom@10.4.1)(eslint@9.22.0) eslint-plugin-jsx-a11y: 6.10.2(eslint@9.22.0) eslint-plugin-perfectionist: 3.9.1(eslint@9.22.0)(typescript@5.7.3) eslint-plugin-react-hooks: 0.0.0-experimental-d331ba04-20250307(eslint@9.22.0) @@ -4367,12 +5026,65 @@ snapshots: '@pinojs/redact@0.4.0': {} + '@rolldown/binding-android-arm64@1.0.0-rc.15': + optional: true + + '@rolldown/binding-darwin-arm64@1.0.0-rc.15': + optional: true + + '@rolldown/binding-darwin-x64@1.0.0-rc.15': + optional: true + + '@rolldown/binding-freebsd-x64@1.0.0-rc.15': + optional: true + + '@rolldown/binding-linux-arm-gnueabihf@1.0.0-rc.15': + optional: true + + '@rolldown/binding-linux-arm64-gnu@1.0.0-rc.15': + optional: true + + '@rolldown/binding-linux-arm64-musl@1.0.0-rc.15': + optional: true + + '@rolldown/binding-linux-ppc64-gnu@1.0.0-rc.15': + optional: true + + '@rolldown/binding-linux-s390x-gnu@1.0.0-rc.15': + optional: true + + '@rolldown/binding-linux-x64-gnu@1.0.0-rc.15': + optional: true + + '@rolldown/binding-linux-x64-musl@1.0.0-rc.15': + optional: true + + '@rolldown/binding-openharmony-arm64@1.0.0-rc.15': + optional: true + + '@rolldown/binding-wasm32-wasi@1.0.0-rc.15': + dependencies: + '@emnapi/core': 1.9.2 + '@emnapi/runtime': 1.9.2 + '@napi-rs/wasm-runtime': 1.1.4(@emnapi/core@1.9.2)(@emnapi/runtime@1.9.2) + optional: true + + '@rolldown/binding-win32-arm64-msvc@1.0.0-rc.15': + optional: true + + '@rolldown/binding-win32-x64-msvc@1.0.0-rc.15': + optional: true + + '@rolldown/pluginutils@1.0.0-rc.15': {} + '@sec-ant/readable-stream@0.4.1': {} '@sindresorhus/is@7.2.0': {} '@sindresorhus/merge-streams@4.0.0': {} + '@standard-schema/spec@1.1.0': {} + '@swc/cli@0.8.1(@swc/core@1.15.26)': dependencies: '@swc/core': 1.15.26 @@ -4454,6 +5166,27 @@ snapshots: dependencies: '@swc/counter': 0.1.3 + '@testing-library/dom@10.4.1': + dependencies: + '@babel/code-frame': 7.27.1 + '@babel/runtime': 7.29.2 + '@types/aria-query': 5.0.4 + aria-query: 5.3.0 + dom-accessibility-api: 0.5.16 + lz-string: 1.5.0 + picocolors: 1.1.1 + pretty-format: 27.5.1 + + '@testing-library/react@16.3.2(@testing-library/dom@10.4.1)(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)': + dependencies: + '@babel/runtime': 7.29.2 + '@testing-library/dom': 10.4.1 + react: 19.2.5 + react-dom: 19.2.5(react@19.2.5) + optionalDependencies: + '@types/react': 19.2.14 + '@types/react-dom': 19.2.3(@types/react@19.2.14) + '@tokenizer/inflate@0.4.1': dependencies: debug: 4.4.3 @@ -4463,10 +5196,24 @@ snapshots: '@tokenizer/token@0.3.0': {} + '@tybys/wasm-util@0.10.1': + dependencies: + tslib: 2.8.1 + optional: true + + '@types/aria-query@5.0.4': {} + '@types/busboy@1.5.4': dependencies: '@types/node': 25.0.3 + '@types/chai@5.2.3': + dependencies: + '@types/deep-eql': 4.0.2 + assertion-error: 2.0.1 + + '@types/deep-eql@4.0.2': {} + '@types/doctrine@0.0.9': {} '@types/eslint@9.6.1': @@ -4706,6 +5453,47 @@ snapshots: '@typescript-eslint/types': 8.58.2 eslint-visitor-keys: 5.0.1 + '@vitest/expect@4.1.4': + dependencies: + '@standard-schema/spec': 1.1.0 + '@types/chai': 5.2.3 + '@vitest/spy': 4.1.4 + '@vitest/utils': 4.1.4 + chai: 6.2.2 + tinyrainbow: 3.1.0 + + '@vitest/mocker@4.1.4(vite@8.0.8(@types/node@25.0.3)(esbuild@0.27.3)(sass@1.77.4)(tsx@4.21.0))': + dependencies: + '@vitest/spy': 4.1.4 + estree-walker: 3.0.3 + magic-string: 0.30.21 + optionalDependencies: + vite: 8.0.8(@types/node@25.0.3)(esbuild@0.27.3)(sass@1.77.4)(tsx@4.21.0) + + '@vitest/pretty-format@4.1.4': + dependencies: + tinyrainbow: 3.1.0 + + '@vitest/runner@4.1.4': + dependencies: + '@vitest/utils': 4.1.4 + pathe: 2.0.3 + + '@vitest/snapshot@4.1.4': + dependencies: + '@vitest/pretty-format': 4.1.4 + '@vitest/utils': 4.1.4 + magic-string: 0.30.21 + pathe: 2.0.3 + + '@vitest/spy@4.1.4': {} + + '@vitest/utils@4.1.4': + dependencies: + '@vitest/pretty-format': 4.1.4 + convert-source-map: 2.0.0 + tinyrainbow: 3.1.0 + '@xhmikosr/archive-type@8.0.1': dependencies: file-type: 21.3.4 @@ -4826,6 +5614,8 @@ snapshots: dependencies: color-convert: 2.0.1 + ansi-styles@5.2.0: {} + anymatch@3.1.3: dependencies: normalize-path: 3.0.0 @@ -4835,6 +5625,10 @@ snapshots: argparse@2.0.1: {} + aria-query@5.3.0: + dependencies: + dequal: 2.0.3 + aria-query@5.3.2: {} array-buffer-byte-length@1.0.2: @@ -4877,6 +5671,8 @@ snapshots: get-intrinsic: 1.3.0 is-array-buffer: 3.0.5 + assertion-error@2.0.1: {} + ast-types-flow@0.0.8: {} async-function@1.0.0: {} @@ -4907,6 +5703,10 @@ snapshots: base64-js@1.5.1: {} + bidi-js@1.0.3: + dependencies: + require-from-string: 2.0.2 + binary-extensions@2.3.0: {} binary-version-check@6.1.0: @@ -4991,6 +5791,8 @@ snapshots: caniuse-lite@1.0.30001760: {} + chai@6.2.2: {} + chalk@4.1.2: dependencies: ansi-styles: 4.3.0 @@ -5052,6 +5854,8 @@ snapshots: convert-source-map@1.9.0: {} + convert-source-map@2.0.0: {} + copyfiles@2.4.1: dependencies: glob: 7.2.3 @@ -5087,12 +5891,24 @@ snapshots: crypt@0.0.2: {} + css-tree@3.2.1: + dependencies: + mdn-data: 2.27.1 + source-map-js: 1.2.1 + cssfilter@0.0.10: {} csstype@3.2.3: {} damerau-levenshtein@1.0.8: {} + data-urls@7.0.0: + dependencies: + whatwg-mimetype: 5.0.0 + whatwg-url: 16.0.1 + transitivePeerDependencies: + - '@noble/hashes' + data-view-buffer@1.0.2: dependencies: call-bound: 1.0.4 @@ -5127,6 +5943,8 @@ snapshots: dependencies: ms: 2.1.3 + decimal.js@10.6.0: {} + decompress-response@10.0.0: dependencies: mimic-response: 4.0.0 @@ -5149,13 +5967,14 @@ snapshots: dequal@2.0.3: {} - detect-libc@2.1.2: - optional: true + detect-libc@2.1.2: {} doctrine@3.0.0: dependencies: esutils: 2.0.3 + dom-accessibility-api@0.5.16: {} + dom-helpers@5.2.1: dependencies: '@babel/runtime': 7.29.2 @@ -5186,6 +6005,8 @@ snapshots: graceful-fs: 4.2.11 tapable: 2.3.2 + entities@6.0.1: {} + error-ex@1.3.4: dependencies: is-arrayish: 0.2.1 @@ -5251,6 +6072,8 @@ snapshots: es-errors@1.3.0: {} + es-module-lexer@2.0.0: {} + es-object-atoms@1.1.1: dependencies: es-errors: 1.3.0 @@ -5337,11 +6160,13 @@ snapshots: - supports-color - typescript - eslint-plugin-jest-dom@5.5.0(eslint@9.22.0): + eslint-plugin-jest-dom@5.5.0(@testing-library/dom@10.4.1)(eslint@9.22.0): dependencies: '@babel/runtime': 7.29.2 eslint: 9.22.0 requireindex: 1.2.0 + optionalDependencies: + '@testing-library/dom': 10.4.1 eslint-plugin-jest@28.11.0(@typescript-eslint/eslint-plugin@8.26.1(@typescript-eslint/parser@8.26.1(eslint@9.22.0)(typescript@5.9.3))(eslint@9.22.0)(typescript@5.9.3))(eslint@9.22.0)(typescript@5.7.3): dependencies: @@ -5586,6 +6411,10 @@ snapshots: estraverse@5.3.0: {} + estree-walker@3.0.3: + dependencies: + '@types/estree': 1.0.8 + esutils@2.0.3: {} events-universal@1.0.1: @@ -5621,6 +6450,8 @@ snapshots: strip-final-newline: 4.0.0 yoctocolors: 2.1.2 + expect-type@1.3.0: {} + ext-list@2.2.2: dependencies: mime-db: 1.54.0 @@ -5875,6 +6706,12 @@ snapshots: dependencies: react-is: 16.13.1 + html-encoding-sniffer@6.0.0: + dependencies: + '@exodus/bytes': 1.15.0 + transitivePeerDependencies: + - '@noble/hashes' + http-cache-semantics@4.2.0: {} http-status@2.1.0: {} @@ -6005,6 +6842,8 @@ snapshots: is-plain-obj@4.1.0: {} + is-potential-custom-element-name@1.0.1: {} + is-regex@1.2.1: dependencies: call-bound: 1.0.4 @@ -6072,6 +6911,32 @@ snapshots: jsdoc-type-pratt-parser@4.8.0: {} + jsdom@29.0.2: + dependencies: + '@asamuzakjp/css-color': 5.1.11 + '@asamuzakjp/dom-selector': 7.0.10 + '@bramus/specificity': 2.4.2 + '@csstools/css-syntax-patches-for-csstree': 1.1.3(css-tree@3.2.1) + '@exodus/bytes': 1.15.0 + css-tree: 3.2.1 + data-urls: 7.0.0 + decimal.js: 10.6.0 + html-encoding-sniffer: 6.0.0 + is-potential-custom-element-name: 1.0.1 + lru-cache: 11.3.5 + parse5: 8.0.0 + saxes: 6.0.0 + symbol-tree: 3.2.4 + tough-cookie: 6.0.1 + undici: 7.25.0 + w3c-xmlserializer: 5.0.0 + webidl-conversions: 8.0.1 + whatwg-mimetype: 5.0.0 + whatwg-url: 16.0.1 + xml-name-validator: 5.0.0 + transitivePeerDependencies: + - '@noble/hashes' + jsesc@3.1.0: {} json-buffer@3.0.1: {} @@ -6128,6 +6993,55 @@ snapshots: prelude-ls: 1.2.1 type-check: 0.4.0 + lightningcss-android-arm64@1.32.0: + optional: true + + lightningcss-darwin-arm64@1.32.0: + optional: true + + lightningcss-darwin-x64@1.32.0: + optional: true + + lightningcss-freebsd-x64@1.32.0: + optional: true + + lightningcss-linux-arm-gnueabihf@1.32.0: + optional: true + + lightningcss-linux-arm64-gnu@1.32.0: + optional: true + + lightningcss-linux-arm64-musl@1.32.0: + optional: true + + lightningcss-linux-x64-gnu@1.32.0: + optional: true + + lightningcss-linux-x64-musl@1.32.0: + optional: true + + lightningcss-win32-arm64-msvc@1.32.0: + optional: true + + lightningcss-win32-x64-msvc@1.32.0: + optional: true + + lightningcss@1.32.0: + dependencies: + detect-libc: 2.1.2 + optionalDependencies: + lightningcss-android-arm64: 1.32.0 + lightningcss-darwin-arm64: 1.32.0 + lightningcss-darwin-x64: 1.32.0 + lightningcss-freebsd-x64: 1.32.0 + lightningcss-linux-arm-gnueabihf: 1.32.0 + lightningcss-linux-arm64-gnu: 1.32.0 + lightningcss-linux-arm64-musl: 1.32.0 + lightningcss-linux-x64-gnu: 1.32.0 + lightningcss-linux-x64-musl: 1.32.0 + lightningcss-win32-arm64-msvc: 1.32.0 + lightningcss-win32-x64-msvc: 1.32.0 + lines-and-columns@1.2.4: {} locate-path@6.0.0: @@ -6146,6 +7060,12 @@ snapshots: lru-cache@11.3.5: {} + lz-string@1.5.0: {} + + magic-string@0.30.21: + dependencies: + '@jridgewell/sourcemap-codec': 1.5.5 + make-asynchronous@1.1.0: dependencies: p-event: 6.0.1 @@ -6162,6 +7082,8 @@ snapshots: crypt: 0.0.2 is-buffer: 1.1.6 + mdn-data@2.27.1: {} + memoize-one@6.0.0: {} memory-pager@1.5.0: {} @@ -6350,6 +7272,8 @@ snapshots: define-properties: 1.2.1 es-object-atoms: 1.1.1 + obug@2.1.1: {} + on-exit-leak-free@2.1.2: {} once@1.4.0: @@ -6413,6 +7337,10 @@ snapshots: parse-ms@4.0.0: {} + parse5@8.0.0: + dependencies: + entities: 6.0.1 + path-exists@4.0.0: {} path-is-absolute@1.0.1: {} @@ -6432,6 +7360,8 @@ snapshots: path-type@4.0.0: {} + pathe@2.0.3: {} + payload@3.83.0(graphql@16.12.0)(typescript@5.9.3): dependencies: '@next/env': 15.5.9 @@ -6530,10 +7460,22 @@ snapshots: picocolors: 1.1.1 source-map-js: 1.2.1 + postcss@8.5.10: + dependencies: + nanoid: 3.3.11 + picocolors: 1.1.1 + source-map-js: 1.2.1 + prelude-ls@1.2.1: {} prettier@3.8.3: {} + pretty-format@27.5.1: + dependencies: + ansi-regex: 5.0.1 + ansi-styles: 5.2.0 + react-is: 17.0.2 + pretty-ms@9.3.0: dependencies: parse-ms: 4.0.0 @@ -6589,6 +7531,8 @@ snapshots: react-is@16.13.1: {} + react-is@17.0.2: {} + react-select@5.9.0(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.5))(react@19.2.5): dependencies: '@babel/runtime': 7.29.2 @@ -6707,6 +7651,27 @@ snapshots: glob: 13.0.6 package-json-from-dist: 1.0.1 + rolldown@1.0.0-rc.15: + dependencies: + '@oxc-project/types': 0.124.0 + '@rolldown/pluginutils': 1.0.0-rc.15 + optionalDependencies: + '@rolldown/binding-android-arm64': 1.0.0-rc.15 + '@rolldown/binding-darwin-arm64': 1.0.0-rc.15 + '@rolldown/binding-darwin-x64': 1.0.0-rc.15 + '@rolldown/binding-freebsd-x64': 1.0.0-rc.15 + '@rolldown/binding-linux-arm-gnueabihf': 1.0.0-rc.15 + '@rolldown/binding-linux-arm64-gnu': 1.0.0-rc.15 + '@rolldown/binding-linux-arm64-musl': 1.0.0-rc.15 + '@rolldown/binding-linux-ppc64-gnu': 1.0.0-rc.15 + '@rolldown/binding-linux-s390x-gnu': 1.0.0-rc.15 + '@rolldown/binding-linux-x64-gnu': 1.0.0-rc.15 + '@rolldown/binding-linux-x64-musl': 1.0.0-rc.15 + '@rolldown/binding-openharmony-arm64': 1.0.0-rc.15 + '@rolldown/binding-wasm32-wasi': 1.0.0-rc.15 + '@rolldown/binding-win32-arm64-msvc': 1.0.0-rc.15 + '@rolldown/binding-win32-x64-msvc': 1.0.0-rc.15 + run-parallel@1.2.0: dependencies: queue-microtask: 1.2.3 @@ -6744,6 +7709,10 @@ snapshots: immutable: 4.3.8 source-map-js: 1.2.1 + saxes@6.0.0: + dependencies: + xmlchars: 2.2.0 + scheduler@0.25.0: {} scheduler@0.27.0: {} @@ -6860,6 +7829,8 @@ snapshots: sift@17.1.3: {} + siginfo@2.0.0: {} + signal-exit@4.1.0: {} simple-wcswidth@1.1.2: {} @@ -6899,8 +7870,12 @@ snapshots: stable-hash@0.0.4: {} + stackback@0.0.2: {} + state-local@1.0.7: {} + std-env@4.1.0: {} + stop-iteration-iterator@1.1.0: dependencies: es-errors: 1.3.0 @@ -7000,6 +7975,8 @@ snapshots: supports-preserve-symlinks-flag@1.0.0: {} + symbol-tree@3.2.4: {} + tabbable@6.3.0: {} tapable@2.3.2: {} @@ -7034,11 +8011,23 @@ snapshots: dependencies: convert-hrtime: 5.0.0 + tinybench@2.9.0: {} + + tinyexec@1.1.1: {} + tinyglobby@0.2.16: dependencies: fdir: 6.5.0(picomatch@4.0.4) picomatch: 4.0.4 + tinyrainbow@3.1.0: {} + + tldts-core@7.0.28: {} + + tldts@7.0.28: + dependencies: + tldts-core: 7.0.28 + to-regex-range@5.0.1: dependencies: is-number: 7.0.0 @@ -7049,10 +8038,18 @@ snapshots: '@tokenizer/token': 0.3.0 ieee754: 1.2.1 + tough-cookie@6.0.1: + dependencies: + tldts: 7.0.28 + tr46@5.1.1: dependencies: punycode: 2.3.1 + tr46@6.0.0: + dependencies: + punycode: 2.3.1 + truncate-utf8-bytes@1.0.2: dependencies: utf8-byte-length: 1.0.5 @@ -7152,6 +8149,8 @@ snapshots: undici@7.24.4: {} + undici@7.25.0: {} + unicorn-magic@0.3.0: {} untildify@4.0.0: {} @@ -7177,15 +8176,73 @@ snapshots: uuid@11.1.0: {} + vite@8.0.8(@types/node@25.0.3)(esbuild@0.27.3)(sass@1.77.4)(tsx@4.21.0): + dependencies: + lightningcss: 1.32.0 + picomatch: 4.0.4 + postcss: 8.5.10 + rolldown: 1.0.0-rc.15 + tinyglobby: 0.2.16 + optionalDependencies: + '@types/node': 25.0.3 + esbuild: 0.27.3 + fsevents: 2.3.3 + sass: 1.77.4 + tsx: 4.21.0 + + vitest@4.1.4(@types/node@25.0.3)(jsdom@29.0.2)(vite@8.0.8(@types/node@25.0.3)(esbuild@0.27.3)(sass@1.77.4)(tsx@4.21.0)): + dependencies: + '@vitest/expect': 4.1.4 + '@vitest/mocker': 4.1.4(vite@8.0.8(@types/node@25.0.3)(esbuild@0.27.3)(sass@1.77.4)(tsx@4.21.0)) + '@vitest/pretty-format': 4.1.4 + '@vitest/runner': 4.1.4 + '@vitest/snapshot': 4.1.4 + '@vitest/spy': 4.1.4 + '@vitest/utils': 4.1.4 + es-module-lexer: 2.0.0 + expect-type: 1.3.0 + magic-string: 0.30.21 + obug: 2.1.1 + pathe: 2.0.3 + picomatch: 4.0.4 + std-env: 4.1.0 + tinybench: 2.9.0 + tinyexec: 1.1.1 + tinyglobby: 0.2.16 + tinyrainbow: 3.1.0 + vite: 8.0.8(@types/node@25.0.3)(esbuild@0.27.3)(sass@1.77.4)(tsx@4.21.0) + why-is-node-running: 2.3.0 + optionalDependencies: + '@types/node': 25.0.3 + jsdom: 29.0.2 + transitivePeerDependencies: + - msw + + w3c-xmlserializer@5.0.0: + dependencies: + xml-name-validator: 5.0.0 + web-worker@1.5.0: {} webidl-conversions@7.0.0: {} + webidl-conversions@8.0.1: {} + + whatwg-mimetype@5.0.0: {} + whatwg-url@14.2.0: dependencies: tr46: 5.1.1 webidl-conversions: 7.0.0 + whatwg-url@16.0.1: + dependencies: + '@exodus/bytes': 1.15.0 + tr46: 6.0.0 + webidl-conversions: 8.0.1 + transitivePeerDependencies: + - '@noble/hashes' + which-boxed-primitive@1.1.1: dependencies: is-bigint: 1.1.0 @@ -7231,6 +8288,11 @@ snapshots: dependencies: isexe: 2.0.0 + why-is-node-running@2.3.0: + dependencies: + siginfo: 2.0.0 + stackback: 0.0.2 + word-wrap@1.2.5: {} wrap-ansi@7.0.0: @@ -7243,6 +8305,10 @@ snapshots: ws@8.18.3: {} + xml-name-validator@5.0.0: {} + + xmlchars@2.2.0: {} + xss@1.0.15: dependencies: commander: 2.20.3 diff --git a/alt-text/src/components/AltTextHealthWidget.test.tsx b/alt-text/src/components/AltTextHealthWidget.test.tsx new file mode 100644 index 00000000..337c650a --- /dev/null +++ b/alt-text/src/components/AltTextHealthWidget.test.tsx @@ -0,0 +1,89 @@ +// @vitest-environment jsdom +import type { WidgetServerProps } from 'payload' + +import { cleanup, render } from '@testing-library/react' +import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' + +vi.mock('../utilities/altTextHealth.js', () => ({ + getAltTextHealthWidgetData: vi.fn(() => + Promise.resolve({ + collections: [ + { + collection: 'media', + completeDocs: 1, + invalidDocIds: ['doc-1', 'doc-2'], + missingDocs: 2, + partialDocs: 0, + totalDocs: 3, + }, + ], + errors: [], + isLocalized: false, + localeCount: 0, + totalDocs: 3, + }), + ), +})) + +vi.mock('../utilities/getCollectionLabel.js', () => ({ + getCollectionLabel: (slug: string) => slug, +})) + +vi.mock('@payloadcms/ui/elements/Pill', () => ({ + Pill: ({ children, to }: { children: React.ReactNode; to?: string }) => + to ? {children} : {children}, +})) + +const { AltTextHealthWidget } = await import('./AltTextHealthWidget.js') + +function createReq({ adminRoute }: { adminRoute: string }): WidgetServerProps['req'] { + return { + i18n: { language: 'en' }, + locale: undefined, + payload: { + config: { + collections: [{ slug: 'media' }], + routes: { admin: adminRoute }, + }, + }, + t: (key: string) => key, + } as unknown as WidgetServerProps['req'] +} + +describe('AltTextHealthWidget', () => { + beforeEach(() => { + // noop + }) + + afterEach(() => { + cleanup() + }) + + it('links to /admin when the default admin route is configured', async () => { + const element = await AltTextHealthWidget({ + req: createReq({ adminRoute: '/admin' }), + } as WidgetServerProps) + const { container } = render(element) + + const anchors = Array.from(container.querySelectorAll('a')).map((a) => a.getAttribute('href')) + expect(anchors).toContain('/admin/collections/media') + expect( + anchors.some((href) => + href?.startsWith('/admin/collections/media?where[id][in]=doc-1,doc-2'), + ), + ).toBe(true) + }) + + it('links to the custom admin route when one is configured', async () => { + const element = await AltTextHealthWidget({ + req: createReq({ adminRoute: '/cms' }), + } as WidgetServerProps) + const { container } = render(element) + + const anchors = Array.from(container.querySelectorAll('a')).map((a) => a.getAttribute('href')) + expect(anchors).toContain('/cms/collections/media') + expect( + anchors.some((href) => href?.startsWith('/cms/collections/media?where[id][in]=doc-1,doc-2')), + ).toBe(true) + }) +}) diff --git a/alt-text/src/components/AltTextHealthWidget.tsx b/alt-text/src/components/AltTextHealthWidget.tsx index ba48c14d..0c04d88c 100644 --- a/alt-text/src/components/AltTextHealthWidget.tsx +++ b/alt-text/src/components/AltTextHealthWidget.tsx @@ -2,6 +2,7 @@ import type { TFunction } from '@payloadcms/translations' import type { WidgetServerProps } from 'payload' import { Pill } from '@payloadcms/ui/elements/Pill' +import { formatAdminURL } from 'payload/shared' import type { PluginAltTextTranslationKeys } from '../translations/index.js' @@ -16,6 +17,7 @@ export async function AltTextHealthWidget({ req }: WidgetServerProps) { const t = req.t as TFunction const { collections, errors, isLocalized, localeCount, totalDocs } = await getAltTextHealthWidgetData(req) + const adminRoute = req.payload.config.routes.admin return (
{t('@jhb.software/payload-alt-text-plugin:statusUnhealthy', { diff --git a/alt-text/src/components/BulkGenerateAltTextsButton.test.tsx b/alt-text/src/components/BulkGenerateAltTextsButton.test.tsx new file mode 100644 index 00000000..c20c837b --- /dev/null +++ b/alt-text/src/components/BulkGenerateAltTextsButton.test.tsx @@ -0,0 +1,87 @@ +// @vitest-environment jsdom +import { cleanup, fireEvent, render, screen } from '@testing-library/react' +import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' + +const mockConfig = { routes: { api: '/api' }, serverURL: '' } +const mockSetSelection = vi.fn() + +vi.mock('next/navigation.js', () => ({ + useRouter: () => ({ refresh: vi.fn() }), +})) + +vi.mock('@payloadcms/ui', () => ({ + Button: ({ + children, + disabled, + onClick, + }: { + children: React.ReactNode + disabled?: boolean + onClick?: () => void + }) => ( + + ), + toast: { error: vi.fn(), success: vi.fn(), warning: vi.fn() }, + useConfig: () => ({ config: mockConfig }), + useSelection: () => ({ + selected: new Map([ + ['doc-1', true], + ['doc-2', true], + ]), + setSelection: mockSetSelection, + }), + useTranslation: () => ({ t: (key: string) => key }), +})) + +const { BulkGenerateAltTextsButton } = await import('./BulkGenerateAltTextsButton.js') + +describe('BulkGenerateAltTextsButton', () => { + beforeEach(() => { + mockConfig.routes.api = '/api' + mockConfig.serverURL = '' + mockSetSelection.mockClear() + vi.stubGlobal( + 'fetch', + vi.fn(() => + Promise.resolve({ + json: () => Promise.resolve({ erroredDocs: [], totalDocs: 2, updatedDocs: 2 }), + ok: true, + } as Response), + ), + ) + }) + + afterEach(() => { + cleanup() + vi.restoreAllMocks() + }) + + it('posts to the default /api route when no custom route is configured', async () => { + render() + fireEvent.click(screen.getByRole('button')) + await Promise.resolve() + await Promise.resolve() + + expect(fetch).toHaveBeenCalledWith( + '/api/alt-text-plugin/generate/bulk', + expect.objectContaining({ method: 'POST' }), + ) + }) + + it('posts to the configured API route when a custom route is set', async () => { + mockConfig.routes.api = '/custom-api' + mockConfig.serverURL = 'https://cms.example.com' + + render() + fireEvent.click(screen.getByRole('button')) + await Promise.resolve() + await Promise.resolve() + + expect(fetch).toHaveBeenCalledWith( + 'https://cms.example.com/custom-api/alt-text-plugin/generate/bulk', + expect.objectContaining({ method: 'POST' }), + ) + }) +}) diff --git a/alt-text/src/components/BulkGenerateAltTextsButton.tsx b/alt-text/src/components/BulkGenerateAltTextsButton.tsx index 6aad2e79..0107827a 100644 --- a/alt-text/src/components/BulkGenerateAltTextsButton.tsx +++ b/alt-text/src/components/BulkGenerateAltTextsButton.tsx @@ -1,6 +1,6 @@ 'use client' -import { Button, toast, useSelection, useTranslation } from '@payloadcms/ui' +import { Button, toast, useConfig, useSelection, useTranslation } from '@payloadcms/ui' import { useRouter } from 'next/navigation.js' import { useTransition } from 'react' @@ -16,6 +16,12 @@ export function BulkGenerateAltTextsButton({ collectionSlug }: { collectionSlug: const { t } = useTranslation() const [isPending, startTransition] = useTransition() const { selected, setSelection } = useSelection() + const { + config: { + routes: { api: apiRoute }, + serverURL, + }, + } = useConfig() const selectedIds = Array.from(selected.entries()) .filter(([, isSelected]) => isSelected) @@ -30,13 +36,16 @@ export function BulkGenerateAltTextsButton({ collectionSlug }: { collectionSlug: } try { - const response = await fetch('/api/alt-text-plugin/generate/bulk', { - body: JSON.stringify({ - collection: collectionSlug, - ids: selectedIds, - }), - method: 'POST', - }) + const response = await fetch( + `${serverURL ?? ''}${apiRoute}/alt-text-plugin/generate/bulk`, + { + body: JSON.stringify({ + collection: collectionSlug, + ids: selectedIds, + }), + method: 'POST', + }, + ) if (!response.ok) { toast.error(t('@jhb.software/payload-alt-text-plugin:failedToGenerate')) diff --git a/alt-text/src/components/GenerateAltTextButton.test.tsx b/alt-text/src/components/GenerateAltTextButton.test.tsx new file mode 100644 index 00000000..64f8750f --- /dev/null +++ b/alt-text/src/components/GenerateAltTextButton.test.tsx @@ -0,0 +1,92 @@ +// @vitest-environment jsdom +import { cleanup, fireEvent, render, screen } from '@testing-library/react' +import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' + +const mockConfig = { routes: { api: '/api' }, serverURL: '' } +const mockSetAltText = vi.fn() +const mockSetKeywords = vi.fn() + +vi.mock('@payloadcms/ui', () => ({ + Button: ({ + children, + disabled, + onClick, + }: { + children: React.ReactNode + disabled?: boolean + onClick?: () => void + }) => ( + + ), + toast: { error: vi.fn(), success: vi.fn() }, + useConfig: () => ({ config: mockConfig }), + useDocumentInfo: () => ({ id: 'doc-1', collectionSlug: 'media' }), + useField: ({ path }: { path: string }) => { + if (path === 'alt') { + return { setValue: mockSetAltText, value: '' } + } + if (path === 'keywords') { + return { setValue: mockSetKeywords, value: '' } + } + if (path === 'mimeType') { + return { setValue: vi.fn(), value: 'image/png' } + } + return { setValue: vi.fn(), value: '' } + }, + useLocale: () => ({ code: 'en' }), + useTranslation: () => ({ t: (key: string) => key }), +})) + +const { GenerateAltTextButton } = await import('./GenerateAltTextButton.js') + +describe('GenerateAltTextButton', () => { + beforeEach(() => { + mockConfig.routes.api = '/api' + mockConfig.serverURL = '' + mockSetAltText.mockClear() + mockSetKeywords.mockClear() + vi.stubGlobal( + 'fetch', + vi.fn(() => + Promise.resolve({ + json: () => Promise.resolve({ altText: 'A cat', keywords: ['cat'] }), + ok: true, + } as Response), + ), + ) + }) + + afterEach(() => { + cleanup() + vi.restoreAllMocks() + }) + + it('posts to the default /api route when no custom route is configured', async () => { + render() + fireEvent.click(screen.getByRole('button')) + await Promise.resolve() + await Promise.resolve() + + expect(fetch).toHaveBeenCalledWith( + '/api/alt-text-plugin/generate', + expect.objectContaining({ method: 'POST' }), + ) + }) + + it('posts to the configured API route when a custom route is set', async () => { + mockConfig.routes.api = '/custom-api' + mockConfig.serverURL = 'https://cms.example.com' + + render() + fireEvent.click(screen.getByRole('button')) + await Promise.resolve() + await Promise.resolve() + + expect(fetch).toHaveBeenCalledWith( + 'https://cms.example.com/custom-api/alt-text-plugin/generate', + expect.objectContaining({ method: 'POST' }), + ) + }) +}) diff --git a/alt-text/src/components/GenerateAltTextButton.tsx b/alt-text/src/components/GenerateAltTextButton.tsx index 6004ba6c..a987a1c8 100644 --- a/alt-text/src/components/GenerateAltTextButton.tsx +++ b/alt-text/src/components/GenerateAltTextButton.tsx @@ -1,6 +1,14 @@ 'use client' -import { Button, toast, useDocumentInfo, useField, useLocale, useTranslation } from '@payloadcms/ui' +import { + Button, + toast, + useConfig, + useDocumentInfo, + useField, + useLocale, + useTranslation, +} from '@payloadcms/ui' import { useTransition } from 'react' import type { @@ -16,6 +24,12 @@ export function GenerateAltTextButton({ supportedMimeTypes }: { supportedMimeTyp const { id, collectionSlug } = useDocumentInfo() const locale = useLocale() const [isPending, startTransition] = useTransition() + const { + config: { + routes: { api: apiRoute }, + serverURL, + }, + } = useConfig() const { setValue: setKeywords } = useField({ path: 'keywords' }) const { setValue: setAltText } = useField({ path: 'alt' }) @@ -32,7 +46,7 @@ export function GenerateAltTextButton({ supportedMimeTypes }: { supportedMimeTyp startTransition(async () => { try { - const response = await fetch('/api/alt-text-plugin/generate', { + const response = await fetch(`${serverURL ?? ''}${apiRoute}/alt-text-plugin/generate`, { body: JSON.stringify({ id: id as string, collection: collectionSlug, diff --git a/alt-text/tsconfig.build.json b/alt-text/tsconfig.build.json new file mode 100644 index 00000000..07310ec7 --- /dev/null +++ b/alt-text/tsconfig.build.json @@ -0,0 +1,4 @@ +{ + "extends": "./tsconfig.json", + "exclude": ["./src/**/*.test.ts", "./src/**/*.test.tsx"] +} diff --git a/alt-text/vitest.config.ts b/alt-text/vitest.config.ts new file mode 100644 index 00000000..077c3851 --- /dev/null +++ b/alt-text/vitest.config.ts @@ -0,0 +1,12 @@ +import { defineConfig } from 'vitest/config' + +export default defineConfig({ + oxc: { + jsx: 'automatic', + }, + test: { + // The legacy test/*.test.ts files use node:test instead of vitest. + // They are executed separately via `test:health`. + exclude: ['**/node_modules/**', '**/dev/**', '**/dev_unlocalized/**', 'test/**'], + }, +}) diff --git a/pages/CHANGELOG.md b/pages/CHANGELOG.md index 36fddd12..3166de33 100644 --- a/pages/CHANGELOG.md +++ b/pages/CHANGELOG.md @@ -2,6 +2,7 @@ ## Unreleased +- fix: respect a user-customized `routes.api` when `getBreadcrumbs` is called from client-side field components. `getBreadcrumbs` now takes an optional `apiURL` argument (required when called without a `req`) that the `PathField` supplies from `useConfig()`. The internal `fetchRestApi` helper has been removed and inlined. - style: standardize icons to use Geist icon set (16x16 filled) - refactor: use i18next interpolation for translations diff --git a/pages/package.json b/pages/package.json index ea3f0a00..d4a56e41 100644 --- a/pages/package.json +++ b/pages/package.json @@ -22,7 +22,8 @@ "clean": "rimraf --glob {dist,*.tsbuildinfo}", "dev": "tsc -w", "format": "prettier --write src \"*.{json,md,js,mjs,cjs}\"", - "test": "pnpm test:localized && pnpm test:unlocalized && pnpm test:multi-tenant", + "test": "pnpm test:unit && pnpm test:localized && pnpm test:unlocalized && pnpm test:multi-tenant", + "test:unit": "vitest run", "test:localized": "pnpm test:localized:mongodb && pnpm test:localized:sqlite", "test:localized:mongodb": "cd dev && pnpm test:mongodb", "test:localized:sqlite": "cd dev && pnpm test:sqlite", @@ -57,7 +58,8 @@ "eslint": "^9.0.0", "prettier": "^3.8.3", "rimraf": "6.1.3", - "typescript": "5.9.3" + "typescript": "5.9.3", + "vitest": "^4.1.2" }, "files": [ "dist" diff --git a/pages/pnpm-lock.yaml b/pages/pnpm-lock.yaml index 53e50250..d0e2c5c4 100644 --- a/pages/pnpm-lock.yaml +++ b/pages/pnpm-lock.yaml @@ -57,6 +57,9 @@ importers: typescript: specifier: 5.9.3 version: 5.9.3 + vitest: + specifier: ^4.1.2 + version: 4.1.4(@types/node@25.6.0)(vite@8.0.8(@types/node@25.6.0)(esbuild@0.27.2)(sass@1.77.4)(tsx@4.21.0)) dev: dependencies: @@ -5913,7 +5916,7 @@ snapshots: '@typescript-eslint/eslint-plugin@8.26.1(@typescript-eslint/parser@8.26.1(eslint@9.22.0)(typescript@5.9.3))(eslint@9.22.0)(typescript@5.7.3)': dependencies: '@eslint-community/regexpp': 4.12.2 - '@typescript-eslint/parser': 8.26.1(eslint@9.22.0)(typescript@5.9.3) + '@typescript-eslint/parser': 8.26.1(eslint@9.22.0)(typescript@5.7.3) '@typescript-eslint/scope-manager': 8.26.1 '@typescript-eslint/type-utils': 8.26.1(eslint@9.22.0)(typescript@5.7.3) '@typescript-eslint/utils': 8.26.1(eslint@9.22.0)(typescript@5.7.3) @@ -5930,7 +5933,7 @@ snapshots: '@typescript-eslint/eslint-plugin@8.26.1(@typescript-eslint/parser@8.26.1(eslint@9.22.0)(typescript@5.9.3))(eslint@9.22.0)(typescript@5.9.3)': dependencies: '@eslint-community/regexpp': 4.12.2 - '@typescript-eslint/parser': 8.26.1(eslint@9.22.0)(typescript@5.9.3) + '@typescript-eslint/parser': 8.26.1(eslint@9.22.0)(typescript@5.7.3) '@typescript-eslint/scope-manager': 8.26.1 '@typescript-eslint/type-utils': 8.26.1(eslint@9.22.0)(typescript@5.9.3) '@typescript-eslint/utils': 8.26.1(eslint@9.22.0)(typescript@5.9.3) @@ -5957,18 +5960,6 @@ snapshots: transitivePeerDependencies: - supports-color - '@typescript-eslint/parser@8.26.1(eslint@9.22.0)(typescript@5.9.3)': - dependencies: - '@typescript-eslint/scope-manager': 8.26.1 - '@typescript-eslint/types': 8.26.1 - '@typescript-eslint/typescript-estree': 8.26.1(typescript@5.9.3) - '@typescript-eslint/visitor-keys': 8.26.1 - debug: 4.4.3 - eslint: 9.22.0 - typescript: 5.9.3 - transitivePeerDependencies: - - supports-color - '@typescript-eslint/project-service@8.58.2(typescript@5.7.3)': dependencies: '@typescript-eslint/tsconfig-utils': 8.58.2(typescript@5.7.3) @@ -6058,6 +6049,7 @@ snapshots: typescript: 5.9.3 transitivePeerDependencies: - supports-color + optional: true '@typescript-eslint/typescript-estree@8.58.2(typescript@5.7.3)': dependencies: @@ -8798,6 +8790,7 @@ snapshots: ts-api-utils@2.5.0(typescript@5.9.3): dependencies: typescript: 5.9.3 + optional: true ts-essentials@10.0.3(typescript@5.9.3): optionalDependencies: diff --git a/pages/src/components/client/PathField.tsx b/pages/src/components/client/PathField.tsx index cca5472b..8bc006ad 100644 --- a/pages/src/components/client/PathField.tsx +++ b/pages/src/components/client/PathField.tsx @@ -68,6 +68,7 @@ export const PathField: TextFieldClientComponent = ({ field, path: fieldPath }) doc[breadcrumbLabelFieldName] = breadcrumbLabel const fechtchedBreadcrumbs = (await getBreadcrumbsForDoc({ + apiURL: `${config.serverURL ?? ''}${config.routes.api}`, breadcrumbLabelField: breadcrumbLabelFieldName, data: doc, locale, diff --git a/pages/src/utils/fetchRestApi.ts b/pages/src/utils/fetchRestApi.ts deleted file mode 100644 index 58bc7381..00000000 --- a/pages/src/utils/fetchRestApi.ts +++ /dev/null @@ -1,19 +0,0 @@ -import { stringify } from 'qs-esm' - -/** Fetches a document via the Payload REST API. This should only be used if the local API is not available. */ -export async function fetchRestApi(path: string, options: Record): Promise { - const response = await fetch('/api' + path + '?' + stringify(options), { - headers: { - 'Content-Type': 'application/json', - }, - method: 'GET', - }) - - if (!response.ok) { - throw new Error( - `Failed to fetch the requested document via the Payload REST API. ${response.statusText}`, - ) - } - - return await response.json() -} diff --git a/pages/src/utils/getBreadcrumbs.ts b/pages/src/utils/getBreadcrumbs.ts index 4b5def2a..0af0013c 100644 --- a/pages/src/utils/getBreadcrumbs.ts +++ b/pages/src/utils/getBreadcrumbs.ts @@ -1,14 +1,16 @@ import type { CollectionSlug, PayloadRequest } from 'payload' +import { stringify } from 'qs-esm' + import type { Breadcrumb } from '../types/Breadcrumb.js' import type { Locale } from '../types/Locale.js' -import { fetchRestApi } from './fetchRestApi.js' import { pathFromBreadcrumbs } from './pathFromBreadcrumbs.js' import { ROOT_PAGE_SLUG } from './setRootPageVirtualFields.js' /** Returns the breadcrumbs to the given document. */ export async function getBreadcrumbs({ + apiURL, breadcrumbLabelField, data, locale, @@ -17,6 +19,12 @@ export async function getBreadcrumbs({ parentField, req, }: { + /** + * Base URL of the Payload REST API (e.g. `${serverURL}${routes.api}`). + * Required when `req` is undefined (i.e. when called from a client component) + * so the plugin respects a user-customized `routes.api`. + */ + apiURL?: string breadcrumbLabelField: string data: Record // eslint-disable-next-line @typescript-eslint/no-redundant-type-constituents @@ -61,23 +69,30 @@ export async function getBreadcrumbs({ throw new Error('Parent ID not found for document with id ' + data.id) } - const parent = req - ? await findByIDCached({ - id: parentId, - collection: parentCollection, - locale, - req, - }) - : await fetchRestApi<{ breadcrumbs: Breadcrumb[]; id: number | string }>( - `/${parentCollection}/${parentId}`, - { - depth: 0, - locale, - select: { - breadcrumbs: true, - }, - }, + let parent: null | Record | undefined + if (req) { + parent = await findByIDCached({ + id: parentId, + collection: parentCollection, + locale, + req, + }) + } else { + if (!apiURL) { + throw new Error('[Pages Plugin] getBreadcrumbs requires `apiURL` when called without `req`.') + } + const query = stringify({ depth: 0, locale, select: { breadcrumbs: true } }) + const response = await fetch(`${apiURL}/${parentCollection}/${parentId}?${query}`, { + headers: { 'Content-Type': 'application/json' }, + method: 'GET', + }) + if (!response.ok) { + throw new Error( + `Failed to fetch the parent document via the Payload REST API. ${response.statusText}`, ) + } + parent = (await response.json()) as Record + } if (!parent) { // This can be the case, when the parent document got deleted. diff --git a/pages/test/getBreadcrumbs.test.ts b/pages/test/getBreadcrumbs.test.ts new file mode 100644 index 00000000..a9d06ba6 --- /dev/null +++ b/pages/test/getBreadcrumbs.test.ts @@ -0,0 +1,76 @@ +import { afterEach, beforeEach, describe, expect, test, vi } from 'vitest' + +import { getBreadcrumbs } from '../src/utils/getBreadcrumbs.js' + +describe('getBreadcrumbs (client path)', () => { + let recordedUrl: string | undefined + + beforeEach(() => { + recordedUrl = undefined + vi.stubGlobal( + 'fetch', + vi.fn((input: RequestInfo | URL) => { + recordedUrl = typeof input === 'string' ? input : input.toString() + return Promise.resolve({ + json: () => + Promise.resolve({ + breadcrumbs: [{ label: 'Root', path: '/root', slug: 'root' }], + id: 'parent-1', + }), + ok: true, + statusText: 'OK', + } as Response) + }), + ) + }) + + afterEach(() => { + vi.restoreAllMocks() + }) + + test('fetches the parent via the provided apiURL', async () => { + await getBreadcrumbs({ + apiURL: '/api', + breadcrumbLabelField: 'title', + data: { id: 'child-1', parent: 'parent-1', slug: 'child', title: 'Child' }, + locale: undefined, + locales: undefined, + parentCollection: 'pages', + parentField: 'parent', + req: undefined, + }) + + expect(recordedUrl).toBeDefined() + expect(recordedUrl).toMatch(/^\/api\/pages\/parent-1/) + }) + + test('respects a user-customized routes.api baked into apiURL', async () => { + await getBreadcrumbs({ + apiURL: 'https://cms.example.com/custom-api', + breadcrumbLabelField: 'title', + data: { id: 'child-1', parent: 'parent-1', slug: 'child', title: 'Child' }, + locale: undefined, + locales: undefined, + parentCollection: 'pages', + parentField: 'parent', + req: undefined, + }) + + expect(recordedUrl).toBeDefined() + expect(recordedUrl).toMatch(/^https:\/\/cms\.example\.com\/custom-api\/pages\/parent-1/) + }) + + test('throws a clear error when called without req and without apiURL', async () => { + await expect(() => + getBreadcrumbs({ + breadcrumbLabelField: 'title', + data: { id: 'child-1', parent: 'parent-1', slug: 'child', title: 'Child' }, + locale: undefined, + locales: undefined, + parentCollection: 'pages', + parentField: 'parent', + req: undefined, + }), + ).rejects.toThrow(/requires `apiURL` when called without `req`/) + }) +}) diff --git a/pages/vitest.config.ts b/pages/vitest.config.ts new file mode 100644 index 00000000..3e427974 --- /dev/null +++ b/pages/vitest.config.ts @@ -0,0 +1,7 @@ +import { defineConfig } from 'vitest/config' + +export default defineConfig({ + test: { + include: ['test/**/*.test.ts'], + }, +}) diff --git a/vercel-deployments/CHANGELOG.md b/vercel-deployments/CHANGELOG.md index 9fb23322..ca996aa4 100644 --- a/vercel-deployments/CHANGELOG.md +++ b/vercel-deployments/CHANGELOG.md @@ -1,5 +1,9 @@ # Changelog +## Unreleased + +- fix: respect a user-customized `routes.api` in the deployment poller and trigger button (the fetch previously hardcoded `/api/vercel-deployments`) + ## 0.2.1 - style: standardize icons to use Geist icon set (16x16 filled) diff --git a/vercel-deployments/eslint.config.js b/vercel-deployments/eslint.config.js index 71a819a6..3f42ca69 100644 --- a/vercel-deployments/eslint.config.js +++ b/vercel-deployments/eslint.config.js @@ -30,6 +30,15 @@ export default [ 'no-console': 'off', // TODO: remove this rule and use the Payload logger instead }, }, + { + // Test files use `vi.mock` to stub hooks like `useConfig`, `useRouter`, etc. + // The mock arrow functions share names with real hooks, so the rule flags them + // as "useless custom hooks" — but they are intentionally plain mocks. + files: ['src/**/*.test.ts', 'src/**/*.test.tsx'], + rules: { + '@eslint-react/hooks-extra/no-useless-custom-hooks': 'off', + }, + }, { ignores: defaultESLintIgnores, }, diff --git a/vercel-deployments/package.json b/vercel-deployments/package.json index 4b5eb6e9..af436ff7 100644 --- a/vercel-deployments/package.json +++ b/vercel-deployments/package.json @@ -20,7 +20,7 @@ "types": "./src/index.ts", "scripts": { "build": "pnpm copyfiles && pnpm build:types && pnpm build:swc", - "build:swc": "swc ./src -d ./dist --config-file .swcrc --strip-leading-paths --ignore '**/*.test.ts'", + "build:swc": "swc ./src -d ./dist --config-file .swcrc --strip-leading-paths --ignore '**/*.test.ts,**/*.test.tsx'", "build:types": "tsc --project tsconfig.build.json --outDir dist --rootDir ./src", "copyfiles": "copyfiles -u 1 \"src/**/*.{html,css,scss,ttf,woff,woff2,eot,svg,jpg,png,json}\" dist/", "clean": "rimraf --glob {dist,*.tsbuildinfo}", diff --git a/vercel-deployments/src/components/DeploymentStatusPoller.test.tsx b/vercel-deployments/src/components/DeploymentStatusPoller.test.tsx index 0a726a92..57057bbe 100644 --- a/vercel-deployments/src/components/DeploymentStatusPoller.test.tsx +++ b/vercel-deployments/src/components/DeploymentStatusPoller.test.tsx @@ -7,6 +7,11 @@ vi.mock('next/navigation.js', () => ({ useRouter: () => ({ refresh: mockRefresh }), })) +const mockConfig = { routes: { api: '/api' }, serverURL: '' } +vi.mock('@payloadcms/ui', () => ({ + useConfig: () => ({ config: mockConfig }), +})) + const { DeploymentStatusPoller } = await import('./DeploymentStatusPoller.js') function mockFetchResponse(data: object) { @@ -42,6 +47,8 @@ describe('DeploymentStatusPoller', () => { beforeEach(() => { vi.useFakeTimers() mockRefresh.mockClear() + mockConfig.routes.api = '/api' + mockConfig.serverURL = '' vi.stubGlobal( 'fetch', vi.fn(() => Promise.resolve(mockFetchResponse(idleDeploymentsResponse))), @@ -130,4 +137,47 @@ describe('DeploymentStatusPoller', () => { expect(fetch).toHaveBeenCalledWith('/api/vercel-deployments', expect.anything()) }) + + it('uses the configured API route when polling the list endpoint', async () => { + mockConfig.routes.api = '/custom-api' + mockConfig.serverURL = 'https://cms.example.com' + + render( + +
+ , + ) + await flushPolling() + + expect(fetch).toHaveBeenCalledWith( + 'https://cms.example.com/custom-api/vercel-deployments', + expect.anything(), + ) + }) + + it('uses the configured API route when polling an active deployment', async () => { + mockConfig.routes.api = '/custom-api' + mockConfig.serverURL = 'https://cms.example.com' + + // Start with a building deployment so the poller switches to active mode + vi.mocked(fetch).mockResolvedValue(mockFetchResponse(buildingDeploymentsResponse)) + + render( + +
+ , + ) + await flushPolling() + + vi.mocked(fetch).mockResolvedValue(mockFetchResponse({ id: 'dpl-2', status: 'BUILDING' })) + vi.mocked(fetch).mockClear() + + await vi.advanceTimersByTimeAsync(5 * 1000) + await flushPolling() + + expect(fetch).toHaveBeenCalledWith( + expect.stringContaining('https://cms.example.com/custom-api/vercel-deployments?id=dpl-2'), + expect.anything(), + ) + }) }) diff --git a/vercel-deployments/src/components/DeploymentStatusPoller.tsx b/vercel-deployments/src/components/DeploymentStatusPoller.tsx index b2744acd..6194b975 100644 --- a/vercel-deployments/src/components/DeploymentStatusPoller.tsx +++ b/vercel-deployments/src/components/DeploymentStatusPoller.tsx @@ -1,5 +1,6 @@ 'use client' +import { useConfig } from '@payloadcms/ui' import { useRouter } from 'next/navigation.js' import React, { createContext, use, useCallback, useEffect, useRef, useState } from 'react' @@ -27,6 +28,13 @@ export const useDeploymentPoller = () => use(PollerContext) */ export const DeploymentStatusPoller: React.FC<{ children: React.ReactNode }> = ({ children }) => { const router = useRouter() + const { + config: { + routes: { api: apiRoute }, + serverURL, + }, + } = useConfig() + const deploymentsEndpoint = `${serverURL ?? ''}${apiRoute}/vercel-deployments` const intervalRef = useRef>(null) const activeDeploymentIdRef = useRef(null) const [isBuilding, setIsBuilding] = useState(false) @@ -59,7 +67,7 @@ export const DeploymentStatusPoller: React.FC<{ children: React.ReactNode }> = ( } try { - const res = await fetch(`/api/vercel-deployments?id=${encodeURIComponent(deploymentId)}`, { + const res = await fetch(`${deploymentsEndpoint}?id=${encodeURIComponent(deploymentId)}`, { credentials: 'include', }) if (!res.ok) { @@ -87,12 +95,12 @@ export const DeploymentStatusPoller: React.FC<{ children: React.ReactNode }> = ( } catch { // Silently ignore polling errors } - }, [router]) + }, [router, deploymentsEndpoint]) // Poll the list endpoint to detect any in-progress deployments (idle mode) const pollDeploymentsList = useCallback(async () => { try { - const res = await fetch('/api/vercel-deployments', { credentials: 'include' }) + const res = await fetch(deploymentsEndpoint, { credentials: 'include' }) if (!res.ok) { return } @@ -122,7 +130,7 @@ export const DeploymentStatusPoller: React.FC<{ children: React.ReactNode }> = ( } catch { // Silently ignore polling errors } - }, [router]) + }, [router, deploymentsEndpoint]) // Switch between idle and active polling based on build state useEffect(() => { diff --git a/vercel-deployments/src/components/TriggerDeploymentButton.test.tsx b/vercel-deployments/src/components/TriggerDeploymentButton.test.tsx new file mode 100644 index 00000000..f5a67a9a --- /dev/null +++ b/vercel-deployments/src/components/TriggerDeploymentButton.test.tsx @@ -0,0 +1,76 @@ +// @vitest-environment jsdom +import { cleanup, fireEvent, render, screen } from '@testing-library/react' +import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' + +const mockNotifyBuildTriggered = vi.fn() +vi.mock('./DeploymentStatusPoller.js', () => ({ + useDeploymentPoller: () => ({ notifyBuildTriggered: mockNotifyBuildTriggered }), +})) + +vi.mock('../react-hooks/useDashboardTranslation.js', () => ({ + useDashboardTranslation: () => ({ t: (key: string) => key }), +})) + +vi.mock('@payloadcms/ui', () => ({ + Button: ({ children, onClick }: { children: React.ReactNode; onClick?: () => void }) => ( + + ), + toast: { error: vi.fn(), success: vi.fn() }, + useConfig: () => ({ config: mockConfig }), +})) + +const mockConfig = { routes: { api: '/api' }, serverURL: '' } + +const { TriggerFrontendDeploymentButton } = await import('./TriggerDeploymentButton.js') + +describe('TriggerFrontendDeploymentButton', () => { + beforeEach(() => { + mockConfig.routes.api = '/api' + mockConfig.serverURL = '' + mockNotifyBuildTriggered.mockClear() + vi.stubGlobal( + 'fetch', + vi.fn(() => + Promise.resolve({ + json: () => Promise.resolve({ id: 'dpl-1' }), + ok: true, + } as Response), + ), + ) + }) + + afterEach(() => { + cleanup() + vi.restoreAllMocks() + }) + + it('posts to the default /api route when no custom route is configured', async () => { + render() + fireEvent.click(screen.getByRole('button')) + // wait for the transition microtask + fetch promise to settle + await Promise.resolve() + await Promise.resolve() + + expect(fetch).toHaveBeenCalledWith( + '/api/vercel-deployments', + expect.objectContaining({ method: 'POST' }), + ) + }) + + it('posts to the configured API route when a custom route is set', async () => { + mockConfig.routes.api = '/custom-api' + mockConfig.serverURL = 'https://cms.example.com' + + render() + fireEvent.click(screen.getByRole('button')) + await Promise.resolve() + await Promise.resolve() + + expect(fetch).toHaveBeenCalledWith( + 'https://cms.example.com/custom-api/vercel-deployments', + expect.objectContaining({ method: 'POST' }), + ) + }) +}) diff --git a/vercel-deployments/src/components/TriggerDeploymentButton.tsx b/vercel-deployments/src/components/TriggerDeploymentButton.tsx index b31c605e..b3960121 100644 --- a/vercel-deployments/src/components/TriggerDeploymentButton.tsx +++ b/vercel-deployments/src/components/TriggerDeploymentButton.tsx @@ -1,5 +1,5 @@ 'use client' -import { Button, toast } from '@payloadcms/ui' +import { Button, toast, useConfig } from '@payloadcms/ui' import React, { useTransition } from 'react' import { useDashboardTranslation } from '../react-hooks/useDashboardTranslation.js' @@ -11,11 +11,17 @@ export const TriggerFrontendDeploymentButton: React.FC = () => { const [isPending, startTransition] = useTransition() const { t } = useDashboardTranslation() const { notifyBuildTriggered } = useDeploymentPoller() + const { + config: { + routes: { api: apiRoute }, + serverURL, + }, + } = useConfig() const handleClick = () => { startTransition(async () => { try { - const res = await fetch('/api/vercel-deployments', { + const res = await fetch(`${serverURL ?? ''}${apiRoute}/vercel-deployments`, { credentials: 'include', method: 'POST', })