From b9064244e323f6cc20173eac2c0dbea7f71bc8ee Mon Sep 17 00:00:00 2001 From: Jesus Armando Anaya Date: Sun, 12 Apr 2026 19:38:56 -0700 Subject: [PATCH 1/7] Add project tooling and configuration --- .editorconfig | 12 + .npmrc | 2 + .nvmrc | 1 + .prettierrc | 8 + eslint.config.js | 95 ++ package.json | 96 ++ pnpm-lock.yaml | 3912 ++++++++++++++++++++++++++++++++++++++++++++++ tsconfig.json | 21 + tsup.config.ts | 30 + vitest.config.ts | 13 + 10 files changed, 4190 insertions(+) create mode 100644 .editorconfig create mode 100644 .npmrc create mode 100644 .nvmrc create mode 100644 .prettierrc create mode 100644 eslint.config.js create mode 100644 package.json create mode 100644 pnpm-lock.yaml create mode 100644 tsconfig.json create mode 100644 tsup.config.ts create mode 100644 vitest.config.ts diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..8c52ff9 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,12 @@ +root = true + +[*] +charset = utf-8 +end_of_line = lf +indent_style = space +indent_size = 2 +insert_final_newline = true +trim_trailing_whitespace = true + +[*.md] +trim_trailing_whitespace = false diff --git a/.npmrc b/.npmrc new file mode 100644 index 0000000..55b8fde --- /dev/null +++ b/.npmrc @@ -0,0 +1,2 @@ +enable-pre-post-scripts=true +allowed-build-scripts=esbuild diff --git a/.nvmrc b/.nvmrc new file mode 100644 index 0000000..2bd5a0a --- /dev/null +++ b/.nvmrc @@ -0,0 +1 @@ +22 diff --git a/.prettierrc b/.prettierrc new file mode 100644 index 0000000..d798661 --- /dev/null +++ b/.prettierrc @@ -0,0 +1,8 @@ +{ + "singleQuote": true, + "trailingComma": "all", + "semi": true, + "printWidth": 100, + "tabWidth": 2, + "useTabs": false +} diff --git a/eslint.config.js b/eslint.config.js new file mode 100644 index 0000000..a2f659f --- /dev/null +++ b/eslint.config.js @@ -0,0 +1,95 @@ +// This file is part of opencv-js-react project from Robomous. +// It is subject to the license terms in the LICENSE file found in the top-level directory + +import js from '@eslint/js'; +import tsPlugin from '@typescript-eslint/eslint-plugin'; +import tsParser from '@typescript-eslint/parser'; +import reactPlugin from 'eslint-plugin-react'; +import reactHooksPlugin from 'eslint-plugin-react-hooks'; +import globals from 'globals'; + +export default [ + { + ignores: ['dist/**', 'build/**', 'node_modules/**', 'coverage/**'], + }, + js.configs.recommended, + { + files: ['src/**/*.{ts,tsx}'], + languageOptions: { + parser: tsParser, + parserOptions: { + ecmaVersion: 'latest', + sourceType: 'module', + ecmaFeatures: { jsx: true }, + }, + globals: { + ...globals.browser, + }, + }, + plugins: { + '@typescript-eslint': tsPlugin, + react: reactPlugin, + 'react-hooks': reactHooksPlugin, + }, + rules: { + ...tsPlugin.configs.recommended.rules, + ...reactPlugin.configs.recommended.rules, + ...reactHooksPlugin.configs.recommended.rules, + 'react/react-in-jsx-scope': 'off', + '@typescript-eslint/no-explicit-any': 'warn', + // TypeScript handles undefined variables more accurately than ESLint's no-undef + 'no-undef': 'off', + }, + settings: { + react: { version: 'detect' }, + }, + }, + { + files: ['tests/**/*.{ts,tsx}'], + languageOptions: { + parser: tsParser, + parserOptions: { + ecmaVersion: 'latest', + sourceType: 'module', + ecmaFeatures: { jsx: true }, + }, + globals: { + ...globals.browser, + ...globals.node, + }, + }, + plugins: { + '@typescript-eslint': tsPlugin, + react: reactPlugin, + 'react-hooks': reactHooksPlugin, + }, + rules: { + ...tsPlugin.configs.recommended.rules, + ...reactPlugin.configs.recommended.rules, + ...reactHooksPlugin.configs.recommended.rules, + 'react/react-in-jsx-scope': 'off', + '@typescript-eslint/no-explicit-any': 'warn', + '@typescript-eslint/no-unused-vars': ['error', { argsIgnorePattern: '^_', varsIgnorePattern: '^_' }], + 'no-undef': 'off', + }, + settings: { + react: { version: 'detect' }, + }, + }, + { + files: ['scripts/**/*.mjs'], + languageOptions: { + globals: { + ...globals.node, + }, + }, + }, + { + files: ['tsup.config.ts', 'vitest.config.ts', 'eslint.config.js'], + languageOptions: { + globals: { + ...globals.node, + }, + }, + }, +]; diff --git a/package.json b/package.json new file mode 100644 index 0000000..1c3d576 --- /dev/null +++ b/package.json @@ -0,0 +1,96 @@ +{ + "name": "opencv-js-react", + "version": "0.1.0", + "description": "A React wrapper for the official OpenCV.js library.", + "type": "module", + "main": "./dist/index.cjs", + "module": "./dist/index.js", + "types": "./dist/index.d.ts", + "exports": { + ".": { + "import": { + "types": "./dist/index.d.ts", + "default": "./dist/index.js" + }, + "require": { + "types": "./dist/index.d.cts", + "default": "./dist/index.cjs" + } + } + }, + "files": [ + "dist", + "LICENSE", + "README.md" + ], + "sideEffects": false, + "scripts": { + "clean": "rm -rf dist build/opencv-js/checkouts build/opencv-js/releases", + "vendor:opencv": "node scripts/vendor-opencv.mjs", + "verify:opencv": "node scripts/verify-opencv-version.mjs", + "build:opencv:release": "bash scripts/build-opencv-js-release.sh", + "build": "tsup", + "dev": "tsup --watch", + "typecheck": "tsc --noEmit", + "lint": "eslint .", + "format": "prettier --write .", + "test": "vitest run", + "test:watch": "vitest", + "test:all-versions": "bash scripts/test-all-versions.sh", + "audit:deps": "pnpm audit --audit-level moderate", + "prepack": "pnpm run typecheck && pnpm run test && pnpm run build" + }, + "peerDependencies": { + "react": "^18.0.0 || ^19.0.0", + "react-dom": "^18.0.0 || ^19.0.0" + }, + "devDependencies": { + "@eslint/js": "^10.0.1", + "@testing-library/jest-dom": "^6.6.3", + "@testing-library/react": "^16.3.0", + "@testing-library/user-event": "^14.5.2", + "@types/react": "^19.1.0", + "@types/react-dom": "^19.1.2", + "@typescript-eslint/eslint-plugin": "^8.31.0", + "@typescript-eslint/parser": "^8.31.0", + "eslint": "^9.25.0", + "eslint-plugin-react": "^7.37.5", + "eslint-plugin-react-hooks": "^5.2.0", + "globals": "^17.5.0", + "jsdom": "^26.1.0", + "prettier": "^3.5.3", + "react": "^19.1.0", + "react-dom": "^19.1.0", + "tsup": "^8.4.0", + "typescript": "^5.8.3", + "vitest": "^3.1.1" + }, + "packageManager": "pnpm@10.9.0", + "engines": { + "node": ">=22.0.0" + }, + "license": "Apache-2.0", + "repository": { + "type": "git", + "url": "https://github.com/Robomous/opencv-js-react.git" + }, + "bugs": { + "url": "https://github.com/Robomous/opencv-js-react/issues" + }, + "homepage": "https://github.com/Robomous/opencv-js-react#readme", + "keywords": [ + "opencv", + "opencv.js", + "react", + "computer-vision", + "webassembly", + "wasm", + "image-processing", + "canvas" + ], + "pnpm": { + "onlyBuiltDependencies": [ + "esbuild" + ] + } +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml new file mode 100644 index 0000000..8955ba1 --- /dev/null +++ b/pnpm-lock.yaml @@ -0,0 +1,3912 @@ +lockfileVersion: '9.0' + +settings: + autoInstallPeers: true + excludeLinksFromLockfile: false + +importers: + + .: + devDependencies: + '@eslint/js': + specifier: ^10.0.1 + version: 10.0.1(eslint@9.39.4) + '@testing-library/jest-dom': + specifier: ^6.6.3 + version: 6.9.1 + '@testing-library/react': + specifier: ^16.3.0 + 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) + '@testing-library/user-event': + specifier: ^14.5.2 + version: 14.6.1(@testing-library/dom@10.4.1) + '@types/react': + specifier: ^19.1.0 + version: 19.2.14 + '@types/react-dom': + specifier: ^19.1.2 + version: 19.2.3(@types/react@19.2.14) + '@typescript-eslint/eslint-plugin': + specifier: ^8.31.0 + version: 8.58.1(@typescript-eslint/parser@8.58.1(eslint@9.39.4)(typescript@5.9.3))(eslint@9.39.4)(typescript@5.9.3) + '@typescript-eslint/parser': + specifier: ^8.31.0 + version: 8.58.1(eslint@9.39.4)(typescript@5.9.3) + eslint: + specifier: ^9.25.0 + version: 9.39.4 + eslint-plugin-react: + specifier: ^7.37.5 + version: 7.37.5(eslint@9.39.4) + eslint-plugin-react-hooks: + specifier: ^5.2.0 + version: 5.2.0(eslint@9.39.4) + globals: + specifier: ^17.5.0 + version: 17.5.0 + jsdom: + specifier: ^26.1.0 + version: 26.1.0 + prettier: + specifier: ^3.5.3 + version: 3.8.2 + react: + specifier: ^19.1.0 + version: 19.2.5 + react-dom: + specifier: ^19.1.0 + version: 19.2.5(react@19.2.5) + tsup: + specifier: ^8.4.0 + version: 8.5.1(postcss@8.5.9)(typescript@5.9.3) + typescript: + specifier: ^5.8.3 + version: 5.9.3 + vitest: + specifier: ^3.1.1 + version: 3.2.4(jsdom@26.1.0) + +packages: + + '@adobe/css-tools@4.4.4': + resolution: {integrity: sha512-Elp+iwUx5rN5+Y8xLt5/GRoG20WGoDCQ/1Fb+1LiGtvwbDavuSk0jhD/eZdckHAuzcDzccnkv+rEjyWfRx18gg==} + + '@asamuzakjp/css-color@3.2.0': + resolution: {integrity: sha512-K1A6z8tS3XsmCMM86xoWdn7Fkdn9m6RSVtocUrJYIwZnFVkng/PvkEoWtOWmP+Scc6saYWHWZYbndEEXxl24jw==} + + '@babel/code-frame@7.29.0': + resolution: {integrity: sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw==} + engines: {node: '>=6.9.0'} + + '@babel/helper-validator-identifier@7.28.5': + resolution: {integrity: sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==} + engines: {node: '>=6.9.0'} + + '@babel/runtime@7.29.2': + resolution: {integrity: sha512-JiDShH45zKHWyGe4ZNVRrCjBz8Nh9TMmZG1kh4QTK8hCBTWBi8Da+i7s1fJw7/lYpM4ccepSNfqzZ/QvABBi5g==} + engines: {node: '>=6.9.0'} + + '@csstools/color-helpers@5.1.0': + resolution: {integrity: sha512-S11EXWJyy0Mz5SYvRmY8nJYTFFd1LCNV+7cXyAgQtOOuzb4EsgfqDufL+9esx72/eLhsRdGZwaldu/h+E4t4BA==} + engines: {node: '>=18'} + + '@csstools/css-calc@2.1.4': + resolution: {integrity: sha512-3N8oaj+0juUw/1H3YwmDDJXCgTB1gKU6Hc/bB502u9zR0q2vd786XJH9QfrKIEgFlZmhZiq6epXl4rHqhzsIgQ==} + engines: {node: '>=18'} + peerDependencies: + '@csstools/css-parser-algorithms': ^3.0.5 + '@csstools/css-tokenizer': ^3.0.4 + + '@csstools/css-color-parser@3.1.0': + resolution: {integrity: sha512-nbtKwh3a6xNVIp/VRuXV64yTKnb1IjTAEEh3irzS+HkKjAOYLTGNb9pmVNntZ8iVBHcWDA2Dof0QtPgFI1BaTA==} + engines: {node: '>=18'} + peerDependencies: + '@csstools/css-parser-algorithms': ^3.0.5 + '@csstools/css-tokenizer': ^3.0.4 + + '@csstools/css-parser-algorithms@3.0.5': + resolution: {integrity: sha512-DaDeUkXZKjdGhgYaHNJTV9pV7Y9B3b644jCLs9Upc3VeNGg6LWARAT6O+Q+/COo+2gg/bM5rhpMAtf70WqfBdQ==} + engines: {node: '>=18'} + peerDependencies: + '@csstools/css-tokenizer': ^3.0.4 + + '@csstools/css-tokenizer@3.0.4': + resolution: {integrity: sha512-Vd/9EVDiu6PPJt9yAh6roZP6El1xHrdvIVGjyBsHR0RYwNHgL7FJPyIIW4fANJNG6FtyZfvlRPpFI4ZM/lubvw==} + engines: {node: '>=18'} + + '@esbuild/aix-ppc64@0.27.7': + resolution: {integrity: sha512-EKX3Qwmhz1eMdEJokhALr0YiD0lhQNwDqkPYyPhiSwKrh7/4KRjQc04sZ8db+5DVVnZ1LmbNDI1uAMPEUBnQPg==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [aix] + + '@esbuild/android-arm64@0.27.7': + resolution: {integrity: sha512-62dPZHpIXzvChfvfLJow3q5dDtiNMkwiRzPylSCfriLvZeq0a1bWChrGx/BbUbPwOrsWKMn8idSllklzBy+dgQ==} + engines: {node: '>=18'} + cpu: [arm64] + os: [android] + + '@esbuild/android-arm@0.27.7': + resolution: {integrity: sha512-jbPXvB4Yj2yBV7HUfE2KHe4GJX51QplCN1pGbYjvsyCZbQmies29EoJbkEc+vYuU5o45AfQn37vZlyXy4YJ8RQ==} + engines: {node: '>=18'} + cpu: [arm] + os: [android] + + '@esbuild/android-x64@0.27.7': + resolution: {integrity: sha512-x5VpMODneVDb70PYV2VQOmIUUiBtY3D3mPBG8NxVk5CogneYhkR7MmM3yR/uMdITLrC1ml/NV1rj4bMJuy9MCg==} + engines: {node: '>=18'} + cpu: [x64] + os: [android] + + '@esbuild/darwin-arm64@0.27.7': + resolution: {integrity: sha512-5lckdqeuBPlKUwvoCXIgI2D9/ABmPq3Rdp7IfL70393YgaASt7tbju3Ac+ePVi3KDH6N2RqePfHnXkaDtY9fkw==} + engines: {node: '>=18'} + cpu: [arm64] + os: [darwin] + + '@esbuild/darwin-x64@0.27.7': + resolution: {integrity: sha512-rYnXrKcXuT7Z+WL5K980jVFdvVKhCHhUwid+dDYQpH+qu+TefcomiMAJpIiC2EM3Rjtq0sO3StMV/+3w3MyyqQ==} + engines: {node: '>=18'} + cpu: [x64] + os: [darwin] + + '@esbuild/freebsd-arm64@0.27.7': + resolution: {integrity: sha512-B48PqeCsEgOtzME2GbNM2roU29AMTuOIN91dsMO30t+Ydis3z/3Ngoj5hhnsOSSwNzS+6JppqWsuhTp6E82l2w==} + engines: {node: '>=18'} + cpu: [arm64] + os: [freebsd] + + '@esbuild/freebsd-x64@0.27.7': + resolution: {integrity: sha512-jOBDK5XEjA4m5IJK3bpAQF9/Lelu/Z9ZcdhTRLf4cajlB+8VEhFFRjWgfy3M1O4rO2GQ/b2dLwCUGpiF/eATNQ==} + engines: {node: '>=18'} + cpu: [x64] + os: [freebsd] + + '@esbuild/linux-arm64@0.27.7': + resolution: {integrity: sha512-RZPHBoxXuNnPQO9rvjh5jdkRmVizktkT7TCDkDmQ0W2SwHInKCAV95GRuvdSvA7w4VMwfCjUiPwDi0ZO6Nfe9A==} + engines: {node: '>=18'} + cpu: [arm64] + os: [linux] + + '@esbuild/linux-arm@0.27.7': + resolution: {integrity: sha512-RkT/YXYBTSULo3+af8Ib0ykH8u2MBh57o7q/DAs3lTJlyVQkgQvlrPTnjIzzRPQyavxtPtfg0EopvDyIt0j1rA==} + engines: {node: '>=18'} + cpu: [arm] + os: [linux] + + '@esbuild/linux-ia32@0.27.7': + resolution: {integrity: sha512-GA48aKNkyQDbd3KtkplYWT102C5sn/EZTY4XROkxONgruHPU72l+gW+FfF8tf2cFjeHaRbWpOYa/uRBz/Xq1Pg==} + engines: {node: '>=18'} + cpu: [ia32] + os: [linux] + + '@esbuild/linux-loong64@0.27.7': + resolution: {integrity: sha512-a4POruNM2oWsD4WKvBSEKGIiWQF8fZOAsycHOt6JBpZ+JN2n2JH9WAv56SOyu9X5IqAjqSIPTaJkqN8F7XOQ5Q==} + engines: {node: '>=18'} + cpu: [loong64] + os: [linux] + + '@esbuild/linux-mips64el@0.27.7': + resolution: {integrity: sha512-KabT5I6StirGfIz0FMgl1I+R1H73Gp0ofL9A3nG3i/cYFJzKHhouBV5VWK1CSgKvVaG4q1RNpCTR2LuTVB3fIw==} + engines: {node: '>=18'} + cpu: [mips64el] + os: [linux] + + '@esbuild/linux-ppc64@0.27.7': + resolution: {integrity: sha512-gRsL4x6wsGHGRqhtI+ifpN/vpOFTQtnbsupUF5R5YTAg+y/lKelYR1hXbnBdzDjGbMYjVJLJTd2OFmMewAgwlQ==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [linux] + + '@esbuild/linux-riscv64@0.27.7': + resolution: {integrity: sha512-hL25LbxO1QOngGzu2U5xeXtxXcW+/GvMN3ejANqXkxZ/opySAZMrc+9LY/WyjAan41unrR3YrmtTsUpwT66InQ==} + engines: {node: '>=18'} + cpu: [riscv64] + os: [linux] + + '@esbuild/linux-s390x@0.27.7': + resolution: {integrity: sha512-2k8go8Ycu1Kb46vEelhu1vqEP+UeRVj2zY1pSuPdgvbd5ykAw82Lrro28vXUrRmzEsUV0NzCf54yARIK8r0fdw==} + engines: {node: '>=18'} + cpu: [s390x] + os: [linux] + + '@esbuild/linux-x64@0.27.7': + resolution: {integrity: sha512-hzznmADPt+OmsYzw1EE33ccA+HPdIqiCRq7cQeL1Jlq2gb1+OyWBkMCrYGBJ+sxVzve2ZJEVeePbLM2iEIZSxA==} + engines: {node: '>=18'} + cpu: [x64] + os: [linux] + + '@esbuild/netbsd-arm64@0.27.7': + resolution: {integrity: sha512-b6pqtrQdigZBwZxAn1UpazEisvwaIDvdbMbmrly7cDTMFnw/+3lVxxCTGOrkPVnsYIosJJXAsILG9XcQS+Yu6w==} + engines: {node: '>=18'} + cpu: [arm64] + os: [netbsd] + + '@esbuild/netbsd-x64@0.27.7': + resolution: {integrity: sha512-OfatkLojr6U+WN5EDYuoQhtM+1xco+/6FSzJJnuWiUw5eVcicbyK3dq5EeV/QHT1uy6GoDhGbFpprUiHUYggrw==} + engines: {node: '>=18'} + cpu: [x64] + os: [netbsd] + + '@esbuild/openbsd-arm64@0.27.7': + resolution: {integrity: sha512-AFuojMQTxAz75Fo8idVcqoQWEHIXFRbOc1TrVcFSgCZtQfSdc1RXgB3tjOn/krRHENUB4j00bfGjyl2mJrU37A==} + engines: {node: '>=18'} + cpu: [arm64] + os: [openbsd] + + '@esbuild/openbsd-x64@0.27.7': + resolution: {integrity: sha512-+A1NJmfM8WNDv5CLVQYJ5PshuRm/4cI6WMZRg1by1GwPIQPCTs1GLEUHwiiQGT5zDdyLiRM/l1G0Pv54gvtKIg==} + engines: {node: '>=18'} + cpu: [x64] + os: [openbsd] + + '@esbuild/openharmony-arm64@0.27.7': + resolution: {integrity: sha512-+KrvYb/C8zA9CU/g0sR6w2RBw7IGc5J2BPnc3dYc5VJxHCSF1yNMxTV5LQ7GuKteQXZtspjFbiuW5/dOj7H4Yw==} + engines: {node: '>=18'} + cpu: [arm64] + os: [openharmony] + + '@esbuild/sunos-x64@0.27.7': + resolution: {integrity: sha512-ikktIhFBzQNt/QDyOL580ti9+5mL/YZeUPKU2ivGtGjdTYoqz6jObj6nOMfhASpS4GU4Q/Clh1QtxWAvcYKamA==} + engines: {node: '>=18'} + cpu: [x64] + os: [sunos] + + '@esbuild/win32-arm64@0.27.7': + resolution: {integrity: sha512-7yRhbHvPqSpRUV7Q20VuDwbjW5kIMwTHpptuUzV+AA46kiPze5Z7qgt6CLCK3pWFrHeNfDd1VKgyP4O+ng17CA==} + engines: {node: '>=18'} + cpu: [arm64] + os: [win32] + + '@esbuild/win32-ia32@0.27.7': + resolution: {integrity: sha512-SmwKXe6VHIyZYbBLJrhOoCJRB/Z1tckzmgTLfFYOfpMAx63BJEaL9ExI8x7v0oAO3Zh6D/Oi1gVxEYr5oUCFhw==} + engines: {node: '>=18'} + cpu: [ia32] + os: [win32] + + '@esbuild/win32-x64@0.27.7': + resolution: {integrity: sha512-56hiAJPhwQ1R4i+21FVF7V8kSD5zZTdHcVuRFMW0hn753vVfQN8xlx4uOPT4xoGH0Z/oVATuR82AiqSTDIpaHg==} + engines: {node: '>=18'} + cpu: [x64] + os: [win32] + + '@eslint-community/eslint-utils@4.9.1': + resolution: {integrity: sha512-phrYmNiYppR7znFEdqgfWHXR6NCkZEK7hwWDHZUjit/2/U0r6XvkDl0SYnoM51Hq7FhCGdLDT6zxCCOY1hexsQ==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + peerDependencies: + eslint: ^6.0.0 || ^7.0.0 || >=8.0.0 + + '@eslint-community/regexpp@4.12.2': + resolution: {integrity: sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==} + engines: {node: ^12.0.0 || ^14.0.0 || >=16.0.0} + + '@eslint/config-array@0.21.2': + resolution: {integrity: sha512-nJl2KGTlrf9GjLimgIru+V/mzgSK0ABCDQRvxw5BjURL7WfH5uoWmizbH7QB6MmnMBd8cIC9uceWnezL1VZWWw==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@eslint/config-helpers@0.4.2': + resolution: {integrity: sha512-gBrxN88gOIf3R7ja5K9slwNayVcZgK6SOUORm2uBzTeIEfeVaIhOpCtTox3P6R7o2jLFwLFTLnC7kU/RGcYEgw==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@eslint/core@0.17.0': + resolution: {integrity: sha512-yL/sLrpmtDaFEiUj1osRP4TI2MDz1AddJL+jZ7KSqvBuliN4xqYY54IfdN8qD8Toa6g1iloph1fxQNkjOxrrpQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@eslint/eslintrc@3.3.5': + resolution: {integrity: sha512-4IlJx0X0qftVsN5E+/vGujTRIFtwuLbNsVUe7TO6zYPDR1O6nFwvwhIKEKSrl6dZchmYBITazxKoUYOjdtjlRg==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@eslint/js@10.0.1': + resolution: {integrity: sha512-zeR9k5pd4gxjZ0abRoIaxdc7I3nDktoXZk2qOv9gCNWx3mVwEn32VRhyLaRsDiJjTs0xq/T8mfPtyuXu7GWBcA==} + engines: {node: ^20.19.0 || ^22.13.0 || >=24} + peerDependencies: + eslint: ^10.0.0 + peerDependenciesMeta: + eslint: + optional: true + + '@eslint/js@9.39.4': + resolution: {integrity: sha512-nE7DEIchvtiFTwBw4Lfbu59PG+kCofhjsKaCWzxTpt4lfRjRMqG6uMBzKXuEcyXhOHoUp9riAm7/aWYGhXZ9cw==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@eslint/object-schema@2.1.7': + resolution: {integrity: sha512-VtAOaymWVfZcmZbp6E2mympDIHvyjXs/12LqWYjVw6qjrfF+VK+fyG33kChz3nnK+SU5/NeHOqrTEHS8sXO3OA==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@eslint/plugin-kit@0.4.1': + resolution: {integrity: sha512-43/qtrDUokr7LJqoF2c3+RInu/t4zfrpYdoSDfYyhg52rwLV6TnOvdG4fXm7IkSB3wErkcmJS9iEhjVtOSEjjA==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@humanfs/core@0.19.1': + resolution: {integrity: sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==} + engines: {node: '>=18.18.0'} + + '@humanfs/node@0.16.7': + resolution: {integrity: sha512-/zUx+yOsIrG4Y43Eh2peDeKCxlRt/gET6aHfaKpuq267qXdYDFViVHfMaLyygZOnl0kGWxFIgsBy8QFuTLUXEQ==} + engines: {node: '>=18.18.0'} + + '@humanwhocodes/module-importer@1.0.1': + resolution: {integrity: sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==} + engines: {node: '>=12.22'} + + '@humanwhocodes/retry@0.4.3': + resolution: {integrity: sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==} + engines: {node: '>=18.18'} + + '@jridgewell/gen-mapping@0.3.13': + resolution: {integrity: sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==} + + '@jridgewell/resolve-uri@3.1.2': + resolution: {integrity: sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==} + engines: {node: '>=6.0.0'} + + '@jridgewell/sourcemap-codec@1.5.5': + resolution: {integrity: sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==} + + '@jridgewell/trace-mapping@0.3.31': + resolution: {integrity: sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==} + + '@rollup/rollup-android-arm-eabi@4.60.1': + resolution: {integrity: sha512-d6FinEBLdIiK+1uACUttJKfgZREXrF0Qc2SmLII7W2AD8FfiZ9Wjd+rD/iRuf5s5dWrr1GgwXCvPqOuDquOowA==} + cpu: [arm] + os: [android] + + '@rollup/rollup-android-arm64@4.60.1': + resolution: {integrity: sha512-YjG/EwIDvvYI1YvYbHvDz/BYHtkY4ygUIXHnTdLhG+hKIQFBiosfWiACWortsKPKU/+dUwQQCKQM3qrDe8c9BA==} + cpu: [arm64] + os: [android] + + '@rollup/rollup-darwin-arm64@4.60.1': + resolution: {integrity: sha512-mjCpF7GmkRtSJwon+Rq1N8+pI+8l7w5g9Z3vWj4T7abguC4Czwi3Yu/pFaLvA3TTeMVjnu3ctigusqWUfjZzvw==} + cpu: [arm64] + os: [darwin] + + '@rollup/rollup-darwin-x64@4.60.1': + resolution: {integrity: sha512-haZ7hJ1JT4e9hqkoT9R/19XW2QKqjfJVv+i5AGg57S+nLk9lQnJ1F/eZloRO3o9Scy9CM3wQ9l+dkXtcBgN5Ew==} + cpu: [x64] + os: [darwin] + + '@rollup/rollup-freebsd-arm64@4.60.1': + resolution: {integrity: sha512-czw90wpQq3ZsAVBlinZjAYTKduOjTywlG7fEeWKUA7oCmpA8xdTkxZZlwNJKWqILlq0wehoZcJYfBvOyhPTQ6w==} + cpu: [arm64] + os: [freebsd] + + '@rollup/rollup-freebsd-x64@4.60.1': + resolution: {integrity: sha512-KVB2rqsxTHuBtfOeySEyzEOB7ltlB/ux38iu2rBQzkjbwRVlkhAGIEDiiYnO2kFOkJp+Z7pUXKyrRRFuFUKt+g==} + cpu: [x64] + os: [freebsd] + + '@rollup/rollup-linux-arm-gnueabihf@4.60.1': + resolution: {integrity: sha512-L+34Qqil+v5uC0zEubW7uByo78WOCIrBvci69E7sFASRl0X7b/MB6Cqd1lky/CtcSVTydWa2WZwFuWexjS5o6g==} + cpu: [arm] + os: [linux] + + '@rollup/rollup-linux-arm-musleabihf@4.60.1': + resolution: {integrity: sha512-n83O8rt4v34hgFzlkb1ycniJh7IR5RCIqt6mz1VRJD6pmhRi0CXdmfnLu9dIUS6buzh60IvACM842Ffb3xd6Gg==} + cpu: [arm] + os: [linux] + + '@rollup/rollup-linux-arm64-gnu@4.60.1': + resolution: {integrity: sha512-Nql7sTeAzhTAja3QXeAI48+/+GjBJ+QmAH13snn0AJSNL50JsDqotyudHyMbO2RbJkskbMbFJfIJKWA6R1LCJQ==} + cpu: [arm64] + os: [linux] + + '@rollup/rollup-linux-arm64-musl@4.60.1': + resolution: {integrity: sha512-+pUymDhd0ys9GcKZPPWlFiZ67sTWV5UU6zOJat02M1+PiuSGDziyRuI/pPue3hoUwm2uGfxdL+trT6Z9rxnlMA==} + cpu: [arm64] + os: [linux] + + '@rollup/rollup-linux-loong64-gnu@4.60.1': + resolution: {integrity: sha512-VSvgvQeIcsEvY4bKDHEDWcpW4Yw7BtlKG1GUT4FzBUlEKQK0rWHYBqQt6Fm2taXS+1bXvJT6kICu5ZwqKCnvlQ==} + cpu: [loong64] + os: [linux] + + '@rollup/rollup-linux-loong64-musl@4.60.1': + resolution: {integrity: sha512-4LqhUomJqwe641gsPp6xLfhqWMbQV04KtPp7/dIp0nzPxAkNY1AbwL5W0MQpcalLYk07vaW9Kp1PBhdpZYYcEw==} + cpu: [loong64] + os: [linux] + + '@rollup/rollup-linux-ppc64-gnu@4.60.1': + resolution: {integrity: sha512-tLQQ9aPvkBxOc/EUT6j3pyeMD6Hb8QF2BTBnCQWP/uu1lhc9AIrIjKnLYMEroIz/JvtGYgI9dF3AxHZNaEH0rw==} + cpu: [ppc64] + os: [linux] + + '@rollup/rollup-linux-ppc64-musl@4.60.1': + resolution: {integrity: sha512-RMxFhJwc9fSXP6PqmAz4cbv3kAyvD1etJFjTx4ONqFP9DkTkXsAMU4v3Vyc5BgzC+anz7nS/9tp4obsKfqkDHg==} + cpu: [ppc64] + os: [linux] + + '@rollup/rollup-linux-riscv64-gnu@4.60.1': + resolution: {integrity: sha512-QKgFl+Yc1eEk6MmOBfRHYF6lTxiiiV3/z/BRrbSiW2I7AFTXoBFvdMEyglohPj//2mZS4hDOqeB0H1ACh3sBbg==} + cpu: [riscv64] + os: [linux] + + '@rollup/rollup-linux-riscv64-musl@4.60.1': + resolution: {integrity: sha512-RAjXjP/8c6ZtzatZcA1RaQr6O1TRhzC+adn8YZDnChliZHviqIjmvFwHcxi4JKPSDAt6Uhf/7vqcBzQJy0PDJg==} + cpu: [riscv64] + os: [linux] + + '@rollup/rollup-linux-s390x-gnu@4.60.1': + resolution: {integrity: sha512-wcuocpaOlaL1COBYiA89O6yfjlp3RwKDeTIA0hM7OpmhR1Bjo9j31G1uQVpDlTvwxGn2nQs65fBFL5UFd76FcQ==} + cpu: [s390x] + os: [linux] + + '@rollup/rollup-linux-x64-gnu@4.60.1': + resolution: {integrity: sha512-77PpsFQUCOiZR9+LQEFg9GClyfkNXj1MP6wRnzYs0EeWbPcHs02AXu4xuUbM1zhwn3wqaizle3AEYg5aeoohhg==} + cpu: [x64] + os: [linux] + + '@rollup/rollup-linux-x64-musl@4.60.1': + resolution: {integrity: sha512-5cIATbk5vynAjqqmyBjlciMJl1+R/CwX9oLk/EyiFXDWd95KpHdrOJT//rnUl4cUcskrd0jCCw3wpZnhIHdD9w==} + cpu: [x64] + os: [linux] + + '@rollup/rollup-openbsd-x64@4.60.1': + resolution: {integrity: sha512-cl0w09WsCi17mcmWqqglez9Gk8isgeWvoUZ3WiJFYSR3zjBQc2J5/ihSjpl+VLjPqjQ/1hJRcqBfLjssREQILw==} + cpu: [x64] + os: [openbsd] + + '@rollup/rollup-openharmony-arm64@4.60.1': + resolution: {integrity: sha512-4Cv23ZrONRbNtbZa37mLSueXUCtN7MXccChtKpUnQNgF010rjrjfHx3QxkS2PI7LqGT5xXyYs1a7LbzAwT0iCA==} + cpu: [arm64] + os: [openharmony] + + '@rollup/rollup-win32-arm64-msvc@4.60.1': + resolution: {integrity: sha512-i1okWYkA4FJICtr7KpYzFpRTHgy5jdDbZiWfvny21iIKky5YExiDXP+zbXzm3dUcFpkEeYNHgQ5fuG236JPq0g==} + cpu: [arm64] + os: [win32] + + '@rollup/rollup-win32-ia32-msvc@4.60.1': + resolution: {integrity: sha512-u09m3CuwLzShA0EYKMNiFgcjjzwqtUMLmuCJLeZWjjOYA3IT2Di09KaxGBTP9xVztWyIWjVdsB2E9goMjZvTQg==} + cpu: [ia32] + os: [win32] + + '@rollup/rollup-win32-x64-gnu@4.60.1': + resolution: {integrity: sha512-k+600V9Zl1CM7eZxJgMyTUzmrmhB/0XZnF4pRypKAlAgxmedUA+1v9R+XOFv56W4SlHEzfeMtzujLJD22Uz5zg==} + cpu: [x64] + os: [win32] + + '@rollup/rollup-win32-x64-msvc@4.60.1': + resolution: {integrity: sha512-lWMnixq/QzxyhTV6NjQJ4SFo1J6PvOX8vUx5Wb4bBPsEb+8xZ89Bz6kOXpfXj9ak9AHTQVQzlgzBEc1SyM27xQ==} + cpu: [x64] + os: [win32] + + '@testing-library/dom@10.4.1': + resolution: {integrity: sha512-o4PXJQidqJl82ckFaXUeoAW+XysPLauYI43Abki5hABd853iMhitooc6znOnczgbTYmEP6U6/y1ZyKAIsvMKGg==} + engines: {node: '>=18'} + + '@testing-library/jest-dom@6.9.1': + resolution: {integrity: sha512-zIcONa+hVtVSSep9UT3jZ5rizo2BsxgyDYU7WFD5eICBE7no3881HGeb/QkGfsJs6JTkY1aQhT7rIPC7e+0nnA==} + engines: {node: '>=14', npm: '>=6', yarn: '>=1'} + + '@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 + + '@testing-library/user-event@14.6.1': + resolution: {integrity: sha512-vq7fv0rnt+QTXgPxr5Hjc210p6YKq2kmdziLgnsZGgLJ9e6VAShx1pACLuRjd/AS/sr7phAR58OIIpf0LlmQNw==} + engines: {node: '>=12', npm: '>=6'} + peerDependencies: + '@testing-library/dom': '>=7.21.4' + + '@types/aria-query@5.0.4': + resolution: {integrity: sha512-rfT93uj5s0PRL7EzccGMs3brplhcrghnDoV26NqKhCAS1hVo+WdNsPvE/yb6ilfr5hi2MEk6d5EWJTKdxg8jVw==} + + '@types/chai@5.2.3': + resolution: {integrity: sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA==} + + '@types/deep-eql@4.0.2': + resolution: {integrity: sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==} + + '@types/estree@1.0.8': + resolution: {integrity: sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==} + + '@types/json-schema@7.0.15': + resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==} + + '@types/react-dom@19.2.3': + resolution: {integrity: sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ==} + peerDependencies: + '@types/react': ^19.2.0 + + '@types/react@19.2.14': + resolution: {integrity: sha512-ilcTH/UniCkMdtexkoCN0bI7pMcJDvmQFPvuPvmEaYA/NSfFTAgdUSLAoVjaRJm7+6PvcM+q1zYOwS4wTYMF9w==} + + '@typescript-eslint/eslint-plugin@8.58.1': + resolution: {integrity: sha512-eSkwoemjo76bdXl2MYqtxg51HNwUSkWfODUOQ3PaTLZGh9uIWWFZIjyjaJnex7wXDu+TRx+ATsnSxdN9YWfRTQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + '@typescript-eslint/parser': ^8.58.1 + eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 + typescript: '>=4.8.4 <6.1.0' + + '@typescript-eslint/parser@8.58.1': + resolution: {integrity: sha512-gGkiNMPqerb2cJSVcruigx9eHBlLG14fSdPdqMoOcBfh+vvn4iCq2C8MzUB89PrxOXk0y3GZ1yIWb9aOzL93bw==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 + typescript: '>=4.8.4 <6.1.0' + + '@typescript-eslint/project-service@8.58.1': + resolution: {integrity: sha512-gfQ8fk6cxhtptek+/8ZIqw8YrRW5048Gug8Ts5IYcMLCw18iUgrZAEY/D7s4hkI0FxEfGakKuPK/XUMPzPxi5g==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + typescript: '>=4.8.4 <6.1.0' + + '@typescript-eslint/scope-manager@8.58.1': + resolution: {integrity: sha512-TPYUEqJK6avLcEjumWsIuTpuYODTTDAtoMdt8ZZa93uWMTX13Nb8L5leSje1NluammvU+oI3QRr5lLXPgihX3w==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@typescript-eslint/tsconfig-utils@8.58.1': + resolution: {integrity: sha512-JAr2hOIct2Q+qk3G+8YFfqkqi7sC86uNryT+2i5HzMa2MPjw4qNFvtjnw1IiA1rP7QhNKVe21mSSLaSjwA1Olw==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + typescript: '>=4.8.4 <6.1.0' + + '@typescript-eslint/type-utils@8.58.1': + resolution: {integrity: sha512-HUFxvTJVroT+0rXVJC7eD5zol6ID+Sn5npVPWoFuHGg9Ncq5Q4EYstqR+UOqaNRFXi5TYkpXXkLhoCHe3G0+7w==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 + typescript: '>=4.8.4 <6.1.0' + + '@typescript-eslint/types@8.58.1': + resolution: {integrity: sha512-io/dV5Aw5ezwzfPBBWLoT+5QfVtP8O7q4Kftjn5azJ88bYyp/ZMCsyW1lpKK46EXJcaYMZ1JtYj+s/7TdzmQMw==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@typescript-eslint/typescript-estree@8.58.1': + resolution: {integrity: sha512-w4w7WR7GHOjqqPnvAYbazq+Y5oS68b9CzasGtnd6jIeOIeKUzYzupGTB2T4LTPSv4d+WPeccbxuneTFHYgAAWg==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + typescript: '>=4.8.4 <6.1.0' + + '@typescript-eslint/utils@8.58.1': + resolution: {integrity: sha512-Ln8R0tmWC7pTtLOzgJzYTXSCjJ9rDNHAqTaVONF4FEi2qwce8mD9iSOxOpLFFvWp/wBFlew0mjM1L1ihYWfBdQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 + typescript: '>=4.8.4 <6.1.0' + + '@typescript-eslint/visitor-keys@8.58.1': + resolution: {integrity: sha512-y+vH7QE8ycjoa0bWciFg7OpFcipUuem1ujhrdLtq1gByKwfbC7bPeKsiny9e0urg93DqwGcHey+bGRKCnF1nZQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@vitest/expect@3.2.4': + resolution: {integrity: sha512-Io0yyORnB6sikFlt8QW5K7slY4OjqNX9jmJQ02QDda8lyM6B5oNgVWoSoKPac8/kgnCUzuHQKrSLtu/uOqqrig==} + + '@vitest/mocker@3.2.4': + resolution: {integrity: sha512-46ryTE9RZO/rfDd7pEqFl7etuyzekzEhUbTW3BvmeO/BcCMEgq59BKhek3dXDWgAj4oMK6OZi+vRr1wPW6qjEQ==} + peerDependencies: + msw: ^2.4.9 + vite: ^5.0.0 || ^6.0.0 || ^7.0.0-0 + peerDependenciesMeta: + msw: + optional: true + vite: + optional: true + + '@vitest/pretty-format@3.2.4': + resolution: {integrity: sha512-IVNZik8IVRJRTr9fxlitMKeJeXFFFN0JaB9PHPGQ8NKQbGpfjlTx9zO4RefN8gp7eqjNy8nyK3NZmBzOPeIxtA==} + + '@vitest/runner@3.2.4': + resolution: {integrity: sha512-oukfKT9Mk41LreEW09vt45f8wx7DordoWUZMYdY/cyAk7w5TWkTRCNZYF7sX7n2wB7jyGAl74OxgwhPgKaqDMQ==} + + '@vitest/snapshot@3.2.4': + resolution: {integrity: sha512-dEYtS7qQP2CjU27QBC5oUOxLE/v5eLkGqPE0ZKEIDGMs4vKWe7IjgLOeauHsR0D5YuuycGRO5oSRXnwnmA78fQ==} + + '@vitest/spy@3.2.4': + resolution: {integrity: sha512-vAfasCOe6AIK70iP5UD11Ac4siNUNJ9i/9PZ3NKx07sG6sUxeag1LWdNrMWeKKYBLlzuK+Gn65Yd5nyL6ds+nw==} + + '@vitest/utils@3.2.4': + resolution: {integrity: sha512-fB2V0JFrQSMsCo9HiSq3Ezpdv4iYaXRG1Sx8edX3MwxfyNn83mKiGzOcH+Fkxt4MHxr3y42fQi1oeAInqgX2QA==} + + acorn-jsx@5.3.2: + resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==} + peerDependencies: + acorn: ^6.0.0 || ^7.0.0 || ^8.0.0 + + acorn@8.16.0: + resolution: {integrity: sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==} + engines: {node: '>=0.4.0'} + hasBin: true + + agent-base@7.1.4: + resolution: {integrity: sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==} + engines: {node: '>= 14'} + + ajv@6.14.0: + resolution: {integrity: sha512-IWrosm/yrn43eiKqkfkHis7QioDleaXQHdDVPKg0FSwwd/DuvyX79TZnFOnYpB7dcsFAMmtFztZuXPDvSePkFw==} + + ansi-regex@5.0.1: + resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==} + engines: {node: '>=8'} + + ansi-styles@4.3.0: + resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==} + engines: {node: '>=8'} + + ansi-styles@5.2.0: + resolution: {integrity: sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==} + engines: {node: '>=10'} + + any-promise@1.3.0: + resolution: {integrity: sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==} + + 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'} + + array-buffer-byte-length@1.0.2: + resolution: {integrity: sha512-LHE+8BuR7RYGDKvnrmcuSq3tDcKv9OFEXQt/HpbZhY7V6h0zlUXutnAD82GiFx9rdieCMjkvtcsPqBwgUl1Iiw==} + engines: {node: '>= 0.4'} + + array-includes@3.1.9: + resolution: {integrity: sha512-FmeCCAenzH0KH381SPT5FZmiA/TmpndpcaShhfgEN9eCVjnFBqq3l1xrI42y8+PPLI6hypzou4GXw00WHmPBLQ==} + engines: {node: '>= 0.4'} + + array.prototype.findlast@1.2.5: + resolution: {integrity: sha512-CVvd6FHg1Z3POpBLxO6E6zr+rSKEQ9L6rZHAaY7lLfhKsWYUBBOuMs0e9o24oopj6H+geRCX0YJ+TJLBK2eHyQ==} + engines: {node: '>= 0.4'} + + array.prototype.flat@1.3.3: + resolution: {integrity: sha512-rwG/ja1neyLqCuGZ5YYrznA62D4mZXg0i1cIskIUKSiqF3Cje9/wXAls9B9s1Wa2fomMsIv8czB8jZcPmxCXFg==} + engines: {node: '>= 0.4'} + + array.prototype.flatmap@1.3.3: + resolution: {integrity: sha512-Y7Wt51eKJSyi80hFrJCePGGNo5ktJCslFuboqJsbf57CCPcm5zztluPlc4/aD8sWsKvlwatezpV4U1efk8kpjg==} + engines: {node: '>= 0.4'} + + array.prototype.tosorted@1.1.4: + resolution: {integrity: sha512-p6Fx8B7b7ZhL/gmUsAy0D15WhvDccw3mnGNbZpi3pmeJdxtWsj2jEaI4Y6oo3XiHfzuSgPwKc04MYt6KgvC/wA==} + engines: {node: '>= 0.4'} + + arraybuffer.prototype.slice@1.0.4: + resolution: {integrity: sha512-BNoCY6SXXPQ7gF2opIP4GBE+Xw7U+pHMYKuzjgCN3GwiaIR09UUeKfheyIry77QtrCBlC0KK0q5/TER/tYh3PQ==} + engines: {node: '>= 0.4'} + + assertion-error@2.0.1: + resolution: {integrity: sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==} + engines: {node: '>=12'} + + async-function@1.0.0: + resolution: {integrity: sha512-hsU18Ae8CDTR6Kgu9DYf0EbCr/a5iGL0rytQDobUcdpYOKokk8LEjVphnXkDkgpi0wYVsqrXuP0bZxJaTqdgoA==} + engines: {node: '>= 0.4'} + + available-typed-arrays@1.0.7: + resolution: {integrity: sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==} + engines: {node: '>= 0.4'} + + balanced-match@1.0.2: + resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} + + balanced-match@4.0.4: + resolution: {integrity: sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==} + engines: {node: 18 || 20 || >=22} + + brace-expansion@1.1.14: + resolution: {integrity: sha512-MWPGfDxnyzKU7rNOW9SP/c50vi3xrmrua/+6hfPbCS2ABNWfx24vPidzvC7krjU/RTo235sV776ymlsMtGKj8g==} + + brace-expansion@5.0.5: + resolution: {integrity: sha512-VZznLgtwhn+Mact9tfiwx64fA9erHH/MCXEUfB/0bX/6Fz6ny5EGTXYltMocqg4xFAQZtnO3DHWWXi8RiuN7cQ==} + engines: {node: 18 || 20 || >=22} + + bundle-require@5.1.0: + resolution: {integrity: sha512-3WrrOuZiyaaZPWiEt4G3+IffISVC9HYlWueJEBWED4ZH4aIAC2PnkdnuRrR94M+w6yGWn4AglWtJtBI8YqvgoA==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + peerDependencies: + esbuild: '>=0.18' + + cac@6.7.14: + resolution: {integrity: sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==} + engines: {node: '>=8'} + + call-bind-apply-helpers@1.0.2: + resolution: {integrity: sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==} + engines: {node: '>= 0.4'} + + call-bind@1.0.9: + resolution: {integrity: sha512-a/hy+pNsFUTR+Iz8TCJvXudKVLAnz/DyeSUo10I5yvFDQJBFU2s9uqQpoSrJlroHUKoKqzg+epxyP9lqFdzfBQ==} + engines: {node: '>= 0.4'} + + call-bound@1.0.4: + resolution: {integrity: sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==} + engines: {node: '>= 0.4'} + + callsites@3.1.0: + resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==} + engines: {node: '>=6'} + + chai@5.3.3: + resolution: {integrity: sha512-4zNhdJD/iOjSH0A05ea+Ke6MU5mmpQcbQsSOkgdaUMJ9zTlDTD/GYlwohmIE2u0gaxHYiVHEn1Fw9mZ/ktJWgw==} + engines: {node: '>=18'} + + chalk@4.1.2: + resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==} + engines: {node: '>=10'} + + check-error@2.1.3: + resolution: {integrity: sha512-PAJdDJusoxnwm1VwW07VWwUN1sl7smmC3OKggvndJFadxxDRyFJBX/ggnu/KE4kQAB7a3Dp8f/YXC1FlUprWmA==} + engines: {node: '>= 16'} + + chokidar@4.0.3: + resolution: {integrity: sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==} + engines: {node: '>= 14.16.0'} + + color-convert@2.0.1: + resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==} + engines: {node: '>=7.0.0'} + + color-name@1.1.4: + resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} + + commander@4.1.1: + resolution: {integrity: sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==} + engines: {node: '>= 6'} + + concat-map@0.0.1: + resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==} + + confbox@0.1.8: + resolution: {integrity: sha512-RMtmw0iFkeR4YV+fUOSucriAQNb9g8zFR52MWCtl+cCZOFRNL6zeB395vPzFhEjjn4fMxXudmELnl/KF/WrK6w==} + + consola@3.4.2: + resolution: {integrity: sha512-5IKcdX0nnYavi6G7TtOhwkYzyjfJlatbjMjuLSfE2kYT5pMDOilZ4OvMhi637CcDICTmz3wARPoyhqyX1Y+XvA==} + engines: {node: ^14.18.0 || >=16.10.0} + + cross-spawn@7.0.6: + resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==} + engines: {node: '>= 8'} + + css.escape@1.5.1: + resolution: {integrity: sha512-YUifsXXuknHlUsmlgyY0PKzgPOr7/FjCePfHNt0jxm83wHZi44VDMQ7/fGNkjY3/jV1MC+1CmZbaHzugyeRtpg==} + + cssstyle@4.6.0: + resolution: {integrity: sha512-2z+rWdzbbSZv6/rhtvzvqeZQHrBaqgogqt85sqFNbabZOuFbCVFb8kPeEtZjiKkbrm395irpNKiYeFeLiQnFPg==} + engines: {node: '>=18'} + + csstype@3.2.3: + resolution: {integrity: sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==} + + data-urls@5.0.0: + resolution: {integrity: sha512-ZYP5VBHshaDAiVZxjbRVcFJpc+4xGgT0bK3vzy1HLN8jTO975HEbuYzZJcHoQEY5K1a0z8YayJkyVETa08eNTg==} + engines: {node: '>=18'} + + data-view-buffer@1.0.2: + resolution: {integrity: sha512-EmKO5V3OLXh1rtK2wgXRansaK1/mtVdTUEiEI0W8RkvgT05kfxaH29PliLnpLP73yYO6142Q72QNa8Wx/A5CqQ==} + engines: {node: '>= 0.4'} + + data-view-byte-length@1.0.2: + resolution: {integrity: sha512-tuhGbE6CfTM9+5ANGf+oQb72Ky/0+s3xKUpHvShfiz2RxMFgFPjsXuRLBVMtvMs15awe45SRb83D6wH4ew6wlQ==} + engines: {node: '>= 0.4'} + + data-view-byte-offset@1.0.1: + resolution: {integrity: sha512-BS8PfmtDGnrgYdOonGZQdLZslWIeCGFP9tpan0hi1Co2Zr2NKADsvGYA8XxuG/4UWgJ6Cjtv+YJnB6MM69QGlQ==} + engines: {node: '>= 0.4'} + + debug@4.4.3: + resolution: {integrity: sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==} + engines: {node: '>=6.0'} + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true + + decimal.js@10.6.0: + resolution: {integrity: sha512-YpgQiITW3JXGntzdUmyUR1V812Hn8T1YVXhCu+wO3OpS4eU9l4YdD3qjyiKdV6mvV29zapkMeD390UVEf2lkUg==} + + deep-eql@5.0.2: + resolution: {integrity: sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q==} + engines: {node: '>=6'} + + deep-is@0.1.4: + resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==} + + define-data-property@1.1.4: + resolution: {integrity: sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==} + engines: {node: '>= 0.4'} + + define-properties@1.2.1: + resolution: {integrity: sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==} + engines: {node: '>= 0.4'} + + dequal@2.0.3: + resolution: {integrity: sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==} + engines: {node: '>=6'} + + doctrine@2.1.0: + resolution: {integrity: sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==} + engines: {node: '>=0.10.0'} + + dom-accessibility-api@0.5.16: + resolution: {integrity: sha512-X7BJ2yElsnOJ30pZF4uIIDfBEVgF4XEBxL9Bxhy6dnrm5hkzqmsWHGTiHqRiITNhMyFLyAiWndIJP7Z1NTteDg==} + + dom-accessibility-api@0.6.3: + resolution: {integrity: sha512-7ZgogeTnjuHbo+ct10G9Ffp0mif17idi0IyWNVA/wcwcm7NPOD/WEHVP3n7n3MhXqxoIYm8d6MuZohYWIZ4T3w==} + + dunder-proto@1.0.1: + resolution: {integrity: sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==} + engines: {node: '>= 0.4'} + + entities@6.0.1: + resolution: {integrity: sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g==} + engines: {node: '>=0.12'} + + es-abstract@1.24.2: + resolution: {integrity: sha512-2FpH9Q5i2RRwyEP1AylXe6nYLR5OhaJTZwmlcP0dL/+JCbgg7yyEo/sEK6HeGZRf3dFpWwThaRHVApXSkW3xeg==} + engines: {node: '>= 0.4'} + + es-define-property@1.0.1: + resolution: {integrity: sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==} + engines: {node: '>= 0.4'} + + es-errors@1.3.0: + resolution: {integrity: sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==} + engines: {node: '>= 0.4'} + + es-iterator-helpers@1.3.2: + resolution: {integrity: sha512-HVLACW1TppGYjJ8H6/jqH/pqOtKRw6wMlrB23xfExmFWxFquAIWCmwoLsOyN96K4a5KbmOf5At9ZUO3GZbetAw==} + engines: {node: '>= 0.4'} + + es-module-lexer@1.7.0: + resolution: {integrity: sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==} + + es-object-atoms@1.1.1: + resolution: {integrity: sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==} + engines: {node: '>= 0.4'} + + es-set-tostringtag@2.1.0: + resolution: {integrity: sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==} + engines: {node: '>= 0.4'} + + es-shim-unscopables@1.1.0: + resolution: {integrity: sha512-d9T8ucsEhh8Bi1woXCf+TIKDIROLG5WCkxg8geBCbvk22kzwC5G2OnXVMO6FUsvQlgUUXQ2itephWDLqDzbeCw==} + engines: {node: '>= 0.4'} + + es-to-primitive@1.3.0: + resolution: {integrity: sha512-w+5mJ3GuFL+NjVtJlvydShqE1eN3h3PbI7/5LAsYJP/2qtuMXjfL2LpHSRqo4b4eSF5K/DH1JXKUAHSB2UW50g==} + engines: {node: '>= 0.4'} + + esbuild@0.27.7: + resolution: {integrity: sha512-IxpibTjyVnmrIQo5aqNpCgoACA/dTKLTlhMHihVHhdkxKyPO1uBBthumT0rdHmcsk9uMonIWS0m4FljWzILh3w==} + engines: {node: '>=18'} + hasBin: true + + escape-string-regexp@4.0.0: + resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==} + engines: {node: '>=10'} + + eslint-plugin-react-hooks@5.2.0: + resolution: {integrity: sha512-+f15FfK64YQwZdJNELETdn5ibXEUQmW1DZL6KXhNnc2heoy/sg9VJJeT7n8TlMWouzWqSWavFkIhHyIbIAEapg==} + engines: {node: '>=10'} + peerDependencies: + eslint: ^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 || ^9.0.0 + + eslint-plugin-react@7.37.5: + resolution: {integrity: sha512-Qteup0SqU15kdocexFNAJMvCJEfa2xUKNV4CC1xsVMrIIqEy3SQ/rqyxCWNzfrd3/ldy6HMlD2e0JDVpDg2qIA==} + engines: {node: '>=4'} + peerDependencies: + eslint: ^3 || ^4 || ^5 || ^6 || ^7 || ^8 || ^9.7 + + eslint-scope@8.4.0: + resolution: {integrity: sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + eslint-visitor-keys@3.4.3: + resolution: {integrity: sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + + eslint-visitor-keys@4.2.1: + resolution: {integrity: sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + eslint-visitor-keys@5.0.1: + resolution: {integrity: sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA==} + engines: {node: ^20.19.0 || ^22.13.0 || >=24} + + eslint@9.39.4: + resolution: {integrity: sha512-XoMjdBOwe/esVgEvLmNsD3IRHkm7fbKIUGvrleloJXUZgDHig2IPWNniv+GwjyJXzuNqVjlr5+4yVUZjycJwfQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + hasBin: true + peerDependencies: + jiti: '*' + peerDependenciesMeta: + jiti: + optional: true + + espree@10.4.0: + resolution: {integrity: sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + esquery@1.7.0: + resolution: {integrity: sha512-Ap6G0WQwcU/LHsvLwON1fAQX9Zp0A2Y6Y/cJBl9r/JbW90Zyg4/zbG6zzKa2OTALELarYHmKu0GhpM5EO+7T0g==} + engines: {node: '>=0.10'} + + esrecurse@4.3.0: + resolution: {integrity: sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==} + engines: {node: '>=4.0'} + + estraverse@5.3.0: + 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'} + + expect-type@1.3.0: + resolution: {integrity: sha512-knvyeauYhqjOYvQ66MznSMs83wmHrCycNEN6Ao+2AeYEfxUIkuiVxdEa1qlGEPK+We3n0THiDciYSsCcgW/DoA==} + engines: {node: '>=12.0.0'} + + fast-deep-equal@3.1.3: + resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} + + fast-json-stable-stringify@2.1.0: + resolution: {integrity: sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==} + + fast-levenshtein@2.0.6: + resolution: {integrity: sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==} + + fdir@6.5.0: + resolution: {integrity: sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==} + engines: {node: '>=12.0.0'} + peerDependencies: + picomatch: ^3 || ^4 + peerDependenciesMeta: + picomatch: + optional: true + + file-entry-cache@8.0.0: + resolution: {integrity: sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==} + engines: {node: '>=16.0.0'} + + find-up@5.0.0: + resolution: {integrity: sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==} + engines: {node: '>=10'} + + fix-dts-default-cjs-exports@1.0.1: + resolution: {integrity: sha512-pVIECanWFC61Hzl2+oOCtoJ3F17kglZC/6N94eRWycFgBH35hHx0Li604ZIzhseh97mf2p0cv7vVrOZGoqhlEg==} + + flat-cache@4.0.1: + resolution: {integrity: sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==} + engines: {node: '>=16'} + + flatted@3.4.2: + resolution: {integrity: sha512-PjDse7RzhcPkIJwy5t7KPWQSZ9cAbzQXcafsetQoD7sOJRQlGikNbx7yZp2OotDnJyrDcbyRq3Ttb18iYOqkxA==} + + for-each@0.3.5: + resolution: {integrity: sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg==} + engines: {node: '>= 0.4'} + + fsevents@2.3.3: + resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} + engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} + os: [darwin] + + function-bind@1.1.2: + resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==} + + function.prototype.name@1.1.8: + resolution: {integrity: sha512-e5iwyodOHhbMr/yNrc7fDYG4qlbIvI5gajyzPnb5TCwyhjApznQh1BMFou9b30SevY43gCJKXycoCBjMbsuW0Q==} + engines: {node: '>= 0.4'} + + functions-have-names@1.2.3: + resolution: {integrity: sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==} + + generator-function@2.0.1: + resolution: {integrity: sha512-SFdFmIJi+ybC0vjlHN0ZGVGHc3lgE0DxPAT0djjVg+kjOnSqclqmj0KQ7ykTOLP6YxoqOvuAODGdcHJn+43q3g==} + engines: {node: '>= 0.4'} + + get-intrinsic@1.3.0: + resolution: {integrity: sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==} + engines: {node: '>= 0.4'} + + get-proto@1.0.1: + resolution: {integrity: sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==} + engines: {node: '>= 0.4'} + + get-symbol-description@1.1.0: + resolution: {integrity: sha512-w9UMqWwJxHNOvoNzSJ2oPF5wvYcvP7jUvYzhp67yEhTi17ZDBBC1z9pTdGuzjD+EFIqLSYRweZjqfiPzQ06Ebg==} + engines: {node: '>= 0.4'} + + glob-parent@6.0.2: + resolution: {integrity: sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==} + engines: {node: '>=10.13.0'} + + globals@14.0.0: + resolution: {integrity: sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==} + engines: {node: '>=18'} + + globals@17.5.0: + resolution: {integrity: sha512-qoV+HK2yFl/366t2/Cb3+xxPUo5BuMynomoDmiaZBIdbs+0pYbjfZU+twLhGKp4uCZ/+NbtpVepH5bGCxRyy2g==} + engines: {node: '>=18'} + + globalthis@1.0.4: + resolution: {integrity: sha512-DpLKbNU4WylpxJykQujfCcwYWiV/Jhm50Goo0wrVILAv5jOr9d+H+UR3PhSCD2rCCEIg0uc+G+muBTwD54JhDQ==} + engines: {node: '>= 0.4'} + + gopd@1.2.0: + resolution: {integrity: sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==} + engines: {node: '>= 0.4'} + + has-bigints@1.1.0: + resolution: {integrity: sha512-R3pbpkcIqv2Pm3dUwgjclDRVmWpTJW2DcMzcIhEXEx1oh/CEMObMm3KLmRJOdvhM7o4uQBnwr8pzRK2sJWIqfg==} + engines: {node: '>= 0.4'} + + has-flag@4.0.0: + resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==} + engines: {node: '>=8'} + + has-property-descriptors@1.0.2: + resolution: {integrity: sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==} + + has-proto@1.2.0: + resolution: {integrity: sha512-KIL7eQPfHQRC8+XluaIw7BHUwwqL19bQn4hzNgdr+1wXoU0KKj6rufu47lhY7KbJR2C6T6+PfyN0Ea7wkSS+qQ==} + engines: {node: '>= 0.4'} + + has-symbols@1.1.0: + resolution: {integrity: sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==} + engines: {node: '>= 0.4'} + + has-tostringtag@1.0.2: + resolution: {integrity: sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==} + engines: {node: '>= 0.4'} + + hasown@2.0.2: + resolution: {integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==} + engines: {node: '>= 0.4'} + + html-encoding-sniffer@4.0.0: + resolution: {integrity: sha512-Y22oTqIU4uuPgEemfz7NDJz6OeKf12Lsu+QC+s3BVpda64lTiMYCyGwg5ki4vFxkMwQdeZDl2adZoqUgdFuTgQ==} + engines: {node: '>=18'} + + http-proxy-agent@7.0.2: + resolution: {integrity: sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==} + engines: {node: '>= 14'} + + https-proxy-agent@7.0.6: + resolution: {integrity: sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==} + engines: {node: '>= 14'} + + iconv-lite@0.6.3: + resolution: {integrity: sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==} + engines: {node: '>=0.10.0'} + + ignore@5.3.2: + resolution: {integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==} + engines: {node: '>= 4'} + + ignore@7.0.5: + resolution: {integrity: sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==} + engines: {node: '>= 4'} + + import-fresh@3.3.1: + resolution: {integrity: sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==} + engines: {node: '>=6'} + + imurmurhash@0.1.4: + resolution: {integrity: sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==} + engines: {node: '>=0.8.19'} + + indent-string@4.0.0: + resolution: {integrity: sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==} + engines: {node: '>=8'} + + internal-slot@1.1.0: + resolution: {integrity: sha512-4gd7VpWNQNB4UKKCFFVcp1AVv+FMOgs9NKzjHKusc8jTMhd5eL1NqQqOpE0KzMds804/yHlglp3uxgluOqAPLw==} + engines: {node: '>= 0.4'} + + is-array-buffer@3.0.5: + resolution: {integrity: sha512-DDfANUiiG2wC1qawP66qlTugJeL5HyzMpfr8lLK+jMQirGzNod0B12cFB/9q838Ru27sBwfw78/rdoU7RERz6A==} + engines: {node: '>= 0.4'} + + is-async-function@2.1.1: + resolution: {integrity: sha512-9dgM/cZBnNvjzaMYHVoxxfPj2QXt22Ev7SuuPrs+xav0ukGB0S6d4ydZdEiM48kLx5kDV+QBPrpVnFyefL8kkQ==} + engines: {node: '>= 0.4'} + + is-bigint@1.1.0: + resolution: {integrity: sha512-n4ZT37wG78iz03xPRKJrHTdZbe3IicyucEtdRsV5yglwc3GyUfbAfpSeD0FJ41NbUNSt5wbhqfp1fS+BgnvDFQ==} + engines: {node: '>= 0.4'} + + is-boolean-object@1.2.2: + resolution: {integrity: sha512-wa56o2/ElJMYqjCjGkXri7it5FbebW5usLw/nPmCMs5DeZ7eziSYZhSmPRn0txqeW4LnAmQQU7FgqLpsEFKM4A==} + engines: {node: '>= 0.4'} + + is-callable@1.2.7: + resolution: {integrity: sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==} + engines: {node: '>= 0.4'} + + is-core-module@2.16.1: + resolution: {integrity: sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==} + engines: {node: '>= 0.4'} + + is-data-view@1.0.2: + resolution: {integrity: sha512-RKtWF8pGmS87i2D6gqQu/l7EYRlVdfzemCJN/P3UOs//x1QE7mfhvzHIApBTRf7axvT6DMGwSwBXYCT0nfB9xw==} + engines: {node: '>= 0.4'} + + is-date-object@1.1.0: + resolution: {integrity: sha512-PwwhEakHVKTdRNVOw+/Gyh0+MzlCl4R6qKvkhuvLtPMggI1WAHt9sOwZxQLSGpUaDnrdyDsomoRgNnCfKNSXXg==} + engines: {node: '>= 0.4'} + + is-extglob@2.1.1: + resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==} + engines: {node: '>=0.10.0'} + + is-finalizationregistry@1.1.1: + resolution: {integrity: sha512-1pC6N8qWJbWoPtEjgcL2xyhQOP491EQjeUo3qTKcmV8YSDDJrOepfG8pcC7h/QgnQHYSv0mJ3Z/ZWxmatVrysg==} + engines: {node: '>= 0.4'} + + is-generator-function@1.1.2: + resolution: {integrity: sha512-upqt1SkGkODW9tsGNG5mtXTXtECizwtS2kA161M+gJPc1xdb/Ax629af6YrTwcOeQHbewrPNlE5Dx7kzvXTizA==} + engines: {node: '>= 0.4'} + + is-glob@4.0.3: + resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==} + engines: {node: '>=0.10.0'} + + is-map@2.0.3: + resolution: {integrity: sha512-1Qed0/Hr2m+YqxnM09CjA2d/i6YZNfF6R2oRAOj36eUdS6qIV/huPJNSEpKbupewFs+ZsJlxsjjPbc0/afW6Lw==} + engines: {node: '>= 0.4'} + + is-negative-zero@2.0.3: + resolution: {integrity: sha512-5KoIu2Ngpyek75jXodFvnafB6DJgr3u8uuK0LEZJjrU19DrMD3EVERaR8sjz8CCGgpZvxPl9SuE1GMVPFHx1mw==} + engines: {node: '>= 0.4'} + + is-number-object@1.1.1: + resolution: {integrity: sha512-lZhclumE1G6VYD8VHe35wFaIif+CTy5SJIi5+3y4psDgWu4wPDoBhF8NxUOinEc7pHgiTsT6MaBb92rKhhD+Xw==} + engines: {node: '>= 0.4'} + + 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'} + + is-set@2.0.3: + resolution: {integrity: sha512-iPAjerrse27/ygGLxw+EBR9agv9Y6uLeYVJMu+QNCoouJ1/1ri0mGrcWpfCqFZuzzx3WjtwxG098X+n4OuRkPg==} + engines: {node: '>= 0.4'} + + is-shared-array-buffer@1.0.4: + resolution: {integrity: sha512-ISWac8drv4ZGfwKl5slpHG9OwPNty4jOWPRIhBpxOoD+hqITiwuipOQ2bNthAzwA3B4fIjO4Nln74N0S9byq8A==} + engines: {node: '>= 0.4'} + + is-string@1.1.1: + resolution: {integrity: sha512-BtEeSsoaQjlSPBemMQIrY1MY0uM6vnS1g5fmufYOtnxLGUZM2178PKbhsk7Ffv58IX+ZtcvoGwccYsh0PglkAA==} + engines: {node: '>= 0.4'} + + is-symbol@1.1.1: + resolution: {integrity: sha512-9gGx6GTtCQM73BgmHQXfDmLtfjjTUDSyoxTCbp5WtoixAhfgsDirWIcVQ/IHpvI5Vgd5i/J5F7B9cN/WlVbC/w==} + engines: {node: '>= 0.4'} + + is-typed-array@1.1.15: + resolution: {integrity: sha512-p3EcsicXjit7SaskXHs1hA91QxgTw46Fv6EFKKGS5DRFLD8yKnohjF3hxoju94b/OcMZoQukzpPpBE9uLVKzgQ==} + engines: {node: '>= 0.4'} + + is-weakmap@2.0.2: + resolution: {integrity: sha512-K5pXYOm9wqY1RgjpL3YTkF39tni1XajUIkawTLUo9EZEVUFga5gSQJF8nNS7ZwJQ02y+1YCNYcMh+HIf1ZqE+w==} + engines: {node: '>= 0.4'} + + is-weakref@1.1.1: + resolution: {integrity: sha512-6i9mGWSlqzNMEqpCp93KwRS1uUOodk2OJ6b+sq7ZPDSy2WuI5NFIxp/254TytR8ftefexkWn5xNiHUNpPOfSew==} + engines: {node: '>= 0.4'} + + is-weakset@2.0.4: + resolution: {integrity: sha512-mfcwb6IzQyOKTs84CQMrOwW4gQcaTOAWJ0zzJCl2WSPDrWk/OzDaImWFH3djXhb24g4eudZfLRozAvPGw4d9hQ==} + engines: {node: '>= 0.4'} + + isarray@2.0.5: + resolution: {integrity: sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==} + + isexe@2.0.0: + resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} + + iterator.prototype@1.1.5: + resolution: {integrity: sha512-H0dkQoCa3b2VEeKQBOxFph+JAbcrQdE7KC0UkqwpLmv2EC4P41QXP+rqo9wYodACiG5/WM5s9oDApTU8utwj9g==} + engines: {node: '>= 0.4'} + + joycon@3.1.1: + resolution: {integrity: sha512-34wB/Y7MW7bzjKRjUKTa46I2Z7eV62Rkhva+KkopW7Qvv/OSWBqvkSY7vusOPrNuZcUG3tApvdVgNB8POj3SPw==} + engines: {node: '>=10'} + + js-tokens@4.0.0: + resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} + + js-tokens@9.0.1: + resolution: {integrity: sha512-mxa9E9ITFOt0ban3j6L5MpjwegGz6lBQmM1IJkWeBZGcMxto50+eWdjC/52xDbS2vy0k7vIMK0Fe2wfL9OQSpQ==} + + js-yaml@4.1.1: + resolution: {integrity: sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==} + hasBin: true + + jsdom@26.1.0: + resolution: {integrity: sha512-Cvc9WUhxSMEo4McES3P7oK3QaXldCfNWp7pl2NNeiIFlCoLr3kfq9kb1fxftiwk1FLV7CvpvDfonxtzUDeSOPg==} + engines: {node: '>=18'} + peerDependencies: + canvas: ^3.0.0 + peerDependenciesMeta: + canvas: + optional: true + + json-buffer@3.0.1: + resolution: {integrity: sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==} + + json-schema-traverse@0.4.1: + resolution: {integrity: sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==} + + json-stable-stringify-without-jsonify@1.0.1: + resolution: {integrity: sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==} + + jsx-ast-utils@3.3.5: + resolution: {integrity: sha512-ZZow9HBI5O6EPgSJLUb8n2NKgmVWTwCvHGwFuJlMjvLFqlGG6pjirPhtdsseaLZjSibD8eegzmYpUZwoIlj2cQ==} + engines: {node: '>=4.0'} + + keyv@4.5.4: + resolution: {integrity: sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==} + + levn@0.4.1: + resolution: {integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==} + engines: {node: '>= 0.8.0'} + + lilconfig@3.1.3: + resolution: {integrity: sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw==} + engines: {node: '>=14'} + + lines-and-columns@1.2.4: + resolution: {integrity: sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==} + + load-tsconfig@0.2.5: + resolution: {integrity: sha512-IXO6OCs9yg8tMKzfPZ1YmheJbZCiEsnBdcB03l0OcfK9prKnJb96siuHCr5Fl37/yo9DnKU+TLpxzTUspw9shg==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + + locate-path@6.0.0: + resolution: {integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==} + engines: {node: '>=10'} + + lodash.merge@4.6.2: + resolution: {integrity: sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==} + + loose-envify@1.4.0: + resolution: {integrity: sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==} + hasBin: true + + loupe@3.2.1: + resolution: {integrity: sha512-CdzqowRJCeLU72bHvWqwRBBlLcMEtIvGrlvef74kMnV2AolS9Y8xUv1I0U/MNAWMhBlKIoyuEgoJ0t/bbwHbLQ==} + + lru-cache@10.4.3: + resolution: {integrity: sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==} + + 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==} + + math-intrinsics@1.1.0: + resolution: {integrity: sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==} + engines: {node: '>= 0.4'} + + min-indent@1.0.1: + resolution: {integrity: sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==} + engines: {node: '>=4'} + + minimatch@10.2.5: + resolution: {integrity: sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg==} + engines: {node: 18 || 20 || >=22} + + minimatch@3.1.5: + resolution: {integrity: sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==} + + mlly@1.8.2: + resolution: {integrity: sha512-d+ObxMQFmbt10sretNDytwt85VrbkhhUA/JBGm1MPaWJ65Cl4wOgLaB1NYvJSZ0Ef03MMEU/0xpPMXUIQ29UfA==} + + ms@2.1.3: + resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} + + mz@2.7.0: + resolution: {integrity: sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==} + + nanoid@3.3.11: + resolution: {integrity: sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==} + engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} + hasBin: true + + natural-compare@1.4.0: + resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==} + + node-exports-info@1.6.0: + resolution: {integrity: sha512-pyFS63ptit/P5WqUkt+UUfe+4oevH+bFeIiPPdfb0pFeYEu/1ELnJu5l+5EcTKYL5M7zaAa7S8ddywgXypqKCw==} + engines: {node: '>= 0.4'} + + nwsapi@2.2.23: + resolution: {integrity: sha512-7wfH4sLbt4M0gCDzGE6vzQBo0bfTKjU7Sfpqy/7gs1qBfYz2vEJH6vXcBKpO3+6Yu1telwd0t9HpyOoLEQQbIQ==} + + object-assign@4.1.1: + resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==} + engines: {node: '>=0.10.0'} + + object-inspect@1.13.4: + resolution: {integrity: sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==} + engines: {node: '>= 0.4'} + + object-keys@1.1.1: + resolution: {integrity: sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==} + engines: {node: '>= 0.4'} + + object.assign@4.1.7: + resolution: {integrity: sha512-nK28WOo+QIjBkDduTINE4JkF/UJJKyf2EJxvJKfblDpyg0Q+pkOHNTL0Qwy6NP6FhE/EnzV73BxxqcJaXY9anw==} + engines: {node: '>= 0.4'} + + object.entries@1.1.9: + resolution: {integrity: sha512-8u/hfXFRBD1O0hPUjioLhoWFHRmt6tKA4/vZPyckBr18l1KE9uHrFaFaUi8MDRTpi4uak2goyPTSNJLXX2k2Hw==} + engines: {node: '>= 0.4'} + + object.fromentries@2.0.8: + resolution: {integrity: sha512-k6E21FzySsSK5a21KRADBd/NGneRegFO5pLHfdQLpRDETUNJueLXs3WCzyQ3tFRDYgbq3KHGXfTbi2bs8WQ6rQ==} + engines: {node: '>= 0.4'} + + object.values@1.2.1: + resolution: {integrity: sha512-gXah6aZrcUxjWg2zR2MwouP2eHlCBzdV4pygudehaKXSGW4v2AsRQUK+lwwXhii6KFZcunEnmSUoYp5CXibxtA==} + engines: {node: '>= 0.4'} + + optionator@0.9.4: + resolution: {integrity: sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==} + engines: {node: '>= 0.8.0'} + + own-keys@1.0.1: + resolution: {integrity: sha512-qFOyK5PjiWZd+QQIh+1jhdb9LpxTF0qs7Pm8o5QHYZ0M3vKqSqzsZaEB6oWlxZ+q2sJBMI/Ktgd2N5ZwQoRHfg==} + engines: {node: '>= 0.4'} + + p-limit@3.1.0: + resolution: {integrity: sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==} + engines: {node: '>=10'} + + p-locate@5.0.0: + resolution: {integrity: sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==} + engines: {node: '>=10'} + + parent-module@1.0.1: + resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==} + engines: {node: '>=6'} + + parse5@7.3.0: + resolution: {integrity: sha512-IInvU7fabl34qmi9gY8XOVxhYyMyuH2xUNpb2q8/Y+7552KlejkRvqvD19nMoUW/uQGGbqNpA6Tufu5FL5BZgw==} + + path-exists@4.0.0: + resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==} + engines: {node: '>=8'} + + path-key@3.1.1: + resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==} + engines: {node: '>=8'} + + path-parse@1.0.7: + resolution: {integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==} + + pathe@2.0.3: + resolution: {integrity: sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==} + + pathval@2.0.1: + resolution: {integrity: sha512-//nshmD55c46FuFw26xV/xFAaB5HF9Xdap7HJBBnrKdAd6/GxDBaNA1870O79+9ueg61cZLSVc+OaFlfmObYVQ==} + engines: {node: '>= 14.16'} + + picocolors@1.1.1: + resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} + + picomatch@4.0.4: + resolution: {integrity: sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==} + engines: {node: '>=12'} + + pirates@4.0.7: + resolution: {integrity: sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA==} + engines: {node: '>= 6'} + + pkg-types@1.3.1: + resolution: {integrity: sha512-/Jm5M4RvtBFVkKWRu2BLUTNP8/M2a+UwuAX+ae4770q1qVGtfjG+WTCupoZixokjmHiry8uI+dlY8KXYV5HVVQ==} + + possible-typed-array-names@1.1.0: + resolution: {integrity: sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg==} + engines: {node: '>= 0.4'} + + postcss-load-config@6.0.1: + resolution: {integrity: sha512-oPtTM4oerL+UXmx+93ytZVN82RrlY/wPUV8IeDxFrzIjXOLF1pN+EmKPLbubvKHT2HC20xXsCAH2Z+CKV6Oz/g==} + engines: {node: '>= 18'} + peerDependencies: + jiti: '>=1.21.0' + postcss: '>=8.0.9' + tsx: ^4.8.1 + yaml: ^2.4.2 + peerDependenciesMeta: + jiti: + optional: true + postcss: + optional: true + tsx: + optional: true + yaml: + optional: true + + postcss@8.5.9: + resolution: {integrity: sha512-7a70Nsot+EMX9fFU3064K/kdHWZqGVY+BADLyXc8Dfv+mTLLVl6JzJpPaCZ2kQL9gIJvKXSLMHhqdRRjwQeFtw==} + engines: {node: ^10 || ^12 || >=14} + + prelude-ls@1.2.1: + resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==} + engines: {node: '>= 0.8.0'} + + prettier@3.8.2: + resolution: {integrity: sha512-8c3mgTe0ASwWAJK+78dpviD+A8EqhndQPUBpNUIPt6+xWlIigCwfN01lWr9MAede4uqXGTEKeQWTvzb3vjia0Q==} + 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} + + prop-types@15.8.1: + resolution: {integrity: sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==} + + punycode@2.3.1: + resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==} + engines: {node: '>=6'} + + react-dom@19.2.5: + resolution: {integrity: sha512-J5bAZz+DXMMwW/wV3xzKke59Af6CHY7G4uYLN1OvBcKEsWOs4pQExj86BBKamxl/Ik5bx9whOrvBlSDfWzgSag==} + peerDependencies: + react: ^19.2.5 + + 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@19.2.5: + resolution: {integrity: sha512-llUJLzz1zTUBrskt2pwZgLq59AemifIftw4aB7JxOqf1HY2FDaGDxgwpAPVzHU1kdWabH7FauP4i1oEeer2WCA==} + engines: {node: '>=0.10.0'} + + readdirp@4.1.2: + resolution: {integrity: sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==} + engines: {node: '>= 14.18.0'} + + redent@3.0.0: + resolution: {integrity: sha512-6tDA8g98We0zd0GvVeMT9arEOnTw9qM03L9cJXaCjrip1OO764RDBLBfrB4cwzNGDj5OA5ioymC9GkizgWJDUg==} + engines: {node: '>=8'} + + reflect.getprototypeof@1.0.10: + resolution: {integrity: sha512-00o4I+DVrefhv+nX0ulyi3biSHCPDe+yLv5o/p6d/UVlirijB8E16FtfwSAi4g3tcqrQ4lRAqQSoFEZJehYEcw==} + engines: {node: '>= 0.4'} + + regexp.prototype.flags@1.5.4: + resolution: {integrity: sha512-dYqgNSZbDwkaJ2ceRd9ojCGjBq+mOm9LmtXnAnEGyHhN/5R7iDW2TRw3h+o/jCFxus3P2LfWIIiwowAjANm7IA==} + engines: {node: '>= 0.4'} + + resolve-from@4.0.0: + resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==} + engines: {node: '>=4'} + + resolve-from@5.0.0: + resolution: {integrity: sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==} + engines: {node: '>=8'} + + resolve@2.0.0-next.6: + resolution: {integrity: sha512-3JmVl5hMGtJ3kMmB3zi3DL25KfkCEyy3Tw7Gmw7z5w8M9WlwoPFnIvwChzu1+cF3iaK3sp18hhPz8ANeimdJfA==} + engines: {node: '>= 0.4'} + hasBin: true + + rollup@4.60.1: + resolution: {integrity: sha512-VmtB2rFU/GroZ4oL8+ZqXgSA38O6GR8KSIvWmEFv63pQ0G6KaBH9s07PO8XTXP4vI+3UJUEypOfjkGfmSBBR0w==} + engines: {node: '>=18.0.0', npm: '>=8.0.0'} + hasBin: true + + rrweb-cssom@0.8.0: + resolution: {integrity: sha512-guoltQEx+9aMf2gDZ0s62EcV8lsXR+0w8915TC3ITdn2YueuNjdAYh/levpU9nFaoChh9RUS5ZdQMrKfVEN9tw==} + + safe-array-concat@1.1.3: + resolution: {integrity: sha512-AURm5f0jYEOydBj7VQlVvDrjeFgthDdEF5H1dP+6mNpoXOMo1quQqJ4wvJDyRZ9+pO3kGWoOdmV08cSv2aJV6Q==} + engines: {node: '>=0.4'} + + safe-push-apply@1.0.0: + resolution: {integrity: sha512-iKE9w/Z7xCzUMIZqdBsp6pEQvwuEebH4vdpjcDWnyzaI6yl6O9FHvVpmGelvEHNsoY6wGblkxR6Zty/h00WiSA==} + engines: {node: '>= 0.4'} + + safe-regex-test@1.1.0: + resolution: {integrity: sha512-x/+Cz4YrimQxQccJf5mKEbIa1NzeCRNI5Ecl/ekmlYaampdNLPalVyIcCZNNH3MvmqBugV5TMYZXv0ljslUlaw==} + engines: {node: '>= 0.4'} + + safer-buffer@2.1.2: + resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==} + + saxes@6.0.0: + resolution: {integrity: sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA==} + engines: {node: '>=v12.22.7'} + + scheduler@0.27.0: + resolution: {integrity: sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==} + + semver@6.3.1: + resolution: {integrity: sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==} + hasBin: true + + semver@7.7.4: + resolution: {integrity: sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==} + engines: {node: '>=10'} + hasBin: true + + set-function-length@1.2.2: + resolution: {integrity: sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==} + engines: {node: '>= 0.4'} + + set-function-name@2.0.2: + resolution: {integrity: sha512-7PGFlmtwsEADb0WYyvCMa1t+yke6daIG4Wirafur5kcf+MhUnPms1UeR0CKQdTZD81yESwMHbtn+TR+dMviakQ==} + engines: {node: '>= 0.4'} + + set-proto@1.0.0: + resolution: {integrity: sha512-RJRdvCo6IAnPdsvP/7m6bsQqNnn1FCBX5ZNtFL98MmFF/4xAIJTIg1YbHW5DC2W5SKZanrC6i4HsJqlajw/dZw==} + engines: {node: '>= 0.4'} + + shebang-command@2.0.0: + resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==} + engines: {node: '>=8'} + + shebang-regex@3.0.0: + resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==} + engines: {node: '>=8'} + + side-channel-list@1.0.1: + resolution: {integrity: sha512-mjn/0bi/oUURjc5Xl7IaWi/OJJJumuoJFQJfDDyO46+hBWsfaVM65TBHq2eoZBhzl9EchxOijpkbRC8SVBQU0w==} + engines: {node: '>= 0.4'} + + side-channel-map@1.0.1: + resolution: {integrity: sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==} + engines: {node: '>= 0.4'} + + side-channel-weakmap@1.0.2: + resolution: {integrity: sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==} + engines: {node: '>= 0.4'} + + side-channel@1.1.0: + resolution: {integrity: sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==} + engines: {node: '>= 0.4'} + + siginfo@2.0.0: + resolution: {integrity: sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==} + + source-map-js@1.2.1: + resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==} + engines: {node: '>=0.10.0'} + + source-map@0.7.6: + resolution: {integrity: sha512-i5uvt8C3ikiWeNZSVZNWcfZPItFQOsYTUAOkcUPGd8DqDy1uOUikjt5dG+uRlwyvR108Fb9DOd4GvXfT0N2/uQ==} + engines: {node: '>= 12'} + + stackback@0.0.2: + resolution: {integrity: sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==} + + std-env@3.10.0: + resolution: {integrity: sha512-5GS12FdOZNliM5mAOxFRg7Ir0pWz8MdpYm6AY6VPkGpbA7ZzmbzNcBJQ0GPvvyWgcY7QAhCgf9Uy89I03faLkg==} + + stop-iteration-iterator@1.1.0: + resolution: {integrity: sha512-eLoXW/DHyl62zxY4SCaIgnRhuMr6ri4juEYARS8E6sCEqzKpOiE521Ucofdx+KnDZl5xmvGYaaKCk5FEOxJCoQ==} + engines: {node: '>= 0.4'} + + string.prototype.matchall@4.0.12: + resolution: {integrity: sha512-6CC9uyBL+/48dYizRf7H7VAYCMCNTBeM78x/VTUe9bFEaxBepPJDa1Ow99LqI/1yF7kuy7Q3cQsYMrcjGUcskA==} + engines: {node: '>= 0.4'} + + string.prototype.repeat@1.0.0: + resolution: {integrity: sha512-0u/TldDbKD8bFCQ/4f5+mNRrXwZ8hg2w7ZR8wa16e8z9XpePWl3eGEcUD0OXpEH/VJH/2G3gjUtR3ZOiBe2S/w==} + + string.prototype.trim@1.2.10: + resolution: {integrity: sha512-Rs66F0P/1kedk5lyYyH9uBzuiI/kNRmwJAR9quK6VOtIpZ2G+hMZd+HQbbv25MgCA6gEffoMZYxlTod4WcdrKA==} + engines: {node: '>= 0.4'} + + string.prototype.trimend@1.0.9: + resolution: {integrity: sha512-G7Ok5C6E/j4SGfyLCloXTrngQIQU3PWtXGst3yM7Bea9FRURf1S42ZHlZZtsNque2FN2PoUhfZXYLNWwEr4dLQ==} + engines: {node: '>= 0.4'} + + string.prototype.trimstart@1.0.8: + resolution: {integrity: sha512-UXSH262CSZY1tfu3G3Secr6uGLCFVPMhIqHjlgCUtCCcgihYc/xKs9djMTMUOb2j1mVSeU8EU6NWc/iQKU6Gfg==} + engines: {node: '>= 0.4'} + + strip-indent@3.0.0: + resolution: {integrity: sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ==} + engines: {node: '>=8'} + + strip-json-comments@3.1.1: + resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==} + engines: {node: '>=8'} + + strip-literal@3.1.0: + resolution: {integrity: sha512-8r3mkIM/2+PpjHoOtiAW8Rg3jJLHaV7xPwG+YRGrv6FP0wwk/toTpATxWYOW0BKdWwl82VT2tFYi5DlROa0Mxg==} + + sucrase@3.35.1: + resolution: {integrity: sha512-DhuTmvZWux4H1UOnWMB3sk0sbaCVOoQZjv8u1rDoTV0HTdGem9hkAZtl4JZy8P2z4Bg0nT+YMeOFyVr4zcG5Tw==} + engines: {node: '>=16 || 14 >=14.17'} + hasBin: true + + supports-color@7.2.0: + resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==} + engines: {node: '>=8'} + + supports-preserve-symlinks-flag@1.0.0: + resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==} + engines: {node: '>= 0.4'} + + symbol-tree@3.2.4: + resolution: {integrity: sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==} + + thenify-all@1.6.0: + resolution: {integrity: sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==} + engines: {node: '>=0.8'} + + thenify@3.3.1: + resolution: {integrity: sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==} + + tinybench@2.9.0: + resolution: {integrity: sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==} + + tinyexec@0.3.2: + resolution: {integrity: sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA==} + + tinyglobby@0.2.16: + resolution: {integrity: sha512-pn99VhoACYR8nFHhxqix+uvsbXineAasWm5ojXoN8xEwK5Kd3/TrhNn1wByuD52UxWRLy8pu+kRMniEi6Eq9Zg==} + engines: {node: '>=12.0.0'} + + tinypool@1.1.1: + resolution: {integrity: sha512-Zba82s87IFq9A9XmjiX5uZA/ARWDrB03OHlq+Vw1fSdt0I+4/Kutwy8BP4Y/y/aORMo61FQ0vIb5j44vSo5Pkg==} + engines: {node: ^18.0.0 || >=20.0.0} + + tinyrainbow@2.0.0: + resolution: {integrity: sha512-op4nsTR47R6p0vMUUoYl/a+ljLFVtlfaXkLQmqfLR1qHma1h/ysYk4hEXZ880bf2CYgTskvTa/e196Vd5dDQXw==} + engines: {node: '>=14.0.0'} + + tinyspy@4.0.4: + resolution: {integrity: sha512-azl+t0z7pw/z958Gy9svOTuzqIk6xq+NSheJzn5MMWtWTFywIacg2wUlzKFGtt3cthx0r2SxMK0yzJOR0IES7Q==} + engines: {node: '>=14.0.0'} + + tldts-core@6.1.86: + resolution: {integrity: sha512-Je6p7pkk+KMzMv2XXKmAE3McmolOQFdxkKw0R8EYNr7sELW46JqnNeTX8ybPiQgvg1ymCoF8LXs5fzFaZvJPTA==} + + tldts@6.1.86: + resolution: {integrity: sha512-WMi/OQ2axVTf/ykqCQgXiIct+mSQDFdH2fkwhPwgEwvJ1kSzZRiinb0zF2Xb8u4+OqPChmyI6MEu4EezNJz+FQ==} + hasBin: true + + tough-cookie@5.1.2: + resolution: {integrity: sha512-FVDYdxtnj0G6Qm/DhNPSb8Ju59ULcup3tuJxkFb5K8Bv2pUXILbf0xZWU8PX8Ov19OXljbUyveOFwRMwkXzO+A==} + engines: {node: '>=16'} + + tr46@5.1.1: + resolution: {integrity: sha512-hdF5ZgjTqgAntKkklYw0R03MG2x/bSzTtkxmIRw/sTNV8YXsCJ1tfLAX23lhxhHJlEf3CRCOCGGWw3vI3GaSPw==} + engines: {node: '>=18'} + + tree-kill@1.2.2: + resolution: {integrity: sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==} + hasBin: true + + ts-api-utils@2.5.0: + resolution: {integrity: sha512-OJ/ibxhPlqrMM0UiNHJ/0CKQkoKF243/AEmplt3qpRgkW8VG7IfOS41h7V8TjITqdByHzrjcS/2si+y4lIh8NA==} + engines: {node: '>=18.12'} + peerDependencies: + typescript: '>=4.8.4' + + ts-interface-checker@0.1.13: + resolution: {integrity: sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==} + + tsup@8.5.1: + resolution: {integrity: sha512-xtgkqwdhpKWr3tKPmCkvYmS9xnQK3m3XgxZHwSUjvfTjp7YfXe5tT3GgWi0F2N+ZSMsOeWeZFh7ZZFg5iPhing==} + engines: {node: '>=18'} + hasBin: true + peerDependencies: + '@microsoft/api-extractor': ^7.36.0 + '@swc/core': ^1 + postcss: ^8.4.12 + typescript: '>=4.5.0' + peerDependenciesMeta: + '@microsoft/api-extractor': + optional: true + '@swc/core': + optional: true + postcss: + optional: true + typescript: + optional: true + + type-check@0.4.0: + resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==} + engines: {node: '>= 0.8.0'} + + typed-array-buffer@1.0.3: + resolution: {integrity: sha512-nAYYwfY3qnzX30IkA6AQZjVbtK6duGontcQm1WSG1MD94YLqK0515GNApXkoxKOWMusVssAHWLh9SeaoefYFGw==} + engines: {node: '>= 0.4'} + + typed-array-byte-length@1.0.3: + resolution: {integrity: sha512-BaXgOuIxz8n8pIq3e7Atg/7s+DpiYrxn4vdot3w9KbnBhcRQq6o3xemQdIfynqSeXeDrF32x+WvfzmOjPiY9lg==} + engines: {node: '>= 0.4'} + + typed-array-byte-offset@1.0.4: + resolution: {integrity: sha512-bTlAFB/FBYMcuX81gbL4OcpH5PmlFHqlCCpAl8AlEzMz5k53oNDvN8p1PNOWLEmI2x4orp3raOFB51tv9X+MFQ==} + engines: {node: '>= 0.4'} + + typed-array-length@1.0.7: + resolution: {integrity: sha512-3KS2b+kL7fsuk/eJZ7EQdnEmQoaho/r6KUef7hxvltNA5DR8NAUM+8wJMbJyZ4G9/7i3v5zPBIMN5aybAh2/Jg==} + engines: {node: '>= 0.4'} + + typescript@5.9.3: + resolution: {integrity: sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==} + engines: {node: '>=14.17'} + hasBin: true + + ufo@1.6.3: + resolution: {integrity: sha512-yDJTmhydvl5lJzBmy/hyOAA0d+aqCBuwl818haVdYCRrWV84o7YyeVm4QlVHStqNrrJSTb6jKuFAVqAFsr+K3Q==} + + unbox-primitive@1.1.0: + resolution: {integrity: sha512-nWJ91DjeOkej/TA8pXQ3myruKpKEYgqvpw9lz4OPHj/NWFNluYrjbz9j01CJ8yKQd2g4jFoOkINCTW2I5LEEyw==} + engines: {node: '>= 0.4'} + + uri-js@4.4.1: + resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==} + + vite-node@3.2.4: + resolution: {integrity: sha512-EbKSKh+bh1E1IFxeO0pg1n4dvoOTt0UDiXMd/qn++r98+jPO1xtJilvXldeuQ8giIB5IkpjCgMleHMNEsGH6pg==} + engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0} + hasBin: true + + vite@7.3.2: + resolution: {integrity: sha512-Bby3NOsna2jsjfLVOHKes8sGwgl4TT0E6vvpYgnAYDIF/tie7MRaFthmKuHx1NSXjiTueXH3do80FMQgvEktRg==} + engines: {node: ^20.19.0 || >=22.12.0} + hasBin: true + peerDependencies: + '@types/node': ^20.19.0 || >=22.12.0 + jiti: '>=1.21.0' + less: ^4.0.0 + lightningcss: ^1.21.0 + sass: ^1.70.0 + sass-embedded: ^1.70.0 + stylus: '>=0.54.8' + sugarss: ^5.0.0 + terser: ^5.16.0 + tsx: ^4.8.1 + yaml: ^2.4.2 + peerDependenciesMeta: + '@types/node': + optional: true + jiti: + optional: true + less: + optional: true + lightningcss: + 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@3.2.4: + resolution: {integrity: sha512-LUCP5ev3GURDysTWiP47wRRUpLKMOfPh+yKTx3kVIEiu5KOMeqzpnYNsKyOoVrULivR8tLcks4+lga33Whn90A==} + engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0} + hasBin: true + peerDependencies: + '@edge-runtime/vm': '*' + '@types/debug': ^4.1.12 + '@types/node': ^18.0.0 || ^20.0.0 || >=22.0.0 + '@vitest/browser': 3.2.4 + '@vitest/ui': 3.2.4 + happy-dom: '*' + jsdom: '*' + peerDependenciesMeta: + '@edge-runtime/vm': + optional: true + '@types/debug': + optional: true + '@types/node': + optional: true + '@vitest/browser': + 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'} + + webidl-conversions@7.0.0: + resolution: {integrity: sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==} + engines: {node: '>=12'} + + whatwg-encoding@3.1.1: + resolution: {integrity: sha512-6qN4hJdMwfYBtE3YBTTHhoeuUrDBPZmbQaxWAqSALV/MeEnR5z1xd8UKud2RAkFoPkmB+hli1TZSnyi84xz1vQ==} + engines: {node: '>=18'} + deprecated: Use @exodus/bytes instead for a more spec-conformant and faster implementation + + whatwg-mimetype@4.0.0: + resolution: {integrity: sha512-QaKxh0eNIi2mE9p2vEdzfagOKHCcj1pJ56EEHGQOVxp8r9/iszLUUV7v89x9O1p/T+NlTM5W7jW6+cz4Fq1YVg==} + engines: {node: '>=18'} + + whatwg-url@14.2.0: + resolution: {integrity: sha512-De72GdQZzNTUBBChsXueQUnPKDkg/5A5zp7pFDuQAj5UFoENpiACU0wlCvzpAGnTkj++ihpKwKyYewn/XNUbKw==} + engines: {node: '>=18'} + + which-boxed-primitive@1.1.1: + resolution: {integrity: sha512-TbX3mj8n0odCBFVlY8AxkqcHASw3L60jIuF8jFP78az3C2YhmGvqbHBpAjTRH2/xqYunrJ9g1jSyjCjpoWzIAA==} + engines: {node: '>= 0.4'} + + which-builtin-type@1.2.1: + resolution: {integrity: sha512-6iBczoX+kDQ7a3+YJBnh3T+KZRxM/iYNPXicqk66/Qfm1b93iu+yOImkg0zHbj5LNOcNv1TEADiZ0xa34B4q6Q==} + engines: {node: '>= 0.4'} + + which-collection@1.0.2: + resolution: {integrity: sha512-K4jVyjnBdgvc86Y6BkaLZEN933SwYOuBFkdmBu9ZfkcAbdVbpITnDmjvZ/aQjRXQrv5EPkTnD1s39GiiqbngCw==} + engines: {node: '>= 0.4'} + + which-typed-array@1.1.20: + resolution: {integrity: sha512-LYfpUkmqwl0h9A2HL09Mms427Q1RZWuOHsukfVcKRq9q95iQxdw0ix1JQrqbcDR9PH1QDwf5Qo8OZb5lksZ8Xg==} + engines: {node: '>= 0.4'} + + which@2.0.2: + resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==} + 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'} + + ws@8.20.0: + resolution: {integrity: sha512-sAt8BhgNbzCtgGbt2OxmpuryO63ZoDk/sqaB/znQm94T4fCEsy/yV+7CdC1kJhOU9lboAEU7R3kquuycDoibVA==} + engines: {node: '>=10.0.0'} + peerDependencies: + bufferutil: ^4.0.1 + utf-8-validate: '>=5.0.2' + peerDependenciesMeta: + bufferutil: + optional: true + utf-8-validate: + optional: true + + xml-name-validator@5.0.0: + resolution: {integrity: sha512-EvGK8EJ3DhaHfbRlETOWAS5pO9MZITeauHKJyb8wyajUfQUenkIg2MvLDTZ4T/TgIcm3HU0TFBgWWboAZ30UHg==} + engines: {node: '>=18'} + + xmlchars@2.2.0: + resolution: {integrity: sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==} + + yocto-queue@0.1.0: + resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==} + engines: {node: '>=10'} + +snapshots: + + '@adobe/css-tools@4.4.4': {} + + '@asamuzakjp/css-color@3.2.0': + dependencies: + '@csstools/css-calc': 2.1.4(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4) + '@csstools/css-color-parser': 3.1.0(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4) + '@csstools/css-parser-algorithms': 3.0.5(@csstools/css-tokenizer@3.0.4) + '@csstools/css-tokenizer': 3.0.4 + lru-cache: 10.4.3 + + '@babel/code-frame@7.29.0': + dependencies: + '@babel/helper-validator-identifier': 7.28.5 + js-tokens: 4.0.0 + picocolors: 1.1.1 + + '@babel/helper-validator-identifier@7.28.5': {} + + '@babel/runtime@7.29.2': {} + + '@csstools/color-helpers@5.1.0': {} + + '@csstools/css-calc@2.1.4(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4)': + dependencies: + '@csstools/css-parser-algorithms': 3.0.5(@csstools/css-tokenizer@3.0.4) + '@csstools/css-tokenizer': 3.0.4 + + '@csstools/css-color-parser@3.1.0(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4)': + dependencies: + '@csstools/color-helpers': 5.1.0 + '@csstools/css-calc': 2.1.4(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4) + '@csstools/css-parser-algorithms': 3.0.5(@csstools/css-tokenizer@3.0.4) + '@csstools/css-tokenizer': 3.0.4 + + '@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4)': + dependencies: + '@csstools/css-tokenizer': 3.0.4 + + '@csstools/css-tokenizer@3.0.4': {} + + '@esbuild/aix-ppc64@0.27.7': + optional: true + + '@esbuild/android-arm64@0.27.7': + optional: true + + '@esbuild/android-arm@0.27.7': + optional: true + + '@esbuild/android-x64@0.27.7': + optional: true + + '@esbuild/darwin-arm64@0.27.7': + optional: true + + '@esbuild/darwin-x64@0.27.7': + optional: true + + '@esbuild/freebsd-arm64@0.27.7': + optional: true + + '@esbuild/freebsd-x64@0.27.7': + optional: true + + '@esbuild/linux-arm64@0.27.7': + optional: true + + '@esbuild/linux-arm@0.27.7': + optional: true + + '@esbuild/linux-ia32@0.27.7': + optional: true + + '@esbuild/linux-loong64@0.27.7': + optional: true + + '@esbuild/linux-mips64el@0.27.7': + optional: true + + '@esbuild/linux-ppc64@0.27.7': + optional: true + + '@esbuild/linux-riscv64@0.27.7': + optional: true + + '@esbuild/linux-s390x@0.27.7': + optional: true + + '@esbuild/linux-x64@0.27.7': + optional: true + + '@esbuild/netbsd-arm64@0.27.7': + optional: true + + '@esbuild/netbsd-x64@0.27.7': + optional: true + + '@esbuild/openbsd-arm64@0.27.7': + optional: true + + '@esbuild/openbsd-x64@0.27.7': + optional: true + + '@esbuild/openharmony-arm64@0.27.7': + optional: true + + '@esbuild/sunos-x64@0.27.7': + optional: true + + '@esbuild/win32-arm64@0.27.7': + optional: true + + '@esbuild/win32-ia32@0.27.7': + optional: true + + '@esbuild/win32-x64@0.27.7': + optional: true + + '@eslint-community/eslint-utils@4.9.1(eslint@9.39.4)': + dependencies: + eslint: 9.39.4 + eslint-visitor-keys: 3.4.3 + + '@eslint-community/regexpp@4.12.2': {} + + '@eslint/config-array@0.21.2': + dependencies: + '@eslint/object-schema': 2.1.7 + debug: 4.4.3 + minimatch: 3.1.5 + transitivePeerDependencies: + - supports-color + + '@eslint/config-helpers@0.4.2': + dependencies: + '@eslint/core': 0.17.0 + + '@eslint/core@0.17.0': + dependencies: + '@types/json-schema': 7.0.15 + + '@eslint/eslintrc@3.3.5': + dependencies: + ajv: 6.14.0 + debug: 4.4.3 + espree: 10.4.0 + globals: 14.0.0 + ignore: 5.3.2 + import-fresh: 3.3.1 + js-yaml: 4.1.1 + minimatch: 3.1.5 + strip-json-comments: 3.1.1 + transitivePeerDependencies: + - supports-color + + '@eslint/js@10.0.1(eslint@9.39.4)': + optionalDependencies: + eslint: 9.39.4 + + '@eslint/js@9.39.4': {} + + '@eslint/object-schema@2.1.7': {} + + '@eslint/plugin-kit@0.4.1': + dependencies: + '@eslint/core': 0.17.0 + levn: 0.4.1 + + '@humanfs/core@0.19.1': {} + + '@humanfs/node@0.16.7': + dependencies: + '@humanfs/core': 0.19.1 + '@humanwhocodes/retry': 0.4.3 + + '@humanwhocodes/module-importer@1.0.1': {} + + '@humanwhocodes/retry@0.4.3': {} + + '@jridgewell/gen-mapping@0.3.13': + dependencies: + '@jridgewell/sourcemap-codec': 1.5.5 + '@jridgewell/trace-mapping': 0.3.31 + + '@jridgewell/resolve-uri@3.1.2': {} + + '@jridgewell/sourcemap-codec@1.5.5': {} + + '@jridgewell/trace-mapping@0.3.31': + dependencies: + '@jridgewell/resolve-uri': 3.1.2 + '@jridgewell/sourcemap-codec': 1.5.5 + + '@rollup/rollup-android-arm-eabi@4.60.1': + optional: true + + '@rollup/rollup-android-arm64@4.60.1': + optional: true + + '@rollup/rollup-darwin-arm64@4.60.1': + optional: true + + '@rollup/rollup-darwin-x64@4.60.1': + optional: true + + '@rollup/rollup-freebsd-arm64@4.60.1': + optional: true + + '@rollup/rollup-freebsd-x64@4.60.1': + optional: true + + '@rollup/rollup-linux-arm-gnueabihf@4.60.1': + optional: true + + '@rollup/rollup-linux-arm-musleabihf@4.60.1': + optional: true + + '@rollup/rollup-linux-arm64-gnu@4.60.1': + optional: true + + '@rollup/rollup-linux-arm64-musl@4.60.1': + optional: true + + '@rollup/rollup-linux-loong64-gnu@4.60.1': + optional: true + + '@rollup/rollup-linux-loong64-musl@4.60.1': + optional: true + + '@rollup/rollup-linux-ppc64-gnu@4.60.1': + optional: true + + '@rollup/rollup-linux-ppc64-musl@4.60.1': + optional: true + + '@rollup/rollup-linux-riscv64-gnu@4.60.1': + optional: true + + '@rollup/rollup-linux-riscv64-musl@4.60.1': + optional: true + + '@rollup/rollup-linux-s390x-gnu@4.60.1': + optional: true + + '@rollup/rollup-linux-x64-gnu@4.60.1': + optional: true + + '@rollup/rollup-linux-x64-musl@4.60.1': + optional: true + + '@rollup/rollup-openbsd-x64@4.60.1': + optional: true + + '@rollup/rollup-openharmony-arm64@4.60.1': + optional: true + + '@rollup/rollup-win32-arm64-msvc@4.60.1': + optional: true + + '@rollup/rollup-win32-ia32-msvc@4.60.1': + optional: true + + '@rollup/rollup-win32-x64-gnu@4.60.1': + optional: true + + '@rollup/rollup-win32-x64-msvc@4.60.1': + optional: true + + '@testing-library/dom@10.4.1': + dependencies: + '@babel/code-frame': 7.29.0 + '@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/jest-dom@6.9.1': + dependencies: + '@adobe/css-tools': 4.4.4 + aria-query: 5.3.2 + css.escape: 1.5.1 + dom-accessibility-api: 0.6.3 + picocolors: 1.1.1 + redent: 3.0.0 + + '@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) + + '@testing-library/user-event@14.6.1(@testing-library/dom@10.4.1)': + dependencies: + '@testing-library/dom': 10.4.1 + + '@types/aria-query@5.0.4': {} + + '@types/chai@5.2.3': + dependencies: + '@types/deep-eql': 4.0.2 + assertion-error: 2.0.1 + + '@types/deep-eql@4.0.2': {} + + '@types/estree@1.0.8': {} + + '@types/json-schema@7.0.15': {} + + '@types/react-dom@19.2.3(@types/react@19.2.14)': + dependencies: + '@types/react': 19.2.14 + + '@types/react@19.2.14': + dependencies: + csstype: 3.2.3 + + '@typescript-eslint/eslint-plugin@8.58.1(@typescript-eslint/parser@8.58.1(eslint@9.39.4)(typescript@5.9.3))(eslint@9.39.4)(typescript@5.9.3)': + dependencies: + '@eslint-community/regexpp': 4.12.2 + '@typescript-eslint/parser': 8.58.1(eslint@9.39.4)(typescript@5.9.3) + '@typescript-eslint/scope-manager': 8.58.1 + '@typescript-eslint/type-utils': 8.58.1(eslint@9.39.4)(typescript@5.9.3) + '@typescript-eslint/utils': 8.58.1(eslint@9.39.4)(typescript@5.9.3) + '@typescript-eslint/visitor-keys': 8.58.1 + eslint: 9.39.4 + ignore: 7.0.5 + natural-compare: 1.4.0 + ts-api-utils: 2.5.0(typescript@5.9.3) + typescript: 5.9.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/parser@8.58.1(eslint@9.39.4)(typescript@5.9.3)': + dependencies: + '@typescript-eslint/scope-manager': 8.58.1 + '@typescript-eslint/types': 8.58.1 + '@typescript-eslint/typescript-estree': 8.58.1(typescript@5.9.3) + '@typescript-eslint/visitor-keys': 8.58.1 + debug: 4.4.3 + eslint: 9.39.4 + typescript: 5.9.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/project-service@8.58.1(typescript@5.9.3)': + dependencies: + '@typescript-eslint/tsconfig-utils': 8.58.1(typescript@5.9.3) + '@typescript-eslint/types': 8.58.1 + debug: 4.4.3 + typescript: 5.9.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/scope-manager@8.58.1': + dependencies: + '@typescript-eslint/types': 8.58.1 + '@typescript-eslint/visitor-keys': 8.58.1 + + '@typescript-eslint/tsconfig-utils@8.58.1(typescript@5.9.3)': + dependencies: + typescript: 5.9.3 + + '@typescript-eslint/type-utils@8.58.1(eslint@9.39.4)(typescript@5.9.3)': + dependencies: + '@typescript-eslint/types': 8.58.1 + '@typescript-eslint/typescript-estree': 8.58.1(typescript@5.9.3) + '@typescript-eslint/utils': 8.58.1(eslint@9.39.4)(typescript@5.9.3) + debug: 4.4.3 + eslint: 9.39.4 + ts-api-utils: 2.5.0(typescript@5.9.3) + typescript: 5.9.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/types@8.58.1': {} + + '@typescript-eslint/typescript-estree@8.58.1(typescript@5.9.3)': + dependencies: + '@typescript-eslint/project-service': 8.58.1(typescript@5.9.3) + '@typescript-eslint/tsconfig-utils': 8.58.1(typescript@5.9.3) + '@typescript-eslint/types': 8.58.1 + '@typescript-eslint/visitor-keys': 8.58.1 + debug: 4.4.3 + minimatch: 10.2.5 + semver: 7.7.4 + tinyglobby: 0.2.16 + ts-api-utils: 2.5.0(typescript@5.9.3) + typescript: 5.9.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/utils@8.58.1(eslint@9.39.4)(typescript@5.9.3)': + dependencies: + '@eslint-community/eslint-utils': 4.9.1(eslint@9.39.4) + '@typescript-eslint/scope-manager': 8.58.1 + '@typescript-eslint/types': 8.58.1 + '@typescript-eslint/typescript-estree': 8.58.1(typescript@5.9.3) + eslint: 9.39.4 + typescript: 5.9.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/visitor-keys@8.58.1': + dependencies: + '@typescript-eslint/types': 8.58.1 + eslint-visitor-keys: 5.0.1 + + '@vitest/expect@3.2.4': + dependencies: + '@types/chai': 5.2.3 + '@vitest/spy': 3.2.4 + '@vitest/utils': 3.2.4 + chai: 5.3.3 + tinyrainbow: 2.0.0 + + '@vitest/mocker@3.2.4(vite@7.3.2)': + dependencies: + '@vitest/spy': 3.2.4 + estree-walker: 3.0.3 + magic-string: 0.30.21 + optionalDependencies: + vite: 7.3.2 + + '@vitest/pretty-format@3.2.4': + dependencies: + tinyrainbow: 2.0.0 + + '@vitest/runner@3.2.4': + dependencies: + '@vitest/utils': 3.2.4 + pathe: 2.0.3 + strip-literal: 3.1.0 + + '@vitest/snapshot@3.2.4': + dependencies: + '@vitest/pretty-format': 3.2.4 + magic-string: 0.30.21 + pathe: 2.0.3 + + '@vitest/spy@3.2.4': + dependencies: + tinyspy: 4.0.4 + + '@vitest/utils@3.2.4': + dependencies: + '@vitest/pretty-format': 3.2.4 + loupe: 3.2.1 + tinyrainbow: 2.0.0 + + acorn-jsx@5.3.2(acorn@8.16.0): + dependencies: + acorn: 8.16.0 + + acorn@8.16.0: {} + + agent-base@7.1.4: {} + + ajv@6.14.0: + dependencies: + fast-deep-equal: 3.1.3 + fast-json-stable-stringify: 2.1.0 + json-schema-traverse: 0.4.1 + uri-js: 4.4.1 + + ansi-regex@5.0.1: {} + + ansi-styles@4.3.0: + dependencies: + color-convert: 2.0.1 + + ansi-styles@5.2.0: {} + + any-promise@1.3.0: {} + + 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: + dependencies: + call-bound: 1.0.4 + is-array-buffer: 3.0.5 + + array-includes@3.1.9: + dependencies: + call-bind: 1.0.9 + call-bound: 1.0.4 + define-properties: 1.2.1 + es-abstract: 1.24.2 + es-object-atoms: 1.1.1 + get-intrinsic: 1.3.0 + is-string: 1.1.1 + math-intrinsics: 1.1.0 + + array.prototype.findlast@1.2.5: + dependencies: + call-bind: 1.0.9 + define-properties: 1.2.1 + es-abstract: 1.24.2 + es-errors: 1.3.0 + es-object-atoms: 1.1.1 + es-shim-unscopables: 1.1.0 + + array.prototype.flat@1.3.3: + dependencies: + call-bind: 1.0.9 + define-properties: 1.2.1 + es-abstract: 1.24.2 + es-shim-unscopables: 1.1.0 + + array.prototype.flatmap@1.3.3: + dependencies: + call-bind: 1.0.9 + define-properties: 1.2.1 + es-abstract: 1.24.2 + es-shim-unscopables: 1.1.0 + + array.prototype.tosorted@1.1.4: + dependencies: + call-bind: 1.0.9 + define-properties: 1.2.1 + es-abstract: 1.24.2 + es-errors: 1.3.0 + es-shim-unscopables: 1.1.0 + + arraybuffer.prototype.slice@1.0.4: + dependencies: + array-buffer-byte-length: 1.0.2 + call-bind: 1.0.9 + define-properties: 1.2.1 + es-abstract: 1.24.2 + es-errors: 1.3.0 + get-intrinsic: 1.3.0 + is-array-buffer: 3.0.5 + + assertion-error@2.0.1: {} + + async-function@1.0.0: {} + + available-typed-arrays@1.0.7: + dependencies: + possible-typed-array-names: 1.1.0 + + balanced-match@1.0.2: {} + + balanced-match@4.0.4: {} + + brace-expansion@1.1.14: + dependencies: + balanced-match: 1.0.2 + concat-map: 0.0.1 + + brace-expansion@5.0.5: + dependencies: + balanced-match: 4.0.4 + + bundle-require@5.1.0(esbuild@0.27.7): + dependencies: + esbuild: 0.27.7 + load-tsconfig: 0.2.5 + + cac@6.7.14: {} + + call-bind-apply-helpers@1.0.2: + dependencies: + es-errors: 1.3.0 + function-bind: 1.1.2 + + call-bind@1.0.9: + dependencies: + call-bind-apply-helpers: 1.0.2 + es-define-property: 1.0.1 + get-intrinsic: 1.3.0 + set-function-length: 1.2.2 + + call-bound@1.0.4: + dependencies: + call-bind-apply-helpers: 1.0.2 + get-intrinsic: 1.3.0 + + callsites@3.1.0: {} + + chai@5.3.3: + dependencies: + assertion-error: 2.0.1 + check-error: 2.1.3 + deep-eql: 5.0.2 + loupe: 3.2.1 + pathval: 2.0.1 + + chalk@4.1.2: + dependencies: + ansi-styles: 4.3.0 + supports-color: 7.2.0 + + check-error@2.1.3: {} + + chokidar@4.0.3: + dependencies: + readdirp: 4.1.2 + + color-convert@2.0.1: + dependencies: + color-name: 1.1.4 + + color-name@1.1.4: {} + + commander@4.1.1: {} + + concat-map@0.0.1: {} + + confbox@0.1.8: {} + + consola@3.4.2: {} + + cross-spawn@7.0.6: + dependencies: + path-key: 3.1.1 + shebang-command: 2.0.0 + which: 2.0.2 + + css.escape@1.5.1: {} + + cssstyle@4.6.0: + dependencies: + '@asamuzakjp/css-color': 3.2.0 + rrweb-cssom: 0.8.0 + + csstype@3.2.3: {} + + data-urls@5.0.0: + dependencies: + whatwg-mimetype: 4.0.0 + whatwg-url: 14.2.0 + + data-view-buffer@1.0.2: + dependencies: + call-bound: 1.0.4 + es-errors: 1.3.0 + is-data-view: 1.0.2 + + data-view-byte-length@1.0.2: + dependencies: + call-bound: 1.0.4 + es-errors: 1.3.0 + is-data-view: 1.0.2 + + data-view-byte-offset@1.0.1: + dependencies: + call-bound: 1.0.4 + es-errors: 1.3.0 + is-data-view: 1.0.2 + + debug@4.4.3: + dependencies: + ms: 2.1.3 + + decimal.js@10.6.0: {} + + deep-eql@5.0.2: {} + + deep-is@0.1.4: {} + + define-data-property@1.1.4: + dependencies: + es-define-property: 1.0.1 + es-errors: 1.3.0 + gopd: 1.2.0 + + define-properties@1.2.1: + dependencies: + define-data-property: 1.1.4 + has-property-descriptors: 1.0.2 + object-keys: 1.1.1 + + dequal@2.0.3: {} + + doctrine@2.1.0: + dependencies: + esutils: 2.0.3 + + dom-accessibility-api@0.5.16: {} + + dom-accessibility-api@0.6.3: {} + + dunder-proto@1.0.1: + dependencies: + call-bind-apply-helpers: 1.0.2 + es-errors: 1.3.0 + gopd: 1.2.0 + + entities@6.0.1: {} + + es-abstract@1.24.2: + dependencies: + array-buffer-byte-length: 1.0.2 + arraybuffer.prototype.slice: 1.0.4 + available-typed-arrays: 1.0.7 + call-bind: 1.0.9 + call-bound: 1.0.4 + data-view-buffer: 1.0.2 + data-view-byte-length: 1.0.2 + data-view-byte-offset: 1.0.1 + es-define-property: 1.0.1 + es-errors: 1.3.0 + es-object-atoms: 1.1.1 + es-set-tostringtag: 2.1.0 + es-to-primitive: 1.3.0 + function.prototype.name: 1.1.8 + get-intrinsic: 1.3.0 + get-proto: 1.0.1 + get-symbol-description: 1.1.0 + globalthis: 1.0.4 + gopd: 1.2.0 + has-property-descriptors: 1.0.2 + has-proto: 1.2.0 + has-symbols: 1.1.0 + hasown: 2.0.2 + internal-slot: 1.1.0 + is-array-buffer: 3.0.5 + is-callable: 1.2.7 + is-data-view: 1.0.2 + is-negative-zero: 2.0.3 + is-regex: 1.2.1 + is-set: 2.0.3 + is-shared-array-buffer: 1.0.4 + is-string: 1.1.1 + is-typed-array: 1.1.15 + is-weakref: 1.1.1 + math-intrinsics: 1.1.0 + object-inspect: 1.13.4 + object-keys: 1.1.1 + object.assign: 4.1.7 + own-keys: 1.0.1 + regexp.prototype.flags: 1.5.4 + safe-array-concat: 1.1.3 + safe-push-apply: 1.0.0 + safe-regex-test: 1.1.0 + set-proto: 1.0.0 + stop-iteration-iterator: 1.1.0 + string.prototype.trim: 1.2.10 + string.prototype.trimend: 1.0.9 + string.prototype.trimstart: 1.0.8 + typed-array-buffer: 1.0.3 + typed-array-byte-length: 1.0.3 + typed-array-byte-offset: 1.0.4 + typed-array-length: 1.0.7 + unbox-primitive: 1.1.0 + which-typed-array: 1.1.20 + + es-define-property@1.0.1: {} + + es-errors@1.3.0: {} + + es-iterator-helpers@1.3.2: + dependencies: + call-bind: 1.0.9 + call-bound: 1.0.4 + define-properties: 1.2.1 + es-abstract: 1.24.2 + es-errors: 1.3.0 + es-set-tostringtag: 2.1.0 + function-bind: 1.1.2 + get-intrinsic: 1.3.0 + globalthis: 1.0.4 + gopd: 1.2.0 + has-property-descriptors: 1.0.2 + has-proto: 1.2.0 + has-symbols: 1.1.0 + internal-slot: 1.1.0 + iterator.prototype: 1.1.5 + math-intrinsics: 1.1.0 + + es-module-lexer@1.7.0: {} + + es-object-atoms@1.1.1: + dependencies: + es-errors: 1.3.0 + + es-set-tostringtag@2.1.0: + dependencies: + es-errors: 1.3.0 + get-intrinsic: 1.3.0 + has-tostringtag: 1.0.2 + hasown: 2.0.2 + + es-shim-unscopables@1.1.0: + dependencies: + hasown: 2.0.2 + + es-to-primitive@1.3.0: + dependencies: + is-callable: 1.2.7 + is-date-object: 1.1.0 + is-symbol: 1.1.1 + + esbuild@0.27.7: + optionalDependencies: + '@esbuild/aix-ppc64': 0.27.7 + '@esbuild/android-arm': 0.27.7 + '@esbuild/android-arm64': 0.27.7 + '@esbuild/android-x64': 0.27.7 + '@esbuild/darwin-arm64': 0.27.7 + '@esbuild/darwin-x64': 0.27.7 + '@esbuild/freebsd-arm64': 0.27.7 + '@esbuild/freebsd-x64': 0.27.7 + '@esbuild/linux-arm': 0.27.7 + '@esbuild/linux-arm64': 0.27.7 + '@esbuild/linux-ia32': 0.27.7 + '@esbuild/linux-loong64': 0.27.7 + '@esbuild/linux-mips64el': 0.27.7 + '@esbuild/linux-ppc64': 0.27.7 + '@esbuild/linux-riscv64': 0.27.7 + '@esbuild/linux-s390x': 0.27.7 + '@esbuild/linux-x64': 0.27.7 + '@esbuild/netbsd-arm64': 0.27.7 + '@esbuild/netbsd-x64': 0.27.7 + '@esbuild/openbsd-arm64': 0.27.7 + '@esbuild/openbsd-x64': 0.27.7 + '@esbuild/openharmony-arm64': 0.27.7 + '@esbuild/sunos-x64': 0.27.7 + '@esbuild/win32-arm64': 0.27.7 + '@esbuild/win32-ia32': 0.27.7 + '@esbuild/win32-x64': 0.27.7 + + escape-string-regexp@4.0.0: {} + + eslint-plugin-react-hooks@5.2.0(eslint@9.39.4): + dependencies: + eslint: 9.39.4 + + eslint-plugin-react@7.37.5(eslint@9.39.4): + dependencies: + array-includes: 3.1.9 + array.prototype.findlast: 1.2.5 + array.prototype.flatmap: 1.3.3 + array.prototype.tosorted: 1.1.4 + doctrine: 2.1.0 + es-iterator-helpers: 1.3.2 + eslint: 9.39.4 + estraverse: 5.3.0 + hasown: 2.0.2 + jsx-ast-utils: 3.3.5 + minimatch: 3.1.5 + object.entries: 1.1.9 + object.fromentries: 2.0.8 + object.values: 1.2.1 + prop-types: 15.8.1 + resolve: 2.0.0-next.6 + semver: 6.3.1 + string.prototype.matchall: 4.0.12 + string.prototype.repeat: 1.0.0 + + eslint-scope@8.4.0: + dependencies: + esrecurse: 4.3.0 + estraverse: 5.3.0 + + eslint-visitor-keys@3.4.3: {} + + eslint-visitor-keys@4.2.1: {} + + eslint-visitor-keys@5.0.1: {} + + eslint@9.39.4: + dependencies: + '@eslint-community/eslint-utils': 4.9.1(eslint@9.39.4) + '@eslint-community/regexpp': 4.12.2 + '@eslint/config-array': 0.21.2 + '@eslint/config-helpers': 0.4.2 + '@eslint/core': 0.17.0 + '@eslint/eslintrc': 3.3.5 + '@eslint/js': 9.39.4 + '@eslint/plugin-kit': 0.4.1 + '@humanfs/node': 0.16.7 + '@humanwhocodes/module-importer': 1.0.1 + '@humanwhocodes/retry': 0.4.3 + '@types/estree': 1.0.8 + ajv: 6.14.0 + chalk: 4.1.2 + cross-spawn: 7.0.6 + debug: 4.4.3 + escape-string-regexp: 4.0.0 + eslint-scope: 8.4.0 + eslint-visitor-keys: 4.2.1 + espree: 10.4.0 + esquery: 1.7.0 + esutils: 2.0.3 + fast-deep-equal: 3.1.3 + file-entry-cache: 8.0.0 + find-up: 5.0.0 + glob-parent: 6.0.2 + ignore: 5.3.2 + imurmurhash: 0.1.4 + is-glob: 4.0.3 + json-stable-stringify-without-jsonify: 1.0.1 + lodash.merge: 4.6.2 + minimatch: 3.1.5 + natural-compare: 1.4.0 + optionator: 0.9.4 + transitivePeerDependencies: + - supports-color + + espree@10.4.0: + dependencies: + acorn: 8.16.0 + acorn-jsx: 5.3.2(acorn@8.16.0) + eslint-visitor-keys: 4.2.1 + + esquery@1.7.0: + dependencies: + estraverse: 5.3.0 + + esrecurse@4.3.0: + dependencies: + estraverse: 5.3.0 + + estraverse@5.3.0: {} + + estree-walker@3.0.3: + dependencies: + '@types/estree': 1.0.8 + + esutils@2.0.3: {} + + expect-type@1.3.0: {} + + fast-deep-equal@3.1.3: {} + + fast-json-stable-stringify@2.1.0: {} + + fast-levenshtein@2.0.6: {} + + fdir@6.5.0(picomatch@4.0.4): + optionalDependencies: + picomatch: 4.0.4 + + file-entry-cache@8.0.0: + dependencies: + flat-cache: 4.0.1 + + find-up@5.0.0: + dependencies: + locate-path: 6.0.0 + path-exists: 4.0.0 + + fix-dts-default-cjs-exports@1.0.1: + dependencies: + magic-string: 0.30.21 + mlly: 1.8.2 + rollup: 4.60.1 + + flat-cache@4.0.1: + dependencies: + flatted: 3.4.2 + keyv: 4.5.4 + + flatted@3.4.2: {} + + for-each@0.3.5: + dependencies: + is-callable: 1.2.7 + + fsevents@2.3.3: + optional: true + + function-bind@1.1.2: {} + + function.prototype.name@1.1.8: + dependencies: + call-bind: 1.0.9 + call-bound: 1.0.4 + define-properties: 1.2.1 + functions-have-names: 1.2.3 + hasown: 2.0.2 + is-callable: 1.2.7 + + functions-have-names@1.2.3: {} + + generator-function@2.0.1: {} + + get-intrinsic@1.3.0: + dependencies: + call-bind-apply-helpers: 1.0.2 + es-define-property: 1.0.1 + es-errors: 1.3.0 + es-object-atoms: 1.1.1 + function-bind: 1.1.2 + get-proto: 1.0.1 + gopd: 1.2.0 + has-symbols: 1.1.0 + hasown: 2.0.2 + math-intrinsics: 1.1.0 + + get-proto@1.0.1: + dependencies: + dunder-proto: 1.0.1 + es-object-atoms: 1.1.1 + + get-symbol-description@1.1.0: + dependencies: + call-bound: 1.0.4 + es-errors: 1.3.0 + get-intrinsic: 1.3.0 + + glob-parent@6.0.2: + dependencies: + is-glob: 4.0.3 + + globals@14.0.0: {} + + globals@17.5.0: {} + + globalthis@1.0.4: + dependencies: + define-properties: 1.2.1 + gopd: 1.2.0 + + gopd@1.2.0: {} + + has-bigints@1.1.0: {} + + has-flag@4.0.0: {} + + has-property-descriptors@1.0.2: + dependencies: + es-define-property: 1.0.1 + + has-proto@1.2.0: + dependencies: + dunder-proto: 1.0.1 + + has-symbols@1.1.0: {} + + has-tostringtag@1.0.2: + dependencies: + has-symbols: 1.1.0 + + hasown@2.0.2: + dependencies: + function-bind: 1.1.2 + + html-encoding-sniffer@4.0.0: + dependencies: + whatwg-encoding: 3.1.1 + + http-proxy-agent@7.0.2: + dependencies: + agent-base: 7.1.4 + debug: 4.4.3 + transitivePeerDependencies: + - supports-color + + https-proxy-agent@7.0.6: + dependencies: + agent-base: 7.1.4 + debug: 4.4.3 + transitivePeerDependencies: + - supports-color + + iconv-lite@0.6.3: + dependencies: + safer-buffer: 2.1.2 + + ignore@5.3.2: {} + + ignore@7.0.5: {} + + import-fresh@3.3.1: + dependencies: + parent-module: 1.0.1 + resolve-from: 4.0.0 + + imurmurhash@0.1.4: {} + + indent-string@4.0.0: {} + + internal-slot@1.1.0: + dependencies: + es-errors: 1.3.0 + hasown: 2.0.2 + side-channel: 1.1.0 + + is-array-buffer@3.0.5: + dependencies: + call-bind: 1.0.9 + call-bound: 1.0.4 + get-intrinsic: 1.3.0 + + is-async-function@2.1.1: + dependencies: + async-function: 1.0.0 + call-bound: 1.0.4 + get-proto: 1.0.1 + has-tostringtag: 1.0.2 + safe-regex-test: 1.1.0 + + is-bigint@1.1.0: + dependencies: + has-bigints: 1.1.0 + + is-boolean-object@1.2.2: + dependencies: + call-bound: 1.0.4 + has-tostringtag: 1.0.2 + + is-callable@1.2.7: {} + + is-core-module@2.16.1: + dependencies: + hasown: 2.0.2 + + is-data-view@1.0.2: + dependencies: + call-bound: 1.0.4 + get-intrinsic: 1.3.0 + is-typed-array: 1.1.15 + + is-date-object@1.1.0: + dependencies: + call-bound: 1.0.4 + has-tostringtag: 1.0.2 + + is-extglob@2.1.1: {} + + is-finalizationregistry@1.1.1: + dependencies: + call-bound: 1.0.4 + + is-generator-function@1.1.2: + dependencies: + call-bound: 1.0.4 + generator-function: 2.0.1 + get-proto: 1.0.1 + has-tostringtag: 1.0.2 + safe-regex-test: 1.1.0 + + is-glob@4.0.3: + dependencies: + is-extglob: 2.1.1 + + is-map@2.0.3: {} + + is-negative-zero@2.0.3: {} + + is-number-object@1.1.1: + dependencies: + call-bound: 1.0.4 + has-tostringtag: 1.0.2 + + is-potential-custom-element-name@1.0.1: {} + + is-regex@1.2.1: + dependencies: + call-bound: 1.0.4 + gopd: 1.2.0 + has-tostringtag: 1.0.2 + hasown: 2.0.2 + + is-set@2.0.3: {} + + is-shared-array-buffer@1.0.4: + dependencies: + call-bound: 1.0.4 + + is-string@1.1.1: + dependencies: + call-bound: 1.0.4 + has-tostringtag: 1.0.2 + + is-symbol@1.1.1: + dependencies: + call-bound: 1.0.4 + has-symbols: 1.1.0 + safe-regex-test: 1.1.0 + + is-typed-array@1.1.15: + dependencies: + which-typed-array: 1.1.20 + + is-weakmap@2.0.2: {} + + is-weakref@1.1.1: + dependencies: + call-bound: 1.0.4 + + is-weakset@2.0.4: + dependencies: + call-bound: 1.0.4 + get-intrinsic: 1.3.0 + + isarray@2.0.5: {} + + isexe@2.0.0: {} + + iterator.prototype@1.1.5: + dependencies: + define-data-property: 1.1.4 + es-object-atoms: 1.1.1 + get-intrinsic: 1.3.0 + get-proto: 1.0.1 + has-symbols: 1.1.0 + set-function-name: 2.0.2 + + joycon@3.1.1: {} + + js-tokens@4.0.0: {} + + js-tokens@9.0.1: {} + + js-yaml@4.1.1: + dependencies: + argparse: 2.0.1 + + jsdom@26.1.0: + dependencies: + cssstyle: 4.6.0 + data-urls: 5.0.0 + decimal.js: 10.6.0 + html-encoding-sniffer: 4.0.0 + http-proxy-agent: 7.0.2 + https-proxy-agent: 7.0.6 + is-potential-custom-element-name: 1.0.1 + nwsapi: 2.2.23 + parse5: 7.3.0 + rrweb-cssom: 0.8.0 + saxes: 6.0.0 + symbol-tree: 3.2.4 + tough-cookie: 5.1.2 + w3c-xmlserializer: 5.0.0 + webidl-conversions: 7.0.0 + whatwg-encoding: 3.1.1 + whatwg-mimetype: 4.0.0 + whatwg-url: 14.2.0 + ws: 8.20.0 + xml-name-validator: 5.0.0 + transitivePeerDependencies: + - bufferutil + - supports-color + - utf-8-validate + + json-buffer@3.0.1: {} + + json-schema-traverse@0.4.1: {} + + json-stable-stringify-without-jsonify@1.0.1: {} + + jsx-ast-utils@3.3.5: + dependencies: + array-includes: 3.1.9 + array.prototype.flat: 1.3.3 + object.assign: 4.1.7 + object.values: 1.2.1 + + keyv@4.5.4: + dependencies: + json-buffer: 3.0.1 + + levn@0.4.1: + dependencies: + prelude-ls: 1.2.1 + type-check: 0.4.0 + + lilconfig@3.1.3: {} + + lines-and-columns@1.2.4: {} + + load-tsconfig@0.2.5: {} + + locate-path@6.0.0: + dependencies: + p-locate: 5.0.0 + + lodash.merge@4.6.2: {} + + loose-envify@1.4.0: + dependencies: + js-tokens: 4.0.0 + + loupe@3.2.1: {} + + lru-cache@10.4.3: {} + + lz-string@1.5.0: {} + + magic-string@0.30.21: + dependencies: + '@jridgewell/sourcemap-codec': 1.5.5 + + math-intrinsics@1.1.0: {} + + min-indent@1.0.1: {} + + minimatch@10.2.5: + dependencies: + brace-expansion: 5.0.5 + + minimatch@3.1.5: + dependencies: + brace-expansion: 1.1.14 + + mlly@1.8.2: + dependencies: + acorn: 8.16.0 + pathe: 2.0.3 + pkg-types: 1.3.1 + ufo: 1.6.3 + + ms@2.1.3: {} + + mz@2.7.0: + dependencies: + any-promise: 1.3.0 + object-assign: 4.1.1 + thenify-all: 1.6.0 + + nanoid@3.3.11: {} + + natural-compare@1.4.0: {} + + node-exports-info@1.6.0: + dependencies: + array.prototype.flatmap: 1.3.3 + es-errors: 1.3.0 + object.entries: 1.1.9 + semver: 6.3.1 + + nwsapi@2.2.23: {} + + object-assign@4.1.1: {} + + object-inspect@1.13.4: {} + + object-keys@1.1.1: {} + + object.assign@4.1.7: + dependencies: + call-bind: 1.0.9 + call-bound: 1.0.4 + define-properties: 1.2.1 + es-object-atoms: 1.1.1 + has-symbols: 1.1.0 + object-keys: 1.1.1 + + object.entries@1.1.9: + dependencies: + call-bind: 1.0.9 + call-bound: 1.0.4 + define-properties: 1.2.1 + es-object-atoms: 1.1.1 + + object.fromentries@2.0.8: + dependencies: + call-bind: 1.0.9 + define-properties: 1.2.1 + es-abstract: 1.24.2 + es-object-atoms: 1.1.1 + + object.values@1.2.1: + dependencies: + call-bind: 1.0.9 + call-bound: 1.0.4 + define-properties: 1.2.1 + es-object-atoms: 1.1.1 + + optionator@0.9.4: + dependencies: + deep-is: 0.1.4 + fast-levenshtein: 2.0.6 + levn: 0.4.1 + prelude-ls: 1.2.1 + type-check: 0.4.0 + word-wrap: 1.2.5 + + own-keys@1.0.1: + dependencies: + get-intrinsic: 1.3.0 + object-keys: 1.1.1 + safe-push-apply: 1.0.0 + + p-limit@3.1.0: + dependencies: + yocto-queue: 0.1.0 + + p-locate@5.0.0: + dependencies: + p-limit: 3.1.0 + + parent-module@1.0.1: + dependencies: + callsites: 3.1.0 + + parse5@7.3.0: + dependencies: + entities: 6.0.1 + + path-exists@4.0.0: {} + + path-key@3.1.1: {} + + path-parse@1.0.7: {} + + pathe@2.0.3: {} + + pathval@2.0.1: {} + + picocolors@1.1.1: {} + + picomatch@4.0.4: {} + + pirates@4.0.7: {} + + pkg-types@1.3.1: + dependencies: + confbox: 0.1.8 + mlly: 1.8.2 + pathe: 2.0.3 + + possible-typed-array-names@1.1.0: {} + + postcss-load-config@6.0.1(postcss@8.5.9): + dependencies: + lilconfig: 3.1.3 + optionalDependencies: + postcss: 8.5.9 + + postcss@8.5.9: + dependencies: + nanoid: 3.3.11 + picocolors: 1.1.1 + source-map-js: 1.2.1 + + prelude-ls@1.2.1: {} + + prettier@3.8.2: {} + + pretty-format@27.5.1: + dependencies: + ansi-regex: 5.0.1 + ansi-styles: 5.2.0 + react-is: 17.0.2 + + prop-types@15.8.1: + dependencies: + loose-envify: 1.4.0 + object-assign: 4.1.1 + react-is: 16.13.1 + + punycode@2.3.1: {} + + react-dom@19.2.5(react@19.2.5): + dependencies: + react: 19.2.5 + scheduler: 0.27.0 + + react-is@16.13.1: {} + + react-is@17.0.2: {} + + react@19.2.5: {} + + readdirp@4.1.2: {} + + redent@3.0.0: + dependencies: + indent-string: 4.0.0 + strip-indent: 3.0.0 + + reflect.getprototypeof@1.0.10: + dependencies: + call-bind: 1.0.9 + define-properties: 1.2.1 + es-abstract: 1.24.2 + es-errors: 1.3.0 + es-object-atoms: 1.1.1 + get-intrinsic: 1.3.0 + get-proto: 1.0.1 + which-builtin-type: 1.2.1 + + regexp.prototype.flags@1.5.4: + dependencies: + call-bind: 1.0.9 + define-properties: 1.2.1 + es-errors: 1.3.0 + get-proto: 1.0.1 + gopd: 1.2.0 + set-function-name: 2.0.2 + + resolve-from@4.0.0: {} + + resolve-from@5.0.0: {} + + resolve@2.0.0-next.6: + dependencies: + es-errors: 1.3.0 + is-core-module: 2.16.1 + node-exports-info: 1.6.0 + object-keys: 1.1.1 + path-parse: 1.0.7 + supports-preserve-symlinks-flag: 1.0.0 + + rollup@4.60.1: + dependencies: + '@types/estree': 1.0.8 + optionalDependencies: + '@rollup/rollup-android-arm-eabi': 4.60.1 + '@rollup/rollup-android-arm64': 4.60.1 + '@rollup/rollup-darwin-arm64': 4.60.1 + '@rollup/rollup-darwin-x64': 4.60.1 + '@rollup/rollup-freebsd-arm64': 4.60.1 + '@rollup/rollup-freebsd-x64': 4.60.1 + '@rollup/rollup-linux-arm-gnueabihf': 4.60.1 + '@rollup/rollup-linux-arm-musleabihf': 4.60.1 + '@rollup/rollup-linux-arm64-gnu': 4.60.1 + '@rollup/rollup-linux-arm64-musl': 4.60.1 + '@rollup/rollup-linux-loong64-gnu': 4.60.1 + '@rollup/rollup-linux-loong64-musl': 4.60.1 + '@rollup/rollup-linux-ppc64-gnu': 4.60.1 + '@rollup/rollup-linux-ppc64-musl': 4.60.1 + '@rollup/rollup-linux-riscv64-gnu': 4.60.1 + '@rollup/rollup-linux-riscv64-musl': 4.60.1 + '@rollup/rollup-linux-s390x-gnu': 4.60.1 + '@rollup/rollup-linux-x64-gnu': 4.60.1 + '@rollup/rollup-linux-x64-musl': 4.60.1 + '@rollup/rollup-openbsd-x64': 4.60.1 + '@rollup/rollup-openharmony-arm64': 4.60.1 + '@rollup/rollup-win32-arm64-msvc': 4.60.1 + '@rollup/rollup-win32-ia32-msvc': 4.60.1 + '@rollup/rollup-win32-x64-gnu': 4.60.1 + '@rollup/rollup-win32-x64-msvc': 4.60.1 + fsevents: 2.3.3 + + rrweb-cssom@0.8.0: {} + + safe-array-concat@1.1.3: + dependencies: + call-bind: 1.0.9 + call-bound: 1.0.4 + get-intrinsic: 1.3.0 + has-symbols: 1.1.0 + isarray: 2.0.5 + + safe-push-apply@1.0.0: + dependencies: + es-errors: 1.3.0 + isarray: 2.0.5 + + safe-regex-test@1.1.0: + dependencies: + call-bound: 1.0.4 + es-errors: 1.3.0 + is-regex: 1.2.1 + + safer-buffer@2.1.2: {} + + saxes@6.0.0: + dependencies: + xmlchars: 2.2.0 + + scheduler@0.27.0: {} + + semver@6.3.1: {} + + semver@7.7.4: {} + + set-function-length@1.2.2: + dependencies: + define-data-property: 1.1.4 + es-errors: 1.3.0 + function-bind: 1.1.2 + get-intrinsic: 1.3.0 + gopd: 1.2.0 + has-property-descriptors: 1.0.2 + + set-function-name@2.0.2: + dependencies: + define-data-property: 1.1.4 + es-errors: 1.3.0 + functions-have-names: 1.2.3 + has-property-descriptors: 1.0.2 + + set-proto@1.0.0: + dependencies: + dunder-proto: 1.0.1 + es-errors: 1.3.0 + es-object-atoms: 1.1.1 + + shebang-command@2.0.0: + dependencies: + shebang-regex: 3.0.0 + + shebang-regex@3.0.0: {} + + side-channel-list@1.0.1: + dependencies: + es-errors: 1.3.0 + object-inspect: 1.13.4 + + side-channel-map@1.0.1: + dependencies: + call-bound: 1.0.4 + es-errors: 1.3.0 + get-intrinsic: 1.3.0 + object-inspect: 1.13.4 + + side-channel-weakmap@1.0.2: + dependencies: + call-bound: 1.0.4 + es-errors: 1.3.0 + get-intrinsic: 1.3.0 + object-inspect: 1.13.4 + side-channel-map: 1.0.1 + + side-channel@1.1.0: + dependencies: + es-errors: 1.3.0 + object-inspect: 1.13.4 + side-channel-list: 1.0.1 + side-channel-map: 1.0.1 + side-channel-weakmap: 1.0.2 + + siginfo@2.0.0: {} + + source-map-js@1.2.1: {} + + source-map@0.7.6: {} + + stackback@0.0.2: {} + + std-env@3.10.0: {} + + stop-iteration-iterator@1.1.0: + dependencies: + es-errors: 1.3.0 + internal-slot: 1.1.0 + + string.prototype.matchall@4.0.12: + dependencies: + call-bind: 1.0.9 + call-bound: 1.0.4 + define-properties: 1.2.1 + es-abstract: 1.24.2 + es-errors: 1.3.0 + es-object-atoms: 1.1.1 + get-intrinsic: 1.3.0 + gopd: 1.2.0 + has-symbols: 1.1.0 + internal-slot: 1.1.0 + regexp.prototype.flags: 1.5.4 + set-function-name: 2.0.2 + side-channel: 1.1.0 + + string.prototype.repeat@1.0.0: + dependencies: + define-properties: 1.2.1 + es-abstract: 1.24.2 + + string.prototype.trim@1.2.10: + dependencies: + call-bind: 1.0.9 + call-bound: 1.0.4 + define-data-property: 1.1.4 + define-properties: 1.2.1 + es-abstract: 1.24.2 + es-object-atoms: 1.1.1 + has-property-descriptors: 1.0.2 + + string.prototype.trimend@1.0.9: + dependencies: + call-bind: 1.0.9 + call-bound: 1.0.4 + define-properties: 1.2.1 + es-object-atoms: 1.1.1 + + string.prototype.trimstart@1.0.8: + dependencies: + call-bind: 1.0.9 + define-properties: 1.2.1 + es-object-atoms: 1.1.1 + + strip-indent@3.0.0: + dependencies: + min-indent: 1.0.1 + + strip-json-comments@3.1.1: {} + + strip-literal@3.1.0: + dependencies: + js-tokens: 9.0.1 + + sucrase@3.35.1: + dependencies: + '@jridgewell/gen-mapping': 0.3.13 + commander: 4.1.1 + lines-and-columns: 1.2.4 + mz: 2.7.0 + pirates: 4.0.7 + tinyglobby: 0.2.16 + ts-interface-checker: 0.1.13 + + supports-color@7.2.0: + dependencies: + has-flag: 4.0.0 + + supports-preserve-symlinks-flag@1.0.0: {} + + symbol-tree@3.2.4: {} + + thenify-all@1.6.0: + dependencies: + thenify: 3.3.1 + + thenify@3.3.1: + dependencies: + any-promise: 1.3.0 + + tinybench@2.9.0: {} + + tinyexec@0.3.2: {} + + tinyglobby@0.2.16: + dependencies: + fdir: 6.5.0(picomatch@4.0.4) + picomatch: 4.0.4 + + tinypool@1.1.1: {} + + tinyrainbow@2.0.0: {} + + tinyspy@4.0.4: {} + + tldts-core@6.1.86: {} + + tldts@6.1.86: + dependencies: + tldts-core: 6.1.86 + + tough-cookie@5.1.2: + dependencies: + tldts: 6.1.86 + + tr46@5.1.1: + dependencies: + punycode: 2.3.1 + + tree-kill@1.2.2: {} + + ts-api-utils@2.5.0(typescript@5.9.3): + dependencies: + typescript: 5.9.3 + + ts-interface-checker@0.1.13: {} + + tsup@8.5.1(postcss@8.5.9)(typescript@5.9.3): + dependencies: + bundle-require: 5.1.0(esbuild@0.27.7) + cac: 6.7.14 + chokidar: 4.0.3 + consola: 3.4.2 + debug: 4.4.3 + esbuild: 0.27.7 + fix-dts-default-cjs-exports: 1.0.1 + joycon: 3.1.1 + picocolors: 1.1.1 + postcss-load-config: 6.0.1(postcss@8.5.9) + resolve-from: 5.0.0 + rollup: 4.60.1 + source-map: 0.7.6 + sucrase: 3.35.1 + tinyexec: 0.3.2 + tinyglobby: 0.2.16 + tree-kill: 1.2.2 + optionalDependencies: + postcss: 8.5.9 + typescript: 5.9.3 + transitivePeerDependencies: + - jiti + - supports-color + - tsx + - yaml + + type-check@0.4.0: + dependencies: + prelude-ls: 1.2.1 + + typed-array-buffer@1.0.3: + dependencies: + call-bound: 1.0.4 + es-errors: 1.3.0 + is-typed-array: 1.1.15 + + typed-array-byte-length@1.0.3: + dependencies: + call-bind: 1.0.9 + for-each: 0.3.5 + gopd: 1.2.0 + has-proto: 1.2.0 + is-typed-array: 1.1.15 + + typed-array-byte-offset@1.0.4: + dependencies: + available-typed-arrays: 1.0.7 + call-bind: 1.0.9 + for-each: 0.3.5 + gopd: 1.2.0 + has-proto: 1.2.0 + is-typed-array: 1.1.15 + reflect.getprototypeof: 1.0.10 + + typed-array-length@1.0.7: + dependencies: + call-bind: 1.0.9 + for-each: 0.3.5 + gopd: 1.2.0 + is-typed-array: 1.1.15 + possible-typed-array-names: 1.1.0 + reflect.getprototypeof: 1.0.10 + + typescript@5.9.3: {} + + ufo@1.6.3: {} + + unbox-primitive@1.1.0: + dependencies: + call-bound: 1.0.4 + has-bigints: 1.1.0 + has-symbols: 1.1.0 + which-boxed-primitive: 1.1.1 + + uri-js@4.4.1: + dependencies: + punycode: 2.3.1 + + vite-node@3.2.4: + dependencies: + cac: 6.7.14 + debug: 4.4.3 + es-module-lexer: 1.7.0 + pathe: 2.0.3 + vite: 7.3.2 + transitivePeerDependencies: + - '@types/node' + - jiti + - less + - lightningcss + - sass + - sass-embedded + - stylus + - sugarss + - supports-color + - terser + - tsx + - yaml + + vite@7.3.2: + dependencies: + esbuild: 0.27.7 + fdir: 6.5.0(picomatch@4.0.4) + picomatch: 4.0.4 + postcss: 8.5.9 + rollup: 4.60.1 + tinyglobby: 0.2.16 + optionalDependencies: + fsevents: 2.3.3 + + vitest@3.2.4(jsdom@26.1.0): + dependencies: + '@types/chai': 5.2.3 + '@vitest/expect': 3.2.4 + '@vitest/mocker': 3.2.4(vite@7.3.2) + '@vitest/pretty-format': 3.2.4 + '@vitest/runner': 3.2.4 + '@vitest/snapshot': 3.2.4 + '@vitest/spy': 3.2.4 + '@vitest/utils': 3.2.4 + chai: 5.3.3 + debug: 4.4.3 + expect-type: 1.3.0 + magic-string: 0.30.21 + pathe: 2.0.3 + picomatch: 4.0.4 + std-env: 3.10.0 + tinybench: 2.9.0 + tinyexec: 0.3.2 + tinyglobby: 0.2.16 + tinypool: 1.1.1 + tinyrainbow: 2.0.0 + vite: 7.3.2 + vite-node: 3.2.4 + why-is-node-running: 2.3.0 + optionalDependencies: + jsdom: 26.1.0 + transitivePeerDependencies: + - jiti + - less + - lightningcss + - msw + - sass + - sass-embedded + - stylus + - sugarss + - supports-color + - terser + - tsx + - yaml + + w3c-xmlserializer@5.0.0: + dependencies: + xml-name-validator: 5.0.0 + + webidl-conversions@7.0.0: {} + + whatwg-encoding@3.1.1: + dependencies: + iconv-lite: 0.6.3 + + whatwg-mimetype@4.0.0: {} + + whatwg-url@14.2.0: + dependencies: + tr46: 5.1.1 + webidl-conversions: 7.0.0 + + which-boxed-primitive@1.1.1: + dependencies: + is-bigint: 1.1.0 + is-boolean-object: 1.2.2 + is-number-object: 1.1.1 + is-string: 1.1.1 + is-symbol: 1.1.1 + + which-builtin-type@1.2.1: + dependencies: + call-bound: 1.0.4 + function.prototype.name: 1.1.8 + has-tostringtag: 1.0.2 + is-async-function: 2.1.1 + is-date-object: 1.1.0 + is-finalizationregistry: 1.1.1 + is-generator-function: 1.1.2 + is-regex: 1.2.1 + is-weakref: 1.1.1 + isarray: 2.0.5 + which-boxed-primitive: 1.1.1 + which-collection: 1.0.2 + which-typed-array: 1.1.20 + + which-collection@1.0.2: + dependencies: + is-map: 2.0.3 + is-set: 2.0.3 + is-weakmap: 2.0.2 + is-weakset: 2.0.4 + + which-typed-array@1.1.20: + dependencies: + available-typed-arrays: 1.0.7 + call-bind: 1.0.9 + call-bound: 1.0.4 + for-each: 0.3.5 + get-proto: 1.0.1 + gopd: 1.2.0 + has-tostringtag: 1.0.2 + + which@2.0.2: + 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: {} + + ws@8.20.0: {} + + xml-name-validator@5.0.0: {} + + xmlchars@2.2.0: {} + + yocto-queue@0.1.0: {} diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..a3da958 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,21 @@ +{ + "compilerOptions": { + "target": "ES2020", + "module": "ESNext", + "moduleResolution": "bundler", + "lib": ["ES2020", "DOM", "DOM.Iterable"], + "jsx": "react-jsx", + "strict": true, + "declaration": true, + "declarationMap": true, + "sourceMap": true, + "outDir": "dist", + "rootDir": "src", + "esModuleInterop": true, + "skipLibCheck": true, + "isolatedModules": true, + "useDefineForClassFields": true + }, + "include": ["src"], + "exclude": ["node_modules", "dist", "build", "tests"] +} diff --git a/tsup.config.ts b/tsup.config.ts new file mode 100644 index 0000000..c3a8e75 --- /dev/null +++ b/tsup.config.ts @@ -0,0 +1,30 @@ +// This file is part of opencv-js-react project from Robomous. +// It is subject to the license terms in the LICENSE file found in the top-level directory + +import { defineConfig } from 'tsup'; +import { copyFile, mkdir } from 'node:fs/promises'; +import { existsSync } from 'node:fs'; + +export default defineConfig({ + entry: ['src/index.ts'], + format: ['esm', 'cjs'], + dts: true, + sourcemap: true, + minify: true, + clean: true, + external: ['react', 'react-dom', 'react/jsx-runtime'], + treeshake: true, + async onSuccess() { + const src = 'src/assets/opencv'; + const dest = 'dist/assets/opencv'; + if (existsSync(src)) { + await mkdir(dest, { recursive: true }); + const { readdirSync } = await import('node:fs'); + for (const file of readdirSync(src)) { + if (file.endsWith('.js') || file.endsWith('.wasm') || file.endsWith('.md')) { + await copyFile(`${src}/${file}`, `${dest}/${file}`); + } + } + } + }, +}); diff --git a/vitest.config.ts b/vitest.config.ts new file mode 100644 index 0000000..583da03 --- /dev/null +++ b/vitest.config.ts @@ -0,0 +1,13 @@ +// This file is part of opencv-js-react project from Robomous. +// It is subject to the license terms in the LICENSE file found in the top-level directory + +import { defineConfig } from 'vitest/config'; + +export default defineConfig({ + test: { + environment: 'jsdom', + setupFiles: ['./tests/setup.ts'], + include: ['tests/**/*.test.{ts,tsx}'], + globals: true, + }, +}); From 5f0a376c5fa3c1449467ac4bfadbb9542443b2df Mon Sep 17 00:00:00 2001 From: Jesus Armando Anaya Date: Sun, 12 Apr 2026 19:39:03 -0700 Subject: [PATCH 2/7] Add core library source --- src/assets/opencv/README.md | 47 ++++++++++ src/components/OpenCVCanvas.tsx | 152 ++++++++++++++++++++++++++++++++ src/constants.ts | 20 +++++ src/global.d.ts | 20 +++++ src/hooks/useOpenCV.ts | 60 +++++++++++++ src/index.ts | 18 ++++ src/loader/loadOpenCV.ts | 129 +++++++++++++++++++++++++++ src/types.ts | 126 ++++++++++++++++++++++++++ 8 files changed, 572 insertions(+) create mode 100644 src/assets/opencv/README.md create mode 100644 src/components/OpenCVCanvas.tsx create mode 100644 src/constants.ts create mode 100644 src/global.d.ts create mode 100644 src/hooks/useOpenCV.ts create mode 100644 src/index.ts create mode 100644 src/loader/loadOpenCV.ts create mode 100644 src/types.ts diff --git a/src/assets/opencv/README.md b/src/assets/opencv/README.md new file mode 100644 index 0000000..4904988 --- /dev/null +++ b/src/assets/opencv/README.md @@ -0,0 +1,47 @@ +# OpenCV.js Local Asset (optional) + +The `opencv-js-react` package does **not** bundle an OpenCV.js binary. By default it loads +OpenCV.js from the official CDN at runtime: + +``` +https://docs.opencv.org/4.13.0/opencv.js +``` + +You can override this with any URL via the `src` option: + +```tsx +useOpenCV({ src: '/assets/opencv-4.13.0-contrib.min.js' }) + +``` + +## When to build a local asset + +Use the build workflow when you need: + +- **Contrib modules** (SIFT, SURF, etc.) — not available on the official CDN +- **Self-hosting** — strict CSP / offline environments +- **A specific older version** not available on the CDN + +## How to build + +**Requires:** Docker + +```bash +# Standard build +pnpm run build:opencv:release -- --version 4.13.0 + +# Contrib build +pnpm run build:opencv:release -- --version 4.13.0 --with-contrib + +# Then copy the artifact here for local testing +pnpm run vendor:opencv +``` + +Output is written to `build/opencv-js/releases/`. Point the `src` option at the resulting +file to use it in your app. + +## Note + +Files matching `*.js` and `*.wasm` in this directory are gitignored. If a file is +present when `pnpm run build` runs, tsup will copy it into `dist/assets/opencv/` — +useful for local testing but not part of the published package. diff --git a/src/components/OpenCVCanvas.tsx b/src/components/OpenCVCanvas.tsx new file mode 100644 index 0000000..5554f03 --- /dev/null +++ b/src/components/OpenCVCanvas.tsx @@ -0,0 +1,152 @@ +// This file is part of opencv-js-react project from Robomous. +// It is subject to the license terms in the LICENSE file found in the top-level directory + +import React, { useRef, useEffect, useCallback } from 'react'; +import type { OpenCVCanvasProps } from '../types'; +import { useOpenCV } from '../hooks/useOpenCV'; + +/** + * A React component that renders an internal canvas, loads a source image, + * draws it, and calls `onProcess` with the OpenCV instance once ready. + * + * The consumer is responsible for calling `mat.delete()` on all created Mats + * inside `onProcess` to prevent memory leaks. + * + * @example + * { + * let src = null; + * try { + * src = cv.imread(canvas); + * cv.imshow(outputCanvas || canvas, src); + * } finally { + * if (src) src.delete(); + * } + * }} + * /> + */ +export function OpenCVCanvas({ + src, + outputCanvasRef, + autoProcess = true, + className, + crossOrigin, + onLoad, + onProcess, + onError, +}: OpenCVCanvasProps) { + const { cv, isReady } = useOpenCV(); + const canvasRef = useRef(null); + const imageRef = useRef(null); + const isImageDrawnRef = useRef(false); + + // Refs for stable access to latest callbacks and state in effects + const cvRef = useRef(cv); + cvRef.current = cv; + const isReadyRef = useRef(isReady); + isReadyRef.current = isReady; + const autoProcessRef = useRef(autoProcess); + autoProcessRef.current = autoProcess; + const onProcessRef = useRef(onProcess); + onProcessRef.current = onProcess; + const onErrorRef = useRef(onError); + onErrorRef.current = onError; + const outputCanvasRefRef = useRef(outputCanvasRef); + outputCanvasRefRef.current = outputCanvasRef; + + const runProcessing = useCallback(() => { + if (!autoProcessRef.current || !onProcessRef.current) return; + if (!isReadyRef.current || !cvRef.current) return; + if (!isImageDrawnRef.current) return; + + const canvas = canvasRef.current; + const image = imageRef.current; + if (!canvas || !image) return; + + const context = canvas.getContext('2d'); + if (!context) return; + + const outputCanvas = outputCanvasRefRef.current?.current ?? undefined; + + if (outputCanvas) { + outputCanvas.width = canvas.width; + outputCanvas.height = canvas.height; + } + + const run = async () => { + try { + await onProcessRef.current!({ + canvas, + context, + image, + cv: cvRef.current!, + outputCanvas, + }); + } catch (err) { + onErrorRef.current?.(err instanceof Error ? err : new Error(String(err))); + } + }; + + run(); + }, []); + + // Load and draw the source image whenever `src` changes + useEffect(() => { + let stale = false; + isImageDrawnRef.current = false; + + const canvas = canvasRef.current; + if (!canvas) return; + + const img = new Image(); + if (crossOrigin !== undefined) { + img.crossOrigin = crossOrigin; + } + + img.onload = () => { + if (stale) return; + + canvas.width = img.naturalWidth; + canvas.height = img.naturalHeight; + + const context = canvas.getContext('2d'); + if (!context) { + onErrorRef.current?.(new Error('Failed to get 2D context from canvas')); + return; + } + + context.drawImage(img, 0, 0); + imageRef.current = img; + isImageDrawnRef.current = true; + + onLoad?.({ canvas, context, image: img }); + + // If cv is already ready, run processing immediately + runProcessing(); + }; + + img.onerror = () => { + if (stale) return; + onErrorRef.current?.(new Error(`Failed to load image: ${src}`)); + }; + + img.src = src; + + return () => { + stale = true; + }; + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [src, crossOrigin]); + + // Run processing when OpenCV becomes ready (image may already be drawn) + useEffect(() => { + if (isReady && cv) { + runProcessing(); + } + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [isReady, cv]); + + return ; +} diff --git a/src/constants.ts b/src/constants.ts new file mode 100644 index 0000000..080c00c --- /dev/null +++ b/src/constants.ts @@ -0,0 +1,20 @@ +// This file is part of opencv-js-react project from Robomous. +// It is subject to the license terms in the LICENSE file found in the top-level directory + +/** The OpenCV version vendored in this package. */ +export const OPENCV_VERSION = '4.13.0'; + +/** ID applied to the injected OpenCV script tag — prevents duplicate injection. */ +export const OPENCV_SCRIPT_ID = 'opencv-js-react-script'; + +/** + * Default URL for OpenCV.js — the official CDN build for the current version. + * + * To use a different version, contrib build, or self-hosted asset, pass the + * `src` option to `useOpenCV()` or `OpenCVCanvas`: + * + * useOpenCV({ src: '/assets/opencv-4.13.0-contrib.min.js' }) + */ +export function getDefaultOpenCVUrl(): string { + return `https://docs.opencv.org/${OPENCV_VERSION}/opencv.js`; +} diff --git a/src/global.d.ts b/src/global.d.ts new file mode 100644 index 0000000..b3f191d --- /dev/null +++ b/src/global.d.ts @@ -0,0 +1,20 @@ +// This file is part of opencv-js-react project from Robomous. +// It is subject to the license terms in the LICENSE file found in the top-level directory + +import type { OpenCVNamespace } from './types'; + +declare global { + interface Window { + cv?: OpenCVNamespace | OpenCVFactory; + } +} + +/** + * OpenCV.js 4.x exposes `window.cv` as either: + * - A factory function/promise that resolves to the cv namespace + * - The cv namespace directly (if already initialized) + */ +export type OpenCVFactory = { + then: (resolve: (cv: OpenCVNamespace) => void) => void; + onRuntimeInitialized?: () => void; +} & Partial; diff --git a/src/hooks/useOpenCV.ts b/src/hooks/useOpenCV.ts new file mode 100644 index 0000000..7dfaa0f --- /dev/null +++ b/src/hooks/useOpenCV.ts @@ -0,0 +1,60 @@ +// This file is part of opencv-js-react project from Robomous. +// It is subject to the license terms in the LICENSE file found in the top-level directory + +import { useState, useEffect, useCallback, useRef } from 'react'; +import type { UseOpenCVOptions, UseOpenCVReturn, OpenCVNamespace } from '../types'; +import { loadOpenCV, getCV } from '../loader/loadOpenCV'; + +/** + * Hook that lazily loads OpenCV.js in the browser and exposes its readiness state. + * + * Safe to use in SSR environments — no browser APIs are accessed during render. + * + * @example + * const { cv, isReady, isLoading, error, load } = useOpenCV(); + */ +export function useOpenCV(options?: UseOpenCVOptions): UseOpenCVReturn { + const { autoLoad = true, src } = options ?? {}; + + const [cv, setCV] = useState(() => getCV()); + const [isReady, setIsReady] = useState(() => getCV() !== null); + const [isLoading, setIsLoading] = useState(false); + const [error, setError] = useState(null); + + // Stable ref to avoid stale closure issues in load() + const srcRef = useRef(src); + srcRef.current = src; + + const load = useCallback(async () => { + // Already ready + const current = getCV(); + if (current) { + setCV(current); + setIsReady(true); + return; + } + + setIsLoading(true); + setError(null); + + try { + await loadOpenCV(srcRef.current); + const loaded = getCV(); + setCV(loaded); + setIsReady(loaded !== null); + } catch (err) { + setError(err instanceof Error ? err : new Error(String(err))); + setIsReady(false); + } finally { + setIsLoading(false); + } + }, []); + + useEffect(() => { + if (autoLoad) { + load(); + } + }, [autoLoad, load]); + + return { cv, isReady, isLoading, error, load }; +} diff --git a/src/index.ts b/src/index.ts new file mode 100644 index 0000000..9fcd498 --- /dev/null +++ b/src/index.ts @@ -0,0 +1,18 @@ +// This file is part of opencv-js-react project from Robomous. +// It is subject to the license terms in the LICENSE file found in the top-level directory + +export { useOpenCV } from './hooks/useOpenCV'; +export { OpenCVCanvas } from './components/OpenCVCanvas'; +export type { + UseOpenCVOptions, + UseOpenCVReturn, + OpenCVCanvasProps, + LoadPayload, + ProcessPayload, + OpenCVNamespace, + CVMat, + CVSize, + CVPoint, + CVScalar, + CVCLAHE, +} from './types'; diff --git a/src/loader/loadOpenCV.ts b/src/loader/loadOpenCV.ts new file mode 100644 index 0000000..bdfcd5e --- /dev/null +++ b/src/loader/loadOpenCV.ts @@ -0,0 +1,129 @@ +// This file is part of opencv-js-react project from Robomous. +// It is subject to the license terms in the LICENSE file found in the top-level directory + +import type { OpenCVNamespace } from '../types'; +import type { OpenCVFactory } from '../global'; +import { OPENCV_SCRIPT_ID, getDefaultOpenCVUrl } from '../constants'; + +/** Singleton promise — ensures OpenCV.js is injected and initialized only once. */ +let loadPromise: Promise | null = null; + +/** + * Returns the resolved, initialized `window.cv` namespace, or null if not ready. + */ +function getReadyCV(): OpenCVNamespace | null { + const cv = window.cv as OpenCVNamespace | OpenCVFactory | undefined; + if (!cv) return null; + // Check if it's the namespace directly (has imread as a function) + if (typeof (cv as OpenCVNamespace).imread === 'function') { + return cv as OpenCVNamespace; + } + return null; +} + +/** + * Waits for `window.cv` to be initialized. OpenCV.js 4.x exposes `cv` as a + * thenable factory on some builds, or directly calls `cv.onRuntimeInitialized`. + */ +function waitForCV(): Promise { + return new Promise((resolve, reject) => { + const cv = window.cv as OpenCVFactory | OpenCVNamespace | undefined; + + if (!cv) { + reject(new Error('OpenCV script loaded but window.cv is undefined')); + return; + } + + // Already fully initialized (has imread) + if (typeof (cv as OpenCVNamespace).imread === 'function') { + resolve(); + return; + } + + // Promise/thenable pattern (common in OpenCV.js 4.x) + if (typeof (cv as OpenCVFactory).then === 'function') { + (cv as OpenCVFactory).then((resolvedCV) => { + window.cv = resolvedCV; + resolve(); + }); + return; + } + + // onRuntimeInitialized callback pattern (older builds) + const factory = cv as OpenCVFactory; + factory.onRuntimeInitialized = () => { + resolve(); + }; + }); +} + +/** + * Loads OpenCV.js into the browser exactly once (singleton). + * Safe to call multiple times — subsequent calls return the same promise. + * + * @param src - Optional URL override. Defaults to the vendored packaged asset. + */ +export function loadOpenCV(src?: string): Promise { + if (typeof window === 'undefined') { + return Promise.reject(new Error('OpenCV.js cannot be loaded in a non-browser environment')); + } + + // Already initialized + if (getReadyCV()) { + return Promise.resolve(); + } + + // Return existing in-flight load + if (loadPromise) { + return loadPromise; + } + + // Check for existing script tag (e.g. injected by another lib or SSR hydration) + const existing = document.getElementById(OPENCV_SCRIPT_ID); + if (existing) { + loadPromise = waitForCV(); + return loadPromise; + } + + const url = src ?? getDefaultOpenCVUrl(); + const isRemote = url.startsWith('http://') || url.startsWith('https://'); + + loadPromise = new Promise((resolve, reject) => { + const script = document.createElement('script'); + script.id = OPENCV_SCRIPT_ID; + script.async = true; + if (isRemote) { + script.crossOrigin = 'anonymous'; + } + script.src = url; + + script.onload = () => { + waitForCV().then(resolve).catch(reject); + }; + + script.onerror = () => { + loadPromise = null; + reject(new Error(`Failed to load OpenCV.js from: ${url}`)); + }; + + document.head.appendChild(script); + }); + + return loadPromise; +} + +/** + * Returns the current OpenCV namespace if loaded and ready, otherwise null. + */ +export function getCV(): OpenCVNamespace | null { + if (typeof window === 'undefined') return null; + return getReadyCV(); +} + +/** + * Resets the singleton loader state. + * Intended for use in tests only — do not call in production code. + */ +export function _resetForTesting(): void { + loadPromise = null; +} diff --git a/src/types.ts b/src/types.ts new file mode 100644 index 0000000..35a76d6 --- /dev/null +++ b/src/types.ts @@ -0,0 +1,126 @@ +// This file is part of opencv-js-react project from Robomous. +// It is subject to the license terms in the LICENSE file found in the top-level directory + +import type { RefObject } from 'react'; + +/** + * Minimal typing for the OpenCV.js namespace. + * Consumers can augment this via module augmentation if needed. + */ +export interface OpenCVNamespace { + // Core Mat + Mat: new (...args: unknown[]) => CVMat; + MatVector: new () => CVMatVector; + Size: new (width: number, height: number) => CVSize; + Point: new (x: number, y: number) => CVPoint; + Scalar: new (v0: number, v1?: number, v2?: number, v3?: number) => CVScalar; + CLAHE: new (clipLimit?: number, tileGridSize?: CVSize) => CVCLAHE; + + // Image I/O + imread(canvas: HTMLCanvasElement | string): CVMat; + imshow(canvas: HTMLCanvasElement | string, mat: CVMat): void; + + // Color conversion + cvtColor(src: CVMat, dst: CVMat, code: number, dstCn?: number): void; + + // Runtime + onRuntimeInitialized?: () => void; + + // Color conversion constants + COLOR_RGBA2GRAY: number; + COLOR_RGB2GRAY: number; + COLOR_GRAY2RGBA: number; + COLOR_BGR2RGB: number; + COLOR_RGB2BGR: number; + + // Allow additional members from the full OpenCV.js API + [key: string]: unknown; +} + +export interface CVMat { + delete(): void; + rows: number; + cols: number; + data: Uint8Array; + data32F: Float32Array; + size(): CVSize; +} + +export interface CVMatVector { + delete(): void; + size(): number; + get(index: number): CVMat; + push_back(mat: CVMat): void; +} + +export interface CVSize { + width: number; + height: number; +} + +export interface CVPoint { + x: number; + y: number; +} + +export interface CVScalar { + [index: number]: number; +} + +export interface CVCLAHE { + apply(src: CVMat, dst: CVMat): void; + delete(): void; +} + +// ─── Hook types ───────────────────────────────────────────────────────────── + +export interface UseOpenCVOptions { + /** Automatically load OpenCV when the component mounts. Defaults to true. */ + autoLoad?: boolean; + /** Override the default OpenCV.js asset URL. */ + src?: string; +} + +export interface UseOpenCVReturn { + cv: OpenCVNamespace | null; + isReady: boolean; + isLoading: boolean; + error: Error | null; + load: () => Promise; +} + +// ─── Component types ───────────────────────────────────────────────────────── + +export interface LoadPayload { + canvas: HTMLCanvasElement; + context: CanvasRenderingContext2D; + image: HTMLImageElement; +} + +export interface ProcessPayload extends LoadPayload { + cv: OpenCVNamespace; + outputCanvas?: HTMLCanvasElement; +} + +export interface OpenCVCanvasProps { + /** Source image URL to load and draw onto the internal canvas. */ + src: string; + /** Optional ref to an external output canvas for processed results. */ + outputCanvasRef?: RefObject; + /** + * Whether to call `onProcess` automatically after the image loads and + * OpenCV is ready. Defaults to true. + */ + autoProcess?: boolean; + className?: string; + crossOrigin?: '' | 'anonymous' | 'use-credentials'; + /** Called once the source image has been drawn to the internal canvas. */ + onLoad?: (payload: LoadPayload) => void; + /** + * Called when OpenCV is ready and the image has been drawn. + * Use the `finally` block to call `mat.delete()` on all created Mats. + */ + onProcess?: (payload: ProcessPayload) => void | Promise; + /** Called when an error occurs during image loading or processing. */ + onError?: (error: Error) => void; +} From 16baf506d3960a8ae935dfc5ed9474fc4b025d36 Mon Sep 17 00:00:00 2001 From: Jesus Armando Anaya Date: Sun, 12 Apr 2026 19:39:06 -0700 Subject: [PATCH 3/7] Add test suite --- tests/OpenCVCanvas.test.tsx | 246 ++++++++++++++++++++++++++++++++++++ tests/setup.ts | 153 ++++++++++++++++++++++ tests/useOpenCV.test.tsx | 170 +++++++++++++++++++++++++ 3 files changed, 569 insertions(+) create mode 100644 tests/OpenCVCanvas.test.tsx create mode 100644 tests/setup.ts create mode 100644 tests/useOpenCV.test.tsx diff --git a/tests/OpenCVCanvas.test.tsx b/tests/OpenCVCanvas.test.tsx new file mode 100644 index 0000000..d75fd52 --- /dev/null +++ b/tests/OpenCVCanvas.test.tsx @@ -0,0 +1,246 @@ +// This file is part of opencv-js-react project from Robomous. +// It is subject to the license terms in the LICENSE file found in the top-level directory + +import React, { createRef } from 'react'; +import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest'; +import { render, waitFor } from '@testing-library/react'; +import { OpenCVCanvas } from '../src/components/OpenCVCanvas'; +import { simulateOpenCVLoad, mockImageLoad } from './setup'; + +describe('OpenCVCanvas', () => { + let restoreImage: () => void; + let restoreScript: ReturnType['spy'] | undefined; + + beforeEach(() => { + ({ restore: restoreImage } = mockImageLoad()); + ({ spy: restoreScript } = simulateOpenCVLoad()); + }); + + afterEach(() => { + restoreImage(); + restoreScript?.mockRestore(); + }); + + describe('rendering', () => { + it('renders a canvas element', () => { + render(); + expect(document.querySelector('canvas')).toBeInTheDocument(); + }); + + it('applies className to the canvas', () => { + render(); + expect(document.querySelector('canvas')).toHaveClass('my-canvas'); + }); + }); + + describe('image loading', () => { + it('loads the source image', async () => { + render(); + + await waitFor(() => { + // Image constructor should have been called + expect(global.Image).toHaveBeenCalled(); + }); + }); + + it('calls onLoad after the image is drawn', async () => { + const onLoad = vi.fn(); + render( + , + ); + + await waitFor(() => { + expect(onLoad).toHaveBeenCalledOnce(); + }); + + const payload = onLoad.mock.calls[0][0]; + expect(payload).toHaveProperty('canvas'); + expect(payload).toHaveProperty('context'); + expect(payload).toHaveProperty('image'); + }); + + it('calls onError when image fails to load', async () => { + // Override Image mock to trigger error instead of load + const OriginalImage = global.Image; + global.Image = vi.fn().mockImplementation(() => { + const img: Partial = {}; + Object.defineProperty(img, 'src', { + set() { + Promise.resolve().then(() => { + if (img.onerror) (img.onerror as EventListener)(new Event('error')); + }); + }, + get() { return ''; }, + }); + return img; + }) as unknown as typeof Image; + + const onError = vi.fn(); + render(); + + await waitFor(() => { + expect(onError).toHaveBeenCalledOnce(); + }); + + expect(onError.mock.calls[0][0]).toBeInstanceOf(Error); + global.Image = OriginalImage; + }); + }); + + describe('onProcess', () => { + it('calls onProcess when OpenCV is ready and image is drawn', async () => { + const onProcess = vi.fn(); + + render( + , + ); + + await waitFor(() => { + expect(onProcess).toHaveBeenCalledOnce(); + }); + + const payload = onProcess.mock.calls[0][0]; + expect(payload).toHaveProperty('canvas'); + expect(payload).toHaveProperty('context'); + expect(payload).toHaveProperty('image'); + expect(payload).toHaveProperty('cv'); + }); + + it('does not call onProcess when autoProcess is false', async () => { + const onProcess = vi.fn(); + + render( + , + ); + + // Wait for image load to complete + await waitFor(() => { + expect(global.Image).toHaveBeenCalled(); + }); + + // Give enough time for any potential async process call + await new Promise((r) => setTimeout(r, 50)); + expect(onProcess).not.toHaveBeenCalled(); + }); + + it('passes outputCanvas in the onProcess payload when outputCanvasRef is provided', async () => { + const onProcess = vi.fn(); + const outputCanvasRef = createRef(); + + render( +
+ + +
, + ); + + await waitFor(() => { + expect(onProcess).toHaveBeenCalledOnce(); + }); + + const payload = onProcess.mock.calls[0][0]; + expect(payload.outputCanvas).toBe(outputCanvasRef.current); + }); + + it('does not call onProcess when outputCanvasRef is not attached', async () => { + // Unattached ref — current is null + const outputCanvasRef = createRef(); + const onProcess = vi.fn(); + + render( + , + ); + + await waitFor(() => { + expect(onProcess).toHaveBeenCalledOnce(); + }); + + // outputCanvas should be undefined when ref isn't attached + expect(onProcess.mock.calls[0][0].outputCanvas).toBeUndefined(); + }); + + it('forwards processing errors to onError', async () => { + const processingError = new Error('cv operation failed'); + const onProcess = vi.fn().mockRejectedValue(processingError); + const onError = vi.fn(); + + render( + , + ); + + await waitFor(() => { + expect(onError).toHaveBeenCalledOnce(); + }); + + expect(onError.mock.calls[0][0]).toBe(processingError); + }); + + it('handles synchronous errors thrown from onProcess', async () => { + const onProcess = vi.fn().mockImplementation(() => { + throw new Error('sync error'); + }); + const onError = vi.fn(); + + render( + , + ); + + await waitFor(() => { + expect(onError).toHaveBeenCalledOnce(); + }); + + expect(onError.mock.calls[0][0]).toBeInstanceOf(Error); + }); + }); + + describe('race conditions', () => { + it('does not call onLoad when src changes before image loads', async () => { + const onLoad = vi.fn(); + + // Use a slow-loading image mock — track triggers per-image + const OriginalImage = global.Image; + const triggers: Array<() => void> = []; + + global.Image = vi.fn().mockImplementation(() => { + const img: Partial = { + naturalWidth: 100, + naturalHeight: 100, + }; + Object.defineProperty(img, 'src', { + set() { + // Capture a trigger for THIS image instance + const triggerFn = () => { + if (img.onload) (img.onload as EventListener)(new Event('load')); + }; + triggers.push(triggerFn); + }, + get() { return ''; }, + }); + return img; + }) as unknown as typeof Image; + + const { rerender } = render( + , + ); + + // Change src before the first image loads — stale flag set on first effect + rerender(); + + // Trigger the first image's load (stale — should be ignored) + triggers[0]?.(); + + await new Promise((r) => setTimeout(r, 50)); + expect(onLoad).not.toHaveBeenCalled(); + + global.Image = OriginalImage; + }); + }); +}); diff --git a/tests/setup.ts b/tests/setup.ts new file mode 100644 index 0000000..014215e --- /dev/null +++ b/tests/setup.ts @@ -0,0 +1,153 @@ +// This file is part of opencv-js-react project from Robomous. +// It is subject to the license terms in the LICENSE file found in the top-level directory + +import '@testing-library/jest-dom'; +import { afterEach, vi } from 'vitest'; +import { cleanup } from '@testing-library/react'; +import { _resetForTesting } from '../src/loader/loadOpenCV'; + +// Automatically cleanup after each test +afterEach(() => { + cleanup(); + // Reset the OpenCV loader singleton so tests don't bleed into each other + _resetForTesting(); + // Remove any injected script tags + document.querySelectorAll('script[id]').forEach((s) => s.remove()); + // Reset window.cv + delete (window as Window & { cv?: unknown }).cv; +}); + +// ─── Canvas mock ────────────────────────────────────────────────────────────── +// jsdom does not implement CanvasRenderingContext2D — provide a minimal stub. +const mockContext: Partial = { + drawImage: vi.fn(), + getImageData: vi.fn(() => ({ + data: new Uint8ClampedArray(4), + width: 1, + height: 1, + colorSpace: 'srgb', + })), + putImageData: vi.fn(), + clearRect: vi.fn(), + fillRect: vi.fn(), +}; + +HTMLCanvasElement.prototype.getContext = vi.fn( + () => mockContext as CanvasRenderingContext2D, +) as typeof HTMLCanvasElement.prototype.getContext; + +// ─── Image mock ─────────────────────────────────────────────────────────────── +// Provide helpers to control Image load/error in tests. + +export function mockImageLoad(naturalWidth = 100, naturalHeight = 100) { + const originalImage = global.Image; + + const MockImage = vi.fn().mockImplementation(() => { + const img: Partial & { _triggerLoad?: () => void; _triggerError?: () => void } = { + naturalWidth, + naturalHeight, + crossOrigin: '', + src: '', + }; + + // Allow tests to trigger load/error + img._triggerLoad = () => { + if (img.onload) (img.onload as EventListener)(new Event('load')); + }; + img._triggerError = () => { + if (img.onerror) (img.onerror as EventListener)(new Event('error')); + }; + + // Auto-trigger load when src is set (for simpler tests) + Object.defineProperty(img, 'src', { + set(value: string) { + (img as { _src?: string })._src = value; + // Defer to next microtask so event handlers can be attached first + Promise.resolve().then(() => img._triggerLoad!()); + }, + get() { + return (img as { _src?: string })._src ?? ''; + }, + }); + + return img; + }); + + global.Image = MockImage as unknown as typeof Image; + + return { + MockImage, + restore: () => { + global.Image = originalImage; + }, + }; +} + +// ─── OpenCV mock ────────────────────────────────────────────────────────────── +export function mockCV() { + const cv = { + imread: vi.fn(() => ({ delete: vi.fn(), rows: 100, cols: 100 })), + imshow: vi.fn(), + cvtColor: vi.fn(), + COLOR_RGBA2GRAY: 6, + Mat: vi.fn(() => ({ delete: vi.fn() })), + Size: vi.fn(() => ({ width: 8, height: 8 })), + CLAHE: vi.fn(() => ({ apply: vi.fn(), delete: vi.fn() })), + }; + + (window as Window & { cv?: unknown }).cv = cv; + return cv; +} + +// ─── Script injection helper ───────────────────────────────────────────────── +/** + * Simulates a successful OpenCV.js script load by: + * 1. Watching for script injection + * 2. Setting window.cv (with imread) THEN triggering the script's onload + * + * window.cv is intentionally NOT set until the simulated onload fires, + * so the loader goes through the full script-injection path. + */ +export function simulateOpenCVLoad() { + const cv = { + imread: vi.fn(() => ({ delete: vi.fn(), rows: 100, cols: 100 })), + imshow: vi.fn(), + cvtColor: vi.fn(), + COLOR_RGBA2GRAY: 6, + Mat: vi.fn(() => ({ delete: vi.fn() })), + Size: vi.fn(() => ({ width: 8, height: 8 })), + CLAHE: vi.fn(() => ({ apply: vi.fn(), delete: vi.fn() })), + }; + + const originalAppendChild = document.head.appendChild.bind(document.head); + const spy = vi.spyOn(document.head, 'appendChild').mockImplementation((node) => { + const result = originalAppendChild(node); + if (node instanceof HTMLScriptElement) { + Promise.resolve().then(() => { + // Set window.cv BEFORE triggering onload so waitForCV() resolves + (window as Window & { cv?: unknown }).cv = cv; + if (node.onload) (node.onload as EventListener)(new Event('load')); + }); + } + return result; + }); + + return { cv, spy }; +} + +/** + * Simulates a failed OpenCV.js script load. + */ +export function simulateOpenCVLoadError() { + const spy = vi.spyOn(document.head, 'appendChild').mockImplementation((node) => { + const result = document.head.appendChild.call(document.head, node); + if (node instanceof HTMLScriptElement) { + Promise.resolve().then(() => { + if (node.onerror) (node.onerror as EventListener)(new Event('error')); + }); + } + return result; + }); + + return { spy }; +} diff --git a/tests/useOpenCV.test.tsx b/tests/useOpenCV.test.tsx new file mode 100644 index 0000000..7a1cc7b --- /dev/null +++ b/tests/useOpenCV.test.tsx @@ -0,0 +1,170 @@ +// This file is part of opencv-js-react project from Robomous. +// It is subject to the license terms in the LICENSE file found in the top-level directory + +import { describe, it, expect, vi } from 'vitest'; +import { renderHook, act, waitFor } from '@testing-library/react'; +import { useOpenCV } from '../src/hooks/useOpenCV'; +import { OPENCV_SCRIPT_ID } from '../src/constants'; +import { simulateOpenCVLoad, simulateOpenCVLoadError, mockCV } from './setup'; + +describe('useOpenCV', () => { + describe('initial state', () => { + it('returns correct initial state when cv is not loaded', () => { + const { result } = renderHook(() => useOpenCV({ autoLoad: false })); + expect(result.current.cv).toBeNull(); + expect(result.current.isReady).toBe(false); + expect(result.current.isLoading).toBe(false); + expect(result.current.error).toBeNull(); + expect(typeof result.current.load).toBe('function'); + }); + + it('returns isReady=true immediately if window.cv is already initialized', () => { + const cv = mockCV(); + // Simulate cv already having imread (fully initialized) + (cv as typeof cv & { imread: ReturnType }).imread = vi.fn(() => ({ + delete: vi.fn(), + })); + + const { result } = renderHook(() => useOpenCV({ autoLoad: false })); + expect(result.current.isReady).toBe(true); + expect(result.current.cv).not.toBeNull(); + }); + }); + + describe('loading', () => { + it('transitions isLoading from false → true → false during load', async () => { + const { spy } = simulateOpenCVLoad(); + + const { result } = renderHook(() => useOpenCV({ autoLoad: false })); + + expect(result.current.isLoading).toBe(false); + + await act(async () => { + await result.current.load(); + }); + + // After resolution, isLoading should be back to false + expect(result.current.isLoading).toBe(false); + expect(result.current.isReady).toBe(true); + + spy.mockRestore(); + }); + + it('sets isReady=true and cv after successful load', async () => { + const { spy } = simulateOpenCVLoad(); + + const { result } = renderHook(() => useOpenCV({ autoLoad: false })); + + await act(async () => { + await result.current.load(); + }); + + expect(result.current.isReady).toBe(true); + expect(result.current.cv).not.toBeNull(); + expect(result.current.isLoading).toBe(false); + expect(result.current.error).toBeNull(); + + spy.mockRestore(); + }); + + it('auto-loads when autoLoad is true (default)', async () => { + const { spy } = simulateOpenCVLoad(); + + const { result } = renderHook(() => useOpenCV()); + + await waitFor(() => { + expect(result.current.isReady).toBe(true); + }); + + spy.mockRestore(); + }); + }); + + describe('duplicate script prevention', () => { + it('does not inject a second script tag on repeated load() calls', async () => { + const { spy } = simulateOpenCVLoad(); + + const { result } = renderHook(() => useOpenCV({ autoLoad: false })); + + await act(async () => { + await Promise.all([result.current.load(), result.current.load()]); + }); + + const scripts = document.querySelectorAll(`script#${OPENCV_SCRIPT_ID}`); + expect(scripts).toHaveLength(1); + + spy.mockRestore(); + }); + + it('does not inject a second script if one already exists in the DOM', async () => { + const { spy } = simulateOpenCVLoad(); + + // First hook instance loads + const { result: r1 } = renderHook(() => useOpenCV({ autoLoad: false })); + await act(async () => { + await r1.current.load(); + }); + + // Second hook instance should not inject another script + const appendSpy = vi.spyOn(document.head, 'appendChild'); + const { result: r2 } = renderHook(() => useOpenCV({ autoLoad: false })); + await act(async () => { + await r2.current.load(); + }); + + // No new script appended + const scriptAppends = appendSpy.mock.calls.filter( + ([node]) => node instanceof HTMLScriptElement, + ); + expect(scriptAppends).toHaveLength(0); + + spy.mockRestore(); + appendSpy.mockRestore(); + }); + }); + + describe('error handling', () => { + it('sets error state on script load failure', async () => { + const { spy } = simulateOpenCVLoadError(); + + const { result } = renderHook(() => useOpenCV({ autoLoad: false })); + + await act(async () => { + await result.current.load().catch(() => {}); + }); + + expect(result.current.error).toBeInstanceOf(Error); + expect(result.current.isReady).toBe(false); + expect(result.current.cv).toBeNull(); + expect(result.current.isLoading).toBe(false); + + spy.mockRestore(); + }); + }); + + describe('SSR safety', () => { + it('does not crash when window is not accessed during render', () => { + // The hook should not throw — SSR safety is enforced by the loader, + // not the hook itself. The hook only calls loadOpenCV in useEffect. + expect(() => { + renderHook(() => useOpenCV({ autoLoad: false })); + }).not.toThrow(); + }); + + it('returns error when loadOpenCV is called without window (simulated)', async () => { + // Simulate SSR by temporarily having the loader reject + vi.spyOn(document.head, 'appendChild').mockImplementation(() => { + throw new Error('document is not defined'); + }); + + const { result } = renderHook(() => useOpenCV({ autoLoad: false })); + + await act(async () => { + await result.current.load().catch(() => {}); + }); + + expect(result.current.error).not.toBeNull(); + vi.restoreAllMocks(); + }); + }); +}); From 5d86993d75e19beab86154c90863bfdf85450ef9 Mon Sep 17 00:00:00 2001 From: Jesus Armando Anaya Date: Sun, 12 Apr 2026 19:39:09 -0700 Subject: [PATCH 4/7] Add OpenCV.js build and vendor scripts --- scripts/build-opencv-js-release.sh | 317 +++++++++++++++++++++++ scripts/helpers/opencv-release-config.sh | 124 +++++++++ scripts/test-all-versions.sh | 142 ++++++++++ scripts/vendor-opencv.mjs | 73 ++++++ scripts/verify-opencv-version.mjs | 57 ++++ 5 files changed, 713 insertions(+) create mode 100755 scripts/build-opencv-js-release.sh create mode 100755 scripts/helpers/opencv-release-config.sh create mode 100755 scripts/test-all-versions.sh create mode 100644 scripts/vendor-opencv.mjs create mode 100644 scripts/verify-opencv-version.mjs diff --git a/scripts/build-opencv-js-release.sh b/scripts/build-opencv-js-release.sh new file mode 100755 index 0000000..1fd0d6a --- /dev/null +++ b/scripts/build-opencv-js-release.sh @@ -0,0 +1,317 @@ +#!/usr/bin/env bash +# This file is part of opencv-js-react project from Robomous. +# It is subject to the license terms in the LICENSE file found in the top-level directory + +# ============================================================================= +# build-opencv-js-release.sh +# +# Builds OpenCV.js from an official release tag using Docker + emscripten/emsdk. +# Only official stable release versions are supported (see SUPPORTED_RELEASES). +# +# Usage: +# bash scripts/build-opencv-js-release.sh --version 4.13.0 +# bash scripts/build-opencv-js-release.sh --version 4.13.0 --with-contrib +# bash scripts/build-opencv-js-release.sh --version 4.12.0 --docker-image emscripten/emsdk:2.0.10 +# bash scripts/build-opencv-js-release.sh --version 4.10.0 --disable-single-file +# bash scripts/build-opencv-js-release.sh --version 4.13.0 --build-loader +# bash scripts/build-opencv-js-release.sh --help +# ============================================================================= +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +REPO_ROOT="$(cd "${SCRIPT_DIR}/.." && pwd)" + +# Source helper config (SUPPORTED_RELEASES, validation, naming functions) +# shellcheck source=scripts/helpers/opencv-release-config.sh +source "${SCRIPT_DIR}/helpers/opencv-release-config.sh" + +# ============================================================================= +# Default configuration — edit this section to change defaults +# ============================================================================= +DEFAULT_VERSION="4.13.0" +# DEFAULT_DOCKER_IMAGE is resolved per-version after arg parsing (see get_docker_image() in helper) +DEFAULT_DOCKER_IMAGE="" +DEFAULT_USE_DOCKER="true" +DEFAULT_DISABLE_SINGLE_FILE="false" +DEFAULT_BUILD_LOADER="false" +DEFAULT_WITH_CONTRIB="false" +WORK_ROOT="build/opencv-js" +RELEASES_DIR="${WORK_ROOT}/releases" +CHECKOUTS_DIR="${WORK_ROOT}/checkouts" + +# ============================================================================= +# Argument parsing +# ============================================================================= +VERSION="${DEFAULT_VERSION}" +DOCKER_IMAGE="${DEFAULT_DOCKER_IMAGE}" +USE_DOCKER="${DEFAULT_USE_DOCKER}" +DISABLE_SINGLE_FILE="${DEFAULT_DISABLE_SINGLE_FILE}" +BUILD_LOADER="${DEFAULT_BUILD_LOADER}" +WITH_CONTRIB="${DEFAULT_WITH_CONTRIB}" +OUTPUT_DIR="" + +usage() { + cat < OpenCV release version to build (required if not using default) + Supported: ${SUPPORTED_RELEASES[*]} + --with-contrib Include opencv_contrib modules + --docker-image Docker image to use (default: version-specific, see opencv-release-config.sh) + --disable-single-file Emit separate opencv.js + opencv.wasm files + --build-loader Also build the ES module loader + --output-dir Override the release output directory + --help Show this help message + +EXAMPLES: + bash scripts/build-opencv-js-release.sh --version 4.13.0 + bash scripts/build-opencv-js-release.sh --version 4.13.0 --with-contrib + bash scripts/build-opencv-js-release.sh --version 4.12.0 --docker-image emscripten/emsdk:2.0.10 + bash scripts/build-opencv-js-release.sh --version 4.10.0 --disable-single-file + +OUTPUT: + build/opencv-js/releases/OpenCV-/ + build/opencv-js/releases/OpenCV--contrib/ (when --with-contrib) + +EOF +} + +while [[ $# -gt 0 ]]; do + case "$1" in + --version) + VERSION="${2:?'--version requires a value'}" + shift 2 + ;; + --with-contrib) + WITH_CONTRIB="true" + shift + ;; + --docker-image) + DOCKER_IMAGE="${2:?'--docker-image requires a value'}" + shift 2 + ;; + --disable-single-file) + DISABLE_SINGLE_FILE="true" + shift + ;; + --build-loader) + BUILD_LOADER="true" + shift + ;; + --output-dir) + OUTPUT_DIR="${2:?'--output-dir requires a value'}" + shift 2 + ;; + --help|-h) + usage + exit 0 + ;; + *) + echo "ERROR: Unknown option: $1" >&2 + usage >&2 + exit 1 + ;; + esac +done + +# ============================================================================= +# Validation +# ============================================================================= +validate_version "${VERSION}" "build-opencv-js-release.sh" + +# Resolve Docker image: use explicit override, else per-version default +if [[ -z "${DOCKER_IMAGE}" ]]; then + DOCKER_IMAGE="$(get_docker_image "${VERSION}")" +fi + +PACKAGE_NAME="$(get_package_name "${VERSION}" "${WITH_CONTRIB}")" +RELEASE_OUTPUT_DIR="${OUTPUT_DIR:-${REPO_ROOT}/${RELEASES_DIR}/${PACKAGE_NAME}}" +CHECKOUT_DIR="${REPO_ROOT}/${CHECKOUTS_DIR}/${PACKAGE_NAME}" +OPENCV_SRC="${CHECKOUT_DIR}/opencv" +OPENCV_CONTRIB_SRC="${CHECKOUT_DIR}/opencv_contrib" +BUILD_DIR="${CHECKOUT_DIR}/build_js" + +# ============================================================================= +# Summary +# ============================================================================= +echo "============================================================" +echo " opencv-js-react: Building OpenCV.js" +echo "============================================================" +echo " Version: ${VERSION}" +echo " Package name: ${PACKAGE_NAME}" +echo " With contrib: ${WITH_CONTRIB}" +echo " Docker image: ${DOCKER_IMAGE}" +echo " Disable single file: ${DISABLE_SINGLE_FILE}" +echo " Build loader: ${BUILD_LOADER}" +echo " Output: ${RELEASE_OUTPUT_DIR}" +echo "============================================================" +echo "" + +# ============================================================================= +# Ensure required tools are available +# ============================================================================= +if [[ "${USE_DOCKER}" == "true" ]]; then + if ! command -v docker &>/dev/null; then + echo "ERROR: Docker is required but not found in PATH." >&2 + echo "Install Docker: https://docs.docker.com/get-docker/" >&2 + exit 1 + fi +fi + +if ! command -v git &>/dev/null; then + echo "ERROR: git is required but not found in PATH." >&2 + exit 1 +fi + +# ============================================================================= +# Prepare directories +# ============================================================================= +mkdir -p "${CHECKOUT_DIR}" +mkdir -p "${RELEASE_OUTPUT_DIR}" + +# ============================================================================= +# Clone OpenCV source (shallow, release tag only) +# ============================================================================= +if [[ -d "${OPENCV_SRC}/.git" ]]; then + echo "→ Using existing OpenCV checkout at ${OPENCV_SRC}" +else + echo "→ Cloning OpenCV ${VERSION} (shallow)..." + CLONE_CMD="$(build_opencv_clone_cmd "${VERSION}" "${OPENCV_SRC}")" + echo " ${CLONE_CMD}" + eval "${CLONE_CMD}" +fi + +CONTRIB_CLONE_CMD="" +if [[ "${WITH_CONTRIB}" == "true" ]]; then + if [[ -d "${OPENCV_CONTRIB_SRC}/.git" ]]; then + echo "→ Using existing opencv_contrib checkout at ${OPENCV_CONTRIB_SRC}" + else + echo "→ Cloning opencv_contrib ${VERSION} (shallow)..." + CONTRIB_CLONE_CMD="$(build_contrib_clone_cmd "${VERSION}" "${OPENCV_CONTRIB_SRC}")" + echo " ${CONTRIB_CLONE_CMD}" + eval "${CONTRIB_CLONE_CMD}" + fi +fi + +# ============================================================================= +# Build OpenCV.js options +# ============================================================================= +BUILD_OPTS="" + +if [[ "${DISABLE_SINGLE_FILE}" == "true" ]]; then + BUILD_OPTS="${BUILD_OPTS} --disable_single_file" +fi + +if [[ "${BUILD_LOADER}" == "true" ]]; then + BUILD_OPTS="${BUILD_OPTS} --build_loader" +fi + +if [[ "${WITH_CONTRIB}" == "true" ]]; then + BUILD_OPTS="${BUILD_OPTS} --cmake_option=\"-DOPENCV_EXTRA_MODULES_PATH=/src/opencv_contrib/modules\"" +fi + +# ============================================================================= +# Construct and run the Docker build command +# ============================================================================= +echo "" +echo "→ Building OpenCV.js with Docker..." + +# Map checkout dir into Docker as /src so paths are consistent. +# -w /src sets the working directory so build_js.py writes output to /src/build_js/ +DOCKER_CMD="docker run --rm \ + -v \"${CHECKOUT_DIR}\":/src \ + -w /src \ + -u \"$(id -u):$(id -g)\" \ + ${DOCKER_IMAGE} \ + emcmake python3 /src/opencv/platforms/js/build_js.py build_js ${BUILD_OPTS}" + +echo " ${DOCKER_CMD}" +echo "" + +eval "${DOCKER_CMD}" + +# ============================================================================= +# Copy artifacts to release output directory +# ============================================================================= +echo "" +echo "→ Copying artifacts to ${RELEASE_OUTPUT_DIR}..." + +BUILT_DIR="${BUILD_DIR}/bin" + +if [[ ! -f "${BUILT_DIR}/opencv.js" ]]; then + echo "ERROR: Expected built opencv.js at ${BUILT_DIR}/opencv.js" >&2 + echo "Check the build output above for errors." >&2 + exit 1 +fi + +cp "${BUILT_DIR}/opencv.js" "${RELEASE_OUTPUT_DIR}/opencv.js" + +if [[ "${DISABLE_SINGLE_FILE}" == "true" && -f "${BUILT_DIR}/opencv.wasm" ]]; then + cp "${BUILT_DIR}/opencv.wasm" "${RELEASE_OUTPUT_DIR}/opencv.wasm" + echo " Copied opencv.js + opencv.wasm" +else + echo " Copied opencv.js" +fi + +if [[ "${BUILD_LOADER}" == "true" && -f "${BUILT_DIR}/opencv_js_loader.js" ]]; then + cp "${BUILT_DIR}/opencv_js_loader.js" "${RELEASE_OUTPUT_DIR}/opencv_js_loader.js" + echo " Copied opencv_js_loader.js" +fi + +# ============================================================================= +# Generate metadata files +# ============================================================================= +BUILD_DATE="$(date -u '+%Y-%m-%dT%H:%M:%SZ')" + +cat > "${RELEASE_OUTPUT_DIR}/BUILD_INFO.txt" <")" + +cat > "${RELEASE_OUTPUT_DIR}/SOURCE_INFO.txt" <")" + cat >> "${RELEASE_OUTPUT_DIR}/SOURCE_INFO.txt" <> "${RELEASE_OUTPUT_DIR}/SOURCE_INFO.txt" < on the command line. +OPENCV_DEFAULT_DOCKER_IMAGE="emscripten/emsdk:3.1.54" + +# Returns the recommended Docker image for the given OpenCV version. +get_docker_image() { + local version="$1" + case "${version}" in + 4.10.0) echo "emscripten/emsdk:3.1.54" ;; + 4.11.0) echo "emscripten/emsdk:3.1.54" ;; + 4.12.0) echo "emscripten/emsdk:3.1.54" ;; + 4.13.0) echo "emscripten/emsdk:3.1.54" ;; + *) echo "emscripten/emsdk" ;; + esac +} + +# ─── Validation helpers ────────────────────────────────────────────────────── + +# Returns 0 if VERSION is in the SUPPORTED_RELEASES list, 1 otherwise. +is_supported_version() { + local version="$1" + for v in "${SUPPORTED_RELEASES[@]}"; do + if [[ "$v" == "$version" ]]; then + return 0 + fi + done + return 1 +} + +# Validates a version string and exits with an error if invalid. +# Rejects: blank, branch names, SHAs, non-allowlisted versions. +validate_version() { + local version="$1" + local script_name="${2:-build-opencv-js-release.sh}" + + if [[ -z "$version" ]]; then + echo "ERROR: --version is required." >&2 + echo "Supported releases: ${SUPPORTED_RELEASES[*]}" >&2 + exit 1 + fi + + # Reject obvious branch-like names + if [[ "$version" == "main" || "$version" == "master" || "$version" == "HEAD" ]]; then + echo "ERROR: '$version' is a branch name, not a release version." >&2 + echo "Only official release versions are supported." >&2 + exit 1 + fi + + # Reject SHA-like strings (40-char hex or short hex >= 7 chars with no dots) + if [[ "$version" =~ ^[0-9a-f]{7,40}$ ]]; then + echo "ERROR: '$version' looks like a commit SHA. Only release versions are supported." >&2 + exit 1 + fi + + # Must match semver pattern x.y.z + if ! [[ "$version" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]]; then + echo "ERROR: '$version' is not a valid semver release version (expected x.y.z)." >&2 + exit 1 + fi + + if ! is_supported_version "$version"; then + echo "ERROR: OpenCV version '$version' is not in the supported release list." >&2 + echo "Supported releases: ${SUPPORTED_RELEASES[*]}" >&2 + echo "To add support for a new release, update SUPPORTED_RELEASES in scripts/helpers/opencv-release-config.sh" >&2 + exit 1 + fi +} + +# ─── Naming helpers ────────────────────────────────────────────────────────── + +# Returns the package name for the given version and contrib flag. +# Usage: get_package_name "4.13.0" "true" → "OpenCV-4.13.0-contrib" +# Usage: get_package_name "4.13.0" "false" → "OpenCV-4.13.0" +get_package_name() { + local version="$1" + local with_contrib="${2:-false}" + + if [[ "$with_contrib" == "true" ]]; then + echo "OpenCV-${version}-contrib" + else + echo "OpenCV-${version}" + fi +} + +# ─── Clone command helpers ──────────────────────────────────────────────────── + +# Prints the shallow clone command for opencv. +build_opencv_clone_cmd() { + local version="$1" + local dest="$2" + echo "git clone --branch ${version} --depth 1 --single-branch ${OPENCV_REPO_URL} ${dest}" +} + +# Prints the shallow clone command for opencv_contrib. +build_contrib_clone_cmd() { + local version="$1" + local dest="$2" + echo "git clone --branch ${version} --depth 1 --single-branch ${OPENCV_CONTRIB_REPO_URL} ${dest}" +} diff --git a/scripts/test-all-versions.sh b/scripts/test-all-versions.sh new file mode 100755 index 0000000..6d9e5f7 --- /dev/null +++ b/scripts/test-all-versions.sh @@ -0,0 +1,142 @@ +#!/usr/bin/env bash +# This file is part of opencv-js-react project from Robomous. +# It is subject to the license terms in the LICENSE file found in the top-level directory + +# ============================================================================= +# test-all-versions.sh +# +# Validates all supported OpenCV.js artifacts by running vendor → verify → build +# for each version × variant combination (standard + contrib). +# +# Usage: +# bash scripts/test-all-versions.sh +# pnpm run test:all-versions +# ============================================================================= +set -uo pipefail + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +REPO_ROOT="$(cd "${SCRIPT_DIR}/.." && pwd)" + +cd "${REPO_ROOT}" + +VERSIONS=("4.10.0" "4.11.0" "4.12.0" "4.13.0") +VARIANTS=("standard" "contrib") + +PASS=0 +FAIL=0 +declare -a RESULTS + +echo "============================================================" +echo " opencv-js-react: Testing all OpenCV.js versions" +echo "============================================================" +echo "" + +for version in "${VERSIONS[@]}"; do + for variant in "${VARIANTS[@]}"; do + label="${version}" + vendor_args="--version ${version}" + verify_args="--version ${version}" + contrib_flag="" + + if [[ "${variant}" == "contrib" ]]; then + label="${version}-contrib" + vendor_args="${vendor_args} --with-contrib" + contrib_flag=" --with-contrib" + fi + + echo "──────────────────────────────────────────────" + echo " Testing OpenCV-${label}" + echo "──────────────────────────────────────────────" + + # ── 1. Clean previous vendored file for this version ──────────────────── + rm -f "src/assets/opencv/opencv-${version}.min.js" + rm -f "src/assets/opencv/opencv-${version}.wasm" + + # ── 2. Vendor ──────────────────────────────────────────────────────────── + echo " → Vendor..." + if ! node scripts/vendor-opencv.mjs ${vendor_args}; then + RESULTS+=("FAIL OpenCV-${label} (vendor failed)") + ((FAIL++)) || true + continue + fi + + # ── 3. Verify ──────────────────────────────────────────────────────────── + echo " → Verify..." + if ! node scripts/verify-opencv-version.mjs ${verify_args}; then + RESULTS+=("FAIL OpenCV-${label} (verify failed)") + ((FAIL++)) || true + rm -f "src/assets/opencv/opencv-${version}.min.js" + continue + fi + + # ── 4. Build (tsup — clean:true wipes dist/ each time) ────────────────── + echo " → Build..." + if ! pnpm run build --silent 2>/dev/null; then + RESULTS+=("FAIL OpenCV-${label} (build failed)") + ((FAIL++)) || true + rm -f "src/assets/opencv/opencv-${version}.min.js" + continue + fi + + # ── 5. Check dist output ───────────────────────────────────────────────── + DIST_FILE="dist/assets/opencv/opencv-${version}.min.js" + if [[ -f "${DIST_FILE}" ]]; then + size=$(du -sh "${DIST_FILE}" 2>/dev/null | cut -f1) + RESULTS+=("PASS OpenCV-${label} (dist: ${size})") + ((PASS++)) || true + else + RESULTS+=("FAIL OpenCV-${label} (missing from dist: ${DIST_FILE})") + ((FAIL++)) || true + fi + + # ── 6. Clean up vendored file ──────────────────────────────────────────── + rm -f "src/assets/opencv/opencv-${version}.min.js" + rm -f "src/assets/opencv/opencv-${version}.wasm" + + echo "" + done +done + +# ============================================================================= +# Run tests and typecheck once (version-independent) +# ============================================================================= +echo "──────────────────────────────────────────────" +echo " Running test suite (version-independent)" +echo "──────────────────────────────────────────────" +if pnpm run test; then + RESULTS+=("PASS Tests") + ((PASS++)) || true +else + RESULTS+=("FAIL Tests") + ((FAIL++)) || true +fi + +echo "" +echo "──────────────────────────────────────────────" +echo " Running typecheck" +echo "──────────────────────────────────────────────" +if pnpm run typecheck; then + RESULTS+=("PASS Typecheck") + ((PASS++)) || true +else + RESULTS+=("FAIL Typecheck") + ((FAIL++)) || true +fi + +# ============================================================================= +# Results table +# ============================================================================= +echo "" +echo "============================================================" +echo " Results" +echo "============================================================" +for result in "${RESULTS[@]}"; do + echo " ${result}" +done +echo "" +echo " ${PASS} passed, ${FAIL} failed" +echo "============================================================" + +if [[ "${FAIL}" -gt 0 ]]; then + exit 1 +fi diff --git a/scripts/vendor-opencv.mjs b/scripts/vendor-opencv.mjs new file mode 100644 index 0000000..c339dbb --- /dev/null +++ b/scripts/vendor-opencv.mjs @@ -0,0 +1,73 @@ +#!/usr/bin/env node +// This file is part of opencv-js-react project from Robomous. +// It is subject to the license terms in the LICENSE file found in the top-level directory + +/** + * Vendors the locally-built OpenCV.js release artifact into src/assets/opencv/. + * + * Run AFTER scripts/build-opencv-js-release.sh has produced a build: + * pnpm run build:opencv:release -- --version 4.13.0 + * pnpm run vendor:opencv + * + * Usage: + * pnpm run vendor:opencv [--version ] [--with-contrib] + * + * If --version is not specified, defaults to 4.13.0. + */ + +import { copyFile, mkdir, stat } from 'node:fs/promises'; +import { existsSync } from 'node:fs'; +import { join } from 'node:path'; +import { fileURLToPath } from 'node:url'; + +const ROOT = fileURLToPath(new URL('..', import.meta.url)); +const DEFAULT_VERSION = '4.13.0'; + +// ─── Parse args ────────────────────────────────────────────────────────────── +const args = process.argv.slice(2); +let version = DEFAULT_VERSION; +let withContrib = false; + +for (let i = 0; i < args.length; i++) { + if (args[i] === '--version' && args[i + 1]) { + version = args[++i]; + } else if (args[i] === '--with-contrib') { + withContrib = true; + } +} + +// ─── Resolve paths ─────────────────────────────────────────────────────────── +const packageName = withContrib ? `OpenCV-${version}-contrib` : `OpenCV-${version}`; +const releaseDir = join(ROOT, 'build', 'opencv-js', 'releases', packageName); +const srcFile = join(releaseDir, 'opencv.js'); +const destDir = join(ROOT, 'src', 'assets', 'opencv'); +const destFile = join(destDir, `opencv-${version}.min.js`); + +// ─── Validate source exists ────────────────────────────────────────────────── +if (!existsSync(srcFile)) { + console.error(`ERROR: Built opencv.js not found at:\n ${srcFile}`); + console.error(`\nRun the build first:\n pnpm run build:opencv:release -- --version ${version}${withContrib ? ' --with-contrib' : ''}`); + process.exit(1); +} + +const srcStat = await stat(srcFile); +if (srcStat.size === 0) { + console.error(`ERROR: Built opencv.js at ${srcFile} is empty.`); + process.exit(1); +} + +// ─── Copy to src/assets ────────────────────────────────────────────────────── +await mkdir(destDir, { recursive: true }); +await copyFile(srcFile, destFile); + +const destStat = await stat(destFile); +console.log(`✓ Vendored ${packageName}/opencv.js → src/assets/opencv/opencv-${version}.min.js`); +console.log(` Size: ${(destStat.size / 1024).toFixed(1)} KB`); + +// Also copy .wasm if present (for --disable_single_file builds) +const wasmSrc = join(releaseDir, 'opencv.wasm'); +if (existsSync(wasmSrc)) { + const wasmDest = join(destDir, `opencv-${version}.wasm`); + await copyFile(wasmSrc, wasmDest); + console.log(`✓ Also copied opencv.wasm → src/assets/opencv/opencv-${version}.wasm`); +} diff --git a/scripts/verify-opencv-version.mjs b/scripts/verify-opencv-version.mjs new file mode 100644 index 0000000..4b372a4 --- /dev/null +++ b/scripts/verify-opencv-version.mjs @@ -0,0 +1,57 @@ +#!/usr/bin/env node +// This file is part of opencv-js-react project from Robomous. +// It is subject to the license terms in the LICENSE file found in the top-level directory + +/** + * Verifies that the vendored OpenCV.js asset exists and is non-empty. + * Exits with code 1 if the asset is missing or empty. + * + * Run as part of prepack: pnpm run verify:opencv + * + * Usage: + * pnpm run verify:opencv + * pnpm run verify:opencv -- --version 4.10.0 + */ + +import { stat } from 'node:fs/promises'; +import { existsSync } from 'node:fs'; +import { join } from 'node:path'; +import { fileURLToPath } from 'node:url'; + +const ROOT = fileURLToPath(new URL('..', import.meta.url)); +const DEFAULT_VERSION = '4.13.0'; + +// ─── Parse args ────────────────────────────────────────────────────────────── +const args = process.argv.slice(2); +let version = DEFAULT_VERSION; + +for (let i = 0; i < args.length; i++) { + if (args[i] === '--version' && args[i + 1]) { + version = args[++i]; + } +} + +const ASSET_PATH = join(ROOT, 'src', 'assets', 'opencv', `opencv-${version}.min.js`); +const MIN_SIZE_BYTES = 1024; // At least 1KB to rule out empty/placeholder files + +if (!existsSync(ASSET_PATH)) { + console.error(`ERROR: Vendored OpenCV.js asset not found at:`); + console.error(` ${ASSET_PATH}`); + console.error(`\nTo generate it:`); + console.error(` 1. pnpm run build:opencv:release -- --version ${version}`); + console.error(` 2. pnpm run vendor:opencv -- --version ${version}`); + process.exit(1); +} + +const fileStat = await stat(ASSET_PATH); + +if (fileStat.size < MIN_SIZE_BYTES) { + console.error(`ERROR: Vendored OpenCV.js asset is too small (${fileStat.size} bytes).`); + console.error(` Expected a full OpenCV.js build (typically several MB).`); + console.error(` Path: ${ASSET_PATH}`); + process.exit(1); +} + +console.log(`✓ OpenCV ${version} vendored asset verified`); +console.log(` Path: src/assets/opencv/opencv-${version}.min.js`); +console.log(` Size: ${(fileStat.size / 1024 / 1024).toFixed(2)} MB`); From 42235958abd6e3bd9cbafff570d09eaf0da0893d Mon Sep 17 00:00:00 2001 From: Jesus Armando Anaya Date: Sun, 12 Apr 2026 19:39:12 -0700 Subject: [PATCH 5/7] Add CI workflows and Dependabot configuration --- .github/dependabot.yml | 32 ++++++++++++++++++++ .github/workflows/ci.yml | 45 ++++++++++++++++++++++++++++ .github/workflows/release.yml | 56 +++++++++++++++++++++++++++++++++++ 3 files changed, 133 insertions(+) create mode 100644 .github/dependabot.yml create mode 100644 .github/workflows/ci.yml create mode 100644 .github/workflows/release.yml diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000..b3d9033 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,32 @@ +# This file is part of opencv-js-react project from Robomous. +# It is subject to the license terms in the LICENSE file found in the top-level directory + +version: 2 +updates: + # Keep npm dependencies up to date + - package-ecosystem: "npm" + directory: "/" + schedule: + interval: "weekly" + day: "monday" + open-pull-requests-limit: 10 + groups: + typescript-eslint: + patterns: + - "@typescript-eslint/*" + testing: + patterns: + - "@testing-library/*" + - "vitest" + - "jsdom" + types: + patterns: + - "@types/*" + + # Keep GitHub Actions up to date + - package-ecosystem: "github-actions" + directory: "/" + schedule: + interval: "weekly" + day: "monday" + open-pull-requests-limit: 5 diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..136535d --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,45 @@ +name: CI + +on: + pull_request: + push: + branches: + - main + +jobs: + ci: + runs-on: ubuntu-latest + permissions: + contents: read + steps: + - name: Check out repository + uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 + + - name: Set up pnpm + uses: pnpm/action-setup@b906affcce14559ad1aafd4ab0e942779e9f58b1 # v4 + with: + version: 10 + + - name: Set up Node.js + uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4 + with: + node-version: 22 + cache: pnpm + + - name: Install dependencies + run: pnpm install --frozen-lockfile + + - name: Audit dependencies + run: pnpm audit --audit-level moderate + + - name: Lint + run: pnpm run lint + + - name: Typecheck + run: pnpm run typecheck + + - name: Test + run: pnpm run test + + - name: Build + run: pnpm run build diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..d789579 --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,56 @@ +name: Release + +on: + release: + types: + - published + +jobs: + publish: + runs-on: ubuntu-latest + permissions: + contents: read + id-token: write # Required for npm trusted publishing (OIDC) + steps: + - name: Check out repository + uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 + + - name: Set up pnpm + uses: pnpm/action-setup@b906affcce14559ad1aafd4ab0e942779e9f58b1 # v4 + with: + version: 10 + + - name: Set up Node.js + uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4 + with: + node-version: 22 + registry-url: 'https://registry.npmjs.org' + cache: pnpm + + - name: Install dependencies + run: pnpm install --frozen-lockfile + + - name: Audit dependencies + run: pnpm audit --audit-level moderate + + - name: Lint + run: pnpm run lint + + - name: Typecheck + run: pnpm run typecheck + + - name: Test + run: pnpm run test + + - name: Build + run: pnpm run build + + - name: Publish to npm + run: pnpm publish --provenance --access public --no-git-checks + # Uses npm trusted publishing via OIDC — no NODE_AUTH_TOKEN needed. + # To configure: https://docs.npmjs.com/trusted-publishers/ + # + # Fallback (if trusted publishing is not yet configured): + # Set NODE_AUTH_TOKEN secret in repository settings and uncomment: + # env: + # NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} From 89b881250ed38c5b278f719a62ab70a98074b276 Mon Sep 17 00:00:00 2001 From: Jesus Armando Anaya Date: Sun, 12 Apr 2026 19:39:15 -0700 Subject: [PATCH 6/7] Update .gitignore and README for opencv-js-react --- .gitignore | 21 +++- README.md | 311 ++++++++++++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 328 insertions(+), 4 deletions(-) diff --git a/.gitignore b/.gitignore index db5b88b..9cf811d 100644 --- a/.gitignore +++ b/.gitignore @@ -35,7 +35,7 @@ bower_components .lock-wscript # Compiled binary addons (https://nodejs.org/api/addons.html) -build/Release +build/ # Dependency directories node_modules/ @@ -142,9 +142,26 @@ dist vite.config.js.timestamp-* vite.config.ts.timestamp-* +# Build output +dist/ + +# OpenCV local build artifacts (keep .gitkeep) +build/opencv-js/checkouts/ +build/opencv-js/releases/ + +# Vendored OpenCV asset (produced by build + vendor workflow) +src/assets/opencv/*.js + +# OS files +.DS_Store +Thumbs.db + +# Prompt / instruction files +cloudcode_*.md + # Vibe code .agents/ .cursor/ .claude/ CLAUDE.md -AGENTS.md \ No newline at end of file +AGENTS.md diff --git a/README.md b/README.md index 42bb75d..a741fd1 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,309 @@ -# opencv-react -A React wrapper for the official OpenCV.js library. +Robomous.ai + +--- + +# opencv-js-react + +A React wrapper for the official OpenCV.js library — bring computer vision to your React apps using WebAssembly. + +## Features + +- **Two simple primitives**: `useOpenCV` hook and `OpenCVCanvas` component +- **Lazy loading**: OpenCV.js is loaded on demand, not at startup +- **SSR safe**: no browser APIs accessed during server render +- **Singleton loading**: the script is injected exactly once, regardless of how many components use it +- **Dual-canvas workflow**: read from an input canvas, write to an optional separate output canvas +- **Memory leak prevention**: clear documentation and examples for `Mat.delete()` cleanup +- **No bundled binary**: loads OpenCV.js from the official CDN by default; easy to swap for a self-hosted or contrib build + +## Why this package + +OpenCV.js is a powerful WebAssembly port of OpenCV, but integrating it into React requires managing async script loading, WASM initialization timing, canvas lifecycle, and memory cleanup. This package handles all of that so you can focus on writing your image processing logic. + +This package is **browser-oriented**. It is not a generic Node.js image processing wrapper. + +## Installation + +```bash +# pnpm (recommended) +pnpm add opencv-js-react + +# npm +npm install opencv-js-react + +# yarn +yarn add opencv-js-react +``` + +**Peer dependencies** (install separately if not already present): + +```bash +pnpm add react react-dom +``` + +## Quick start + +```tsx +import { OpenCVCanvas } from 'opencv-js-react'; + +export default function App() { + return ( + { + let src = null; + let gray = null; + try { + src = cv.imread(canvas); + gray = new cv.Mat(); + cv.cvtColor(src, gray, cv.COLOR_RGBA2GRAY, 0); + cv.imshow(canvas, gray); + } finally { + if (src) src.delete(); + if (gray) gray.delete(); + } + }} + /> + ); +} +``` + +## Full example: separate input and output canvases + +```tsx +import React, { useRef } from 'react'; +import { OpenCVCanvas } from 'opencv-js-react'; + +export default function Demo() { + const outputCanvasRef = useRef(null); + + return ( +
+ { + let src = null; + let gray = null; + let dst = null; + let clahe = null; + + try { + src = cv.imread(canvas); + gray = new cv.Mat(); + dst = new cv.Mat(); + + cv.cvtColor(src, gray, cv.COLOR_RGBA2GRAY, 0); + clahe = new cv.CLAHE(2.0, new cv.Size(8, 8)); + clahe.apply(gray, dst); + + cv.imshow(outputCanvas || canvas, dst); + } finally { + if (src) src.delete(); + if (gray) gray.delete(); + if (dst) dst.delete(); + if (clahe) clahe.delete(); + } + }} + /> + + +
+ ); +} +``` + +## API reference + +### `useOpenCV(options?)` + +Hook that lazily loads OpenCV.js in the browser. + +```ts +const { cv, isReady, isLoading, error, load } = useOpenCV(options?); +``` + +**Options:** + +| Option | Type | Default | Description | +|--------|------|---------|-------------| +| `autoLoad` | `boolean` | `true` | Automatically load OpenCV when the component mounts | +| `src` | `string` | vendored asset | Override the OpenCV.js script URL | + +**Return value:** + +| Field | Type | Description | +|-------|------|-------------| +| `cv` | `OpenCVNamespace \| null` | The OpenCV namespace once ready, otherwise null | +| `isReady` | `boolean` | Whether OpenCV is fully initialized | +| `isLoading` | `boolean` | Whether OpenCV is currently loading | +| `error` | `Error \| null` | Load error, if any | +| `load` | `() => Promise` | Manually trigger loading | + +**Example:** + +```tsx +function ProcessButton() { + const { cv, isReady, isLoading, load } = useOpenCV({ autoLoad: false }); + + return ( + + ); +} +``` + +### `OpenCVCanvas` + +React component that renders an internal canvas, loads a source image, draws it, and calls your processing callback once OpenCV is ready. + +```tsx + { /* image drawn */ }} + onProcess={({ canvas, context, image, cv, outputCanvas }) => { /* process */ }} + onError={(error) => { /* handle error */ }} +/> +``` + +**Props:** + +| Prop | Type | Default | Description | +|------|------|---------|-------------| +| `src` | `string` | required | Source image URL | +| `outputCanvasRef` | `RefObject` | — | External canvas for processed output | +| `autoProcess` | `boolean` | `true` | Call `onProcess` automatically when ready | +| `className` | `string` | — | CSS class for the internal canvas | +| `crossOrigin` | `'' \| 'anonymous' \| 'use-credentials'` | — | Cross-origin attribute for the image | +| `onLoad` | `(payload: LoadPayload) => void` | — | Called after the image is drawn | +| `onProcess` | `(payload: ProcessPayload) => void \| Promise` | — | Called when OpenCV is ready and image is drawn | +| `onError` | `(error: Error) => void` | — | Called on image load or processing errors | + +**Process payload:** + +```ts +interface ProcessPayload { + canvas: HTMLCanvasElement; // internal input canvas (image is drawn here) + context: CanvasRenderingContext2D; + image: HTMLImageElement; + cv: OpenCVNamespace; // the OpenCV instance + outputCanvas?: HTMLCanvasElement; // external output canvas, if provided +} +``` + +**Important:** Always call `mat.delete()` for every `Mat` you create inside `onProcess` to prevent memory leaks. Use a `try/finally` block as shown in the examples. + +## OpenCV.js source + +`opencv-js-react` does not bundle an OpenCV.js binary. By default it loads from the official CDN: + +``` +https://docs.opencv.org/4.13.0/opencv.js +``` + +To use a different version, a contrib build, or a self-hosted file, pass the `src` option: + +```tsx +// Specific version from the official CDN +useOpenCV({ src: 'https://docs.opencv.org/4.10.0/opencv.js' }) + +// Self-hosted contrib build +useOpenCV({ src: '/assets/opencv-4.13.0-contrib.min.js' }) + +// Same option on OpenCVCanvas + +``` + +### Building a contrib or custom OpenCV.js (optional) + +Contrib modules (SIFT, SURF, etc.) are not available on the official CDN. Build them locally using Docker and the included build script: + +```bash +# Standard build +pnpm run build:opencv:release -- --version 4.13.0 + +# Contrib build +pnpm run build:opencv:release -- --version 4.13.0 --with-contrib + +# With separate .wasm file +pnpm run build:opencv:release -- --version 4.12.0 --disable-single-file +``` + +Supported versions: `4.10.0`, `4.11.0`, `4.12.0`, `4.13.0`. Output is written to `build/opencv-js/releases/`. Non-release versions (branches, SHAs, `main`) are rejected. To add a new release, add it to `SUPPORTED_RELEASES` in `scripts/helpers/opencv-release-config.sh`. + +## Local development + +```bash +git clone https://github.com/Robomous/opencv-js-react.git +cd opencv-js-react +pnpm install + +# Run tests +pnpm run test + +# Build the library +pnpm run build + +# Typecheck +pnpm run typecheck + +# Lint +pnpm run lint +``` + +Watch mode: + +```bash +pnpm run dev # tsup watch +pnpm run test:watch # vitest watch +``` + +## Testing + +```bash +pnpm run test # run all tests once +pnpm run test:watch # run in watch mode +``` + +Tests use [Vitest](https://vitest.dev/) with [Testing Library](https://testing-library.com/) and jsdom. + +## Publishing + +Publishing is triggered automatically when a GitHub release is published. The release workflow runs lint, typecheck, tests, and build, then publishes to npm using trusted publishing (OIDC). No OpenCV.js binary is required or included. + +To configure npm trusted publishing: +- See [npm trusted publishers documentation](https://docs.npmjs.com/trusted-publishers/) +- Enable it in your npm package settings for the `Robomous/opencv-js-react` GitHub repository + +Manual publish: + +```bash +pnpm run prepack # runs typecheck + test + build +pnpm publish --access public +``` + +## Contributing + +1. Fork and clone the repository +2. Install dependencies: `pnpm install` +3. Make your changes +4. Run tests before opening a PR: `pnpm run test` +5. Keep public API changes documented in the README +6. Open a pull request — CI runs automatically + +If you modify the Bash build workflow, preserve the release-only version validation and shallow-clone semantics. Non-release versions (branches, SHAs, `main`) must continue to be rejected. + +PRs trigger CI automatically. Merging to `main` + publishing a GitHub release triggers the npm publish. + +## License + +Apache-2.0 — see [LICENSE](./LICENSE) for details. + +## OpenCV.js sourcing note + +This package does not bundle OpenCV.js. The default CDN URL points to the official [OpenCV.js builds](https://docs.opencv.org/) hosted by the OpenCV project. When building a local artifact using the included scripts, it is compiled from the official [OpenCV source repository](https://github.com/opencv/opencv) via `platforms/js/build_js.py` and Emscripten. No modifications are made to the OpenCV source. OpenCV is licensed under the Apache 2.0 license. From 95234b6c43c4e716113a5fb46480579396855e6f Mon Sep 17 00:00:00 2001 From: Jesus Armando Anaya Date: Sun, 12 Apr 2026 19:39:18 -0700 Subject: [PATCH 7/7] Add usage examples --- examples/README.md | 68 ++++++++++++++++++ examples/basic-hook-usage.tsx | 35 +++++++++ examples/deferred-loading.tsx | 29 ++++++++ examples/edge-detection.tsx | 81 +++++++++++++++++++++ examples/grayscale-filter.tsx | 59 +++++++++++++++ examples/threshold-adjustment.tsx | 116 ++++++++++++++++++++++++++++++ 6 files changed, 388 insertions(+) create mode 100644 examples/README.md create mode 100644 examples/basic-hook-usage.tsx create mode 100644 examples/deferred-loading.tsx create mode 100644 examples/edge-detection.tsx create mode 100644 examples/grayscale-filter.tsx create mode 100644 examples/threshold-adjustment.tsx diff --git a/examples/README.md b/examples/README.md new file mode 100644 index 0000000..94ea4e6 --- /dev/null +++ b/examples/README.md @@ -0,0 +1,68 @@ +# opencv-js-react Examples + +Self-contained code examples showing how to use the `opencv-js-react` library. Copy any file into your own React project that has `opencv-js-react` installed. + +## Prerequisites + +```bash +npm install opencv-js-react react react-dom +# or +pnpm add opencv-js-react react react-dom +``` + +--- + +## Examples + +### [`basic-hook-usage.tsx`](./basic-hook-usage.tsx) +**`useOpenCV` — default automatic loading** + +The minimal usage pattern. OpenCV loads automatically on mount; the component renders loading/error/ready states. Access the full `cv` namespace once `isReady` is true. + +--- + +### [`deferred-loading.tsx`](./deferred-loading.tsx) +**`useOpenCV` — on-demand loading with `autoLoad: false`** + +Defers loading OpenCV until the user clicks a button. Useful for pages where OpenCV is optional — avoids downloading the ~8 MB WASM binary unless it's actually needed. `load()` is a no-op after the first call. + +--- + +### [`grayscale-filter.tsx`](./grayscale-filter.tsx) +**`OpenCVCanvas` — color-to-grayscale conversion** + +Loads an image and converts it to grayscale using `cv.cvtColor`. Shows the core `onProcess` callback pattern, before/after display with `outputCanvasRef`, and proper `Mat` cleanup with `delete()` to prevent memory leaks. + +--- + +### [`edge-detection.tsx`](./edge-detection.tsx) +**`OpenCVCanvas` — Canny edge detection** + +Multi-step processing pipeline: RGBA → gray → Gaussian blur → Canny edges → display. Shows how to chain operations with intermediate Mats and use `try/finally` to ensure all Mats are deleted even when a step throws. + +--- + +### [`threshold-adjustment.tsx`](./threshold-adjustment.tsx) +**`OpenCVCanvas` + `useOpenCV` — interactive binary threshold** + +Combines both exports with React state to re-process an image in real-time as the user moves a slider. Shows how to trigger re-processing manually outside of the `onProcess` callback, using refs to avoid stale closures. + +--- + +## Important: Memory Management + +Every `cv.Mat` you create **must be deleted** when you're done with it. OpenCV.js allocates Mats in WASM memory outside the JavaScript garbage collector. + +```tsx +const src = cv.imread(canvas); +const dst = new cv.Mat(); +try { + cv.cvtColor(src, dst, cv.COLOR_RGBA2GRAY); + cv.imshow(outputCanvas, dst); +} finally { + src.delete(); + dst.delete(); +} +``` + +Forgetting to call `delete()` causes a memory leak that grows with every re-render. diff --git a/examples/basic-hook-usage.tsx b/examples/basic-hook-usage.tsx new file mode 100644 index 0000000..b03f412 --- /dev/null +++ b/examples/basic-hook-usage.tsx @@ -0,0 +1,35 @@ +// This file is part of opencv-js-react project from Robomous. +// It is subject to the license terms in the LICENSE file found in the top-level directory + +/** + * Example: Basic hook usage + * + * The simplest way to use opencv-js-react — load OpenCV with the `useOpenCV` hook + * and access the `cv` namespace for processing. + * + * OpenCV.js loads once (singleton) and is shared across all hook instances in + * the app. Any component that calls useOpenCV() will receive the same instance. + */ +import { useOpenCV } from 'opencv-js-react'; + +export function BasicHookUsage() { + const { cv, isReady, isLoading, error } = useOpenCV(); + + if (isLoading) { + return

Loading OpenCV…

; + } + + if (error) { + return

Failed to load OpenCV: {error.message}

; + } + + if (!isReady || !cv) { + return null; + } + + // cv is the full OpenCV.js namespace — access any function via cv[name] + // or the typed helpers like cv.imread, cv.imshow, cv.cvtColor, etc. + console.log('OpenCV build info:', (cv as { getBuildInformation?: () => string }).getBuildInformation?.()); + + return

