diff --git a/.github/workflows/functions-e2e-test.yml b/.github/workflows/functions-e2e-test.yml new file mode 100644 index 00000000..91b77b8a --- /dev/null +++ b/.github/workflows/functions-e2e-test.yml @@ -0,0 +1,97 @@ +name: Firebase Functions E2E Test + +on: + pull_request: + branches: + - "**" + paths: + - "firebase/dev/functions/**" + - ".github/workflows/functions-e2e-test.yml" + push: + branches: + - "main" + - "release/*" + paths: + - "firebase/dev/functions/**" + workflow_dispatch: + +env: + NODE_VERSION: "21" + JAVA_VERSION: "21" # Firebase Emulator用 + FUNCTIONS_DIR: firebase/functions + +jobs: + e2e-test: + runs-on: ubuntu-latest + + strategy: + matrix: + node-version: [21] + + steps: + # 1. リポジトリのチェックアウト + - name: Checkout repository + uses: actions/checkout@v4 + + # 2. Node.jsのセットアップ + - name: Setup Node.js ${{ matrix.node-version }} + uses: actions/setup-node@v4 + with: + node-version: ${{ matrix.node-version }} + cache: "npm" + cache-dependency-path: ${{ env.FUNCTIONS_DIR }}/package-lock.json + + # 3. Javaのセットアップ(Firebase Emulator用) + - name: Setup Java + uses: actions/setup-java@v4 + with: + distribution: "temurin" + java-version: ${{ env.JAVA_VERSION }} + + # 4. Firebase CLIのインストール + - name: Install Firebase CLI + run: npm install -g firebase-tools + + # 5. 依存関係のインストール + - name: Install dependencies + working-directory: ${{ env.FUNCTIONS_DIR }} + run: npm ci + + # 6. TypeScriptビルド + - name: Build TypeScript + working-directory: ${{ env.FUNCTIONS_DIR }} + run: npm run build + + # 7. Lintチェック + - name: Run lint + working-directory: ${{ env.FUNCTIONS_DIR }} + run: npm run lint + + # 8. E2Eテストの実行 + - name: Run E2E tests + working-directory: ${{ env.FUNCTIONS_DIR }} + run: npm run test:e2e + + # 9. テスト結果のアップロード(失敗時) + - name: Upload test results + if: failure() + uses: actions/upload-artifact@v4 + with: + name: test-results + path: | + ${{ env.FUNCTIONS_DIR }}/coverage/ + ${{ env.FUNCTIONS_DIR }}/firestore-debug.log + retention-days: 7 + + # 10. テストサマリーの作成 + - name: Create test summary + if: always() + run: | + echo "## Firebase Functions E2E Test Results" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + echo "✅ All E2E tests passed successfully!" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + echo "### Test Details" >> $GITHUB_STEP_SUMMARY + echo "- **Node Version**: ${{ matrix.node-version }}" >> $GITHUB_STEP_SUMMARY + echo "- **Java Version**: ${{ env.JAVA_VERSION }}" >> $GITHUB_STEP_SUMMARY + echo "- **Working Directory**: \`${{ env.FUNCTIONS_DIR }}\`" >> $GITHUB_STEP_SUMMARY diff --git a/Makefile b/Makefile new file mode 100644 index 00000000..e5305282 --- /dev/null +++ b/Makefile @@ -0,0 +1,17 @@ +.PHONY: help lint deploy emulator test-e2e + +help: ## ヘルプを表示 + @grep -E '^[a-zA-Z0-9_-]+:.*## .*' $(MAKEFILE_LIST) | awk 'BEGIN {FS = ":.*## "}; {printf "\033[36m%-20s\033[0m %s\n", $$1, $$2}' + +lint: ## ESLintを実行して自動修正 + cd firebase/functions && npm run lint -- --fix && cd ../.. + +deploy: ## Functionsをデプロイ + cd firebase/functions && npm run deploy && cd ../.. + +emulator: ## エミュレーターを起動 + cd firebase/functions && npm run serve && cd ../.. + +test-e2e: ## E2Eテストを実行 + cd firebase/functions && npm run test:e2e && cd ../.. + diff --git a/firebase/dev/.firebaserc b/firebase/.firebaserc similarity index 100% rename from firebase/dev/.firebaserc rename to firebase/.firebaserc diff --git a/firebase/dev/.gitignore b/firebase/.gitignore similarity index 100% rename from firebase/dev/.gitignore rename to firebase/.gitignore diff --git a/firebase/dev/firebase.json b/firebase/firebase.json similarity index 73% rename from firebase/dev/firebase.json rename to firebase/firebase.json index 798f59e4..a29cc358 100644 --- a/firebase/dev/firebase.json +++ b/firebase/firebase.json @@ -5,6 +5,20 @@ "rules": "firestore.rules", "indexes": "firestore.indexes.json" }, + "emulators": { + "auth": { + "port": 9099 + }, + "firestore": { + "port": 8080 + }, + "functions": { + "port": 5001 + }, + "ui": { + "enabled": true + } + }, "functions": [ { "source": "functions", diff --git a/firebase/dev/firestore.indexes.json b/firebase/firestore.indexes.json similarity index 100% rename from firebase/dev/firestore.indexes.json rename to firebase/firestore.indexes.json diff --git a/firebase/dev/firestore.rules b/firebase/firestore.rules similarity index 100% rename from firebase/dev/firestore.rules rename to firebase/firestore.rules diff --git a/firebase/dev/functions/.eslintrc.js b/firebase/functions/.eslintrc.js similarity index 89% rename from firebase/dev/functions/.eslintrc.js rename to firebase/functions/.eslintrc.js index 0f8e2a9b..147b7a83 100644 --- a/firebase/dev/functions/.eslintrc.js +++ b/firebase/functions/.eslintrc.js @@ -14,7 +14,7 @@ module.exports = { ], parser: "@typescript-eslint/parser", parserOptions: { - project: ["tsconfig.json", "tsconfig.dev.json"], + project: ["tsconfig.json", "tsconfig.dev.json", "tsconfig.test.json"], sourceType: "module", }, ignorePatterns: [ diff --git a/firebase/dev/functions/.gitignore b/firebase/functions/.gitignore similarity index 100% rename from firebase/dev/functions/.gitignore rename to firebase/functions/.gitignore diff --git a/firebase/dev/functions/package-lock.json b/firebase/functions/package-lock.json similarity index 91% rename from firebase/dev/functions/package-lock.json rename to firebase/functions/package-lock.json index a6a06a7e..6ecf5dc5 100644 --- a/firebase/dev/functions/package-lock.json +++ b/firebase/functions/package-lock.json @@ -10,12 +10,15 @@ "firebase-functions": "^6.0.1" }, "devDependencies": { + "@types/jest": "^30.0.0", "@typescript-eslint/eslint-plugin": "^5.12.0", "@typescript-eslint/parser": "^5.12.0", "eslint": "^8.9.0", "eslint-config-google": "^0.14.0", "eslint-plugin-import": "^2.25.4", - "firebase-functions-test": "^3.1.0", + "firebase-functions-test": "^3.4.1", + "jest": "^30.2.0", + "ts-jest": "^29.4.6", "typescript": "^5.7.3" }, "engines": { @@ -28,7 +31,6 @@ "integrity": "sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@babel/helper-validator-identifier": "^7.27.1", "js-tokens": "^4.0.0", @@ -39,33 +41,29 @@ } }, "node_modules/@babel/compat-data": { - "version": "7.28.4", - "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.28.4.tgz", - "integrity": "sha512-YsmSKC29MJwf0gF8Rjjrg5LQCmyh+j/nD8/eP7f+BeoQTKYqs9RoWbjGOdy0+1Ekr68RJZMUOPVQaQisnIo4Rw==", + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.28.5.tgz", + "integrity": "sha512-6uFXyCayocRbqhZOB+6XcuZbkMNimwfVGFji8CTZnCzOHVGvDqzvitu1re2AU5LROliz7eQPhB8CpAMvnx9EjA==", "dev": true, - "license": "MIT", - "peer": true, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/core": { - "version": "7.28.4", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.28.4.tgz", - "integrity": "sha512-2BCOP7TN8M+gVDj7/ht3hsaO/B/n5oDbiAyyvnRlNOs+u1o+JWNYTQrmpuNp1/Wq2gcFrI01JAW+paEKDMx/CA==", + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.28.5.tgz", + "integrity": "sha512-e7jT4DxYvIDLk1ZHmU/m/mB19rex9sv0c2ftBtjSBv+kVM/902eh0fINUzD7UwLLNR+jU585GxUJ8/EBfAM5fw==", "dev": true, - "license": "MIT", - "peer": true, "dependencies": { "@babel/code-frame": "^7.27.1", - "@babel/generator": "^7.28.3", + "@babel/generator": "^7.28.5", "@babel/helper-compilation-targets": "^7.27.2", "@babel/helper-module-transforms": "^7.28.3", "@babel/helpers": "^7.28.4", - "@babel/parser": "^7.28.4", + "@babel/parser": "^7.28.5", "@babel/template": "^7.27.2", - "@babel/traverse": "^7.28.4", - "@babel/types": "^7.28.4", + "@babel/traverse": "^7.28.5", + "@babel/types": "^7.28.5", "@jridgewell/remapping": "^2.3.5", "convert-source-map": "^2.0.0", "debug": "^4.1.0", @@ -86,22 +84,18 @@ "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", "dev": true, - "license": "ISC", - "peer": true, "bin": { "semver": "bin/semver.js" } }, "node_modules/@babel/generator": { - "version": "7.28.3", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.28.3.tgz", - "integrity": "sha512-3lSpxGgvnmZznmBkCRnVREPUFJv2wrv9iAoFDvADJc0ypmdOxdUtcLeBgBJ6zE0PMeTKnxeQzyk0xTBq4Ep7zw==", + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.28.5.tgz", + "integrity": "sha512-3EwLFhZ38J4VyIP6WNtt2kUdW9dokXA9Cr4IVIFHuCpZ3H8/YFOl5JjZHisrn1fATPBmKKqXzDFvh9fUwHz6CQ==", "dev": true, - "license": "MIT", - "peer": true, "dependencies": { - "@babel/parser": "^7.28.3", - "@babel/types": "^7.28.2", + "@babel/parser": "^7.28.5", + "@babel/types": "^7.28.5", "@jridgewell/gen-mapping": "^0.3.12", "@jridgewell/trace-mapping": "^0.3.28", "jsesc": "^3.0.2" @@ -115,8 +109,6 @@ "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.27.2.tgz", "integrity": "sha512-2+1thGUUWWjLTYTHZWK1n8Yga0ijBz1XAhUXcKy81rd5g6yh7hGqMp45v7cadSbEHc9G3OTv45SyneRN3ps4DQ==", "dev": true, - "license": "MIT", - "peer": true, "dependencies": { "@babel/compat-data": "^7.27.2", "@babel/helper-validator-option": "^7.27.1", @@ -133,8 +125,6 @@ "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", "dev": true, - "license": "ISC", - "peer": true, "bin": { "semver": "bin/semver.js" } @@ -144,8 +134,6 @@ "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.28.0.tgz", "integrity": "sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==", "dev": true, - "license": "MIT", - "peer": true, "engines": { "node": ">=6.9.0" } @@ -155,8 +143,6 @@ "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.27.1.tgz", "integrity": "sha512-0gSFWUPNXNopqtIPQvlD5WgXYI5GY2kP2cCvoT8kczjbfcfuIljTbcWrulD1CIPIX2gt1wghbDy08yE1p+/r3w==", "dev": true, - "license": "MIT", - "peer": true, "dependencies": { "@babel/traverse": "^7.27.1", "@babel/types": "^7.27.1" @@ -170,8 +156,6 @@ "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.28.3.tgz", "integrity": "sha512-gytXUbs8k2sXS9PnQptz5o0QnpLL51SwASIORY6XaBKF88nsOT0Zw9szLqlSGQDP/4TljBAD5y98p2U1fqkdsw==", "dev": true, - "license": "MIT", - "peer": true, "dependencies": { "@babel/helper-module-imports": "^7.27.1", "@babel/helper-validator-identifier": "^7.27.1", @@ -189,8 +173,6 @@ "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.27.1.tgz", "integrity": "sha512-1gn1Up5YXka3YYAHGKpbideQ5Yjf1tDa9qYcgysz+cNCXukyLl6DjPXhD3VRwSb8c0J9tA4b2+rHEZtc6R0tlw==", "dev": true, - "license": "MIT", - "peer": true, "engines": { "node": ">=6.9.0" } @@ -200,19 +182,15 @@ "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==", "dev": true, - "license": "MIT", - "peer": true, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-validator-identifier": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.27.1.tgz", - "integrity": "sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow==", + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz", + "integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==", "dev": true, - "license": "MIT", - "peer": true, "engines": { "node": ">=6.9.0" } @@ -222,8 +200,6 @@ "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.27.1.tgz", "integrity": "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==", "dev": true, - "license": "MIT", - "peer": true, "engines": { "node": ">=6.9.0" } @@ -233,8 +209,6 @@ "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.28.4.tgz", "integrity": "sha512-HFN59MmQXGHVyYadKLVumYsA9dBFun/ldYxipEjzA4196jpLZd8UjEEBLkbEkvfYreDqJhZxYAWFPtrfhNpj4w==", "dev": true, - "license": "MIT", - "peer": true, "dependencies": { "@babel/template": "^7.27.2", "@babel/types": "^7.28.4" @@ -244,14 +218,12 @@ } }, "node_modules/@babel/parser": { - "version": "7.28.4", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.28.4.tgz", - "integrity": "sha512-yZbBqeM6TkpP9du/I2pUZnJsRMGGvOuIrhjzC1AwHwW+6he4mni6Bp/m8ijn0iOuZuPI2BfkCoSRunpyjnrQKg==", + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.28.5.tgz", + "integrity": "sha512-KKBU1VGYR7ORr3At5HAtUQ+TV3SzRCXmA/8OdDZiLDBIZxVyzXuztPjfLd3BV1PRAQGCMWWSHYhL0F8d5uHBDQ==", "dev": true, - "license": "MIT", - "peer": true, "dependencies": { - "@babel/types": "^7.28.4" + "@babel/types": "^7.28.5" }, "bin": { "parser": "bin/babel-parser.js" @@ -265,8 +237,6 @@ "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.8.4.tgz", "integrity": "sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw==", "dev": true, - "license": "MIT", - "peer": true, "dependencies": { "@babel/helper-plugin-utils": "^7.8.0" }, @@ -279,8 +249,6 @@ "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-bigint/-/plugin-syntax-bigint-7.8.3.tgz", "integrity": "sha512-wnTnFlG+YxQm3vDxpGE57Pj0srRU4sHE/mDkt1qv2YJJSeUAec2ma4WLUnUPeKjyrfntVwe/N6dCXpU+zL3Npg==", "dev": true, - "license": "MIT", - "peer": true, "dependencies": { "@babel/helper-plugin-utils": "^7.8.0" }, @@ -293,8 +261,6 @@ "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.12.13.tgz", "integrity": "sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA==", "dev": true, - "license": "MIT", - "peer": true, "dependencies": { "@babel/helper-plugin-utils": "^7.12.13" }, @@ -307,8 +273,6 @@ "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-static-block/-/plugin-syntax-class-static-block-7.14.5.tgz", "integrity": "sha512-b+YyPmr6ldyNnM6sqYeMWE+bgJcJpO6yS4QD7ymxgH34GBPNDM/THBh8iunyvKIZztiwLH4CJZ0RxTk9emgpjw==", "dev": true, - "license": "MIT", - "peer": true, "dependencies": { "@babel/helper-plugin-utils": "^7.14.5" }, @@ -324,8 +288,6 @@ "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-attributes/-/plugin-syntax-import-attributes-7.27.1.tgz", "integrity": "sha512-oFT0FrKHgF53f4vOsZGi2Hh3I35PfSmVs4IBFLFj4dnafP+hIWDLg3VyKmUHfLoLHlyxY4C7DGtmHuJgn+IGww==", "dev": true, - "license": "MIT", - "peer": true, "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" }, @@ -341,8 +303,6 @@ "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-meta/-/plugin-syntax-import-meta-7.10.4.tgz", "integrity": "sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g==", "dev": true, - "license": "MIT", - "peer": true, "dependencies": { "@babel/helper-plugin-utils": "^7.10.4" }, @@ -355,8 +315,6 @@ "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.8.3.tgz", "integrity": "sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA==", "dev": true, - "license": "MIT", - "peer": true, "dependencies": { "@babel/helper-plugin-utils": "^7.8.0" }, @@ -369,8 +327,6 @@ "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.27.1.tgz", "integrity": "sha512-y8YTNIeKoyhGd9O0Jiyzyyqk8gdjnumGTQPsz0xOZOQ2RmkVJeZ1vmmfIvFEKqucBG6axJGBZDE/7iI5suUI/w==", "dev": true, - "license": "MIT", - "peer": true, "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" }, @@ -386,8 +342,6 @@ "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-logical-assignment-operators/-/plugin-syntax-logical-assignment-operators-7.10.4.tgz", "integrity": "sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig==", "dev": true, - "license": "MIT", - "peer": true, "dependencies": { "@babel/helper-plugin-utils": "^7.10.4" }, @@ -400,8 +354,6 @@ "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-nullish-coalescing-operator/-/plugin-syntax-nullish-coalescing-operator-7.8.3.tgz", "integrity": "sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ==", "dev": true, - "license": "MIT", - "peer": true, "dependencies": { "@babel/helper-plugin-utils": "^7.8.0" }, @@ -414,8 +366,6 @@ "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-numeric-separator/-/plugin-syntax-numeric-separator-7.10.4.tgz", "integrity": "sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug==", "dev": true, - "license": "MIT", - "peer": true, "dependencies": { "@babel/helper-plugin-utils": "^7.10.4" }, @@ -428,8 +378,6 @@ "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.8.3.tgz", "integrity": "sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA==", "dev": true, - "license": "MIT", - "peer": true, "dependencies": { "@babel/helper-plugin-utils": "^7.8.0" }, @@ -442,8 +390,6 @@ "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.8.3.tgz", "integrity": "sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q==", "dev": true, - "license": "MIT", - "peer": true, "dependencies": { "@babel/helper-plugin-utils": "^7.8.0" }, @@ -456,8 +402,6 @@ "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-chaining/-/plugin-syntax-optional-chaining-7.8.3.tgz", "integrity": "sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg==", "dev": true, - "license": "MIT", - "peer": true, "dependencies": { "@babel/helper-plugin-utils": "^7.8.0" }, @@ -470,8 +414,6 @@ "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-private-property-in-object/-/plugin-syntax-private-property-in-object-7.14.5.tgz", "integrity": "sha512-0wVnp9dxJ72ZUJDV27ZfbSj6iHLoytYZmh3rFcxNnvsJF3ktkzLDZPy/mA17HGsaQT3/DQsWYX1f1QGWkCoVUg==", "dev": true, - "license": "MIT", - "peer": true, "dependencies": { "@babel/helper-plugin-utils": "^7.14.5" }, @@ -487,8 +429,6 @@ "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.14.5.tgz", "integrity": "sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw==", "dev": true, - "license": "MIT", - "peer": true, "dependencies": { "@babel/helper-plugin-utils": "^7.14.5" }, @@ -504,8 +444,6 @@ "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.27.1.tgz", "integrity": "sha512-xfYCBMxveHrRMnAWl1ZlPXOZjzkN82THFvLhQhFXFt81Z5HnN+EtUkZhv/zcKpmT3fzmWZB0ywiBrbC3vogbwQ==", "dev": true, - "license": "MIT", - "peer": true, "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" }, @@ -521,8 +459,6 @@ "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.27.2.tgz", "integrity": "sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw==", "dev": true, - "license": "MIT", - "peer": true, "dependencies": { "@babel/code-frame": "^7.27.1", "@babel/parser": "^7.27.2", @@ -533,19 +469,17 @@ } }, "node_modules/@babel/traverse": { - "version": "7.28.4", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.28.4.tgz", - "integrity": "sha512-YEzuboP2qvQavAcjgQNVgsvHIDv6ZpwXvcvjmyySP2DIMuByS/6ioU5G9pYrWHM6T2YDfc7xga9iNzYOs12CFQ==", + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.28.5.tgz", + "integrity": "sha512-TCCj4t55U90khlYkVV/0TfkJkAkUg3jZFA3Neb7unZT8CPok7iiRfaX0F+WnqWqt7OxhOn0uBKXCw4lbL8W0aQ==", "dev": true, - "license": "MIT", - "peer": true, "dependencies": { "@babel/code-frame": "^7.27.1", - "@babel/generator": "^7.28.3", + "@babel/generator": "^7.28.5", "@babel/helper-globals": "^7.28.0", - "@babel/parser": "^7.28.4", + "@babel/parser": "^7.28.5", "@babel/template": "^7.27.2", - "@babel/types": "^7.28.4", + "@babel/types": "^7.28.5", "debug": "^4.3.1" }, "engines": { @@ -553,15 +487,13 @@ } }, "node_modules/@babel/types": { - "version": "7.28.4", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.28.4.tgz", - "integrity": "sha512-bkFqkLhh3pMBUQQkpVgWDWq/lqzc2678eUyDlTBhRqhCHFguYYGM0Efga7tYk4TogG/3x0EEl66/OQ+WGbWB/Q==", + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.28.5.tgz", + "integrity": "sha512-qQ5m48eI/MFLQ5PxQj4PFaprjyCTLI37ElWMmNs0K8Lk3dVeOdNpB3ks8jc7yM5CDmVC73eMVk/trk3fgmrUpA==", "dev": true, - "license": "MIT", - "peer": true, "dependencies": { "@babel/helper-string-parser": "^7.27.1", - "@babel/helper-validator-identifier": "^7.27.1" + "@babel/helper-validator-identifier": "^7.28.5" }, "engines": { "node": ">=6.9.0" @@ -571,31 +503,25 @@ "version": "0.2.3", "resolved": "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz", "integrity": "sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==", - "dev": true, - "license": "MIT", - "peer": true + "dev": true }, "node_modules/@emnapi/core": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.5.0.tgz", - "integrity": "sha512-sbP8GzB1WDzacS8fgNPpHlp6C9VZe+SJP3F90W9rLemaQj2PzIuTEl1qDOYQf58YIpyjViI24y9aPWCjEzY2cg==", + "version": "1.7.1", + "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.7.1.tgz", + "integrity": "sha512-o1uhUASyo921r2XtHYOHy7gdkGLge8ghBEQHMWmyJFoXlpU58kIrhhN3w26lpQb6dspetweapMn2CSNwQ8I4wg==", "dev": true, - "license": "MIT", "optional": true, - "peer": true, "dependencies": { "@emnapi/wasi-threads": "1.1.0", "tslib": "^2.4.0" } }, "node_modules/@emnapi/runtime": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.5.0.tgz", - "integrity": "sha512-97/BJ3iXHww3djw6hYIfErCZFee7qCtrneuLa20UXFCOTCfBM2cvQHjWJ2EG0s0MtdNwInarqCTz35i4wWXHsQ==", + "version": "1.7.1", + "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.7.1.tgz", + "integrity": "sha512-PVtJr5CmLwYAU9PZDMITZoR5iAOShYREoR45EyyLrbntV50mdePTgUn4AmOw90Ifcj+x2kRjdzr1HP3RrNiHGA==", "dev": true, - "license": "MIT", "optional": true, - "peer": true, "dependencies": { "tslib": "^2.4.0" } @@ -605,9 +531,7 @@ "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.1.0.tgz", "integrity": "sha512-WI0DdZ8xFSbgMjR1sFsKABJ/C5OnRrjT06JXbZKexJGrDuPTzZdDYfFlsgcCXCyf+suG5QU2e/y1Wo2V/OapLQ==", "dev": true, - "license": "MIT", "optional": true, - "peer": true, "dependencies": { "tslib": "^2.4.0" } @@ -930,8 +854,6 @@ "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", "dev": true, - "license": "ISC", - "peer": true, "dependencies": { "string-width": "^5.1.2", "string-width-cjs": "npm:string-width@^4.2.0", @@ -949,8 +871,6 @@ "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz", "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==", "dev": true, - "license": "MIT", - "peer": true, "engines": { "node": ">=12" }, @@ -963,8 +883,6 @@ "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.2.tgz", "integrity": "sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA==", "dev": true, - "license": "MIT", - "peer": true, "dependencies": { "ansi-regex": "^6.0.1" }, @@ -980,8 +898,6 @@ "resolved": "https://registry.npmjs.org/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz", "integrity": "sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ==", "dev": true, - "license": "ISC", - "peer": true, "dependencies": { "camelcase": "^5.3.1", "find-up": "^4.1.0", @@ -998,8 +914,6 @@ "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", "dev": true, - "license": "MIT", - "peer": true, "dependencies": { "sprintf-js": "~1.0.2" } @@ -1009,8 +923,6 @@ "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", "dev": true, - "license": "MIT", - "peer": true, "dependencies": { "locate-path": "^5.0.0", "path-exists": "^4.0.0" @@ -1020,12 +932,10 @@ } }, "node_modules/@istanbuljs/load-nyc-config/node_modules/js-yaml": { - "version": "3.14.1", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz", - "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==", + "version": "3.14.2", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.2.tgz", + "integrity": "sha512-PMSmkqxr106Xa156c2M265Z+FTrPl+oxd/rgOQy2tijQeK5TxQ43psO1ZCwhVOSdnn+RzkzlRz/eY4BgJBYVpg==", "dev": true, - "license": "MIT", - "peer": true, "dependencies": { "argparse": "^1.0.7", "esprima": "^4.0.0" @@ -1039,8 +949,6 @@ "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", "dev": true, - "license": "MIT", - "peer": true, "dependencies": { "p-locate": "^4.1.0" }, @@ -1053,8 +961,6 @@ "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", "dev": true, - "license": "MIT", - "peer": true, "dependencies": { "p-try": "^2.0.0" }, @@ -1070,8 +976,6 @@ "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", "dev": true, - "license": "MIT", - "peer": true, "dependencies": { "p-limit": "^2.2.0" }, @@ -1084,8 +988,6 @@ "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", "dev": true, - "license": "MIT", - "peer": true, "engines": { "node": ">=8" } @@ -1095,25 +997,21 @@ "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.3.tgz", "integrity": "sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==", "dev": true, - "license": "MIT", - "peer": true, "engines": { "node": ">=8" } }, "node_modules/@jest/console": { - "version": "30.1.2", - "resolved": "https://registry.npmjs.org/@jest/console/-/console-30.1.2.tgz", - "integrity": "sha512-BGMAxj8VRmoD0MoA/jo9alMXSRoqW8KPeqOfEo1ncxnRLatTBCpRoOwlwlEMdudp68Q6WSGwYrrLtTGOh8fLzw==", + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/@jest/console/-/console-30.2.0.tgz", + "integrity": "sha512-+O1ifRjkvYIkBqASKWgLxrpEhQAAE7hY77ALLUufSk5717KfOShg6IbqLmdsLMPdUiFvA2kTs0R7YZy+l0IzZQ==", "dev": true, - "license": "MIT", - "peer": true, "dependencies": { - "@jest/types": "30.0.5", + "@jest/types": "30.2.0", "@types/node": "*", "chalk": "^4.1.2", - "jest-message-util": "30.1.0", - "jest-util": "30.0.5", + "jest-message-util": "30.2.0", + "jest-util": "30.2.0", "slash": "^3.0.0" }, "engines": { @@ -1121,40 +1019,38 @@ } }, "node_modules/@jest/core": { - "version": "30.1.3", - "resolved": "https://registry.npmjs.org/@jest/core/-/core-30.1.3.tgz", - "integrity": "sha512-LIQz7NEDDO1+eyOA2ZmkiAyYvZuo6s1UxD/e2IHldR6D7UYogVq3arTmli07MkENLq6/3JEQjp0mA8rrHHJ8KQ==", + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/@jest/core/-/core-30.2.0.tgz", + "integrity": "sha512-03W6IhuhjqTlpzh/ojut/pDB2LPRygyWX8ExpgHtQA8H/3K7+1vKmcINx5UzeOX1se6YEsBsOHQ1CRzf3fOwTQ==", "dev": true, - "license": "MIT", - "peer": true, "dependencies": { - "@jest/console": "30.1.2", + "@jest/console": "30.2.0", "@jest/pattern": "30.0.1", - "@jest/reporters": "30.1.3", - "@jest/test-result": "30.1.3", - "@jest/transform": "30.1.2", - "@jest/types": "30.0.5", + "@jest/reporters": "30.2.0", + "@jest/test-result": "30.2.0", + "@jest/transform": "30.2.0", + "@jest/types": "30.2.0", "@types/node": "*", "ansi-escapes": "^4.3.2", "chalk": "^4.1.2", "ci-info": "^4.2.0", "exit-x": "^0.2.2", "graceful-fs": "^4.2.11", - "jest-changed-files": "30.0.5", - "jest-config": "30.1.3", - "jest-haste-map": "30.1.0", - "jest-message-util": "30.1.0", + "jest-changed-files": "30.2.0", + "jest-config": "30.2.0", + "jest-haste-map": "30.2.0", + "jest-message-util": "30.2.0", "jest-regex-util": "30.0.1", - "jest-resolve": "30.1.3", - "jest-resolve-dependencies": "30.1.3", - "jest-runner": "30.1.3", - "jest-runtime": "30.1.3", - "jest-snapshot": "30.1.2", - "jest-util": "30.0.5", - "jest-validate": "30.1.0", - "jest-watcher": "30.1.3", + "jest-resolve": "30.2.0", + "jest-resolve-dependencies": "30.2.0", + "jest-runner": "30.2.0", + "jest-runtime": "30.2.0", + "jest-snapshot": "30.2.0", + "jest-util": "30.2.0", + "jest-validate": "30.2.0", + "jest-watcher": "30.2.0", "micromatch": "^4.0.8", - "pretty-format": "30.0.5", + "pretty-format": "30.2.0", "slash": "^3.0.0" }, "engines": { @@ -1174,51 +1070,43 @@ "resolved": "https://registry.npmjs.org/@jest/diff-sequences/-/diff-sequences-30.0.1.tgz", "integrity": "sha512-n5H8QLDJ47QqbCNn5SuFjCRDrOLEZ0h8vAHCK5RL9Ls7Xa8AQLa/YxAc9UjFqoEDM48muwtBGjtMY5cr0PLDCw==", "dev": true, - "license": "MIT", - "peer": true, "engines": { "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, "node_modules/@jest/environment": { - "version": "30.1.2", - "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-30.1.2.tgz", - "integrity": "sha512-N8t1Ytw4/mr9uN28OnVf0SYE2dGhaIxOVYcwsf9IInBKjvofAjbFRvedvBBlyTYk2knbJTiEjEJ2PyyDIBnd9w==", + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-30.2.0.tgz", + "integrity": "sha512-/QPTL7OBJQ5ac09UDRa3EQes4gt1FTEG/8jZ/4v5IVzx+Cv7dLxlVIvfvSVRiiX2drWyXeBjkMSR8hvOWSog5g==", "dev": true, - "license": "MIT", - "peer": true, "dependencies": { - "@jest/fake-timers": "30.1.2", - "@jest/types": "30.0.5", + "@jest/fake-timers": "30.2.0", + "@jest/types": "30.2.0", "@types/node": "*", - "jest-mock": "30.0.5" + "jest-mock": "30.2.0" }, "engines": { "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, "node_modules/@jest/expect": { - "version": "30.1.2", - "resolved": "https://registry.npmjs.org/@jest/expect/-/expect-30.1.2.tgz", - "integrity": "sha512-tyaIExOwQRCxPCGNC05lIjWJztDwk2gPDNSDGg1zitXJJ8dC3++G/CRjE5mb2wQsf89+lsgAgqxxNpDLiCViTA==", + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/@jest/expect/-/expect-30.2.0.tgz", + "integrity": "sha512-V9yxQK5erfzx99Sf+7LbhBwNWEZ9eZay8qQ9+JSC0TrMR1pMDHLMY+BnVPacWU6Jamrh252/IKo4F1Xn/zfiqA==", "dev": true, - "license": "MIT", - "peer": true, "dependencies": { - "expect": "30.1.2", - "jest-snapshot": "30.1.2" + "expect": "30.2.0", + "jest-snapshot": "30.2.0" }, "engines": { "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, "node_modules/@jest/expect-utils": { - "version": "30.1.2", - "resolved": "https://registry.npmjs.org/@jest/expect-utils/-/expect-utils-30.1.2.tgz", - "integrity": "sha512-HXy1qT/bfdjCv7iC336ExbqqYtZvljrV8odNdso7dWK9bSeHtLlvwWWC3YSybSPL03Gg5rug6WLCZAZFH72m0A==", + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/@jest/expect-utils/-/expect-utils-30.2.0.tgz", + "integrity": "sha512-1JnRfhqpD8HGpOmQp180Fo9Zt69zNtC+9lR+kT7NVL05tNXIi+QC8Csz7lfidMoVLPD3FnOtcmp0CEFnxExGEA==", "dev": true, - "license": "MIT", - "peer": true, "dependencies": { "@jest/get-type": "30.1.0" }, @@ -1227,19 +1115,17 @@ } }, "node_modules/@jest/fake-timers": { - "version": "30.1.2", - "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-30.1.2.tgz", - "integrity": "sha512-Beljfv9AYkr9K+ETX9tvV61rJTY706BhBUtiaepQHeEGfe0DbpvUA5Z3fomwc5Xkhns6NWrcFDZn+72fLieUnA==", + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-30.2.0.tgz", + "integrity": "sha512-HI3tRLjRxAbBy0VO8dqqm7Hb2mIa8d5bg/NJkyQcOk7V118ObQML8RC5luTF/Zsg4474a+gDvhce7eTnP4GhYw==", "dev": true, - "license": "MIT", - "peer": true, "dependencies": { - "@jest/types": "30.0.5", + "@jest/types": "30.2.0", "@sinonjs/fake-timers": "^13.0.0", "@types/node": "*", - "jest-message-util": "30.1.0", - "jest-mock": "30.0.5", - "jest-util": "30.0.5" + "jest-message-util": "30.2.0", + "jest-mock": "30.2.0", + "jest-util": "30.2.0" }, "engines": { "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" @@ -1251,23 +1137,20 @@ "integrity": "sha512-eMbZE2hUnx1WV0pmURZY9XoXPkUYjpc55mb0CrhtdWLtzMQPFvu/rZkTLZFTsdaVQa+Tr4eWAteqcUzoawq/uA==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, "node_modules/@jest/globals": { - "version": "30.1.2", - "resolved": "https://registry.npmjs.org/@jest/globals/-/globals-30.1.2.tgz", - "integrity": "sha512-teNTPZ8yZe3ahbYnvnVRDeOjr+3pu2uiAtNtrEsiMjVPPj+cXd5E/fr8BL7v/T7F31vYdEHrI5cC/2OoO/vM9A==", + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/@jest/globals/-/globals-30.2.0.tgz", + "integrity": "sha512-b63wmnKPaK+6ZZfpYhz9K61oybvbI1aMcIs80++JI1O1rR1vaxHUCNqo3ITu6NU0d4V34yZFoHMn/uoKr/Rwfw==", "dev": true, - "license": "MIT", - "peer": true, "dependencies": { - "@jest/environment": "30.1.2", - "@jest/expect": "30.1.2", - "@jest/types": "30.0.5", - "jest-mock": "30.0.5" + "@jest/environment": "30.2.0", + "@jest/expect": "30.2.0", + "@jest/types": "30.2.0", + "jest-mock": "30.2.0" }, "engines": { "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" @@ -1279,7 +1162,6 @@ "integrity": "sha512-gWp7NfQW27LaBQz3TITS8L7ZCQ0TLvtmI//4OwlQRx4rnWxcPNIYjxZpDcN4+UlGxgm3jS5QPz8IPTCkb59wZA==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@types/node": "*", "jest-regex-util": "30.0.1" @@ -1289,18 +1171,16 @@ } }, "node_modules/@jest/reporters": { - "version": "30.1.3", - "resolved": "https://registry.npmjs.org/@jest/reporters/-/reporters-30.1.3.tgz", - "integrity": "sha512-VWEQmJWfXMOrzdFEOyGjUEOuVXllgZsoPtEHZzfdNz18RmzJ5nlR6kp8hDdY8dDS1yGOXAY7DHT+AOHIPSBV0w==", + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/@jest/reporters/-/reporters-30.2.0.tgz", + "integrity": "sha512-DRyW6baWPqKMa9CzeiBjHwjd8XeAyco2Vt8XbcLFjiwCOEKOvy82GJ8QQnJE9ofsxCMPjH4MfH8fCWIHHDKpAQ==", "dev": true, - "license": "MIT", - "peer": true, "dependencies": { "@bcoe/v8-coverage": "^0.2.3", - "@jest/console": "30.1.2", - "@jest/test-result": "30.1.3", - "@jest/transform": "30.1.2", - "@jest/types": "30.0.5", + "@jest/console": "30.2.0", + "@jest/test-result": "30.2.0", + "@jest/transform": "30.2.0", + "@jest/types": "30.2.0", "@jridgewell/trace-mapping": "^0.3.25", "@types/node": "*", "chalk": "^4.1.2", @@ -1313,9 +1193,9 @@ "istanbul-lib-report": "^3.0.0", "istanbul-lib-source-maps": "^5.0.0", "istanbul-reports": "^3.1.3", - "jest-message-util": "30.1.0", - "jest-util": "30.0.5", - "jest-worker": "30.1.0", + "jest-message-util": "30.2.0", + "jest-util": "30.2.0", + "jest-worker": "30.2.0", "slash": "^3.0.0", "string-length": "^4.0.2", "v8-to-istanbul": "^9.0.1" @@ -1338,7 +1218,6 @@ "integrity": "sha512-DmdYgtezMkh3cpU8/1uyXakv3tJRcmcXxBOcO0tbaozPwpmh4YMsnWrQm9ZmZMfa5ocbxzbFk6O4bDPEc/iAnA==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@sinclair/typebox": "^0.34.0" }, @@ -1347,14 +1226,12 @@ } }, "node_modules/@jest/snapshot-utils": { - "version": "30.1.2", - "resolved": "https://registry.npmjs.org/@jest/snapshot-utils/-/snapshot-utils-30.1.2.tgz", - "integrity": "sha512-vHoMTpimcPSR7OxS2S0V1Cpg8eKDRxucHjoWl5u4RQcnxqQrV3avETiFpl8etn4dqxEGarBeHbIBety/f8mLXw==", + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/@jest/snapshot-utils/-/snapshot-utils-30.2.0.tgz", + "integrity": "sha512-0aVxM3RH6DaiLcjj/b0KrIBZhSX1373Xci4l3cW5xiUWPctZ59zQ7jj4rqcJQ/Z8JuN/4wX3FpJSa3RssVvCug==", "dev": true, - "license": "MIT", - "peer": true, "dependencies": { - "@jest/types": "30.0.5", + "@jest/types": "30.2.0", "chalk": "^4.1.2", "graceful-fs": "^4.2.11", "natural-compare": "^1.4.0" @@ -1368,8 +1245,6 @@ "resolved": "https://registry.npmjs.org/@jest/source-map/-/source-map-30.0.1.tgz", "integrity": "sha512-MIRWMUUR3sdbP36oyNyhbThLHyJ2eEDClPCiHVbrYAe5g3CHRArIVpBw7cdSB5fr+ofSfIb2Tnsw8iEHL0PYQg==", "dev": true, - "license": "MIT", - "peer": true, "dependencies": { "@jridgewell/trace-mapping": "^0.3.25", "callsites": "^3.1.0", @@ -1380,15 +1255,13 @@ } }, "node_modules/@jest/test-result": { - "version": "30.1.3", - "resolved": "https://registry.npmjs.org/@jest/test-result/-/test-result-30.1.3.tgz", - "integrity": "sha512-P9IV8T24D43cNRANPPokn7tZh0FAFnYS2HIfi5vK18CjRkTDR9Y3e1BoEcAJnl4ghZZF4Ecda4M/k41QkvurEQ==", + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/@jest/test-result/-/test-result-30.2.0.tgz", + "integrity": "sha512-RF+Z+0CCHkARz5HT9mcQCBulb1wgCP3FBvl9VFokMX27acKphwyQsNuWH3c+ojd1LeWBLoTYoxF0zm6S/66mjg==", "dev": true, - "license": "MIT", - "peer": true, "dependencies": { - "@jest/console": "30.1.2", - "@jest/types": "30.0.5", + "@jest/console": "30.2.0", + "@jest/types": "30.2.0", "@types/istanbul-lib-coverage": "^2.0.6", "collect-v8-coverage": "^1.0.2" }, @@ -1397,16 +1270,14 @@ } }, "node_modules/@jest/test-sequencer": { - "version": "30.1.3", - "resolved": "https://registry.npmjs.org/@jest/test-sequencer/-/test-sequencer-30.1.3.tgz", - "integrity": "sha512-82J+hzC0qeQIiiZDThh+YUadvshdBswi5nuyXlEmXzrhw5ZQSRHeQ5LpVMD/xc8B3wPePvs6VMzHnntxL+4E3w==", + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/@jest/test-sequencer/-/test-sequencer-30.2.0.tgz", + "integrity": "sha512-wXKgU/lk8fKXMu/l5Hog1R61bL4q5GCdT6OJvdAFz1P+QrpoFuLU68eoKuVc4RbrTtNnTL5FByhWdLgOPSph+Q==", "dev": true, - "license": "MIT", - "peer": true, "dependencies": { - "@jest/test-result": "30.1.3", + "@jest/test-result": "30.2.0", "graceful-fs": "^4.2.11", - "jest-haste-map": "30.1.0", + "jest-haste-map": "30.2.0", "slash": "^3.0.0" }, "engines": { @@ -1414,24 +1285,22 @@ } }, "node_modules/@jest/transform": { - "version": "30.1.2", - "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-30.1.2.tgz", - "integrity": "sha512-UYYFGifSgfjujf1Cbd3iU/IQoSd6uwsj8XHj5DSDf5ERDcWMdJOPTkHWXj4U+Z/uMagyOQZ6Vne8C4nRIrCxqA==", + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-30.2.0.tgz", + "integrity": "sha512-XsauDV82o5qXbhalKxD7p4TZYYdwcaEXC77PPD2HixEFF+6YGppjrAAQurTl2ECWcEomHBMMNS9AH3kcCFx8jA==", "dev": true, - "license": "MIT", - "peer": true, "dependencies": { "@babel/core": "^7.27.4", - "@jest/types": "30.0.5", + "@jest/types": "30.2.0", "@jridgewell/trace-mapping": "^0.3.25", - "babel-plugin-istanbul": "^7.0.0", + "babel-plugin-istanbul": "^7.0.1", "chalk": "^4.1.2", "convert-source-map": "^2.0.0", "fast-json-stable-stringify": "^2.1.0", "graceful-fs": "^4.2.11", - "jest-haste-map": "30.1.0", + "jest-haste-map": "30.2.0", "jest-regex-util": "30.0.1", - "jest-util": "30.0.5", + "jest-util": "30.2.0", "micromatch": "^4.0.8", "pirates": "^4.0.7", "slash": "^3.0.0", @@ -1442,12 +1311,10 @@ } }, "node_modules/@jest/types": { - "version": "30.0.5", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-30.0.5.tgz", - "integrity": "sha512-aREYa3aku9SSnea4aX6bhKn4bgv3AXkgijoQgbYV3yvbiGt6z+MQ85+6mIhx9DsKW2BuB/cLR/A+tcMThx+KLQ==", + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-30.2.0.tgz", + "integrity": "sha512-H9xg1/sfVvyfU7o3zMfBEjQ1gcsdeTMgqHoYdN79tuLqfTtuu7WckRA1R5whDwOzxaZAeMKTYWqP+WCAi0CHsg==", "dev": true, - "license": "MIT", - "peer": true, "dependencies": { "@jest/pattern": "30.0.1", "@jest/schemas": "30.0.5", @@ -1466,8 +1333,6 @@ "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", "dev": true, - "license": "MIT", - "peer": true, "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.0", "@jridgewell/trace-mapping": "^0.3.24" @@ -1478,8 +1343,6 @@ "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz", "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==", "dev": true, - "license": "MIT", - "peer": true, "dependencies": { "@jridgewell/gen-mapping": "^0.3.5", "@jridgewell/trace-mapping": "^0.3.24" @@ -1490,8 +1353,6 @@ "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", "dev": true, - "license": "MIT", - "peer": true, "engines": { "node": ">=6.0.0" } @@ -1500,17 +1361,13 @@ "version": "1.5.5", "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", - "dev": true, - "license": "MIT", - "peer": true + "dev": true }, "node_modules/@jridgewell/trace-mapping": { "version": "0.3.31", "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", "dev": true, - "license": "MIT", - "peer": true, "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", "@jridgewell/sourcemap-codec": "^1.4.14" @@ -1532,9 +1389,7 @@ "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-0.2.12.tgz", "integrity": "sha512-ZVWUcfwY4E/yPitQJl481FjFo3K22D6qF0DuFH6Y/nbnE11GY5uguDxZMGXPQ8WQ0128MXQD7TnfHyK4oWoIJQ==", "dev": true, - "license": "MIT", "optional": true, - "peer": true, "dependencies": { "@emnapi/core": "^1.4.3", "@emnapi/runtime": "^1.4.3", @@ -1594,9 +1449,7 @@ "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==", "dev": true, - "license": "MIT", "optional": true, - "peer": true, "engines": { "node": ">=14" } @@ -1606,8 +1459,6 @@ "resolved": "https://registry.npmjs.org/@pkgr/core/-/core-0.2.9.tgz", "integrity": "sha512-QNqXyfVS2wm9hweSYD2O7F0G06uurj9kZ96TRQE5Y9hU7+tgdZwIkbAKc5Ocy1HxEY2kuDQa6cQ1WRs/O5LFKA==", "dev": true, - "license": "MIT", - "peer": true, "engines": { "node": "^12.20.0 || ^14.18.0 || >=16.0.0" }, @@ -1691,16 +1542,13 @@ "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.34.41.tgz", "integrity": "sha512-6gS8pZzSXdyRHTIqoqSVknxolr1kzfy4/CeDnrzsVz8TTIWUbOBr6gnzOmTYJ3eXQNh4IYHIGi5aIL7sOZ2G/g==", "dev": true, - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/@sinonjs/commons": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-3.0.1.tgz", "integrity": "sha512-K3mCHKQ9sVh8o1C9cxkwxaOmXoAMlDxC1mYyHrjqOWEcBjYr76t96zL2zlj5dUGZ3HSw240X1qgH3Mjf1yJWpQ==", "dev": true, - "license": "BSD-3-Clause", - "peer": true, "dependencies": { "type-detect": "4.0.8" } @@ -1710,8 +1558,6 @@ "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-13.0.5.tgz", "integrity": "sha512-36/hTbH2uaWuGVERyC6da9YwGWnzUZXuPro/F2LfsdOsLnCojz/iSH8MxUt/FD2S5XBSVPhmArFUXcpCQ2Hkiw==", "dev": true, - "license": "BSD-3-Clause", - "peer": true, "dependencies": { "@sinonjs/commons": "^3.0.1" } @@ -1731,9 +1577,7 @@ "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.1.tgz", "integrity": "sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg==", "dev": true, - "license": "MIT", "optional": true, - "peer": true, "dependencies": { "tslib": "^2.4.0" } @@ -1743,8 +1587,6 @@ "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz", "integrity": "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==", "dev": true, - "license": "MIT", - "peer": true, "dependencies": { "@babel/parser": "^7.20.7", "@babel/types": "^7.20.7", @@ -1758,8 +1600,6 @@ "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.27.0.tgz", "integrity": "sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg==", "dev": true, - "license": "MIT", - "peer": true, "dependencies": { "@babel/types": "^7.0.0" } @@ -1769,8 +1609,6 @@ "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.4.tgz", "integrity": "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==", "dev": true, - "license": "MIT", - "peer": true, "dependencies": { "@babel/parser": "^7.1.0", "@babel/types": "^7.0.0" @@ -1781,8 +1619,6 @@ "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.28.0.tgz", "integrity": "sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q==", "dev": true, - "license": "MIT", - "peer": true, "dependencies": { "@babel/types": "^7.28.2" } @@ -1857,8 +1693,7 @@ "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.6.tgz", "integrity": "sha512-2QF/t/auWm0lsy8XtKVPG19v3sSOQlJe/YHZgfjb/KBBHOGSV+J2q/S671rcq9uTBrLAXmZpqJiaQbMT+zNU1w==", "dev": true, - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/@types/istanbul-lib-report": { "version": "3.0.3", @@ -1866,7 +1701,6 @@ "integrity": "sha512-NQn7AHQnk/RSLOxrBbGyJM/aVQ+pjj5HCgasFxc0K/KhoATfQ/47AyUl15I2yBUpihjmas+a+VJBOqecrFH+uA==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@types/istanbul-lib-coverage": "*" } @@ -1877,11 +1711,20 @@ "integrity": "sha512-pk2B1NWalF9toCRu6gjBzR69syFjP4Od8WRAX+0mmf9lAjCRicLOWc+ZrxZHx/0XRjotgkF9t6iaMJ+aXcOdZQ==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@types/istanbul-lib-report": "*" } }, + "node_modules/@types/jest": { + "version": "30.0.0", + "resolved": "https://registry.npmjs.org/@types/jest/-/jest-30.0.0.tgz", + "integrity": "sha512-XTYugzhuwqWjws0CVz8QpM36+T+Dz5mTEBKhNs/esGLnCIlGdRy+Dq78NRjd7ls7r8BC8ZRMOrKlkO1hU0JOwA==", + "dev": true, + "dependencies": { + "expect": "^30.0.0", + "pretty-format": "^30.0.0" + } + }, "node_modules/@types/json-schema": { "version": "7.0.15", "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", @@ -1999,8 +1842,7 @@ "resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.3.tgz", "integrity": "sha512-9aEbYZ3TbYMznPdcdr3SmIrLXwC/AKZXQeCf9Pgao5CKb8CyHuEX5jzWPTkvregvhRJHcpRO6BFoGW9ycaOkYw==", "dev": true, - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/@types/tough-cookie": { "version": "4.0.5", @@ -2015,7 +1857,6 @@ "integrity": "sha512-WpxBCKWPLr4xSsHgz511rFJAM+wS28w2zEO1QDNY5zM/S8ok70NNfztH0xwhqKyaK0OHCbN98LDAZuy1ctxDkA==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@types/yargs-parser": "*" } @@ -2025,8 +1866,7 @@ "resolved": "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-21.0.3.tgz", "integrity": "sha512-I4q9QU9MQv4oEOz4tAHJtNz1cwuLxn2F3xcc2iV5WdqLPpUnj30aUuxt1mAxYTG+oe8CZMV/+6rU4S4gRDzqtQ==", "dev": true, - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/@typescript-eslint/eslint-plugin": { "version": "5.62.0", @@ -2239,12 +2079,10 @@ "arm" ], "dev": true, - "license": "MIT", "optional": true, "os": [ "android" - ], - "peer": true + ] }, "node_modules/@unrs/resolver-binding-android-arm64": { "version": "1.11.1", @@ -2254,12 +2092,10 @@ "arm64" ], "dev": true, - "license": "MIT", "optional": true, "os": [ "android" - ], - "peer": true + ] }, "node_modules/@unrs/resolver-binding-darwin-arm64": { "version": "1.11.1", @@ -2269,12 +2105,10 @@ "arm64" ], "dev": true, - "license": "MIT", "optional": true, "os": [ "darwin" - ], - "peer": true + ] }, "node_modules/@unrs/resolver-binding-darwin-x64": { "version": "1.11.1", @@ -2284,12 +2118,10 @@ "x64" ], "dev": true, - "license": "MIT", "optional": true, "os": [ "darwin" - ], - "peer": true + ] }, "node_modules/@unrs/resolver-binding-freebsd-x64": { "version": "1.11.1", @@ -2299,12 +2131,10 @@ "x64" ], "dev": true, - "license": "MIT", "optional": true, "os": [ "freebsd" - ], - "peer": true + ] }, "node_modules/@unrs/resolver-binding-linux-arm-gnueabihf": { "version": "1.11.1", @@ -2314,12 +2144,10 @@ "arm" ], "dev": true, - "license": "MIT", "optional": true, "os": [ "linux" - ], - "peer": true + ] }, "node_modules/@unrs/resolver-binding-linux-arm-musleabihf": { "version": "1.11.1", @@ -2329,12 +2157,10 @@ "arm" ], "dev": true, - "license": "MIT", "optional": true, "os": [ "linux" - ], - "peer": true + ] }, "node_modules/@unrs/resolver-binding-linux-arm64-gnu": { "version": "1.11.1", @@ -2344,12 +2170,10 @@ "arm64" ], "dev": true, - "license": "MIT", "optional": true, "os": [ "linux" - ], - "peer": true + ] }, "node_modules/@unrs/resolver-binding-linux-arm64-musl": { "version": "1.11.1", @@ -2359,12 +2183,10 @@ "arm64" ], "dev": true, - "license": "MIT", "optional": true, "os": [ "linux" - ], - "peer": true + ] }, "node_modules/@unrs/resolver-binding-linux-ppc64-gnu": { "version": "1.11.1", @@ -2374,12 +2196,10 @@ "ppc64" ], "dev": true, - "license": "MIT", "optional": true, "os": [ "linux" - ], - "peer": true + ] }, "node_modules/@unrs/resolver-binding-linux-riscv64-gnu": { "version": "1.11.1", @@ -2389,12 +2209,10 @@ "riscv64" ], "dev": true, - "license": "MIT", "optional": true, "os": [ "linux" - ], - "peer": true + ] }, "node_modules/@unrs/resolver-binding-linux-riscv64-musl": { "version": "1.11.1", @@ -2404,12 +2222,10 @@ "riscv64" ], "dev": true, - "license": "MIT", "optional": true, "os": [ "linux" - ], - "peer": true + ] }, "node_modules/@unrs/resolver-binding-linux-s390x-gnu": { "version": "1.11.1", @@ -2419,12 +2235,10 @@ "s390x" ], "dev": true, - "license": "MIT", "optional": true, "os": [ "linux" - ], - "peer": true + ] }, "node_modules/@unrs/resolver-binding-linux-x64-gnu": { "version": "1.11.1", @@ -2434,12 +2248,10 @@ "x64" ], "dev": true, - "license": "MIT", "optional": true, "os": [ "linux" - ], - "peer": true + ] }, "node_modules/@unrs/resolver-binding-linux-x64-musl": { "version": "1.11.1", @@ -2449,12 +2261,10 @@ "x64" ], "dev": true, - "license": "MIT", "optional": true, "os": [ "linux" - ], - "peer": true + ] }, "node_modules/@unrs/resolver-binding-wasm32-wasi": { "version": "1.11.1", @@ -2464,9 +2274,7 @@ "wasm32" ], "dev": true, - "license": "MIT", "optional": true, - "peer": true, "dependencies": { "@napi-rs/wasm-runtime": "^0.2.11" }, @@ -2482,12 +2290,10 @@ "arm64" ], "dev": true, - "license": "MIT", "optional": true, "os": [ "win32" - ], - "peer": true + ] }, "node_modules/@unrs/resolver-binding-win32-ia32-msvc": { "version": "1.11.1", @@ -2497,12 +2303,10 @@ "ia32" ], "dev": true, - "license": "MIT", "optional": true, "os": [ "win32" - ], - "peer": true + ] }, "node_modules/@unrs/resolver-binding-win32-x64-msvc": { "version": "1.11.1", @@ -2512,12 +2316,10 @@ "x64" ], "dev": true, - "license": "MIT", "optional": true, "os": [ "win32" - ], - "peer": true + ] }, "node_modules/abort-controller": { "version": "3.0.0", @@ -2600,8 +2402,6 @@ "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz", "integrity": "sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==", "dev": true, - "license": "MIT", - "peer": true, "dependencies": { "type-fest": "^0.21.3" }, @@ -2617,8 +2417,6 @@ "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz", "integrity": "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==", "dev": true, - "license": "(MIT OR CC0-1.0)", - "peer": true, "engines": { "node": ">=10" }, @@ -2657,8 +2455,6 @@ "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", "dev": true, - "license": "ISC", - "peer": true, "dependencies": { "normalize-path": "^3.0.0", "picomatch": "^2.0.4" @@ -2866,17 +2662,15 @@ } }, "node_modules/babel-jest": { - "version": "30.1.2", - "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-30.1.2.tgz", - "integrity": "sha512-IQCus1rt9kaSh7PQxLYRY5NmkNrNlU2TpabzwV7T2jljnpdHOcmnYYv8QmE04Li4S3a2Lj8/yXyET5pBarPr6g==", + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-30.2.0.tgz", + "integrity": "sha512-0YiBEOxWqKkSQWL9nNGGEgndoeL0ZpWrbLMNL5u/Kaxrli3Eaxlt3ZtIDktEvXt4L/R9r3ODr2zKwGM/2BjxVw==", "dev": true, - "license": "MIT", - "peer": true, "dependencies": { - "@jest/transform": "30.1.2", + "@jest/transform": "30.2.0", "@types/babel__core": "^7.20.5", - "babel-plugin-istanbul": "^7.0.0", - "babel-preset-jest": "30.0.1", + "babel-plugin-istanbul": "^7.0.1", + "babel-preset-jest": "30.2.0", "chalk": "^4.1.2", "graceful-fs": "^4.2.11", "slash": "^3.0.0" @@ -2885,7 +2679,7 @@ "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" }, "peerDependencies": { - "@babel/core": "^7.11.0" + "@babel/core": "^7.11.0 || ^8.0.0-0" } }, "node_modules/babel-plugin-istanbul": { @@ -2893,8 +2687,6 @@ "resolved": "https://registry.npmjs.org/babel-plugin-istanbul/-/babel-plugin-istanbul-7.0.1.tgz", "integrity": "sha512-D8Z6Qm8jCvVXtIRkBnqNHX0zJ37rQcFJ9u8WOS6tkYOsRdHBzypCstaxWiu5ZIlqQtviRYbgnRLSoCEvjqcqbA==", "dev": true, - "license": "BSD-3-Clause", - "peer": true, "workspaces": [ "test/babel-8" ], @@ -2910,15 +2702,11 @@ } }, "node_modules/babel-plugin-jest-hoist": { - "version": "30.0.1", - "resolved": "https://registry.npmjs.org/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-30.0.1.tgz", - "integrity": "sha512-zTPME3pI50NsFW8ZBaVIOeAxzEY7XHlmWeXXu9srI+9kNfzCUTy8MFan46xOGZY8NZThMqq+e3qZUKsvXbasnQ==", + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-30.2.0.tgz", + "integrity": "sha512-ftzhzSGMUnOzcCXd6WHdBGMyuwy15Wnn0iyyWGKgBDLxf9/s5ABuraCSpBX2uG0jUg4rqJnxsLc5+oYBqoxVaA==", "dev": true, - "license": "MIT", - "peer": true, "dependencies": { - "@babel/template": "^7.27.2", - "@babel/types": "^7.27.3", "@types/babel__core": "^7.20.5" }, "engines": { @@ -2930,8 +2718,6 @@ "resolved": "https://registry.npmjs.org/babel-preset-current-node-syntax/-/babel-preset-current-node-syntax-1.2.0.tgz", "integrity": "sha512-E/VlAEzRrsLEb2+dv8yp3bo4scof3l9nR4lrld+Iy5NyVqgVYUJnDAmunkhPMisRI32Qc4iRiz425d8vM++2fg==", "dev": true, - "license": "MIT", - "peer": true, "dependencies": { "@babel/plugin-syntax-async-generators": "^7.8.4", "@babel/plugin-syntax-bigint": "^7.8.3", @@ -2954,21 +2740,19 @@ } }, "node_modules/babel-preset-jest": { - "version": "30.0.1", - "resolved": "https://registry.npmjs.org/babel-preset-jest/-/babel-preset-jest-30.0.1.tgz", - "integrity": "sha512-+YHejD5iTWI46cZmcc/YtX4gaKBtdqCHCVfuVinizVpbmyjO3zYmeuyFdfA8duRqQZfgCAMlsfmkVbJ+e2MAJw==", + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/babel-preset-jest/-/babel-preset-jest-30.2.0.tgz", + "integrity": "sha512-US4Z3NOieAQumwFnYdUWKvUKh8+YSnS/gB3t6YBiz0bskpu7Pine8pPCheNxlPEW4wnUkma2a94YuW2q3guvCQ==", "dev": true, - "license": "MIT", - "peer": true, "dependencies": { - "babel-plugin-jest-hoist": "30.0.1", - "babel-preset-current-node-syntax": "^1.1.0" + "babel-plugin-jest-hoist": "30.2.0", + "babel-preset-current-node-syntax": "^1.2.0" }, "engines": { "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" }, "peerDependencies": { - "@babel/core": "^7.11.0" + "@babel/core": "^7.11.0 || ^8.0.0-beta.1" } }, "node_modules/balanced-match": { @@ -3000,12 +2784,10 @@ "optional": true }, "node_modules/baseline-browser-mapping": { - "version": "2.8.4", - "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.8.4.tgz", - "integrity": "sha512-L+YvJwGAgwJBV1p6ffpSTa2KRc69EeeYGYjRVWKs0GKrK+LON0GC0gV+rKSNtALEDvMDqkvCFq9r1r94/Gjwxw==", + "version": "2.9.11", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.9.11.tgz", + "integrity": "sha512-Sg0xJUNDU1sJNGdfGWhVHX0kkZ+HWcvmVymJbj6NSgZZmW/8S9Y2HQ5euytnIgakgxN6papOAWiwDo1ctFDcoQ==", "dev": true, - "license": "Apache-2.0", - "peer": true, "bin": { "baseline-browser-mapping": "dist/cli.js" } @@ -3084,9 +2866,9 @@ } }, "node_modules/browserslist": { - "version": "4.26.0", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.26.0.tgz", - "integrity": "sha512-P9go2WrP9FiPwLv3zqRD/Uoxo0RSHjzFCiQz7d4vbmwNqQFo9T9WCeP/Qn5EbcKQY6DBbkxEXNcpJOmncNrb7A==", + "version": "4.28.1", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.1.tgz", + "integrity": "sha512-ZC5Bd0LgJXgwGqUknZY/vkUQ04r8NXnJZ3yYi4vDmSiZmC/pdSN0NbNRPxZpbtO4uAfDUAFffO8IZoM3Gj8IkA==", "dev": true, "funding": [ { @@ -3102,14 +2884,12 @@ "url": "https://github.com/sponsors/ai" } ], - "license": "MIT", - "peer": true, "dependencies": { - "baseline-browser-mapping": "^2.8.2", - "caniuse-lite": "^1.0.30001741", - "electron-to-chromium": "^1.5.218", - "node-releases": "^2.0.21", - "update-browserslist-db": "^1.1.3" + "baseline-browser-mapping": "^2.9.0", + "caniuse-lite": "^1.0.30001759", + "electron-to-chromium": "^1.5.263", + "node-releases": "^2.0.27", + "update-browserslist-db": "^1.2.0" }, "bin": { "browserslist": "cli.js" @@ -3118,13 +2898,23 @@ "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" } }, + "node_modules/bs-logger": { + "version": "0.2.6", + "resolved": "https://registry.npmjs.org/bs-logger/-/bs-logger-0.2.6.tgz", + "integrity": "sha512-pd8DCoxmbgc7hyPKOvxtqNcjYoOsABPQdcCUjGp3d42VR2CX1ORhk2A87oqqu5R1kk+76nsxZupkmyd+MVtCog==", + "dev": true, + "dependencies": { + "fast-json-stable-stringify": "2.x" + }, + "engines": { + "node": ">= 6" + } + }, "node_modules/bser": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/bser/-/bser-2.1.1.tgz", "integrity": "sha512-gQxTNE/GAfIIrmHLUE3oJyp5FO6HRBfhjnw4/wMmA63ZGDJnWBmgY/lyQBpnDUkGmAhbSe39tx2d/iTOAfglwQ==", "dev": true, - "license": "Apache-2.0", - "peer": true, "dependencies": { "node-int64": "^0.4.0" } @@ -3139,9 +2929,7 @@ "version": "1.1.2", "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", - "dev": true, - "license": "MIT", - "peer": true + "dev": true }, "node_modules/bytes": { "version": "3.1.2", @@ -3215,16 +3003,14 @@ "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", "dev": true, - "license": "MIT", - "peer": true, "engines": { "node": ">=6" } }, "node_modules/caniuse-lite": { - "version": "1.0.30001741", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001741.tgz", - "integrity": "sha512-QGUGitqsc8ARjLdgAfxETDhRbJ0REsP6O3I96TAth/mVjh2cYzN2u+3AzPP3aVSm2FehEItaJw1xd+IGBXWeSw==", + "version": "1.0.30001762", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001762.tgz", + "integrity": "sha512-PxZwGNvH7Ak8WX5iXzoK1KPZttBXNPuaOvI2ZYU7NrlM+d9Ov+TUvlLOBNGzVXAntMSMMlJPd+jY6ovrVjSmUw==", "dev": true, "funding": [ { @@ -3239,9 +3025,7 @@ "type": "github", "url": "https://github.com/sponsors/ai" } - ], - "license": "CC-BY-4.0", - "peer": true + ] }, "node_modules/chalk": { "version": "4.1.2", @@ -3265,8 +3049,6 @@ "resolved": "https://registry.npmjs.org/char-regex/-/char-regex-1.0.2.tgz", "integrity": "sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw==", "dev": true, - "license": "MIT", - "peer": true, "engines": { "node": ">=10" } @@ -3283,18 +3065,15 @@ } ], "license": "MIT", - "peer": true, "engines": { "node": ">=8" } }, "node_modules/cjs-module-lexer": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-2.1.0.tgz", - "integrity": "sha512-UX0OwmYRYQQetfrLEZeewIFFI+wSTofC+pMBLNuH3RUuu/xzG1oz84UCEDOSoQlN3fZ4+AzmV50ZYvGqkMh9yA==", - "dev": true, - "license": "MIT", - "peer": true + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-2.2.0.tgz", + "integrity": "sha512-4bHTS2YuzUvtoLjdy+98ykbNB5jS0+07EvFNXerqZQJ89F7DI6ET7OQo/HJuW6K0aVsKA9hj9/RVb2kQVOrPDQ==", + "dev": true }, "node_modules/cliui": { "version": "8.0.1", @@ -3356,20 +3135,16 @@ "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", "integrity": "sha512-QVb0dM5HvG+uaxitm8wONl7jltx8dqhfU33DcqtOZcLSVIKSDDLDi7+0LbAKiyI8hD9u42m2YxXSkMGWThaecQ==", "dev": true, - "license": "MIT", - "peer": true, "engines": { "iojs": ">= 1.0.0", "node": ">= 0.12.0" } }, "node_modules/collect-v8-coverage": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/collect-v8-coverage/-/collect-v8-coverage-1.0.2.tgz", - "integrity": "sha512-lHl4d5/ONEbLlJvaJNtsF/Lz+WvB07u2ycqTYbdrq7UypDXailES4valYb2eWiJFxZlVmpGekfqoxQhzyFdT4Q==", - "dev": true, - "license": "MIT", - "peer": true + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/collect-v8-coverage/-/collect-v8-coverage-1.0.3.tgz", + "integrity": "sha512-1L5aqIkwPfiodaMgQunkF1zRhNqifHBmtbbbxcr6yVxxBnliw4TDOW6NxpO8DJLgJ16OT+Y4ztZqP6p/FtXnAw==", + "dev": true }, "node_modules/color-convert": { "version": "2.0.1", @@ -3436,9 +3211,7 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", - "dev": true, - "license": "MIT", - "peer": true + "dev": true }, "node_modules/cookie": { "version": "0.7.1", @@ -3555,12 +3328,10 @@ } }, "node_modules/dedent": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/dedent/-/dedent-1.7.0.tgz", - "integrity": "sha512-HGFtf8yhuhGhqO07SV79tRp+br4MnbdjeVxotpn1QBl30pcLLCQjX5b2295ll0fv8RKDKsmWYrl05usHM9CewQ==", + "version": "1.7.1", + "resolved": "https://registry.npmjs.org/dedent/-/dedent-1.7.1.tgz", + "integrity": "sha512-9JmrhGZpOlEgOLdQgSm0zxFaYoQon408V1v49aqTWuXENVlnCuY9JBZcXZiCsZQWDjTm5Qf/nIvAy77mXDAjEg==", "dev": true, - "license": "MIT", - "peer": true, "peerDependencies": { "babel-plugin-macros": "^3.1.0" }, @@ -3582,8 +3353,6 @@ "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz", "integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==", "dev": true, - "license": "MIT", - "peer": true, "engines": { "node": ">=0.10.0" } @@ -3658,8 +3427,6 @@ "resolved": "https://registry.npmjs.org/detect-newline/-/detect-newline-3.1.0.tgz", "integrity": "sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA==", "dev": true, - "license": "MIT", - "peer": true, "engines": { "node": ">=8" } @@ -3721,9 +3488,7 @@ "version": "0.2.0", "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", - "dev": true, - "license": "MIT", - "peer": true + "dev": true }, "node_modules/ecdsa-sig-formatter": { "version": "1.0.11", @@ -3741,20 +3506,16 @@ "license": "MIT" }, "node_modules/electron-to-chromium": { - "version": "1.5.218", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.218.tgz", - "integrity": "sha512-uwwdN0TUHs8u6iRgN8vKeWZMRll4gBkz+QMqdS7DDe49uiK68/UX92lFb61oiFPrpYZNeZIqa4bA7O6Aiasnzg==", - "dev": true, - "license": "ISC", - "peer": true + "version": "1.5.267", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.267.tgz", + "integrity": "sha512-0Drusm6MVRXSOJpGbaSVgcQsuB4hEkMpHXaVstcPmhu5LIedxs1xNK/nIxmQIU/RPC0+1/o0AVZfBTkTNJOdUw==", + "dev": true }, "node_modules/emittery": { "version": "0.13.1", "resolved": "https://registry.npmjs.org/emittery/-/emittery-0.13.1.tgz", "integrity": "sha512-DeWwawk6r5yR9jFgnDKYt4sLS0LmHJJi3ZOnb5/JdbYwj3nW+FxQnHIjhBKz8YLC7oRNPVM9NQ47I3CVx34eqQ==", "dev": true, - "license": "MIT", - "peer": true, "engines": { "node": ">=12" }, @@ -3766,9 +3527,7 @@ "version": "9.2.2", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", - "dev": true, - "license": "MIT", - "peer": true + "dev": true }, "node_modules/encodeurl": { "version": "2.0.0", @@ -3790,12 +3549,10 @@ } }, "node_modules/error-ex": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", - "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.4.tgz", + "integrity": "sha512-sqQamAnR14VgCr1A618A3sGrygcpK+HEbenA/HiEAkkUwcZIIB/tgWqHFxWgOyDh4nB4JCRimh79dR5Ywc9MDQ==", "dev": true, - "license": "MIT", - "peer": true, "dependencies": { "is-arrayish": "^0.2.1" } @@ -4239,8 +3996,6 @@ "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", "dev": true, - "license": "BSD-2-Clause", - "peer": true, "bin": { "esparse": "bin/esparse.js", "esvalidate": "bin/esvalidate.js" @@ -4339,8 +4094,6 @@ "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==", "dev": true, - "license": "MIT", - "peer": true, "dependencies": { "cross-spawn": "^7.0.3", "get-stream": "^6.0.0", @@ -4363,35 +4116,29 @@ "version": "3.0.7", "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", - "dev": true, - "license": "ISC", - "peer": true + "dev": true }, "node_modules/exit-x": { "version": "0.2.2", "resolved": "https://registry.npmjs.org/exit-x/-/exit-x-0.2.2.tgz", "integrity": "sha512-+I6B/IkJc1o/2tiURyz/ivu/O0nKNEArIUB5O7zBrlDVJr22SCLH3xTeEry428LvFhRzIA1g8izguxJ/gbNcVQ==", "dev": true, - "license": "MIT", - "peer": true, "engines": { "node": ">= 0.8.0" } }, "node_modules/expect": { - "version": "30.1.2", - "resolved": "https://registry.npmjs.org/expect/-/expect-30.1.2.tgz", - "integrity": "sha512-xvHszRavo28ejws8FpemjhwswGj4w/BetHIL8cU49u4sGyXDw2+p3YbeDbj6xzlxi6kWTjIRSTJ+9sNXPnF0Zg==", + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/expect/-/expect-30.2.0.tgz", + "integrity": "sha512-u/feCi0GPsI+988gU2FLcsHyAHTU0MX1Wg68NhAnN7z/+C5wqG+CY8J53N9ioe8RXgaoz0nBR/TYMf3AycUuPw==", "dev": true, - "license": "MIT", - "peer": true, "dependencies": { - "@jest/expect-utils": "30.1.2", + "@jest/expect-utils": "30.2.0", "@jest/get-type": "30.1.0", - "jest-matcher-utils": "30.1.2", - "jest-message-util": "30.1.0", - "jest-mock": "30.0.5", - "jest-util": "30.0.5" + "jest-matcher-utils": "30.2.0", + "jest-message-util": "30.2.0", + "jest-mock": "30.2.0", + "jest-util": "30.2.0" }, "engines": { "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" @@ -4571,8 +4318,6 @@ "resolved": "https://registry.npmjs.org/fb-watchman/-/fb-watchman-2.0.2.tgz", "integrity": "sha512-p5161BqbuCaSnB8jIbzQHOlpgsPmK5rJVDfDKO91Axs5NC1uu3HRQm6wt9cd9/+GtQQIO53JdGXXoyDpTAsgYA==", "dev": true, - "license": "Apache-2.0", - "peer": true, "dependencies": { "bser": "2.1.1" } @@ -4704,7 +4449,6 @@ "resolved": "https://registry.npmjs.org/firebase-functions-test/-/firebase-functions-test-3.4.1.tgz", "integrity": "sha512-qAq0oszrBGdf4bnCF6t4FoSgMsepeIXh0Pi/FhikSE6e+TvKKGpfrfUP/5pFjJZxFcLsweoau88KydCql4xSeg==", "dev": true, - "license": "MIT", "dependencies": { "@types/lodash": "^4.14.104", "lodash": "^4.17.5", @@ -4762,8 +4506,6 @@ "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.1.tgz", "integrity": "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==", "dev": true, - "license": "ISC", - "peer": true, "dependencies": { "cross-spawn": "^7.0.6", "signal-exit": "^4.0.1" @@ -4824,12 +4566,10 @@ "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", "dev": true, "hasInstallScript": true, - "license": "MIT", "optional": true, "os": [ "darwin" ], - "peer": true, "engines": { "node": "^8.16.0 || ^10.6.0 || >=11.0.0" } @@ -4932,8 +4672,6 @@ "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", "dev": true, - "license": "MIT", - "peer": true, "engines": { "node": ">=6.9.0" } @@ -4977,8 +4715,6 @@ "resolved": "https://registry.npmjs.org/get-package-type/-/get-package-type-0.1.0.tgz", "integrity": "sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q==", "dev": true, - "license": "MIT", - "peer": true, "engines": { "node": ">=8.0.0" } @@ -5001,8 +4737,6 @@ "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", "dev": true, - "license": "MIT", - "peer": true, "engines": { "node": ">=10" }, @@ -5029,12 +4763,10 @@ } }, "node_modules/glob": { - "version": "10.4.5", - "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz", - "integrity": "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==", + "version": "10.5.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.5.0.tgz", + "integrity": "sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg==", "dev": true, - "license": "ISC", - "peer": true, "dependencies": { "foreground-child": "^3.1.0", "jackspeak": "^3.1.2", @@ -5068,8 +4800,6 @@ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", "dev": true, - "license": "MIT", - "peer": true, "dependencies": { "balanced-match": "^1.0.0" } @@ -5079,8 +4809,6 @@ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", "dev": true, - "license": "ISC", - "peer": true, "dependencies": { "brace-expansion": "^2.0.1" }, @@ -5228,8 +4956,7 @@ "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", "dev": true, - "license": "ISC", - "peer": true + "license": "ISC" }, "node_modules/graphemer": { "version": "1.4.0", @@ -5252,6 +4979,27 @@ "node": ">=14.0.0" } }, + "node_modules/handlebars": { + "version": "4.7.8", + "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.7.8.tgz", + "integrity": "sha512-vafaFqs8MZkRrSX7sFVUdo3ap/eNiLnb4IakshzvP56X5Nr1iGKAIqdX6tMlm6HcNRIkr6AxO5jFEoJzzpT8aQ==", + "dev": true, + "dependencies": { + "minimist": "^1.2.5", + "neo-async": "^2.6.2", + "source-map": "^0.6.1", + "wordwrap": "^1.0.0" + }, + "bin": { + "handlebars": "bin/handlebars" + }, + "engines": { + "node": ">=0.4.7" + }, + "optionalDependencies": { + "uglify-js": "^3.1.4" + } + }, "node_modules/has-bigints": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.1.0.tgz", @@ -5365,9 +5113,7 @@ "version": "2.0.2", "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==", - "dev": true, - "license": "MIT", - "peer": true + "dev": true }, "node_modules/http-errors": { "version": "2.0.0", @@ -5438,8 +5184,6 @@ "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==", "dev": true, - "license": "Apache-2.0", - "peer": true, "engines": { "node": ">=10.17.0" } @@ -5488,8 +5232,6 @@ "resolved": "https://registry.npmjs.org/import-local/-/import-local-3.2.0.tgz", "integrity": "sha512-2SPlun1JUPWoM6t3F0dw0FkCF/jWY8kttcY4f599GLTSjh2OCuuhdTkJQsEcZzBqbXZGKMK2OqW1oZsjtf/gQA==", "dev": true, - "license": "MIT", - "peer": true, "dependencies": { "pkg-dir": "^4.2.0", "resolve-cwd": "^3.0.0" @@ -5578,9 +5320,7 @@ "version": "0.2.1", "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==", - "dev": true, - "license": "MIT", - "peer": true + "dev": true }, "node_modules/is-async-function": { "version": "2.1.1", @@ -5740,8 +5480,6 @@ "resolved": "https://registry.npmjs.org/is-generator-fn/-/is-generator-fn-2.1.0.tgz", "integrity": "sha512-cTIB4yPYL/Grw0EaSzASzg6bBy9gqCofvWN8okThAYIxKJZC+udlRAmGbM0XLeniEJSs8uEgHPGuHSe1XsOLSQ==", "dev": true, - "license": "MIT", - "peer": true, "engines": { "node": ">=6" } @@ -6018,8 +5756,6 @@ "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.2.tgz", "integrity": "sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==", "dev": true, - "license": "BSD-3-Clause", - "peer": true, "engines": { "node": ">=8" } @@ -6029,8 +5765,6 @@ "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-6.0.3.tgz", "integrity": "sha512-Vtgk7L/R2JHyyGW07spoFlB8/lpjiOLTjMdms6AFMraYt3BaJauod/NGrfnVG/y4Ix1JEuMRPDPEj2ua+zz1/Q==", "dev": true, - "license": "BSD-3-Clause", - "peer": true, "dependencies": { "@babel/core": "^7.23.9", "@babel/parser": "^7.23.9", @@ -6047,8 +5781,6 @@ "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.1.tgz", "integrity": "sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==", "dev": true, - "license": "BSD-3-Clause", - "peer": true, "dependencies": { "istanbul-lib-coverage": "^3.0.0", "make-dir": "^4.0.0", @@ -6063,8 +5795,6 @@ "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-5.0.6.tgz", "integrity": "sha512-yg2d+Em4KizZC5niWhQaIomgf5WlL4vOOjZ5xGCmF8SnPE/mDWWXgvRExdcpCgh9lLRRa1/fSYp2ymmbJ1pI+A==", "dev": true, - "license": "BSD-3-Clause", - "peer": true, "dependencies": { "@jridgewell/trace-mapping": "^0.3.23", "debug": "^4.1.1", @@ -6079,8 +5809,6 @@ "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.2.0.tgz", "integrity": "sha512-HGYWWS/ehqTV3xN10i23tkPkpH46MLCIMFNCaaKNavAXTF1RkqxawEPtnjnGZ6XKSInBKkiOA5BKS+aZiY3AvA==", "dev": true, - "license": "BSD-3-Clause", - "peer": true, "dependencies": { "html-escaper": "^2.0.0", "istanbul-lib-report": "^3.0.0" @@ -6094,8 +5822,6 @@ "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz", "integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==", "dev": true, - "license": "BlueOak-1.0.0", - "peer": true, "dependencies": { "@isaacs/cliui": "^8.0.2" }, @@ -6107,17 +5833,15 @@ } }, "node_modules/jest": { - "version": "30.1.3", - "resolved": "https://registry.npmjs.org/jest/-/jest-30.1.3.tgz", - "integrity": "sha512-Ry+p2+NLk6u8Agh5yVqELfUJvRfV51hhVBRIB5yZPY7mU0DGBmOuFG5GebZbMbm86cdQNK0fhJuDX8/1YorISQ==", + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/jest/-/jest-30.2.0.tgz", + "integrity": "sha512-F26gjC0yWN8uAA5m5Ss8ZQf5nDHWGlN/xWZIh8S5SRbsEKBovwZhxGd6LJlbZYxBgCYOtreSUyb8hpXyGC5O4A==", "dev": true, - "license": "MIT", - "peer": true, "dependencies": { - "@jest/core": "30.1.3", - "@jest/types": "30.0.5", + "@jest/core": "30.2.0", + "@jest/types": "30.2.0", "import-local": "^3.2.0", - "jest-cli": "30.1.3" + "jest-cli": "30.2.0" }, "bin": { "jest": "bin/jest.js" @@ -6135,15 +5859,13 @@ } }, "node_modules/jest-changed-files": { - "version": "30.0.5", - "resolved": "https://registry.npmjs.org/jest-changed-files/-/jest-changed-files-30.0.5.tgz", - "integrity": "sha512-bGl2Ntdx0eAwXuGpdLdVYVr5YQHnSZlQ0y9HVDu565lCUAe9sj6JOtBbMmBBikGIegne9piDDIOeiLVoqTkz4A==", + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/jest-changed-files/-/jest-changed-files-30.2.0.tgz", + "integrity": "sha512-L8lR1ChrRnSdfeOvTrwZMlnWV8G/LLjQ0nG9MBclwWZidA2N5FviRki0Bvh20WRMOX31/JYvzdqTJrk5oBdydQ==", "dev": true, - "license": "MIT", - "peer": true, "dependencies": { "execa": "^5.1.1", - "jest-util": "30.0.5", + "jest-util": "30.2.0", "p-limit": "^3.1.0" }, "engines": { @@ -6151,30 +5873,28 @@ } }, "node_modules/jest-circus": { - "version": "30.1.3", - "resolved": "https://registry.npmjs.org/jest-circus/-/jest-circus-30.1.3.tgz", - "integrity": "sha512-Yf3dnhRON2GJT4RYzM89t/EXIWNxKTpWTL9BfF3+geFetWP4XSvJjiU1vrWplOiUkmq8cHLiwuhz+XuUp9DscA==", + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/jest-circus/-/jest-circus-30.2.0.tgz", + "integrity": "sha512-Fh0096NC3ZkFx05EP2OXCxJAREVxj1BcW/i6EWqqymcgYKWjyyDpral3fMxVcHXg6oZM7iULer9wGRFvfpl+Tg==", "dev": true, - "license": "MIT", - "peer": true, "dependencies": { - "@jest/environment": "30.1.2", - "@jest/expect": "30.1.2", - "@jest/test-result": "30.1.3", - "@jest/types": "30.0.5", + "@jest/environment": "30.2.0", + "@jest/expect": "30.2.0", + "@jest/test-result": "30.2.0", + "@jest/types": "30.2.0", "@types/node": "*", "chalk": "^4.1.2", "co": "^4.6.0", "dedent": "^1.6.0", "is-generator-fn": "^2.1.0", - "jest-each": "30.1.0", - "jest-matcher-utils": "30.1.2", - "jest-message-util": "30.1.0", - "jest-runtime": "30.1.3", - "jest-snapshot": "30.1.2", - "jest-util": "30.0.5", + "jest-each": "30.2.0", + "jest-matcher-utils": "30.2.0", + "jest-message-util": "30.2.0", + "jest-runtime": "30.2.0", + "jest-snapshot": "30.2.0", + "jest-util": "30.2.0", "p-limit": "^3.1.0", - "pretty-format": "30.0.5", + "pretty-format": "30.2.0", "pure-rand": "^7.0.0", "slash": "^3.0.0", "stack-utils": "^2.0.6" @@ -6184,22 +5904,20 @@ } }, "node_modules/jest-cli": { - "version": "30.1.3", - "resolved": "https://registry.npmjs.org/jest-cli/-/jest-cli-30.1.3.tgz", - "integrity": "sha512-G8E2Ol3OKch1DEeIBl41NP7OiC6LBhfg25Btv+idcusmoUSpqUkbrneMqbW9lVpI/rCKb/uETidb7DNteheuAQ==", + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/jest-cli/-/jest-cli-30.2.0.tgz", + "integrity": "sha512-Os9ukIvADX/A9sLt6Zse3+nmHtHaE6hqOsjQtNiugFTbKRHYIYtZXNGNK9NChseXy7djFPjndX1tL0sCTlfpAA==", "dev": true, - "license": "MIT", - "peer": true, "dependencies": { - "@jest/core": "30.1.3", - "@jest/test-result": "30.1.3", - "@jest/types": "30.0.5", + "@jest/core": "30.2.0", + "@jest/test-result": "30.2.0", + "@jest/types": "30.2.0", "chalk": "^4.1.2", "exit-x": "^0.2.2", "import-local": "^3.2.0", - "jest-config": "30.1.3", - "jest-util": "30.0.5", - "jest-validate": "30.1.0", + "jest-config": "30.2.0", + "jest-util": "30.2.0", + "jest-validate": "30.2.0", "yargs": "^17.7.2" }, "bin": { @@ -6218,35 +5936,33 @@ } }, "node_modules/jest-config": { - "version": "30.1.3", - "resolved": "https://registry.npmjs.org/jest-config/-/jest-config-30.1.3.tgz", - "integrity": "sha512-M/f7gqdQEPgZNA181Myz+GXCe8jXcJsGjCMXUzRj22FIXsZOyHNte84e0exntOvdPaeh9tA0w+B8qlP2fAezfw==", + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/jest-config/-/jest-config-30.2.0.tgz", + "integrity": "sha512-g4WkyzFQVWHtu6uqGmQR4CQxz/CH3yDSlhzXMWzNjDx843gYjReZnMRanjRCq5XZFuQrGDxgUaiYWE8BRfVckA==", "dev": true, - "license": "MIT", - "peer": true, "dependencies": { "@babel/core": "^7.27.4", "@jest/get-type": "30.1.0", "@jest/pattern": "30.0.1", - "@jest/test-sequencer": "30.1.3", - "@jest/types": "30.0.5", - "babel-jest": "30.1.2", + "@jest/test-sequencer": "30.2.0", + "@jest/types": "30.2.0", + "babel-jest": "30.2.0", "chalk": "^4.1.2", "ci-info": "^4.2.0", "deepmerge": "^4.3.1", "glob": "^10.3.10", "graceful-fs": "^4.2.11", - "jest-circus": "30.1.3", - "jest-docblock": "30.0.1", - "jest-environment-node": "30.1.2", + "jest-circus": "30.2.0", + "jest-docblock": "30.2.0", + "jest-environment-node": "30.2.0", "jest-regex-util": "30.0.1", - "jest-resolve": "30.1.3", - "jest-runner": "30.1.3", - "jest-util": "30.0.5", - "jest-validate": "30.1.0", + "jest-resolve": "30.2.0", + "jest-runner": "30.2.0", + "jest-util": "30.2.0", + "jest-validate": "30.2.0", "micromatch": "^4.0.8", "parse-json": "^5.2.0", - "pretty-format": "30.0.5", + "pretty-format": "30.2.0", "slash": "^3.0.0", "strip-json-comments": "^3.1.1" }, @@ -6271,29 +5987,25 @@ } }, "node_modules/jest-diff": { - "version": "30.1.2", - "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-30.1.2.tgz", - "integrity": "sha512-4+prq+9J61mOVXCa4Qp8ZjavdxzrWQXrI80GNxP8f4tkI2syPuPrJgdRPZRrfUTRvIoUwcmNLbqEJy9W800+NQ==", + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-30.2.0.tgz", + "integrity": "sha512-dQHFo3Pt4/NLlG5z4PxZ/3yZTZ1C7s9hveiOj+GCN+uT109NC2QgsoVZsVOAvbJ3RgKkvyLGXZV9+piDpWbm6A==", "dev": true, - "license": "MIT", - "peer": true, "dependencies": { "@jest/diff-sequences": "30.0.1", "@jest/get-type": "30.1.0", "chalk": "^4.1.2", - "pretty-format": "30.0.5" + "pretty-format": "30.2.0" }, "engines": { "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, "node_modules/jest-docblock": { - "version": "30.0.1", - "resolved": "https://registry.npmjs.org/jest-docblock/-/jest-docblock-30.0.1.tgz", - "integrity": "sha512-/vF78qn3DYphAaIc3jy4gA7XSAz167n9Bm/wn/1XhTLW7tTBIzXtCJpb/vcmc73NIIeeohCbdL94JasyXUZsGA==", + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/jest-docblock/-/jest-docblock-30.2.0.tgz", + "integrity": "sha512-tR/FFgZKS1CXluOQzZvNH3+0z9jXr3ldGSD8bhyuxvlVUwbeLOGynkunvlTMxchC5urrKndYiwCFC0DLVjpOCA==", "dev": true, - "license": "MIT", - "peer": true, "dependencies": { "detect-newline": "^3.1.0" }, @@ -6302,59 +6014,53 @@ } }, "node_modules/jest-each": { - "version": "30.1.0", - "resolved": "https://registry.npmjs.org/jest-each/-/jest-each-30.1.0.tgz", - "integrity": "sha512-A+9FKzxPluqogNahpCv04UJvcZ9B3HamqpDNWNKDjtxVRYB8xbZLFuCr8JAJFpNp83CA0anGQFlpQna9Me+/tQ==", + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/jest-each/-/jest-each-30.2.0.tgz", + "integrity": "sha512-lpWlJlM7bCUf1mfmuqTA8+j2lNURW9eNafOy99knBM01i5CQeY5UH1vZjgT9071nDJac1M4XsbyI44oNOdhlDQ==", "dev": true, - "license": "MIT", - "peer": true, "dependencies": { "@jest/get-type": "30.1.0", - "@jest/types": "30.0.5", + "@jest/types": "30.2.0", "chalk": "^4.1.2", - "jest-util": "30.0.5", - "pretty-format": "30.0.5" + "jest-util": "30.2.0", + "pretty-format": "30.2.0" }, "engines": { "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, "node_modules/jest-environment-node": { - "version": "30.1.2", - "resolved": "https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-30.1.2.tgz", - "integrity": "sha512-w8qBiXtqGWJ9xpJIA98M0EIoq079GOQRQUyse5qg1plShUCQ0Ek1VTTcczqKrn3f24TFAgFtT+4q3aOXvjbsuA==", + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-30.2.0.tgz", + "integrity": "sha512-ElU8v92QJ9UrYsKrxDIKCxu6PfNj4Hdcktcn0JX12zqNdqWHB0N+hwOnnBBXvjLd2vApZtuLUGs1QSY+MsXoNA==", "dev": true, - "license": "MIT", - "peer": true, "dependencies": { - "@jest/environment": "30.1.2", - "@jest/fake-timers": "30.1.2", - "@jest/types": "30.0.5", + "@jest/environment": "30.2.0", + "@jest/fake-timers": "30.2.0", + "@jest/types": "30.2.0", "@types/node": "*", - "jest-mock": "30.0.5", - "jest-util": "30.0.5", - "jest-validate": "30.1.0" + "jest-mock": "30.2.0", + "jest-util": "30.2.0", + "jest-validate": "30.2.0" }, "engines": { "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, "node_modules/jest-haste-map": { - "version": "30.1.0", - "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-30.1.0.tgz", - "integrity": "sha512-JLeM84kNjpRkggcGpQLsV7B8W4LNUWz7oDNVnY1Vjj22b5/fAb3kk3htiD+4Na8bmJmjJR7rBtS2Rmq/NEcADg==", + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-30.2.0.tgz", + "integrity": "sha512-sQA/jCb9kNt+neM0anSj6eZhLZUIhQgwDt7cPGjumgLM4rXsfb9kpnlacmvZz3Q5tb80nS+oG/if+NBKrHC+Xw==", "dev": true, - "license": "MIT", - "peer": true, "dependencies": { - "@jest/types": "30.0.5", + "@jest/types": "30.2.0", "@types/node": "*", "anymatch": "^3.1.3", "fb-watchman": "^2.0.2", "graceful-fs": "^4.2.11", "jest-regex-util": "30.0.1", - "jest-util": "30.0.5", - "jest-worker": "30.1.0", + "jest-util": "30.2.0", + "jest-worker": "30.2.0", "micromatch": "^4.0.8", "walker": "^1.0.8" }, @@ -6366,52 +6072,46 @@ } }, "node_modules/jest-leak-detector": { - "version": "30.1.0", - "resolved": "https://registry.npmjs.org/jest-leak-detector/-/jest-leak-detector-30.1.0.tgz", - "integrity": "sha512-AoFvJzwxK+4KohH60vRuHaqXfWmeBATFZpzpmzNmYTtmRMiyGPVhkXpBqxUQunw+dQB48bDf4NpUs6ivVbRv1g==", + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/jest-leak-detector/-/jest-leak-detector-30.2.0.tgz", + "integrity": "sha512-M6jKAjyzjHG0SrQgwhgZGy9hFazcudwCNovY/9HPIicmNSBuockPSedAP9vlPK6ONFJ1zfyH/M2/YYJxOz5cdQ==", "dev": true, - "license": "MIT", - "peer": true, "dependencies": { "@jest/get-type": "30.1.0", - "pretty-format": "30.0.5" + "pretty-format": "30.2.0" }, "engines": { "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, "node_modules/jest-matcher-utils": { - "version": "30.1.2", - "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-30.1.2.tgz", - "integrity": "sha512-7ai16hy4rSbDjvPTuUhuV8nyPBd6EX34HkBsBcBX2lENCuAQ0qKCPb/+lt8OSWUa9WWmGYLy41PrEzkwRwoGZQ==", + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-30.2.0.tgz", + "integrity": "sha512-dQ94Nq4dbzmUWkQ0ANAWS9tBRfqCrn0bV9AMYdOi/MHW726xn7eQmMeRTpX2ViC00bpNaWXq+7o4lIQ3AX13Hg==", "dev": true, - "license": "MIT", - "peer": true, "dependencies": { "@jest/get-type": "30.1.0", "chalk": "^4.1.2", - "jest-diff": "30.1.2", - "pretty-format": "30.0.5" + "jest-diff": "30.2.0", + "pretty-format": "30.2.0" }, "engines": { "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, "node_modules/jest-message-util": { - "version": "30.1.0", - "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-30.1.0.tgz", - "integrity": "sha512-HizKDGG98cYkWmaLUHChq4iN+oCENohQLb7Z5guBPumYs+/etonmNFlg1Ps6yN9LTPyZn+M+b/9BbnHx3WTMDg==", + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-30.2.0.tgz", + "integrity": "sha512-y4DKFLZ2y6DxTWD4cDe07RglV88ZiNEdlRfGtqahfbIjfsw1nMCPx49Uev4IA/hWn3sDKyAnSPwoYSsAEdcimw==", "dev": true, - "license": "MIT", - "peer": true, "dependencies": { "@babel/code-frame": "^7.27.1", - "@jest/types": "30.0.5", + "@jest/types": "30.2.0", "@types/stack-utils": "^2.0.3", "chalk": "^4.1.2", "graceful-fs": "^4.2.11", "micromatch": "^4.0.8", - "pretty-format": "30.0.5", + "pretty-format": "30.2.0", "slash": "^3.0.0", "stack-utils": "^2.0.6" }, @@ -6420,16 +6120,14 @@ } }, "node_modules/jest-mock": { - "version": "30.0.5", - "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-30.0.5.tgz", - "integrity": "sha512-Od7TyasAAQX/6S+QCbN6vZoWOMwlTtzzGuxJku1GhGanAjz9y+QsQkpScDmETvdc9aSXyJ/Op4rhpMYBWW91wQ==", + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-30.2.0.tgz", + "integrity": "sha512-JNNNl2rj4b5ICpmAcq+WbLH83XswjPbjH4T7yvGzfAGCPh1rw+xVNbtk+FnRslvt9lkCcdn9i1oAoKUuFsOxRw==", "dev": true, - "license": "MIT", - "peer": true, "dependencies": { - "@jest/types": "30.0.5", + "@jest/types": "30.2.0", "@types/node": "*", - "jest-util": "30.0.5" + "jest-util": "30.2.0" }, "engines": { "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" @@ -6440,8 +6138,6 @@ "resolved": "https://registry.npmjs.org/jest-pnp-resolver/-/jest-pnp-resolver-1.2.3.tgz", "integrity": "sha512-+3NpwQEnRoIBtx4fyhblQDPgJI0H1IEIkX7ShLUjPGA7TtUTvI1oiKi3SR4oBR0hQhQR80l4WAe5RrXBwWMA8w==", "dev": true, - "license": "MIT", - "peer": true, "engines": { "node": ">=6" }, @@ -6460,25 +6156,22 @@ "integrity": "sha512-jHEQgBXAgc+Gh4g0p3bCevgRCVRkB4VB70zhoAE48gxeSr1hfUOsM/C2WoJgVL7Eyg//hudYENbm3Ne+/dRVVA==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, "node_modules/jest-resolve": { - "version": "30.1.3", - "resolved": "https://registry.npmjs.org/jest-resolve/-/jest-resolve-30.1.3.tgz", - "integrity": "sha512-DI4PtTqzw9GwELFS41sdMK32Ajp3XZQ8iygeDMWkxlRhm7uUTOFSZFVZABFuxr0jvspn8MAYy54NxZCsuCTSOw==", + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/jest-resolve/-/jest-resolve-30.2.0.tgz", + "integrity": "sha512-TCrHSxPlx3tBY3hWNtRQKbtgLhsXa1WmbJEqBlTBrGafd5fiQFByy2GNCEoGR+Tns8d15GaL9cxEzKOO3GEb2A==", "dev": true, - "license": "MIT", - "peer": true, "dependencies": { "chalk": "^4.1.2", "graceful-fs": "^4.2.11", - "jest-haste-map": "30.1.0", + "jest-haste-map": "30.2.0", "jest-pnp-resolver": "^1.2.3", - "jest-util": "30.0.5", - "jest-validate": "30.1.0", + "jest-util": "30.2.0", + "jest-validate": "30.2.0", "slash": "^3.0.0", "unrs-resolver": "^1.7.11" }, @@ -6487,48 +6180,44 @@ } }, "node_modules/jest-resolve-dependencies": { - "version": "30.1.3", - "resolved": "https://registry.npmjs.org/jest-resolve-dependencies/-/jest-resolve-dependencies-30.1.3.tgz", - "integrity": "sha512-DNfq3WGmuRyHRHfEet+Zm3QOmVFtIarUOQHHryKPc0YL9ROfgWZxl4+aZq/VAzok2SS3gZdniP+dO4zgo59hBg==", + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/jest-resolve-dependencies/-/jest-resolve-dependencies-30.2.0.tgz", + "integrity": "sha512-xTOIGug/0RmIe3mmCqCT95yO0vj6JURrn1TKWlNbhiAefJRWINNPgwVkrVgt/YaerPzY3iItufd80v3lOrFJ2w==", "dev": true, - "license": "MIT", - "peer": true, "dependencies": { "jest-regex-util": "30.0.1", - "jest-snapshot": "30.1.2" + "jest-snapshot": "30.2.0" }, "engines": { "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, "node_modules/jest-runner": { - "version": "30.1.3", - "resolved": "https://registry.npmjs.org/jest-runner/-/jest-runner-30.1.3.tgz", - "integrity": "sha512-dd1ORcxQraW44Uz029TtXj85W11yvLpDuIzNOlofrC8GN+SgDlgY4BvyxJiVeuabA1t6idjNbX59jLd2oplOGQ==", + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/jest-runner/-/jest-runner-30.2.0.tgz", + "integrity": "sha512-PqvZ2B2XEyPEbclp+gV6KO/F1FIFSbIwewRgmROCMBo/aZ6J1w8Qypoj2pEOcg3G2HzLlaP6VUtvwCI8dM3oqQ==", "dev": true, - "license": "MIT", - "peer": true, "dependencies": { - "@jest/console": "30.1.2", - "@jest/environment": "30.1.2", - "@jest/test-result": "30.1.3", - "@jest/transform": "30.1.2", - "@jest/types": "30.0.5", + "@jest/console": "30.2.0", + "@jest/environment": "30.2.0", + "@jest/test-result": "30.2.0", + "@jest/transform": "30.2.0", + "@jest/types": "30.2.0", "@types/node": "*", "chalk": "^4.1.2", "emittery": "^0.13.1", "exit-x": "^0.2.2", "graceful-fs": "^4.2.11", - "jest-docblock": "30.0.1", - "jest-environment-node": "30.1.2", - "jest-haste-map": "30.1.0", - "jest-leak-detector": "30.1.0", - "jest-message-util": "30.1.0", - "jest-resolve": "30.1.3", - "jest-runtime": "30.1.3", - "jest-util": "30.0.5", - "jest-watcher": "30.1.3", - "jest-worker": "30.1.0", + "jest-docblock": "30.2.0", + "jest-environment-node": "30.2.0", + "jest-haste-map": "30.2.0", + "jest-leak-detector": "30.2.0", + "jest-message-util": "30.2.0", + "jest-resolve": "30.2.0", + "jest-runtime": "30.2.0", + "jest-util": "30.2.0", + "jest-watcher": "30.2.0", + "jest-worker": "30.2.0", "p-limit": "^3.1.0", "source-map-support": "0.5.13" }, @@ -6537,33 +6226,31 @@ } }, "node_modules/jest-runtime": { - "version": "30.1.3", - "resolved": "https://registry.npmjs.org/jest-runtime/-/jest-runtime-30.1.3.tgz", - "integrity": "sha512-WS8xgjuNSphdIGnleQcJ3AKE4tBKOVP+tKhCD0u+Tb2sBmsU8DxfbBpZX7//+XOz81zVs4eFpJQwBNji2Y07DA==", + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/jest-runtime/-/jest-runtime-30.2.0.tgz", + "integrity": "sha512-p1+GVX/PJqTucvsmERPMgCPvQJpFt4hFbM+VN3n8TMo47decMUcJbt+rgzwrEme0MQUA/R+1de2axftTHkKckg==", "dev": true, - "license": "MIT", - "peer": true, "dependencies": { - "@jest/environment": "30.1.2", - "@jest/fake-timers": "30.1.2", - "@jest/globals": "30.1.2", + "@jest/environment": "30.2.0", + "@jest/fake-timers": "30.2.0", + "@jest/globals": "30.2.0", "@jest/source-map": "30.0.1", - "@jest/test-result": "30.1.3", - "@jest/transform": "30.1.2", - "@jest/types": "30.0.5", + "@jest/test-result": "30.2.0", + "@jest/transform": "30.2.0", + "@jest/types": "30.2.0", "@types/node": "*", "chalk": "^4.1.2", "cjs-module-lexer": "^2.1.0", "collect-v8-coverage": "^1.0.2", "glob": "^10.3.10", "graceful-fs": "^4.2.11", - "jest-haste-map": "30.1.0", - "jest-message-util": "30.1.0", - "jest-mock": "30.0.5", + "jest-haste-map": "30.2.0", + "jest-message-util": "30.2.0", + "jest-mock": "30.2.0", "jest-regex-util": "30.0.1", - "jest-resolve": "30.1.3", - "jest-snapshot": "30.1.2", - "jest-util": "30.0.5", + "jest-resolve": "30.2.0", + "jest-snapshot": "30.2.0", + "jest-util": "30.2.0", "slash": "^3.0.0", "strip-bom": "^4.0.0" }, @@ -6572,32 +6259,30 @@ } }, "node_modules/jest-snapshot": { - "version": "30.1.2", - "resolved": "https://registry.npmjs.org/jest-snapshot/-/jest-snapshot-30.1.2.tgz", - "integrity": "sha512-4q4+6+1c8B6Cy5pGgFvjDy/Pa6VYRiGu0yQafKkJ9u6wQx4G5PqI2QR6nxTl43yy7IWsINwz6oT4o6tD12a8Dg==", + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/jest-snapshot/-/jest-snapshot-30.2.0.tgz", + "integrity": "sha512-5WEtTy2jXPFypadKNpbNkZ72puZCa6UjSr/7djeecHWOu7iYhSXSnHScT8wBz3Rn8Ena5d5RYRcsyKIeqG1IyA==", "dev": true, - "license": "MIT", - "peer": true, "dependencies": { "@babel/core": "^7.27.4", "@babel/generator": "^7.27.5", "@babel/plugin-syntax-jsx": "^7.27.1", "@babel/plugin-syntax-typescript": "^7.27.1", "@babel/types": "^7.27.3", - "@jest/expect-utils": "30.1.2", + "@jest/expect-utils": "30.2.0", "@jest/get-type": "30.1.0", - "@jest/snapshot-utils": "30.1.2", - "@jest/transform": "30.1.2", - "@jest/types": "30.0.5", - "babel-preset-current-node-syntax": "^1.1.0", + "@jest/snapshot-utils": "30.2.0", + "@jest/transform": "30.2.0", + "@jest/types": "30.2.0", + "babel-preset-current-node-syntax": "^1.2.0", "chalk": "^4.1.2", - "expect": "30.1.2", + "expect": "30.2.0", "graceful-fs": "^4.2.11", - "jest-diff": "30.1.2", - "jest-matcher-utils": "30.1.2", - "jest-message-util": "30.1.0", - "jest-util": "30.0.5", - "pretty-format": "30.0.5", + "jest-diff": "30.2.0", + "jest-matcher-utils": "30.2.0", + "jest-message-util": "30.2.0", + "jest-util": "30.2.0", + "pretty-format": "30.2.0", "semver": "^7.7.2", "synckit": "^0.11.8" }, @@ -6606,14 +6291,12 @@ } }, "node_modules/jest-util": { - "version": "30.0.5", - "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-30.0.5.tgz", - "integrity": "sha512-pvyPWssDZR0FlfMxCBoc0tvM8iUEskaRFALUtGQYzVEAqisAztmy+R8LnU14KT4XA0H/a5HMVTXat1jLne010g==", + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-30.2.0.tgz", + "integrity": "sha512-QKNsM0o3Xe6ISQU869e+DhG+4CK/48aHYdJZGlFQVTjnbvgpcKyxpzk29fGiO7i/J8VENZ+d2iGnSsvmuHywlA==", "dev": true, - "license": "MIT", - "peer": true, "dependencies": { - "@jest/types": "30.0.5", + "@jest/types": "30.2.0", "@types/node": "*", "chalk": "^4.1.2", "ci-info": "^4.2.0", @@ -6630,7 +6313,6 @@ "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=12" }, @@ -6639,19 +6321,17 @@ } }, "node_modules/jest-validate": { - "version": "30.1.0", - "resolved": "https://registry.npmjs.org/jest-validate/-/jest-validate-30.1.0.tgz", - "integrity": "sha512-7P3ZlCFW/vhfQ8pE7zW6Oi4EzvuB4sgR72Q1INfW9m0FGo0GADYlPwIkf4CyPq7wq85g+kPMtPOHNAdWHeBOaA==", + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/jest-validate/-/jest-validate-30.2.0.tgz", + "integrity": "sha512-FBGWi7dP2hpdi8nBoWxSsLvBFewKAg0+uSQwBaof4Y4DPgBabXgpSYC5/lR7VmnIlSpASmCi/ntRWPbv7089Pw==", "dev": true, - "license": "MIT", - "peer": true, "dependencies": { "@jest/get-type": "30.1.0", - "@jest/types": "30.0.5", + "@jest/types": "30.2.0", "camelcase": "^6.3.0", "chalk": "^4.1.2", "leven": "^3.1.0", - "pretty-format": "30.0.5" + "pretty-format": "30.2.0" }, "engines": { "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" @@ -6662,8 +6342,6 @@ "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", "dev": true, - "license": "MIT", - "peer": true, "engines": { "node": ">=10" }, @@ -6672,20 +6350,18 @@ } }, "node_modules/jest-watcher": { - "version": "30.1.3", - "resolved": "https://registry.npmjs.org/jest-watcher/-/jest-watcher-30.1.3.tgz", - "integrity": "sha512-6jQUZCP1BTL2gvG9E4YF06Ytq4yMb4If6YoQGRR6PpjtqOXSP3sKe2kqwB6SQ+H9DezOfZaSLnmka1NtGm3fCQ==", + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/jest-watcher/-/jest-watcher-30.2.0.tgz", + "integrity": "sha512-PYxa28dxJ9g777pGm/7PrbnMeA0Jr7osHP9bS7eJy9DuAjMgdGtxgf0uKMyoIsTWAkIbUW5hSDdJ3urmgXBqxg==", "dev": true, - "license": "MIT", - "peer": true, "dependencies": { - "@jest/test-result": "30.1.3", - "@jest/types": "30.0.5", + "@jest/test-result": "30.2.0", + "@jest/types": "30.2.0", "@types/node": "*", "ansi-escapes": "^4.3.2", "chalk": "^4.1.2", "emittery": "^0.13.1", - "jest-util": "30.0.5", + "jest-util": "30.2.0", "string-length": "^4.0.2" }, "engines": { @@ -6693,16 +6369,14 @@ } }, "node_modules/jest-worker": { - "version": "30.1.0", - "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-30.1.0.tgz", - "integrity": "sha512-uvWcSjlwAAgIu133Tt77A05H7RIk3Ho8tZL50bQM2AkvLdluw9NG48lRCl3Dt+MOH719n/0nnb5YxUwcuJiKRA==", + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-30.2.0.tgz", + "integrity": "sha512-0Q4Uk8WF7BUwqXHuAjc23vmopWJw5WH7w2tqBoUOZpOjW/ZnR44GXXd1r82RvnmI2GZge3ivrYXk/BE2+VtW2g==", "dev": true, - "license": "MIT", - "peer": true, "dependencies": { "@types/node": "*", "@ungap/structured-clone": "^1.3.0", - "jest-util": "30.0.5", + "jest-util": "30.2.0", "merge-stream": "^2.0.0", "supports-color": "^8.1.1" }, @@ -6715,8 +6389,6 @@ "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", "dev": true, - "license": "MIT", - "peer": true, "dependencies": { "has-flag": "^4.0.0" }, @@ -6741,8 +6413,7 @@ "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", "dev": true, - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/js-yaml": { "version": "4.1.0", @@ -6762,8 +6433,6 @@ "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==", "dev": true, - "license": "MIT", - "peer": true, "bin": { "jsesc": "bin/jsesc" }, @@ -6792,9 +6461,7 @@ "version": "2.3.1", "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", - "dev": true, - "license": "MIT", - "peer": true + "dev": true }, "node_modules/json-schema-traverse": { "version": "0.4.1", @@ -6815,8 +6482,6 @@ "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", "dev": true, - "license": "MIT", - "peer": true, "bin": { "json5": "lib/cli.js" }, @@ -6922,8 +6587,6 @@ "resolved": "https://registry.npmjs.org/leven/-/leven-3.1.0.tgz", "integrity": "sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==", "dev": true, - "license": "MIT", - "peer": true, "engines": { "node": ">=6" } @@ -6951,9 +6614,7 @@ "version": "1.2.4", "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", - "dev": true, - "license": "MIT", - "peer": true + "dev": true }, "node_modules/locate-path": { "version": "6.0.0", @@ -7027,6 +6688,12 @@ "integrity": "sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw==", "license": "MIT" }, + "node_modules/lodash.memoize": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz", + "integrity": "sha512-t7j+NzmgnQzTAYXcsHYLgimltOV1MXHtlOWf6GjL9Kj8GK5FInw5JotxvbOs+IvV1/Dzo04/fCGfLVs7aXb4Ag==", + "dev": true + }, "node_modules/lodash.merge": { "version": "4.6.2", "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", @@ -7051,8 +6718,6 @@ "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", "dev": true, - "license": "ISC", - "peer": true, "dependencies": { "yallist": "^3.0.2" } @@ -7090,8 +6755,6 @@ "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-4.0.0.tgz", "integrity": "sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==", "dev": true, - "license": "MIT", - "peer": true, "dependencies": { "semver": "^7.5.3" }, @@ -7102,13 +6765,17 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/make-error": { + "version": "1.3.6", + "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", + "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", + "dev": true + }, "node_modules/makeerror": { "version": "1.0.12", "resolved": "https://registry.npmjs.org/makeerror/-/makeerror-1.0.12.tgz", "integrity": "sha512-JmqCvUhmt43madlpFzG4BQzG2Z3m6tvQDNKdClZnO3VbIudJYmxsT0FNJMeiB2+JTSlTQTSbU8QdesVmwJcmLg==", "dev": true, - "license": "BSD-3-Clause", - "peer": true, "dependencies": { "tmpl": "1.0.5" } @@ -7144,9 +6811,7 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", - "dev": true, - "license": "MIT", - "peer": true + "dev": true }, "node_modules/merge2": { "version": "1.4.1", @@ -7220,8 +6885,6 @@ "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", "dev": true, - "license": "MIT", - "peer": true, "engines": { "node": ">=6" } @@ -7254,8 +6917,6 @@ "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", "dev": true, - "license": "ISC", - "peer": true, "engines": { "node": ">=16 || 14 >=14.17" } @@ -7267,12 +6928,10 @@ "license": "MIT" }, "node_modules/napi-postinstall": { - "version": "0.3.3", - "resolved": "https://registry.npmjs.org/napi-postinstall/-/napi-postinstall-0.3.3.tgz", - "integrity": "sha512-uTp172LLXSxuSYHv/kou+f6KW3SMppU9ivthaVTXian9sOt3XM/zHYHpRZiLgQoxeWfYUnslNWQHF1+G71xcow==", + "version": "0.3.4", + "resolved": "https://registry.npmjs.org/napi-postinstall/-/napi-postinstall-0.3.4.tgz", + "integrity": "sha512-PHI5f1O0EP5xJ9gQmFGMS6IZcrVvTjpXjz7Na41gTE7eE2hK11lg04CECCYEEjdc17EV4DO+fkGEtt7TpTaTiQ==", "dev": true, - "license": "MIT", - "peer": true, "bin": { "napi-postinstall": "lib/cli.js" }, @@ -7306,6 +6965,12 @@ "node": ">= 0.6" } }, + "node_modules/neo-async": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz", + "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==", + "dev": true + }, "node_modules/node-fetch": { "version": "2.7.0", "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", @@ -7340,25 +7005,19 @@ "version": "0.4.0", "resolved": "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz", "integrity": "sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw==", - "dev": true, - "license": "MIT", - "peer": true + "dev": true }, "node_modules/node-releases": { - "version": "2.0.21", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.21.tgz", - "integrity": "sha512-5b0pgg78U3hwXkCM8Z9b2FJdPZlr9Psr9V2gQPESdGHqbntyFJKFW4r5TeWGFzafGY3hzs1JC62VEQMbl1JFkw==", - "dev": true, - "license": "MIT", - "peer": true + "version": "2.0.27", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.27.tgz", + "integrity": "sha512-nmh3lCkYZ3grZvqcCH+fjmQ7X+H0OeZgP40OierEaAptX4XofMh5kwNbWh7lBduUzCcV/8kZ+NDLCwm2iorIlA==", + "dev": true }, "node_modules/normalize-path": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", "dev": true, - "license": "MIT", - "peer": true, "engines": { "node": ">=0.10.0" } @@ -7368,8 +7027,6 @@ "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", "dev": true, - "license": "MIT", - "peer": true, "dependencies": { "path-key": "^3.0.0" }, @@ -7519,8 +7176,6 @@ "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", "dev": true, - "license": "MIT", - "peer": true, "dependencies": { "mimic-fn": "^2.1.0" }, @@ -7604,8 +7259,6 @@ "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", "dev": true, - "license": "MIT", - "peer": true, "engines": { "node": ">=6" } @@ -7614,9 +7267,7 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz", "integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==", - "dev": true, - "license": "BlueOak-1.0.0", - "peer": true + "dev": true }, "node_modules/parent-module": { "version": "1.0.1", @@ -7636,8 +7287,6 @@ "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", "dev": true, - "license": "MIT", - "peer": true, "dependencies": { "@babel/code-frame": "^7.0.0", "error-ex": "^1.3.1", @@ -7702,8 +7351,6 @@ "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz", "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==", "dev": true, - "license": "BlueOak-1.0.0", - "peer": true, "dependencies": { "lru-cache": "^10.2.0", "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" @@ -7719,9 +7366,7 @@ "version": "10.4.3", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", - "dev": true, - "license": "ISC", - "peer": true + "dev": true }, "node_modules/path-to-regexp": { "version": "0.1.12", @@ -7744,8 +7389,7 @@ "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", "dev": true, - "license": "ISC", - "peer": true + "license": "ISC" }, "node_modules/picomatch": { "version": "2.3.1", @@ -7765,8 +7409,6 @@ "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.7.tgz", "integrity": "sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA==", "dev": true, - "license": "MIT", - "peer": true, "engines": { "node": ">= 6" } @@ -7776,8 +7418,6 @@ "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==", "dev": true, - "license": "MIT", - "peer": true, "dependencies": { "find-up": "^4.0.0" }, @@ -7790,8 +7430,6 @@ "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", "dev": true, - "license": "MIT", - "peer": true, "dependencies": { "locate-path": "^5.0.0", "path-exists": "^4.0.0" @@ -7805,8 +7443,6 @@ "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", "dev": true, - "license": "MIT", - "peer": true, "dependencies": { "p-locate": "^4.1.0" }, @@ -7819,8 +7455,6 @@ "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", "dev": true, - "license": "MIT", - "peer": true, "dependencies": { "p-try": "^2.0.0" }, @@ -7836,8 +7470,6 @@ "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", "dev": true, - "license": "MIT", - "peer": true, "dependencies": { "p-limit": "^2.2.0" }, @@ -7866,12 +7498,10 @@ } }, "node_modules/pretty-format": { - "version": "30.0.5", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-30.0.5.tgz", - "integrity": "sha512-D1tKtYvByrBkFLe2wHJl2bwMJIiT8rW+XA+TiataH79/FszLQMrpGEvzUVkzPau7OCO0Qnrhpe87PqtOAIB8Yw==", + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-30.2.0.tgz", + "integrity": "sha512-9uBdv/B4EefsuAL+pWqueZyZS2Ba+LxfFeQ9DN14HU4bN8bhaxKdkpjpB6fs9+pSjIBu+FXQHImEg8j/Lw0+vA==", "dev": true, - "license": "MIT", - "peer": true, "dependencies": { "@jest/schemas": "30.0.5", "ansi-styles": "^5.2.0", @@ -7887,7 +7517,6 @@ "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=10" }, @@ -7969,9 +7598,7 @@ "type": "opencollective", "url": "https://opencollective.com/fast-check" } - ], - "license": "MIT", - "peer": true + ] }, "node_modules/qs": { "version": "6.13.0", @@ -8038,8 +7665,7 @@ "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==", "dev": true, - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/readable-stream": { "version": "3.6.2", @@ -8136,8 +7762,6 @@ "resolved": "https://registry.npmjs.org/resolve-cwd/-/resolve-cwd-3.0.0.tgz", "integrity": "sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg==", "dev": true, - "license": "MIT", - "peer": true, "dependencies": { "resolve-from": "^5.0.0" }, @@ -8150,8 +7774,6 @@ "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", "dev": true, - "license": "MIT", - "peer": true, "engines": { "node": ">=8" } @@ -8347,10 +7969,9 @@ "license": "MIT" }, "node_modules/semver": { - "version": "7.7.2", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz", - "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==", - "license": "ISC", + "version": "7.7.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz", + "integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==", "bin": { "semver": "bin/semver.js" }, @@ -8588,8 +8209,6 @@ "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", "dev": true, - "license": "ISC", - "peer": true, "engines": { "node": ">=14" }, @@ -8612,8 +8231,6 @@ "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", "dev": true, - "license": "BSD-3-Clause", - "peer": true, "engines": { "node": ">=0.10.0" } @@ -8623,8 +8240,6 @@ "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.13.tgz", "integrity": "sha512-SHSKFHadjVA5oR4PPqhtAVdcBWwRYVd6g6cAXnIbRiIwc2EhPrTuKUBdSLvlEKyIP3GCf89fltvcZiP9MMFA1w==", "dev": true, - "license": "MIT", - "peer": true, "dependencies": { "buffer-from": "^1.0.0", "source-map": "^0.6.0" @@ -8634,9 +8249,7 @@ "version": "1.0.3", "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==", - "dev": true, - "license": "BSD-3-Clause", - "peer": true + "dev": true }, "node_modules/stack-utils": { "version": "2.0.6", @@ -8644,7 +8257,6 @@ "integrity": "sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "escape-string-regexp": "^2.0.0" }, @@ -8658,7 +8270,6 @@ "integrity": "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=8" } @@ -8718,8 +8329,6 @@ "resolved": "https://registry.npmjs.org/string-length/-/string-length-4.0.2.tgz", "integrity": "sha512-+l6rNN5fYHNhZZy41RXsYptCjA2Igmq4EG7kZAYFQI1E1VTXarr6ZPXBg6eq7Y6eK4FEhY6AJlyuFIb/v/S0VQ==", "dev": true, - "license": "MIT", - "peer": true, "dependencies": { "char-regex": "^1.0.2", "strip-ansi": "^6.0.0" @@ -8733,8 +8342,6 @@ "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", "dev": true, - "license": "MIT", - "peer": true, "dependencies": { "eastasianwidth": "^0.2.0", "emoji-regex": "^9.2.2", @@ -8753,8 +8360,6 @@ "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", "dev": true, - "license": "MIT", - "peer": true, "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", @@ -8768,17 +8373,13 @@ "version": "8.0.0", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "dev": true, - "license": "MIT", - "peer": true + "dev": true }, "node_modules/string-width/node_modules/ansi-regex": { "version": "6.2.2", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz", "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==", "dev": true, - "license": "MIT", - "peer": true, "engines": { "node": ">=12" }, @@ -8791,8 +8392,6 @@ "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.2.tgz", "integrity": "sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA==", "dev": true, - "license": "MIT", - "peer": true, "dependencies": { "ansi-regex": "^6.0.1" }, @@ -8881,8 +8480,6 @@ "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", "dev": true, - "license": "MIT", - "peer": true, "dependencies": { "ansi-regex": "^5.0.1" }, @@ -8895,8 +8492,6 @@ "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-4.0.0.tgz", "integrity": "sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w==", "dev": true, - "license": "MIT", - "peer": true, "engines": { "node": ">=8" } @@ -8906,8 +8501,6 @@ "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz", "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==", "dev": true, - "license": "MIT", - "peer": true, "engines": { "node": ">=6" } @@ -8976,8 +8569,6 @@ "resolved": "https://registry.npmjs.org/synckit/-/synckit-0.11.11.tgz", "integrity": "sha512-MeQTA1r0litLUf0Rp/iisCaL8761lKAZHaimlbGK4j0HysC4PLfqygQj9srcs0m2RdtDYnF8UuYyKpbjHYp7Jw==", "dev": true, - "license": "MIT", - "peer": true, "dependencies": { "@pkgr/core": "^0.2.9" }, @@ -9051,8 +8642,6 @@ "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-6.0.0.tgz", "integrity": "sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w==", "dev": true, - "license": "ISC", - "peer": true, "dependencies": { "@istanbuljs/schema": "^0.1.2", "glob": "^7.1.4", @@ -9068,8 +8657,6 @@ "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", "deprecated": "Glob versions prior to v9 are no longer supported", "dev": true, - "license": "ISC", - "peer": true, "dependencies": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", @@ -9096,9 +8683,7 @@ "version": "1.0.5", "resolved": "https://registry.npmjs.org/tmpl/-/tmpl-1.0.5.tgz", "integrity": "sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw==", - "dev": true, - "license": "BSD-3-Clause", - "peer": true + "dev": true }, "node_modules/to-regex-range": { "version": "5.0.1", @@ -9136,6 +8721,70 @@ "dev": true, "license": "ISC" }, + "node_modules/ts-jest": { + "version": "29.4.6", + "resolved": "https://registry.npmjs.org/ts-jest/-/ts-jest-29.4.6.tgz", + "integrity": "sha512-fSpWtOO/1AjSNQguk43hb/JCo16oJDnMJf3CdEGNkqsEX3t0KX96xvyX1D7PfLCpVoKu4MfVrqUkFyblYoY4lA==", + "dev": true, + "dependencies": { + "bs-logger": "^0.2.6", + "fast-json-stable-stringify": "^2.1.0", + "handlebars": "^4.7.8", + "json5": "^2.2.3", + "lodash.memoize": "^4.1.2", + "make-error": "^1.3.6", + "semver": "^7.7.3", + "type-fest": "^4.41.0", + "yargs-parser": "^21.1.1" + }, + "bin": { + "ts-jest": "cli.js" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || ^18.0.0 || >=20.0.0" + }, + "peerDependencies": { + "@babel/core": ">=7.0.0-beta.0 <8", + "@jest/transform": "^29.0.0 || ^30.0.0", + "@jest/types": "^29.0.0 || ^30.0.0", + "babel-jest": "^29.0.0 || ^30.0.0", + "jest": "^29.0.0 || ^30.0.0", + "jest-util": "^29.0.0 || ^30.0.0", + "typescript": ">=4.3 <6" + }, + "peerDependenciesMeta": { + "@babel/core": { + "optional": true + }, + "@jest/transform": { + "optional": true + }, + "@jest/types": { + "optional": true + }, + "babel-jest": { + "optional": true + }, + "esbuild": { + "optional": true + }, + "jest-util": { + "optional": true + } + } + }, + "node_modules/ts-jest/node_modules/type-fest": { + "version": "4.41.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-4.41.0.tgz", + "integrity": "sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA==", + "dev": true, + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/tsconfig-paths": { "version": "3.15.0", "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.15.0.tgz", @@ -9219,8 +8868,6 @@ "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==", "dev": true, - "license": "MIT", - "peer": true, "engines": { "node": ">=4" } @@ -9343,6 +8990,19 @@ "node": ">=14.17" } }, + "node_modules/uglify-js": { + "version": "3.19.3", + "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.19.3.tgz", + "integrity": "sha512-v3Xu+yuwBXisp6QYTcH4UbH+xYJXqnq2m/LtQVWKWzYc1iehYnLixoQDN9FH6/j9/oybfd6W9Ghwkl8+UMKTKQ==", + "dev": true, + "optional": true, + "bin": { + "uglifyjs": "bin/uglifyjs" + }, + "engines": { + "node": ">=0.8.0" + } + }, "node_modules/unbox-primitive": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.1.0.tgz", @@ -9383,8 +9043,6 @@ "integrity": "sha512-bSjt9pjaEBnNiGgc9rUiHGKv5l4/TGzDmYw3RhnkJGtLhbnnA/5qJj7x3dNDCRx/PJxu774LlH8lCOlB4hEfKg==", "dev": true, "hasInstallScript": true, - "license": "MIT", - "peer": true, "dependencies": { "napi-postinstall": "^0.3.0" }, @@ -9414,9 +9072,9 @@ } }, "node_modules/update-browserslist-db": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.3.tgz", - "integrity": "sha512-UxhIZQ+QInVdunkDAaiazvvT/+fXL5Osr0JZlJulepYu6Jd7qJtDZjlur0emRlT71EN3ScPoE7gvsuIKKNavKw==", + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz", + "integrity": "sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==", "dev": true, "funding": [ { @@ -9432,8 +9090,6 @@ "url": "https://github.com/sponsors/ai" } ], - "license": "MIT", - "peer": true, "dependencies": { "escalade": "^3.2.0", "picocolors": "^1.1.1" @@ -9489,8 +9145,6 @@ "resolved": "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-9.3.0.tgz", "integrity": "sha512-kiGUalWN+rgBJ/1OHZsBtU4rXZOfj/7rKQxULKlIzwzQSvMJUUNgPwJEEh7gU6xEVxC0ahoOBvN2YI8GH6FNgA==", "dev": true, - "license": "ISC", - "peer": true, "dependencies": { "@jridgewell/trace-mapping": "^0.3.12", "@types/istanbul-lib-coverage": "^2.0.1", @@ -9514,8 +9168,6 @@ "resolved": "https://registry.npmjs.org/walker/-/walker-1.0.8.tgz", "integrity": "sha512-ts/8E8l5b7kY0vlWLewOkDXMmPdLcVV4GmOQLyxuSswIJsweeFZtAsMF7k1Nszz+TYBQrlYRmzOnr398y1JemQ==", "dev": true, - "license": "Apache-2.0", - "peer": true, "dependencies": { "makeerror": "1.0.12" } @@ -9676,13 +9328,17 @@ "node": ">=0.10.0" } }, + "node_modules/wordwrap": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz", + "integrity": "sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q==", + "dev": true + }, "node_modules/wrap-ansi": { "version": "8.1.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", "dev": true, - "license": "MIT", - "peer": true, "dependencies": { "ansi-styles": "^6.1.0", "string-width": "^5.0.1", @@ -9701,8 +9357,6 @@ "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", "dev": true, - "license": "MIT", - "peer": true, "dependencies": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", @@ -9719,17 +9373,13 @@ "version": "8.0.0", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "dev": true, - "license": "MIT", - "peer": true + "dev": true }, "node_modules/wrap-ansi-cjs/node_modules/string-width": { "version": "4.2.3", "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", "dev": true, - "license": "MIT", - "peer": true, "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", @@ -9744,8 +9394,6 @@ "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz", "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==", "dev": true, - "license": "MIT", - "peer": true, "engines": { "node": ">=12" }, @@ -9758,8 +9406,6 @@ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz", "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==", "dev": true, - "license": "MIT", - "peer": true, "engines": { "node": ">=12" }, @@ -9772,8 +9418,6 @@ "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.2.tgz", "integrity": "sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA==", "dev": true, - "license": "MIT", - "peer": true, "dependencies": { "ansi-regex": "^6.0.1" }, @@ -9796,8 +9440,6 @@ "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-5.0.1.tgz", "integrity": "sha512-+QU2zd6OTD8XWIJCbffaiQeH9U73qIqafo1x6V1snCWYGJf6cVE0cDR4D8xRzcEnfI21IFrUPzPGtcPf8AC+Rw==", "dev": true, - "license": "ISC", - "peer": true, "dependencies": { "imurmurhash": "^0.1.4", "signal-exit": "^4.0.1" @@ -9820,9 +9462,7 @@ "version": "3.1.1", "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", - "dev": true, - "license": "ISC", - "peer": true + "dev": true }, "node_modules/yargs": { "version": "17.7.2", diff --git a/firebase/dev/functions/package.json b/firebase/functions/package.json similarity index 56% rename from firebase/dev/functions/package.json rename to firebase/functions/package.json index a73e0414..4aa7396e 100644 --- a/firebase/dev/functions/package.json +++ b/firebase/functions/package.json @@ -8,7 +8,10 @@ "shell": "npm run build && firebase functions:shell", "start": "npm run shell", "deploy": "firebase deploy --only functions", - "logs": "firebase functions:log" + "logs": "firebase functions:log", + "test": "jest --runInBand", + "test:watch": "jest --watch --runInBand", + "test:e2e": "npm run build && firebase emulators:exec --only auth,firestore,functions 'npm test'" }, "engines": { "node": "22" @@ -19,13 +22,24 @@ "firebase-functions": "^6.0.1" }, "devDependencies": { + "@types/jest": "^30.0.0", "@typescript-eslint/eslint-plugin": "^5.12.0", "@typescript-eslint/parser": "^5.12.0", "eslint": "^8.9.0", "eslint-config-google": "^0.14.0", "eslint-plugin-import": "^2.25.4", - "firebase-functions-test": "^3.1.0", + "firebase-functions-test": "^3.4.1", + "jest": "^30.2.0", + "ts-jest": "^29.4.6", "typescript": "^5.7.3" }, - "private": true + "private": true, + "jest": { + "preset": "ts-jest", + "testEnvironment": "node", + "testMatch": ["**/test/**/*.test.ts"], + "collectCoverageFrom": ["src/**/*.ts"], + "setupFilesAfterEnv": ["/test/setup.ts"], + "testTimeout": 30000 + } } diff --git a/firebase/functions/src/deleteUserData.ts b/firebase/functions/src/deleteUserData.ts new file mode 100644 index 00000000..9283f267 --- /dev/null +++ b/firebase/functions/src/deleteUserData.ts @@ -0,0 +1,134 @@ +import * as functions from "firebase-functions/v1"; +import * as logger from "firebase-functions/logger"; +import {getFirestore, FieldValue} from "firebase-admin/firestore"; +import {FirestoreCollections} from "./models/FirestoreCollections"; +import {FirestoreHelper} from "./models/FirestoreHelper"; +import {CohabitantFields} from "./models/Cohabitant"; + +export const deleteuserdata = functions.auth.user().onDelete(async (user) => { + const userId = user.uid; + logger.info(`Starting user data deletion for: ${userId}`); + + const firestoreHelper = new FirestoreHelper(); + const db = getFirestore(); + + try { + // Step 1: Accountドキュメントの検索と取得 + const result = await firestoreHelper.findAccountByUserId(userId); + + if (!result) { + logger.warn(`Account not found for user: ${userId}`); + return; + } + + const {snapshot: accountSnapshot, account} = result; + const linkedCohabitantId = account.cohabitantId; + + logger.info(`Account found. Linked cohabitant: ${linkedCohabitantId}`); + + // Step 2: Accountドキュメントの削除 + await accountSnapshot.ref.delete(); + logger.info(`Account removed for: ${userId}`); + + // Step 3: Cohabitantグループの処理 + if (!linkedCohabitantId) { + logger.info(`User ${userId} has no linked cohabitant group.`); + return; + } + + const cohabitantResult = await firestoreHelper.getCohabitant( + linkedCohabitantId + ); + + if (!cohabitantResult) { + logger.warn(`Cohabitant ${linkedCohabitantId} does not exist.`); + return; + } + + const {snapshot: cohabitantSnapshot, cohabitant} = cohabitantResult; + const memberList = cohabitant.members; + + // Step 4: メンバー数に応じた処理 + // メンバーが2人以下の場合はグループごと削除 + if (memberList.length <= 2) { + // グループ全体を削除 + await removeCohabitantSubcollections(db, linkedCohabitantId); + await cohabitantSnapshot.ref.delete(); + logger.info( + `Cohabitant group ${linkedCohabitantId} fully removed. ` + + `Reason: Insufficient members (${memberList.length} members).` + ); + } else { + // 3人以上のグループの場合は、メンバーリストから削除のみ + await cohabitantSnapshot.ref.update({ + [CohabitantFields.MEMBERS]: FieldValue.arrayRemove(userId), + }); + logger.info( + `User ${userId} removed from cohabitant ` + + `${linkedCohabitantId}. ` + + `Remaining members: ${memberList.length - 1}` + ); + } + } catch (err) { + logger.error("Failed to delete user data:", {err, userId}); + // トリガー関数ではthrowせず、ログに記録するのみ + } +}); + +/** + * Cohabitantのサブコレクションを削除 + * @param {FirebaseFirestore.Firestore} db Firestoreインスタンス + * @param {string} cohabitantId CohabitantのID + * @return {Promise} + */ +async function removeCohabitantSubcollections( + db: FirebaseFirestore.Firestore, + cohabitantId: string +): Promise { + const subcollections = [ + FirestoreCollections.HOUSEWORK, + FirestoreCollections.HOUSEWORK_HISTORY, + ]; + + for (const subcollection of subcollections) { + const path = FirestoreCollections.getCohabitantSubcollection( + cohabitantId, + subcollection + ); + await batchDeleteCollection(db, path, 500); + } +} + +/** + * コレクション内のドキュメントをバッチ削除 + * @param {FirebaseFirestore.Firestore} db Firestoreインスタンス + * @param {string} path コレクションのパス + * @param {number} limit 一度に削除する上限数 + * @return {Promise} + */ +async function batchDeleteCollection( + db: FirebaseFirestore.Firestore, + path: string, + limit: number +): Promise { + const collectionRef = db.collection(path); + const querySnapshot = await collectionRef.limit(limit).get(); + + if (querySnapshot.empty) { + logger.info(`No documents in ${path}`); + return; + } + + const batchOperation = db.batch(); + querySnapshot.docs.forEach((document) => { + batchOperation.delete(document.ref); + }); + await batchOperation.commit(); + + logger.info(`Removed ${querySnapshot.size} documents from ${path}`); + + // 再帰的に残りを削除 + if (querySnapshot.size >= limit) { + await batchDeleteCollection(db, path, limit); + } +} diff --git a/firebase/dev/functions/src/index.ts b/firebase/functions/src/index.ts similarity index 97% rename from firebase/dev/functions/src/index.ts rename to firebase/functions/src/index.ts index 9fad6543..9c331623 100644 --- a/firebase/dev/functions/src/index.ts +++ b/firebase/functions/src/index.ts @@ -31,3 +31,4 @@ initializeApp(); setGlobalOptions({maxInstances: 10}); export * from "./notifyCohabitants"; +export * from "./deleteUserData"; diff --git a/firebase/functions/src/models/Account.ts b/firebase/functions/src/models/Account.ts new file mode 100644 index 00000000..f1997dd3 --- /dev/null +++ b/firebase/functions/src/models/Account.ts @@ -0,0 +1,55 @@ +/** + * Accountドキュメントの構造(Functionsで使用するプロパティのみ) + */ +export interface Account { + id: string; + cohabitantId?: string; + fcmToken?: string; +} + +/** + * Accountドキュメントのフィールド名定数 + */ +export class AccountFields { + static readonly ID = "id"; + static readonly COHABITANT_ID = "cohabitantId"; + static readonly FCM_TOKEN = "fcmToken"; +} + +/** + * FirestoreドキュメントからAccountモデルに変換 + */ +export class AccountConverter { + /** + * Firestoreスナップショットから変換 + * @param {FirebaseFirestore.DocumentSnapshot} snapshot スナップショット + * @return {Account | null} Accountオブジェクトまたはnull + */ + static fromFirestore( + snapshot: FirebaseFirestore.DocumentSnapshot + ): Account | null { + if (!snapshot.exists) { + return null; + } + + const data = snapshot.data(); + if (!data) { + return null; + } + + return this.fromFirestoreData(data); + } + + /** + * Firestoreデータから変換 + * @param {FirebaseFirestore.DocumentData} data Firestoreデータ + * @return {Account} Accountオブジェクト + */ + static fromFirestoreData(data: FirebaseFirestore.DocumentData): Account { + return { + id: data[AccountFields.ID] as string, + cohabitantId: data[AccountFields.COHABITANT_ID] as string | undefined, + fcmToken: data[AccountFields.FCM_TOKEN] as string | undefined, + }; + } +} diff --git a/firebase/functions/src/models/Cohabitant.ts b/firebase/functions/src/models/Cohabitant.ts new file mode 100644 index 00000000..0ed9e2a0 --- /dev/null +++ b/firebase/functions/src/models/Cohabitant.ts @@ -0,0 +1,49 @@ +/** + * Cohabitantドキュメントの構造(Functionsで使用するプロパティのみ) + */ +export interface Cohabitant { + members: string[]; +} + +/** + * Cohabitantドキュメントのフィールド名定数 + */ +export class CohabitantFields { + static readonly MEMBERS = "members"; +} + +/** + * FirestoreドキュメントからCohabitantモデルに変換 + */ +export class CohabitantConverter { + /** + * Firestoreスナップショットから変換 + * @param {FirebaseFirestore.DocumentSnapshot} snapshot スナップショット + * @return {Cohabitant | null} Cohabitantオブジェクトまたはnull + */ + static fromFirestore( + snapshot: FirebaseFirestore.DocumentSnapshot + ): Cohabitant | null { + if (!snapshot.exists) { + return null; + } + + const data = snapshot.data(); + if (!data) { + return null; + } + + return this.fromFirestoreData(data); + } + + /** + * Firestoreデータから変換 + * @param {FirebaseFirestore.DocumentData} data Firestoreデータ + * @return {Cohabitant} Cohabitantオブジェクト + */ + static fromFirestoreData(data: FirebaseFirestore.DocumentData): Cohabitant { + return { + members: (data[CohabitantFields.MEMBERS] as string[]) || [], + }; + } +} diff --git a/firebase/functions/src/models/FirestoreCollections.ts b/firebase/functions/src/models/FirestoreCollections.ts new file mode 100644 index 00000000..7b032b20 --- /dev/null +++ b/firebase/functions/src/models/FirestoreCollections.ts @@ -0,0 +1,46 @@ +/** + * Firestoreのコレクション名を一元管理するクラス + */ +export class FirestoreCollections { + // ルートコレクション + static readonly ACCOUNT = "Account"; + static readonly COHABITANT = "Cohabitant"; + + // Cohabitantのサブコレクション + static readonly HOUSEWORK = "Housework"; + static readonly HOUSEWORK_HISTORY = "HouseworkHistory"; + + /** + * Cohabitantのサブコレクションパスを取得 + * @param {string} cohabitantId - CohabitantドキュメントのID + * @param {string} subcollection - サブコレクション名 + * @return {string} サブコレクションへのパス + */ + static getCohabitantSubcollection( + cohabitantId: string, + subcollection: string + ): string { + return `${this.COHABITANT}/${cohabitantId}/${subcollection}`; + } + + /** + * Houseworkコレクションのパスを取得 + * @param {string} cohabitantId - CohabitantドキュメントのID + * @return {string} Houseworkコレクションへのパス + */ + static getHouseworkPath(cohabitantId: string): string { + return this.getCohabitantSubcollection(cohabitantId, this.HOUSEWORK); + } + + /** + * HouseworkHistoryコレクションのパスを取得 + * @param {string} cohabitantId - CohabitantドキュメントのID + * @return {string} HouseworkHistoryコレクションへのパス + */ + static getHouseworkHistoryPath(cohabitantId: string): string { + return this.getCohabitantSubcollection( + cohabitantId, + this.HOUSEWORK_HISTORY + ); + } +} diff --git a/firebase/functions/src/models/FirestoreHelper.ts b/firebase/functions/src/models/FirestoreHelper.ts new file mode 100644 index 00000000..094f4d0b --- /dev/null +++ b/firebase/functions/src/models/FirestoreHelper.ts @@ -0,0 +1,116 @@ +import {getFirestore} from "firebase-admin/firestore"; +import {FirestoreCollections} from "./FirestoreCollections"; +import {Account, AccountFields, AccountConverter} from "./Account"; +import {Cohabitant, CohabitantConverter} from "./Cohabitant"; + +/** + * Firestoreへのアクセスを型安全に抽象化するヘルパークラス + */ +export class FirestoreHelper { + private db: FirebaseFirestore.Firestore; + + /** + * Firestoreインスタンスを初期化 + */ + constructor() { + this.db = getFirestore(); + } + + /** + * ユーザーIDからAccountドキュメントを検索 + * @param {string} userId - 検索するユーザーID + * @return {Promise<{snapshot: FirebaseFirestore.QueryDocumentSnapshot, + * account: Account} | null>} スナップショットとAccount、 + * または見つからない場合はnull + */ + async findAccountByUserId( + userId: string + ): Promise<{ + snapshot: FirebaseFirestore.QueryDocumentSnapshot; + account: Account; + } | null> { + const querySnapshot = await this.db + .collection(FirestoreCollections.ACCOUNT) + .where(AccountFields.ID, "==", userId) + .limit(1) + .get(); + + if (querySnapshot.empty) { + return null; + } + + const snapshot = querySnapshot.docs[0]; + const account = AccountConverter.fromFirestoreData(snapshot.data()); + + return {snapshot, account}; + } + + /** + * Cohabitantドキュメントを取得 + * @param {string} cohabitantId - CohabitantドキュメントのID + * @return {Promise<{snapshot: FirebaseFirestore.DocumentSnapshot, + * cohabitant: Cohabitant} | null>} スナップショットとCohabitant、 + * または見つからない場合はnull + */ + async getCohabitant( + cohabitantId: string + ): Promise<{ + snapshot: FirebaseFirestore.DocumentSnapshot; + cohabitant: Cohabitant; + } | null> { + const snapshot = await this.db + .collection(FirestoreCollections.COHABITANT) + .doc(cohabitantId) + .get(); + + const cohabitant = CohabitantConverter.fromFirestore(snapshot); + if (!cohabitant) { + return null; + } + + return {snapshot, cohabitant}; + } + + /** + * メンバーIDの配列からAccountドキュメントを取得 + * @param {string[]} userIds - 取得するユーザーIDの配列 + * @return {Promise} Accountの配列 + */ + async getAccountsByUserIds(userIds: string[]): Promise { + if (userIds.length === 0) { + return []; + } + + // Firestoreの"in"クエリは最大10件まで + const chunks = this.chunkArray(userIds, 10); + const accounts: Account[] = []; + + for (const chunk of chunks) { + const querySnapshot = await this.db + .collection(FirestoreCollections.ACCOUNT) + .where(AccountFields.ID, "in", chunk) + .get(); + + querySnapshot.forEach((doc) => { + accounts.push(AccountConverter.fromFirestoreData(doc.data())); + }); + } + + return accounts; + } + + /** + * 配列を指定サイズのチャンクに分割 + * @template T + * @param {Array} array - 分割する配列 + * @param {number} size - チャンクのサイズ + * @return {Array>} チャンクに分割された配列 + */ + private chunkArray(array: T[], size: number): T[][] { + const chunks: T[][] = []; + for (let i = 0; i < array.length; i += size) { + chunks.push(array.slice(i, i + size)); + } + return chunks; + } +} diff --git a/firebase/dev/functions/src/notifyCohabitants.ts b/firebase/functions/src/notifyCohabitants.ts similarity index 80% rename from firebase/dev/functions/src/notifyCohabitants.ts rename to firebase/functions/src/notifyCohabitants.ts index 069024d0..95a10a7e 100644 --- a/firebase/dev/functions/src/notifyCohabitants.ts +++ b/firebase/functions/src/notifyCohabitants.ts @@ -1,7 +1,7 @@ import * as logger from "firebase-functions/logger"; import {onCall, HttpsError} from "firebase-functions/v2/https"; -import {getFirestore} from "firebase-admin/firestore"; import {getMessaging} from "firebase-admin/messaging"; +import {FirestoreHelper} from "./models/FirestoreHelper"; interface NotifyCohabitantsRequest { cohabitantId: string; @@ -40,14 +40,15 @@ export const notifyothercohabitants = onCall( ); } - const db = getFirestore(); + const firestoreHelper = new FirestoreHelper(); try { // 1. Get the cohabitant group document - const cohabitantDocRef = db.collection("Cohabitant").doc(cohabitantId); - const cohabitantDoc = await cohabitantDocRef.get(); + const cohabitantResult = await firestoreHelper.getCohabitant( + cohabitantId + ); - if (!cohabitantDoc.exists) { + if (!cohabitantResult) { logger.error(`Cohabitant group with id ${cohabitantId} not found.`); throw new HttpsError( "not-found", @@ -55,14 +56,16 @@ export const notifyothercohabitants = onCall( ); } - const cohabitantData = cohabitantDoc.data(); - if (!cohabitantData || !cohabitantData.members) { - logger.error(`Cohabitant group ${cohabitantId} has no members field.`); + const {cohabitant} = cohabitantResult; + const members = cohabitant.members; + + if (members.length === 0) { + logger.error(`Cohabitant group ${cohabitantId} has no members.`); return {success: true, message: "No members found in the group."}; } // 2. Filter out the sender to get recipient IDs - const recipientIds = cohabitantData.members.filter( + const recipientIds = members.filter( (memberId: string) => memberId !== senderId ); @@ -72,18 +75,10 @@ export const notifyothercohabitants = onCall( } // 3. Get FCM tokens for the recipients - const tokens: string[] = []; - const accountsQuery = await db - .collection("Account") - .where("id", "in", recipientIds) - .get(); - - accountsQuery.forEach((doc) => { - const accountData = doc.data(); - if (accountData && accountData.fcmToken) { - tokens.push(accountData.fcmToken); - } - }); + const accounts = await firestoreHelper.getAccountsByUserIds(recipientIds); + const tokens: string[] = accounts + .map((account) => account.fcmToken) + .filter((token): token is string => !!token); if (tokens.length === 0) { logger.info("No FCM tokens found for any of the recipients."); diff --git a/firebase/functions/test/e2e/deleteUserData.test.ts b/firebase/functions/test/e2e/deleteUserData.test.ts new file mode 100644 index 00000000..9c1358ec --- /dev/null +++ b/firebase/functions/test/e2e/deleteUserData.test.ts @@ -0,0 +1,156 @@ +import {getAuth} from "firebase-admin/auth"; +import { + createTestUser, + createTestAccount, + createTestCohabitant, + createTestHousework, +} from "../helpers/testData"; +import { + expectAccountNotExists, + expectCohabitantNotExists, + expectCohabitantMembers, + expectSubcollectionEmpty, +} from "../helpers/assertions"; +import {FirestoreCollections} from "../../src/models/FirestoreCollections"; + +describe("deleteUserData E2E Tests", () => { + // 各テストで一意のIDを使用するためのカウンター + let testCounter = 0; + + beforeEach(() => { + testCounter++; + }); + + describe("2人グループのテスト", () => { + it("2人グループで1人削除した場合、Cohabitantグループが完全削除される", async () => { + // Arrange: テストデータ準備 + const uid1 = `test1-user-1-${testCounter}`; + const uid2 = `test1-user-2-${testCounter}`; + const user1 = await createTestUser(uid1, `${uid1}@example.com`); + const user2 = await createTestUser(uid2, `${uid2}@example.com`); + + const cohabitantId = `test1-cohabitant-${testCounter}`; + await createTestCohabitant(cohabitantId, [user1.uid, user2.uid]); + + await createTestAccount(user1.uid, cohabitantId, "token-1"); + await createTestAccount(user2.uid, cohabitantId, "token-2"); + + // サブコレクションにテストデータを追加 + await createTestHousework(cohabitantId, "housework-1", { + title: "Test Housework", + }); + + // Act: ユーザー削除(トリガー発火) + const auth = getAuth(); + await auth.deleteUser(user1.uid); + + // Assert: 検証(ポーリングで確認) + await expectAccountNotExists(user1.uid); + await expectCohabitantNotExists(cohabitantId); + await expectSubcollectionEmpty( + cohabitantId, + FirestoreCollections.HOUSEWORK + ); + }); + }); + + describe("3人以上のグループのテスト", () => { + it("3人グループで1人削除した場合、メンバーリストから削除されるのみ", async () => { + // Arrange + const uid1 = `test2-user-1-${testCounter}`; + const uid2 = `test2-user-2-${testCounter}`; + const uid3 = `test2-user-3-${testCounter}`; + const user1 = await createTestUser(uid1, `${uid1}@example.com`); + const user2 = await createTestUser(uid2, `${uid2}@example.com`); + const user3 = await createTestUser(uid3, `${uid3}@example.com`); + + const cohabitantId = `test2-cohabitant-${testCounter}`; + await createTestCohabitant(cohabitantId, [ + user1.uid, + user2.uid, + user3.uid, + ]); + + await createTestAccount(user1.uid, cohabitantId, "token-1"); + await createTestAccount(user2.uid, cohabitantId, "token-2"); + await createTestAccount(user3.uid, cohabitantId, "token-3"); + + // Act + const auth = getAuth(); + await auth.deleteUser(user1.uid); + + // Assert(ポーリングで確認) + await expectAccountNotExists(user1.uid); + await expectCohabitantMembers(cohabitantId, [user2.uid, user3.uid]); + }); + }); + + describe("Cohabitantに所属していないユーザーのテスト", () => { + it("Cohabitantに所属していないユーザーを削除してもエラーにならない", async () => { + // Arrange + const uid1 = `test3-user-1-${testCounter}`; + const user1 = await createTestUser(uid1, `${uid1}@example.com`); + await createTestAccount(user1.uid); // cohabitantIdなし + + // Act + const auth = getAuth(); + await auth.deleteUser(user1.uid); + + // Assert(ポーリングで確認) + await expectAccountNotExists(user1.uid); + }); + }); + + describe("Accountが存在しないユーザーのテスト", () => { + it("Accountドキュメントが存在しないユーザーを削除してもエラーにならない", async () => { + // Arrange + const uid1 = `test4-user-1-${testCounter}`; + const user1 = await createTestUser(uid1, `${uid1}@example.com`); + // Accountドキュメントは作成しない + + // Act & Assert: エラーなく完了することを確認 + const auth = getAuth(); + await auth.deleteUser(user1.uid); + // この場合はAccountが存在しないので、関数は即座に終了する + }); + }); + + describe("大量のサブコレクションデータのテスト", () => { + it("500件以上のサブコレクションドキュメントも削除される", async () => { + // Arrange + const uid1 = `test5-user-1-${testCounter}`; + const uid2 = `test5-user-2-${testCounter}`; + const user1 = await createTestUser(uid1, `${uid1}@example.com`); + const user2 = await createTestUser(uid2, `${uid2}@example.com`); + + const cohabitantId = `test5-cohabitant-${testCounter}`; + await createTestCohabitant(cohabitantId, [user1.uid, user2.uid]); + + await createTestAccount(user1.uid, cohabitantId); + await createTestAccount(user2.uid, cohabitantId); + + // 600件のHouseworkを作成(バッチ削除のテスト) + const promises = []; + for (let i = 0; i < 600; i++) { + promises.push( + createTestHousework(cohabitantId, `housework-${i}`, { + title: `Housework ${i}`, + }) + ); + } + await Promise.all(promises); + + // Act + const auth = getAuth(); + await auth.deleteUser(user1.uid); + + // Assert(ポーリングで確認、大量データなのでタイムアウトを長めに) + await expectCohabitantNotExists(cohabitantId, 10000); + await expectSubcollectionEmpty( + cohabitantId, + FirestoreCollections.HOUSEWORK, + 10000 + ); + }, 20000); // タイムアウトを20秒に設定 + }); +}); diff --git a/firebase/functions/test/helpers/assertions.ts b/firebase/functions/test/helpers/assertions.ts new file mode 100644 index 00000000..6ffe5cb1 --- /dev/null +++ b/firebase/functions/test/helpers/assertions.ts @@ -0,0 +1,161 @@ +import {getFirestore} from "firebase-admin/firestore"; +import {getAuth} from "firebase-admin/auth"; +import {FirestoreCollections} from "../../src/models/FirestoreCollections"; + +/** + * Accountドキュメントが削除されていることを確認(ポーリング方式) + * @param {string} userId - 確認するユーザーID + * @param {number} timeoutMs - タイムアウト時間(ミリ秒) + * @return {Promise} + */ +export async function expectAccountNotExists( + userId: string, + timeoutMs = 5000 +): Promise { + const db = getFirestore(); + const startTime = Date.now(); + + while (Date.now() - startTime < timeoutMs) { + const snapshot = await db + .collection(FirestoreCollections.ACCOUNT) + .where("id", "==", userId) + .get(); + + if (snapshot.empty) { + return; // 削除された + } + + await new Promise((resolve) => setTimeout(resolve, 100)); // 100ms待機 + } + + // タイムアウト + throw new Error( + `Account for user ${userId} still exists after ${timeoutMs}ms` + ); +} + +/** + * Cohabitantドキュメントが存在しないことを確認(ポーリング方式) + * @param {string} cohabitantId - CohabitantドキュメントのID + * @param {number} timeoutMs - タイムアウト時間(ミリ秒) + * @return {Promise} + */ +export async function expectCohabitantNotExists( + cohabitantId: string, + timeoutMs = 5000 +): Promise { + const db = getFirestore(); + const startTime = Date.now(); + + while (Date.now() - startTime < timeoutMs) { + const doc = await db + .collection(FirestoreCollections.COHABITANT) + .doc(cohabitantId) + .get(); + + if (!doc.exists) { + return; // 削除された + } + + await new Promise((resolve) => setTimeout(resolve, 100)); // 100ms待機 + } + + // タイムアウト + throw new Error( + `Cohabitant ${cohabitantId} still exists after ${timeoutMs}ms` + ); +} + +/** + * Cohabitantのメンバーリストを確認(ポーリング方式) + * @param {string} cohabitantId - CohabitantドキュメントのID + * @param {string[]} expectedMembers - 期待されるメンバーIDの配列 + * @param {number} timeoutMs - タイムアウト時間(ミリ秒) + * @return {Promise} + */ +export async function expectCohabitantMembers( + cohabitantId: string, + expectedMembers: string[], + timeoutMs = 5000 +): Promise { + const db = getFirestore(); + const startTime = Date.now(); + + while (Date.now() - startTime < timeoutMs) { + const doc = await db + .collection(FirestoreCollections.COHABITANT) + .doc(cohabitantId) + .get(); + + if (doc.exists) { + const data = doc.data(); + const members = data?.members || []; + + // メンバーリストが一致したら成功 + const sortedMembers = JSON.stringify(members.sort()); + const sortedExpected = JSON.stringify(expectedMembers.sort()); + if (sortedMembers === sortedExpected) { + return; + } + } + + await new Promise((resolve) => setTimeout(resolve, 100)); // 100ms待機 + } + + // タイムアウト + const errorMsg = `Cohabitant ${cohabitantId} members did not match ` + + `expected after ${timeoutMs}ms`; + throw new Error(errorMsg); +} + +/** + * サブコレクションが空であることを確認(ポーリング方式) + * @param {string} cohabitantId - CohabitantドキュメントのID + * @param {string} subcollection - サブコレクション名 + * @param {number} timeoutMs - タイムアウト時間(ミリ秒) + * @return {Promise} + */ +export async function expectSubcollectionEmpty( + cohabitantId: string, + subcollection: string, + timeoutMs = 5000 +): Promise { + const db = getFirestore(); + const path = FirestoreCollections.getCohabitantSubcollection( + cohabitantId, + subcollection + ); + const startTime = Date.now(); + + while (Date.now() - startTime < timeoutMs) { + const snapshot = await db.collection(path).get(); + + if (snapshot.empty) { + return; // 空になった + } + + await new Promise((resolve) => setTimeout(resolve, 100)); // 100ms待機 + } + + // タイムアウト + const errorMsg = `Subcollection ${subcollection} for ${cohabitantId} ` + + `is not empty after ${timeoutMs}ms`; + throw new Error(errorMsg); +} + +/** + * Firebase Authユーザーが存在しないことを確認 + * @param {string} userId - 確認するユーザーID + * @return {Promise} + */ +export async function expectAuthUserNotExists(userId: string): Promise { + const auth = getAuth(); + try { + await auth.getUser(userId); + throw new Error(`Auth user ${userId} still exists`); + } catch (error: any) { + if (error.code !== "auth/user-not-found") { + throw error; + } + } +} diff --git a/firebase/functions/test/helpers/testData.ts b/firebase/functions/test/helpers/testData.ts new file mode 100644 index 00000000..3efe80fb --- /dev/null +++ b/firebase/functions/test/helpers/testData.ts @@ -0,0 +1,113 @@ +import {getAuth} from "firebase-admin/auth"; +import {getFirestore} from "firebase-admin/firestore"; +import {FirestoreCollections} from "../../src/models/FirestoreCollections"; +import {Account} from "../../src/models/Account"; +import {Cohabitant} from "../../src/models/Cohabitant"; + +export interface TestUser { + uid: string; + email: string; +} + +/** + * テストユーザーを作成 + * @param {string} uid - ユーザーID + * @param {string} email - メールアドレス + * @return {Promise} 作成されたテストユーザー + */ +export async function createTestUser( + uid: string, + email: string +): Promise { + const auth = getAuth(); + await auth.createUser({uid, email}); + return {uid, email}; +} + +/** + * テスト用Accountドキュメントを作成 + * プロダクションコードのAccountモデルを使用 + * @param {string} userId - ユーザーID + * @param {string} cohabitantId - CohabitantドキュメントのID(オプション) + * @param {string} fcmToken - FCMトークン(オプション) + * @return {Promise} + */ +export async function createTestAccount( + userId: string, + cohabitantId?: string, + fcmToken?: string +): Promise { + const db = getFirestore(); + + // Accountモデルに準拠したデータを作成 + const account: Account = { + id: userId, + cohabitantId, + fcmToken, + }; + + // undefinedフィールドを除外してFirestoreに保存 + const accountData = removeUndefinedFields(account); + + await db.collection(FirestoreCollections.ACCOUNT).add(accountData); +} + +/** + * テストCohabitantドキュメントを作成 + * プロダクションコードのCohabitantモデルを使用 + * @param {string} cohabitantId - CohabitantドキュメントのID + * @param {string[]} members - メンバーIDの配列 + * @return {Promise} + */ +export async function createTestCohabitant( + cohabitantId: string, + members: string[] +): Promise { + const db = getFirestore(); + + // Cohabitantモデルに準拠したデータを作成 + const cohabitant: Cohabitant = { + members, + }; + + await db + .collection(FirestoreCollections.COHABITANT) + .doc(cohabitantId) + .set(cohabitant); +} + +/** + * Cohabitantのサブコレクションにテストデータを追加 + * Houseworkモデルはまだ定義されていないため、汎用的なRecord型を使用 + * @param {string} cohabitantId - CohabitantドキュメントのID + * @param {string} houseworkId - HouseworkドキュメントのID + * @param {Record} data - 保存するデータ + * @return {Promise} + */ +export async function createTestHousework( + cohabitantId: string, + houseworkId: string, + data: Record +): Promise { + const db = getFirestore(); + const path = FirestoreCollections.getHouseworkPath(cohabitantId); + await db.collection(path).doc(houseworkId).set(data); +} + +/** + * undefinedフィールドを除外するヘルパー関数 + * @param {T} obj - 処理するオブジェクト + * @return {Partial} undefinedフィールドを除外したオブジェクト + * @template T + */ +function removeUndefinedFields>( + obj: T +): Partial { + const result: Partial = {}; + for (const key in obj) { + if (obj[key] !== undefined) { + result[key] = obj[key]; + } + } + return result; +} diff --git a/firebase/functions/test/setup.ts b/firebase/functions/test/setup.ts new file mode 100644 index 00000000..dec27944 --- /dev/null +++ b/firebase/functions/test/setup.ts @@ -0,0 +1,45 @@ +import {initializeApp, getApps, deleteApp, App} from "firebase-admin/app"; +import {getFirestore} from "firebase-admin/firestore"; + +// Emulator接続設定 +process.env.FIRESTORE_EMULATOR_HOST = "localhost:8080"; +process.env.FIREBASE_AUTH_EMULATOR_HOST = "localhost:9099"; + +let app: App; + +beforeAll(() => { + // テスト用Firebase Admin初期化 + if (getApps().length === 0) { + app = initializeApp({projectId: "homete-ios-dev-e3ef7"}); + } +}); + +afterAll(async () => { + // クリーンアップ + if (app) { + await deleteApp(app); + } +}); + +beforeEach(async () => { + // 各テスト前にFirestoreをクリア(Authはクリアしない) + await clearFirestore(); +}); + +/** + * Firestoreのデータをクリアする + * @return {Promise} + */ +async function clearFirestore() { + const db = getFirestore(); + const collections = ["Account", "Cohabitant"]; + + for (const collectionName of collections) { + const snapshot = await db.collection(collectionName).get(); + const batch = db.batch(); + snapshot.docs.forEach((doc) => { + batch.delete(doc.ref); + }); + await batch.commit(); + } +} diff --git a/firebase/dev/functions/tsconfig.dev.json b/firebase/functions/tsconfig.dev.json similarity index 100% rename from firebase/dev/functions/tsconfig.dev.json rename to firebase/functions/tsconfig.dev.json diff --git a/firebase/dev/functions/tsconfig.json b/firebase/functions/tsconfig.json similarity index 100% rename from firebase/dev/functions/tsconfig.json rename to firebase/functions/tsconfig.json diff --git a/firebase/functions/tsconfig.test.json b/firebase/functions/tsconfig.test.json new file mode 100644 index 00000000..6be9d4b9 --- /dev/null +++ b/firebase/functions/tsconfig.test.json @@ -0,0 +1,7 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "types": ["jest", "node"] + }, + "include": ["test/**/*.ts"] +} diff --git a/homete/Model/Domain/Account/Account.swift b/homete/Model/Domain/Account/Account.swift index 0a246e7d..d5032127 100644 --- a/homete/Model/Domain/Account/Account.swift +++ b/homete/Model/Domain/Account/Account.swift @@ -10,14 +10,13 @@ struct Account: Equatable, Codable { let id: String let userName: String let fcmToken: String? + let cohabitantId: String? } extension Account { - - static let empty: Self = .init(id: "", userName: "", fcmToken: nil) - + static func initial(auth: AccountAuthResult, userName: UserName, fcmToken: String?) -> Self { - return .init(id: auth.id, userName: userName.value, fcmToken: fcmToken) + return .init(id: auth.id, userName: userName.value, fcmToken: fcmToken, cohabitantId: nil) } } diff --git a/homete/Model/Domain/Account/LoginContext.swift b/homete/Model/Domain/Account/LoginContext.swift index 5c1d5f8a..7b03dc58 100644 --- a/homete/Model/Domain/Account/LoginContext.swift +++ b/homete/Model/Domain/Account/LoginContext.swift @@ -10,9 +10,12 @@ import SwiftUI struct LoginContext: Equatable { let account: Account + + /// パートナー登録済みかどうか + var hasCohabitant: Bool { account.cohabitantId != nil } } extension EnvironmentValues { - @Entry var loginContext = LoginContext(account: .init(id: "", userName: "", fcmToken: nil)) + @Entry var loginContext = LoginContext(account: .init(id: "", userName: "", fcmToken: nil, cohabitantId: nil)) } diff --git a/homete/Model/Domain/Authentification/LaunchState.swift b/homete/Model/Domain/Authentification/LaunchState.swift index 95a65ffe..73625e46 100644 --- a/homete/Model/Domain/Authentification/LaunchState.swift +++ b/homete/Model/Domain/Authentification/LaunchState.swift @@ -15,4 +15,10 @@ enum LaunchState: Equatable { case loggedIn(context: LoginContext) /// 未ログイン case notLoggedIn + + var isLoggedIn: Bool { + + if case .loggedIn = self { return true } + return false + } } diff --git a/homete/Model/Domain/Cohabitant/Environment/CohabitantIdEnvironment.swift b/homete/Model/Domain/Cohabitant/Environment/CohabitantIdEnvironment.swift deleted file mode 100644 index 18ec1f94..00000000 --- a/homete/Model/Domain/Cohabitant/Environment/CohabitantIdEnvironment.swift +++ /dev/null @@ -1,13 +0,0 @@ -// -// CohabitantIdEnvironment.swift -// homete -// -// Created by 佐藤汰一 on 2025/09/08. -// - -import SwiftUI - -extension EnvironmentValues { - - @Entry var cohabitantId: String = "" -} diff --git a/homete/Model/Service/AppStorage/AppStorageExtensions.swift b/homete/Model/Service/AppStorage/AppStorageExtensions.swift index e945d2bf..5fede55e 100644 --- a/homete/Model/Service/AppStorage/AppStorageExtensions.swift +++ b/homete/Model/Service/AppStorage/AppStorageExtensions.swift @@ -23,22 +23,6 @@ enum AppStorageOptionalDataKey: String { case archivedPeerIDDataKey } -// MARK: - String型の拡張 - -extension SwiftUI.AppStorage where Value == String { - - init(wrappedValue: String, key: AppStorageStringKey) { - - self.init(wrappedValue: wrappedValue, key.rawValue) - } -} - -enum AppStorageStringKey: String { - - /// 同居人ID - case cohabitantId -} - // MARK: - カスタムオブジェクト用の拡張 extension SwiftUI.AppStorage where Value: RawRepresentable, Value.RawValue == String { diff --git a/homete/Model/Store/AccountStore.swift b/homete/Model/Store/AccountStore.swift index 3e8d1ae2..46de8488 100644 --- a/homete/Model/Store/AccountStore.swift +++ b/homete/Model/Store/AccountStore.swift @@ -43,7 +43,7 @@ final class AccountStore { func registerAccount(auth: AccountAuthResult, userName: UserName) async throws -> Account { - let newAccount = Account(id: auth.id, userName: userName.value, fcmToken: nil) + let newAccount = Account(id: auth.id, userName: userName.value, fcmToken: nil, cohabitantId: nil) try await accountInfoClient.insertOrUpdate(newAccount) account = newAccount return newAccount @@ -60,7 +60,8 @@ final class AccountStore { let updatedAccount = Account( id: account.id, userName: account.userName, - fcmToken: fcmToken + fcmToken: fcmToken, + cohabitantId: account.cohabitantId ) try await accountInfoClient.insertOrUpdate(updatedAccount) self.account = updatedAccount @@ -70,4 +71,21 @@ final class AccountStore { print("failed to update fcmToken: \(error)") } } + + func registerCohabitantId(_ cohabitantId: String) async throws { + + guard let account else { + + preconditionFailure("Not found account.") + } + + let updatedAccount = Account( + id: account.id, + userName: account.userName, + fcmToken: account.fcmToken, + cohabitantId: cohabitantId + ) + try await accountInfoClient.insertOrUpdate(updatedAccount) + self.account = updatedAccount + } } diff --git a/homete/Resouces/Localizable.xcstrings b/homete/Resouces/Localizable.xcstrings index 29361a3c..1bc6042f 100644 --- a/homete/Resouces/Localizable.xcstrings +++ b/homete/Resouces/Localizable.xcstrings @@ -41,7 +41,7 @@ "あと%lld文字" : { }, - "あなたのデータは全て削除され、復元することはできませんがよろしいですか?" : { + "あなたのデータは全て削除され、復元することはできません。\nまた、現在参加しているグループが2名以下の場合は、グループごと削除されます。" : { }, "お手数ですが、再度デバイスを近づけて通信を行ってください" : { diff --git a/homete/Views/Auth/Login/LoginView.swift b/homete/Views/Auth/Login/LoginView.swift index d98fd7df..9f13f1ec 100644 --- a/homete/Views/Auth/Login/LoginView.swift +++ b/homete/Views/Auth/Login/LoginView.swift @@ -24,7 +24,6 @@ struct LoginView: View { SignInUpWithAppleButton { isLoading = true await onSignInWithApple($0) - isLoading = false } .frame(height: .space48) .clipShape(RoundedRectangle(cornerRadius: .space16 / 2)) diff --git a/homete/Views/HomeView/HomeView.swift b/homete/Views/HomeView/HomeView.swift index 3997458a..96071b18 100644 --- a/homete/Views/HomeView/HomeView.swift +++ b/homete/Views/HomeView/HomeView.swift @@ -8,23 +8,22 @@ import SwiftUI struct HomeView: View { - - @AppStorage(key: .cohabitantId) var cohabitantId = "" + @Environment(\.loginContext.hasCohabitant) var hasCohabitant @State var isShowCohabitantRegistrationModal = false @State var isShowSetting = false var body: some View { NavigationStack { ZStack { - if cohabitantId.isEmpty { + if hasCohabitant { + RegisteredContent() + } + else { NotRegisteredContent( isShowCohabitantRegistrationModal: $isShowCohabitantRegistrationModal ) } - else { - RegisteredContent() - } } .fullScreenCover(isPresented: $isShowCohabitantRegistrationModal) { CohabitantRegistrationView() @@ -53,8 +52,11 @@ struct HomeView: View { #Preview("HomeView_登録時") { NavigationStack { HomeView() - .injectAppStorageWithPreview("HomeView_登録時") { - $0.set("testId", forKey: "cohabitantId") - } + .environment(\.loginContext, .init(account: .init( + id: "", + userName: "", + fcmToken: nil, + cohabitantId: "dummy" + ))) } } diff --git a/homete/Views/HouseworkDetailView/SubViews/HouseworkDetailActionContent.swift b/homete/Views/HouseworkDetailView/SubViews/HouseworkDetailActionContent.swift index 32783e85..e5bbea24 100644 --- a/homete/Views/HouseworkDetailView/SubViews/HouseworkDetailActionContent.swift +++ b/homete/Views/HouseworkDetailView/SubViews/HouseworkDetailActionContent.swift @@ -101,7 +101,7 @@ private extension HouseworkDetailActionContent { HouseworkDetailActionContent( isLoading: .constant(false), commonErrorContent: .constant(.initial), - account: .init(id: "", userName: "", fcmToken: nil), + account: .init(id: "", userName: "", fcmToken: nil, cohabitantId: nil), item: .init( id: "", title: "洗濯", @@ -116,7 +116,7 @@ private extension HouseworkDetailActionContent { HouseworkDetailActionContent( isLoading: .constant(false), commonErrorContent: .constant(.initial), - account: .init(id: "dummy", userName: "", fcmToken: nil), + account: .init(id: "dummy", userName: "", fcmToken: nil, cohabitantId: nil), item: .init( id: "", indexedDate: .init(.distantPast), @@ -135,7 +135,7 @@ private extension HouseworkDetailActionContent { HouseworkDetailActionContent( isLoading: .constant(false), commonErrorContent: .constant(.initial), - account: .init(id: "dummy", userName: "", fcmToken: nil), + account: .init(id: "dummy", userName: "", fcmToken: nil, cohabitantId: nil), item: .init( id: "", indexedDate: .init(.distantPast), diff --git a/homete/Views/RegisterCohabitantView/CohabitantRegistrationView.swift b/homete/Views/RegisterCohabitantView/CohabitantRegistrationView.swift index 1023a1c2..840c42f1 100644 --- a/homete/Views/RegisterCohabitantView/CohabitantRegistrationView.swift +++ b/homete/Views/RegisterCohabitantView/CohabitantRegistrationView.swift @@ -10,8 +10,11 @@ import SwiftUI struct CohabitantRegistrationView: View { + @Environment(\.calendar) var calendar @Environment(\.loginContext.account.userName) var userName @Environment(\.dismiss) var dismiss + @Environment(AccountStore.self) var accountStore + @Environment(HouseworkListStore.self) var houseworkListStore // 登録処理を中断するかどうかを確認するアラート @State var isPresentingConfirmCancelAlert = false @@ -32,14 +35,41 @@ struct CohabitantRegistrationView: View { } .alert( "登録処理を終了しますか?", - isPresented: $isPresentingConfirmCancelAlert) { - Button(role: .destructive) { - dismiss() - } label: { - Text("終了する") - } - } message: { - Text("登録を終了すると、また初めから登録し直す必要があります。") + isPresented: $isPresentingConfirmCancelAlert + ) { + Button(role: .destructive) { + dismiss() + } label: { + Text("終了する") + } + } message: { + Text("登録を終了すると、また初めから登録し直す必要があります。") + } + .onCompleteCohabitantRegistration { cohabitantId in + Task { + await onCompleteCohabitantRegistration(cohabitantId) } + } + } +} + +// MARK: プレゼンテーションロジック + +private extension CohabitantRegistrationView { + + func onCompleteCohabitantRegistration(_ cohabitantId: String) async { + + do { + + try await accountStore.registerCohabitantId(cohabitantId) + await houseworkListStore.loadHouseworkList( + currentTime: .now, + cohabitantId: cohabitantId, + calendar: calendar + ) + } catch { + + print("error occurred: \(error)") + } } } diff --git a/homete/Views/RegisterCohabitantView/Extension/onCompleteCohabitantRegistration.swift b/homete/Views/RegisterCohabitantView/Extension/onCompleteCohabitantRegistration.swift new file mode 100644 index 00000000..8f2c5ff1 --- /dev/null +++ b/homete/Views/RegisterCohabitantView/Extension/onCompleteCohabitantRegistration.swift @@ -0,0 +1,16 @@ +import SwiftUI + +extension EnvironmentValues { + + /// パートナーの登録完了通知 + @Entry var onCompleteCohabitantRegistration: (_ cohabitantId: String) -> Void = { _ in } +} + +extension View { + + /// パートナーの登録完了通知 + func onCompleteCohabitantRegistration(handler: @escaping (_ cohabitantId: String) -> Void) -> some View { + + self.environment(\.onCompleteCohabitantRegistration, handler) + } +} diff --git a/homete/Views/RegisterCohabitantView/SubViews/ProcessingState/CohabitantRegistrationProcessingFollowerView.swift b/homete/Views/RegisterCohabitantView/SubViews/ProcessingState/CohabitantRegistrationProcessingFollowerView.swift index 637643ae..5f1c6547 100644 --- a/homete/Views/RegisterCohabitantView/SubViews/ProcessingState/CohabitantRegistrationProcessingFollowerView.swift +++ b/homete/Views/RegisterCohabitantView/SubViews/ProcessingState/CohabitantRegistrationProcessingFollowerView.swift @@ -13,9 +13,9 @@ struct CohabitantRegistrationProcessingFollower: View { @Environment(\.p2pSessionProxy) var p2pSessionProxy @Environment(\.p2pSessionReceiveData) var receiveData @Environment(\.loginContext.account.id) var accountId + @Environment(\.onCompleteCohabitantRegistration) var onCompleteCohabitantRegistration - @AppStorage(key: .cohabitantId) var cohabitantId = "" - + @State var cohabitantId: String? @State var confirmedRolePeers: Set = [] @State var leadPeer: MCPeerID? @@ -63,7 +63,7 @@ private extension CohabitantRegistrationProcessingFollower { confirmedRolePeers.insert(peerID) // すでに同居人IDを受け取っていたら、完了メッセージを送信する - if !cohabitantId.isEmpty { + if cohabitantId != nil { sendCompleteMessageIfNeeded() } @@ -72,6 +72,7 @@ private extension CohabitantRegistrationProcessingFollower { func onReceiveCohabitantId(_ cohabitantId: String) { self.cohabitantId = cohabitantId + onCompleteCohabitantRegistration(cohabitantId) sendCompleteMessageIfNeeded() } diff --git a/homete/Views/RegisterCohabitantView/SubViews/ProcessingState/CohabitantRegistrationProcessingLeader.swift b/homete/Views/RegisterCohabitantView/SubViews/ProcessingState/CohabitantRegistrationProcessingLeader.swift index edb92d51..6c2a8041 100644 --- a/homete/Views/RegisterCohabitantView/SubViews/ProcessingState/CohabitantRegistrationProcessingLeader.swift +++ b/homete/Views/RegisterCohabitantView/SubViews/ProcessingState/CohabitantRegistrationProcessingLeader.swift @@ -17,9 +17,8 @@ struct CohabitantRegistrationProcessingLeader: View { @Environment(\.connectedPeers) var connectedPeers @Environment(\.p2pSessionReceiveData) var receiveData @Environment(\.loginContext.account.id) var accountId - - @AppStorage(key: .cohabitantId) var cohabitantId = "" - + @Environment(\.onCompleteCohabitantRegistration) var onCompleteCohabitantRegistration + // 登録処理の役割の通知が済んでいるデバイスリスト @State var confirmedRolePeers: Set = [] @State var cohabitantsAccountId: Set = [] @@ -27,6 +26,7 @@ struct CohabitantRegistrationProcessingLeader: View { @State var completedRegistrationPeers: Set = [] // 同居人レコードの登録に失敗した時のアラート @State var isPresentingFailedRegistrationIdAlert = false + @State var cohabitantId: String? @Binding var registrationState: CohabitantRegistrationState @@ -41,7 +41,7 @@ struct CohabitantRegistrationProcessingLeader: View { isPresented: $isPresentingFailedRegistrationIdAlert ) { Button("OK") { - cohabitantId = "" + cohabitantId = nil dismiss() } } message: { @@ -73,6 +73,7 @@ private extension CohabitantRegistrationProcessingLeader { // 登録時に使用するアカウントIDをオンメモリに保持しておく if let accountId = data.memberRole?.accountId { + print("dispatchReceivedMessage share accountId") cohabitantsAccountId.insert(accountId) confirmedRolePeers.insert(sender) } @@ -85,10 +86,9 @@ private extension CohabitantRegistrationProcessingLeader { func onReadyRegistration() { - if cohabitantId.isEmpty { - - cohabitantId = UUID().uuidString - } + print("call onReadyRegistration") + let cohabitantId = UUID().uuidString + self.cohabitantId = cohabitantId Task { @@ -118,6 +118,11 @@ private extension CohabitantRegistrationProcessingLeader { func onCompletedRegistration() { + guard let cohabitantId else { + preconditionFailure("Not found required param(cohabitantId)") + } + + onCompleteCohabitantRegistration(cohabitantId) let message = CohabitantRegistrationMessage(type: .complete) p2pSessionProxy?.send( message.encodedData(), diff --git a/homete/Views/RegisterCohabitantView/SubViews/ScanningState/CohabitantRegistrationPeersListView.swift b/homete/Views/RegisterCohabitantView/SubViews/ScanningState/CohabitantRegistrationPeersListView.swift index 79d4d9cb..f6ed928c 100644 --- a/homete/Views/RegisterCohabitantView/SubViews/ScanningState/CohabitantRegistrationPeersListView.swift +++ b/homete/Views/RegisterCohabitantView/SubViews/ScanningState/CohabitantRegistrationPeersListView.swift @@ -45,7 +45,6 @@ struct CohabitantRegistrationPeersListView: View { .frame(height: .space24) } .padding(.horizontal, .space16) - .ignoresSafeArea() .fullScreenLoadingIndicator(isConfirmedReadyRegistration) .alert("表示されているメンバーで登録を開始しますか?", isPresented: $isPresentingConfirmReadyRegistrationAlert) { Button { diff --git a/homete/Views/RootView/RootView.swift b/homete/Views/RootView/RootView.swift index de7bd0ba..4c795139 100644 --- a/homete/Views/RootView/RootView.swift +++ b/homete/Views/RootView/RootView.swift @@ -13,9 +13,7 @@ struct RootView: View { @State var theme = Theme() @State var fcmToken: String? @State var launchState = LaunchState.launching - - @AppStorage(key: .cohabitantId) var localStorageCohabitantId = "" - + @Environment(AccountAuthStore.self) var accountAuthStore @Environment(AccountStore.self) var accountStore @@ -39,24 +37,21 @@ struct RootView: View { } } .animation(.spring, value: launchState) - .onChange(of: accountAuthStore.currentAuth) { - Task { - await onChangeAuth(accountAuthStore.currentAuth) - } + .task(id: accountAuthStore.currentAuth) { + await onChangeAuth() } .task(id: accountStore.account) { - guard let fcmToken else { return } - await accountStore.updateFcmTokenIfNeeded(fcmToken) + await onChangeAccount() } .onReceive(NotificationCenter.default.publisher(for: .didReceiveFcmToken)) { notification in guard let fcmToken = notification.object as? String else { return } self.fcmToken = fcmToken Task { await accountStore.updateFcmTokenIfNeeded(fcmToken) + self.fcmToken = nil } } .apply(theme: theme) - .environment(\.cohabitantId, localStorageCohabitantId) .environment(\.launchStateProxy, .init(launchState: $launchState)) } } @@ -80,8 +75,8 @@ extension RootView { private extension RootView { - func onChangeAuth(_ auth: AccountAuthInfo) async { - guard let authResult = auth.result else { + func onChangeAuth() async { + guard let authResult = accountAuthStore.currentAuth.result else { launchState = .notLoggedIn return } @@ -92,4 +87,16 @@ private extension RootView { launchState = .preLoggedIn(auth: authResult) } } + + func onChangeAccount() async { + + guard launchState.isLoggedIn, + let account = accountStore.account else { return } + + if let fcmToken { + await accountStore.updateFcmTokenIfNeeded(fcmToken) + self.fcmToken = nil + } + launchState = .loggedIn(context: .init(account: account)) + } } diff --git a/homete/Views/SettingView/SettingView.swift b/homete/Views/SettingView/SettingView.swift index e510b30b..4cf5845b 100644 --- a/homete/Views/SettingView/SettingView.swift +++ b/homete/Views/SettingView/SettingView.swift @@ -62,6 +62,7 @@ struct SettingView: View { } } } + .fullScreenLoadingIndicator(isLoading) .alert("ログアウトしますか?", isPresented: $isPresentedLogoutConfirmAlert) { Button("ログアウト", role: .destructive) { tappedLogoutAlertOkButton() @@ -75,7 +76,7 @@ struct SettingView: View { } } } message: { - Text("あなたのデータは全て削除され、復元することはできませんがよろしいですか?") + Text("あなたのデータは全て削除され、復元することはできません。\nまた、現在参加しているグループが2名以下の場合は、グループごと削除されます。") } } } diff --git a/homete/Views/TabView/AppTabView.swift b/homete/Views/TabView/AppTabView.swift index 9fe8386b..9985d49f 100644 --- a/homete/Views/TabView/AppTabView.swift +++ b/homete/Views/TabView/AppTabView.swift @@ -11,7 +11,7 @@ import UserNotifications struct AppTabView: View { @Environment(\.calendar) var calendar - @Environment(\.cohabitantId) var cohabitantId + @Environment(\.loginContext.account.cohabitantId) var cohabitantId @Environment(HouseworkListStore.self) var houseworkListStore @State var type: TabType = .dashboard @@ -59,11 +59,6 @@ struct AppTabView: View { .task { await onAppear() } - .onChange(of: cohabitantId) { - Task { - await onChangeCohabitantId() - } - } } } @@ -74,15 +69,8 @@ private extension AppTabView { func onAppear() async { requestNotificationPermission() - await houseworkListStore.loadHouseworkList( - currentTime: .now, - cohabitantId: cohabitantId, - calendar: calendar - ) - } - - func onChangeCohabitantId() async { + guard let cohabitantId else { return } await houseworkListStore.loadHouseworkList( currentTime: .now, cohabitantId: cohabitantId, diff --git a/hometeTests/Store/AccountStoreTest.swift b/hometeTests/Store/AccountStoreTest.swift index 5f5d52fc..e5038694 100644 --- a/hometeTests/Store/AccountStoreTest.swift +++ b/hometeTests/Store/AccountStoreTest.swift @@ -16,7 +16,12 @@ struct AccountStoreTest { // Arrange let inputAccountId = "test" - let inputAccount = Account(id: inputAccountId, userName: "testUserName", fcmToken: "testToken") + let inputAccount = Account( + id: inputAccountId, + userName: "testUserName", + fcmToken: "testToken", + cohabitantId: nil + ) await confirmation(expectedCount: 1) { confirmation in @@ -44,11 +49,12 @@ struct AccountStoreTest { // Arrange let inputFcmToken = "token" - let initialAccount = Account(id: "testId", userName: "testUser", fcmToken: nil) + let initialAccount = Account(id: "testId", userName: "testUser", fcmToken: nil, cohabitantId: nil) let expectedAccount = Account( id: initialAccount.id, userName: initialAccount.userName, - fcmToken: inputFcmToken + fcmToken: inputFcmToken, + cohabitantId: nil ) let accountInfoClient = AccountInfoClient(insertOrUpdate: { @@ -67,4 +73,41 @@ struct AccountStoreTest { #expect(store.account == expectedAccount) } } + + @Test("パートナーの登録で保持しているアカウントにパートナーグループIDの情報を更新する") + func registerCohabitantId() async throws { + + try await confirmation(expectedCount: 1) { confirmation in + + // Arrange + let inputCohabitantId = "testCohabitantId" + let initialAccount = Account( + id: "testId", + userName: "testUser", + fcmToken: nil, + cohabitantId: nil + ) + let expectedAccount = Account( + id: initialAccount.id, + userName: initialAccount.userName, + fcmToken: nil, + cohabitantId: inputCohabitantId + ) + let accountInfoClient = AccountInfoClient(insertOrUpdate: { + + confirmation() + #expect($0 == expectedAccount) + }) + let store = AccountStore( + appDependencies: .init(accountInfoClient: accountInfoClient), + account: initialAccount + ) + + // Act + try await store.registerCohabitantId(inputCohabitantId) + + // Assert + #expect(store.account == expectedAccount) + } + } }