OpenCV is ready.

; +} diff --git a/examples/deferred-loading.tsx b/examples/deferred-loading.tsx new file mode 100644 index 0000000..92841cc --- /dev/null +++ b/examples/deferred-loading.tsx @@ -0,0 +1,29 @@ +// This file is part of opencv-js-react project from Robomous. +// It is subject to the license terms in the LICENSE file found in the top-level directory + +/** + * Example: Deferred (on-demand) loading + * + * Use `autoLoad: false` when you don't need OpenCV on page load. OpenCV.js is + * a large WASM binary (~8 MB), so deferring the load until the user actually + * needs it can improve initial page performance. + * + * Call `load()` from any event handler to trigger the download on demand. + * Subsequent calls to `load()` are no-ops — OpenCV loads exactly once. + */ +import { useOpenCV } from 'opencv-js-react'; + +export function DeferredLoading() { + const { isReady, isLoading, error, load } = useOpenCV({ autoLoad: false }); + + return ( +
+ + + {error &&

Error: {error.message}

} + {isReady &&

OpenCV loaded successfully.

} +
+ ); +} diff --git a/examples/edge-detection.tsx b/examples/edge-detection.tsx new file mode 100644 index 0000000..4883a98 --- /dev/null +++ b/examples/edge-detection.tsx @@ -0,0 +1,81 @@ +// This file is part of opencv-js-react project from Robomous. +// It is subject to the license terms in the LICENSE file found in the top-level directory + +/** + * Example: Edge detection (Canny) + * + * Runs the Canny edge detector on a source image using `OpenCVCanvas`. + * + * Pipeline: RGBA → gray → Gaussian blur → Canny → RGBA → display + * + * Key patterns shown: + * - Chaining multiple OpenCV operations with intermediate Mats + * - Deleting all intermediate Mats in a try/finally block to prevent leaks + * even when an operation throws + * - Using the full OpenCV.js API through the index signature: cv['GaussianBlur'] + */ +import { useRef } from 'react'; +import { OpenCVCanvas } from 'opencv-js-react'; +import type { OpenCVNamespace, ProcessPayload } from 'opencv-js-react'; + +const IMAGE_URL = 'https://upload.wikimedia.org/wikipedia/commons/thumb/4/47/PNG_transparency_demonstration_1.png/280px-PNG_transparency_demonstration_1.png'; + +function runCanny(cv: OpenCVNamespace, src: HTMLCanvasElement, dst: HTMLCanvasElement) { + const rgba = cv.imread(src); + const gray = new cv.Mat(); + const blurred = new cv.Mat(); + const edges = new cv.Mat(); + const output = new cv.Mat(); + + try { + cv.cvtColor(rgba, gray, cv.COLOR_RGBA2GRAY); + + // GaussianBlur is not in the minimal type declaration, so access via index + (cv as unknown as Record void>)['GaussianBlur']( + gray, blurred, new cv.Size(5, 5), 0 + ); + + // Canny(src, dst, threshold1, threshold2) + (cv as unknown as Record void>)['Canny']( + blurred, edges, 50, 150 + ); + + // Convert single-channel edge map back to RGBA for display + cv.cvtColor(edges, output, cv.COLOR_GRAY2RGBA); + cv.imshow(dst, output); + } finally { + // Clean up all Mats — always use try/finally so leaks don't accumulate on error + rgba.delete(); + gray.delete(); + blurred.delete(); + edges.delete(); + output.delete(); + } +} + +export function EdgeDetection() { + const outputRef = useRef(null); + + function handleProcess({ cv, canvas, outputCanvas }: ProcessPayload) { + if (!outputCanvas) return; + runCanny(cv, canvas, outputCanvas); + } + + return ( +
+
+

Original

+ console.error('Edge detection failed:', err)} + /> +
+
+

Canny edges

+ +
+
+ ); +} diff --git a/examples/grayscale-filter.tsx b/examples/grayscale-filter.tsx new file mode 100644 index 0000000..f87bcfb --- /dev/null +++ b/examples/grayscale-filter.tsx @@ -0,0 +1,59 @@ +// This file is part of opencv-js-react project from Robomous. +// It is subject to the license terms in the LICENSE file found in the top-level directory + +/** + * Example: Grayscale filter + * + * Converts a color image to grayscale using `OpenCVCanvas`. The component draws + * the source image onto an internal canvas, then calls `onProcess` once both + * the image and OpenCV are ready. + * + * Key patterns shown: + * - `outputCanvasRef` — send processed output to a separate canvas (before/after) + * - `cv.cvtColor` — color space conversion + * - `mat.delete()` — ALWAYS delete every Mat you create to avoid memory leaks + */ +import { useRef } from 'react'; +import { OpenCVCanvas } from 'opencv-js-react'; +import type { ProcessPayload } from 'opencv-js-react'; + +const IMAGE_URL = 'https://upload.wikimedia.org/wikipedia/commons/thumb/4/47/PNG_transparency_demonstration_1.png/280px-PNG_transparency_demonstration_1.png'; + +export function GrayscaleFilter() { + const outputRef = useRef(null); + + function handleProcess({ cv, canvas, outputCanvas }: ProcessPayload) { + if (!outputCanvas) return; + + const src = cv.imread(canvas); + const gray = new cv.Mat(); + + // Convert RGBA → grayscale, then back to RGBA so imshow can render it + cv.cvtColor(src, gray, cv.COLOR_RGBA2GRAY); + cv.cvtColor(gray, gray, cv.COLOR_GRAY2RGBA); + cv.imshow(outputCanvas, gray); + + // Delete every Mat you create — OpenCV.js uses WASM memory outside the GC + src.delete(); + gray.delete(); + } + + return ( +
+
+

Original

+ {/* OpenCVCanvas renders the source image on the internal canvas */} + console.error('Processing failed:', err)} + /> +
+
+

Grayscale

+ +
+
+ ); +} diff --git a/examples/threshold-adjustment.tsx b/examples/threshold-adjustment.tsx new file mode 100644 index 0000000..5f9bdea --- /dev/null +++ b/examples/threshold-adjustment.tsx @@ -0,0 +1,116 @@ +// This file is part of opencv-js-react project from Robomous. +// It is subject to the license terms in the LICENSE file found in the top-level directory + +/** + * Example: Interactive threshold adjustment + * + * Combines `useOpenCV` + `OpenCVCanvas` with React state to re-process an image + * in real-time as the user moves a slider. + * + * Key patterns shown: + * - `autoProcess: false` — prevent automatic processing on load; we control when + * processing runs by passing the threshold value through a ref + * - Using `useOpenCV` to check readiness before enabling controls + * - Storing the latest slider value in a ref so `onProcess` always reads the + * current value without creating stale closures + */ +import { useRef, useState } from 'react'; +import { OpenCVCanvas, useOpenCV } from 'opencv-js-react'; +import type { OpenCVNamespace, ProcessPayload } from 'opencv-js-react'; + +const IMAGE_URL = 'https://upload.wikimedia.org/wikipedia/commons/thumb/4/47/PNG_transparency_demonstration_1.png/280px-PNG_transparency_demonstration_1.png'; + +function applyThreshold( + cv: OpenCVNamespace, + src: HTMLCanvasElement, + dst: HTMLCanvasElement, + threshold: number, +) { + const rgba = cv.imread(src); + const gray = new cv.Mat(); + const binary = new cv.Mat(); + const output = new cv.Mat(); + + try { + cv.cvtColor(rgba, gray, cv.COLOR_RGBA2GRAY); + + // threshold(src, dst, thresh, maxval, type) — THRESH_BINARY = 0 + (cv as unknown as Record void>)['threshold']( + gray, binary, threshold, 255, 0 + ); + + cv.cvtColor(binary, output, cv.COLOR_GRAY2RGBA); + cv.imshow(dst, output); + } finally { + rgba.delete(); + gray.delete(); + binary.delete(); + output.delete(); + } +} + +export function ThresholdAdjustment() { + const { isReady } = useOpenCV(); + const [threshold, setThreshold] = useState(128); + + // Keep a ref so onProcess always reads the latest value without re-subscribing + const thresholdRef = useRef(threshold); + const outputRef = useRef(null); + + // Store the latest canvas so we can re-trigger processing from the slider + const canvasRef = useRef(null); + const cvRef = useRef(null); + + function handleProcess({ cv, canvas, outputCanvas }: ProcessPayload) { + if (!outputCanvas) return; + canvasRef.current = canvas; + cvRef.current = cv; + applyThreshold(cv, canvas, outputCanvas, thresholdRef.current); + } + + function handleSliderChange(value: number) { + thresholdRef.current = value; + setThreshold(value); + + // Re-run processing immediately with the new threshold + if (cvRef.current && canvasRef.current && outputRef.current) { + applyThreshold(cvRef.current, canvasRef.current, outputRef.current, value); + } + } + + return ( +
+
+ +
+ +
+
+

Original

+ {/* autoProcess triggers onProcess once on load; slider updates re-run it manually */} + console.error('Threshold failed:', err)} + /> +
+
+

Threshold (t={threshold})

+ +
+
+
+ ); +}