diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml new file mode 100644 index 000000000..3dcd3d268 --- /dev/null +++ b/.github/workflows/test.yml @@ -0,0 +1,68 @@ +name: Unit Tests & Coverage + +on: + push: + branches: [main, pre-stage, dev] + pull_request: + +jobs: + test-api: + runs-on: ubuntu-latest + defaults: + run: + working-directory: api + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-node@v4 + with: + node-version: 22 + cache: npm + cache-dependency-path: api/package-lock.json + - run: npm ci + - run: npm install @rollup/rollup-linux-x64-gnu --no-save + - run: npm run test:coverage + - uses: actions/upload-artifact@v4 + if: always() + with: + name: api-coverage-report + path: api/coverage/ + + test-ui: + runs-on: ubuntu-latest + defaults: + run: + working-directory: ui + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-node@v4 + with: + node-version: 22 + cache: npm + cache-dependency-path: ui/package-lock.json + - run: npm ci --legacy-peer-deps + - run: npm run test:coverage + - uses: actions/upload-artifact@v4 + if: always() + with: + name: ui-coverage-report + path: ui/coverage/ + + test-upload-api: + runs-on: ubuntu-latest + defaults: + run: + working-directory: upload-api + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-node@v4 + with: + node-version: 22 + cache: npm + cache-dependency-path: upload-api/package-lock.json + - run: npm install --legacy-peer-deps + - run: npm run test:coverage + - uses: actions/upload-artifact@v4 + if: always() + with: + name: upload-api-coverage-report + path: upload-api/coverage/ diff --git a/.gitignore b/.gitignore index 07d1ff8d3..e1b544e6f 100644 --- a/.gitignore +++ b/.gitignore @@ -367,3 +367,6 @@ app.json *MigrationData* *.zip app.json + +# Test coverage (global) +coverage/ diff --git a/api/.gitignore b/api/.gitignore index 736178335..1b83d423e 100644 --- a/api/.gitignore +++ b/api/.gitignore @@ -367,4 +367,5 @@ database/ /migration-data **/copy* **copy.ts -manifest.json \ No newline at end of file +manifest.json +coverage/ \ No newline at end of file diff --git a/api/package-lock.json b/api/package-lock.json index 12fddb887..a9e7aff08 100644 --- a/api/package-lock.json +++ b/api/package-lock.json @@ -49,17 +49,21 @@ "@types/jsonwebtoken": "^9.0.5", "@types/lodash": "^4.17.0", "@types/node": "^20.10.4", + "@types/supertest": "^6.0.3", "@types/uuid": "^9.0.8", "@types/wordpress__block-library": "^2.6.3", "@types/wordpress__block-serialization-spec-parser": "^3.1.3", "@types/wordpress__blocks": "^12.5.18", "@typescript-eslint/eslint-plugin": "^6.15.0", "@typescript-eslint/parser": "^6.15.0", + "@vitest/coverage-v8": "^4.0.18", "eslint": "^8.56.0", "eslint-config-prettier": "^8.3.0", "prettier": "^2.4.1", + "supertest": "^7.2.2", "tsx": "^4.7.1", - "typescript": "^5.4.3" + "typescript": "^5.4.3", + "vitest": "^4.0.18" } }, "node_modules/@apollo/client": { @@ -292,6 +296,16 @@ "node": ">=6.9.0" } }, + "node_modules/@bcoe/v8-coverage": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-1.0.2.tgz", + "integrity": "sha512-6zABk/ECA/QYSCQ1NGiVwwbQerUCZ+TQbp64Q3AgmfNvurHH0j8TtXa1qbShXA6qqkpAj4V5W8pP6mLe1mcMqA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, "node_modules/@colors/colors": { "version": "1.6.0", "resolved": "https://registry.npmjs.org/@colors/colors/-/colors-1.6.0.tgz", @@ -2033,6 +2047,19 @@ "@jridgewell/sourcemap-codec": "^1.4.14" } }, + "node_modules/@noble/hashes": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.8.0.tgz", + "integrity": "sha512-jCs9ldd7NwzpgXDIf6P3+NrHh9/sD6CQdxHyjQI+h/6rDNo88ypBxxz45UDuZHz9r3tNz7N/VInSVoVdtXEI4A==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^14.21.3 || >=16" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, "node_modules/@nodelib/fs.scandir": { "version": "2.1.5", "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", @@ -2187,6 +2214,16 @@ "@otplib/plugin-thirty-two": "^12.0.1" } }, + "node_modules/@paralleldrive/cuid2": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/@paralleldrive/cuid2/-/cuid2-2.3.1.tgz", + "integrity": "sha512-XO7cAxhnTZl0Yggq6jOgjiOHhbgcO4NqFqwSmQpjK3b6TEE6Uj/jfSk6wzYyemh3+I0sHirKSetjQwn5cZktFw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@noble/hashes": "^1.1.5" + } + }, "node_modules/@rollup/plugin-commonjs": { "version": "28.0.9", "resolved": "https://registry.npmjs.org/@rollup/plugin-commonjs/-/plugin-commonjs-28.0.9.tgz", @@ -2378,6 +2415,13 @@ "resolved": "https://registry.npmjs.org/@socket.io/component-emitter/-/component-emitter-3.1.2.tgz", "integrity": "sha512-9BCxFwvbGg/RsZK9tjXd8s4UcwR0MWeFQ1XEKIQVVvAGJyINdrqKMcTRyLoK8Rse1GjzLV9cwjWV1olXRWEXVA==" }, + "node_modules/@standard-schema/spec": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.1.0.tgz", + "integrity": "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==", + "dev": true, + "license": "MIT" + }, "node_modules/@tannin/compile": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@tannin/compile/-/compile-1.1.0.tgz", @@ -2426,6 +2470,17 @@ "@types/node": "*" } }, + "node_modules/@types/chai": { + "version": "5.2.3", + "resolved": "https://registry.npmjs.org/@types/chai/-/chai-5.2.3.tgz", + "integrity": "sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/deep-eql": "*", + "assertion-error": "^2.0.1" + } + }, "node_modules/@types/connect": { "version": "3.4.38", "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.38.tgz", @@ -2434,6 +2489,13 @@ "@types/node": "*" } }, + "node_modules/@types/cookiejar": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@types/cookiejar/-/cookiejar-2.1.5.tgz", + "integrity": "sha512-he+DHOWReW0nghN24E1WUqM0efK4kI9oTqDm6XmK8ZPe2djZ90BSNdGnIyCLzCPw7/pogPlGbzI2wHGGmi4O/Q==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/cors": { "version": "2.8.19", "resolved": "https://registry.npmjs.org/@types/cors/-/cors-2.8.19.tgz", @@ -2442,6 +2504,13 @@ "@types/node": "*" } }, + "node_modules/@types/deep-eql": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@types/deep-eql/-/deep-eql-4.0.2.tgz", + "integrity": "sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/estree": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", @@ -2544,6 +2613,13 @@ "integrity": "sha512-RDvF6wTulMPjrNdCoYRC8gNR880JNGT8uB+REUpC2Ns4pRqQJhGz90wh7rgdXDPpCczF3VGktDuFGVnz8zP7HA==", "dev": true }, + "node_modules/@types/methods": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/@types/methods/-/methods-1.1.4.tgz", + "integrity": "sha512-ymXWVrDiCxTBE3+RIrrP533E70eA+9qu7zdWoHuOmGujkYtzf4HQF96b8nwHLqhuf4ykX61IGRIB38CC6/sImQ==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/mime": { "version": "1.3.5", "resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.5.tgz", @@ -2648,6 +2724,30 @@ "@types/node": "*" } }, + "node_modules/@types/superagent": { + "version": "8.1.9", + "resolved": "https://registry.npmjs.org/@types/superagent/-/superagent-8.1.9.tgz", + "integrity": "sha512-pTVjI73witn+9ILmoJdajHGW2jkSaOzhiFYF1Rd3EQ94kymLqB9PjD9ISg7WaALC7+dCHT0FGe9T2LktLq/3GQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/cookiejar": "^2.1.5", + "@types/methods": "^1.1.4", + "@types/node": "*", + "form-data": "^4.0.0" + } + }, + "node_modules/@types/supertest": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/@types/supertest/-/supertest-6.0.3.tgz", + "integrity": "sha512-8WzXq62EXFhJ7QsH3Ocb/iKQ/Ty9ZVWnVzoTKc9tyyFRRF3a74Tk2+TLFgaFFw364Ere+npzHKEJ6ga2LzIL7w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/methods": "^1.1.4", + "@types/superagent": "^8.1.0" + } + }, "node_modules/@types/tough-cookie": { "version": "4.0.5", "resolved": "https://registry.npmjs.org/@types/tough-cookie/-/tough-cookie-4.0.5.tgz", @@ -2919,6 +3019,121 @@ "react": ">= 16.8.0" } }, + "node_modules/@vitest/coverage-v8": { + "version": "4.0.18", + "resolved": "https://registry.npmjs.org/@vitest/coverage-v8/-/coverage-v8-4.0.18.tgz", + "integrity": "sha512-7i+N2i0+ME+2JFZhfuz7Tg/FqKtilHjGyGvoHYQ6iLV0zahbsJ9sljC9OcFcPDbhYKCet+sG8SsVqlyGvPflZg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@bcoe/v8-coverage": "^1.0.2", + "@vitest/utils": "4.0.18", + "ast-v8-to-istanbul": "^0.3.10", + "istanbul-lib-coverage": "^3.2.2", + "istanbul-lib-report": "^3.0.1", + "istanbul-reports": "^3.2.0", + "magicast": "^0.5.1", + "obug": "^2.1.1", + "std-env": "^3.10.0", + "tinyrainbow": "^3.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "@vitest/browser": "4.0.18", + "vitest": "4.0.18" + }, + "peerDependenciesMeta": { + "@vitest/browser": { + "optional": true + } + } + }, + "node_modules/@vitest/expect": { + "version": "4.0.18", + "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-4.0.18.tgz", + "integrity": "sha512-8sCWUyckXXYvx4opfzVY03EOiYVxyNrHS5QxX3DAIi5dpJAAkyJezHCP77VMX4HKA2LDT/Jpfo8i2r5BE3GnQQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@standard-schema/spec": "^1.0.0", + "@types/chai": "^5.2.2", + "@vitest/spy": "4.0.18", + "@vitest/utils": "4.0.18", + "chai": "^6.2.1", + "tinyrainbow": "^3.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/pretty-format": { + "version": "4.0.18", + "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-4.0.18.tgz", + "integrity": "sha512-P24GK3GulZWC5tz87ux0m8OADrQIUVDPIjjj65vBXYG17ZeU3qD7r+MNZ1RNv4l8CGU2vtTRqixrOi9fYk/yKw==", + "dev": true, + "license": "MIT", + "dependencies": { + "tinyrainbow": "^3.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/runner": { + "version": "4.0.18", + "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-4.0.18.tgz", + "integrity": "sha512-rpk9y12PGa22Jg6g5M3UVVnTS7+zycIGk9ZNGN+m6tZHKQb7jrP7/77WfZy13Y/EUDd52NDsLRQhYKtv7XfPQw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/utils": "4.0.18", + "pathe": "^2.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/snapshot": { + "version": "4.0.18", + "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-4.0.18.tgz", + "integrity": "sha512-PCiV0rcl7jKQjbgYqjtakly6T1uwv/5BQ9SwBLekVg/EaYeQFPiXcgrC2Y7vDMA8dM1SUEAEV82kgSQIlXNMvA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/pretty-format": "4.0.18", + "magic-string": "^0.30.21", + "pathe": "^2.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/spy": { + "version": "4.0.18", + "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-4.0.18.tgz", + "integrity": "sha512-cbQt3PTSD7P2OARdVW3qWER5EGq7PHlvE+QfzSC0lbwO+xnt7+XH06ZzFjFRgzUX//JmpxrCu92VdwvEPlWSNw==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/utils": { + "version": "4.0.18", + "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-4.0.18.tgz", + "integrity": "sha512-msMRKLMVLWygpK3u2Hybgi4MNjcYJvwTb0Ru09+fOyCXIgT5raYP041DRRdiJiI3k/2U6SEbAETB3YtBrUkCFA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/pretty-format": "4.0.18", + "tinyrainbow": "^3.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, "node_modules/@wordpress/a11y": { "version": "3.58.0", "resolved": "https://registry.npmjs.org/@wordpress/a11y/-/a11y-3.58.0.tgz", @@ -3670,6 +3885,13 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/asap": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz", + "integrity": "sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA==", + "dev": true, + "license": "MIT" + }, "node_modules/assert": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/assert/-/assert-2.1.0.tgz", @@ -3690,6 +3912,45 @@ "node": ">=0.8" } }, + "node_modules/assertion-error": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-2.0.1.tgz", + "integrity": "sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + } + }, + "node_modules/ast-v8-to-istanbul": { + "version": "0.3.11", + "resolved": "https://registry.npmjs.org/ast-v8-to-istanbul/-/ast-v8-to-istanbul-0.3.11.tgz", + "integrity": "sha512-Qya9fkoofMjCBNVdWINMjB5KZvkYfaO9/anwkWnjxibpWUxo5iHl2sOdP7/uAqaRuUYuoo8rDwnbaaKVFxoUvw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/trace-mapping": "^0.3.31", + "estree-walker": "^3.0.3", + "js-tokens": "^10.0.0" + } + }, + "node_modules/ast-v8-to-istanbul/node_modules/estree-walker": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz", + "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0" + } + }, + "node_modules/ast-v8-to-istanbul/node_modules/js-tokens": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-10.0.0.tgz", + "integrity": "sha512-lM/UBzQmfJRo9ABXbPWemivdCW8V2G8FHaHdypQaIy523snUjog0W71ayWXTjiR+ixeMyVHN2XcpnTd/liPg/Q==", + "dev": true, + "license": "MIT" + }, "node_modules/async": { "version": "3.2.6", "resolved": "https://registry.npmjs.org/async/-/async-3.2.6.tgz", @@ -4090,6 +4351,16 @@ "cdl": "bin/cdl.js" } }, + "node_modules/chai": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/chai/-/chai-6.2.2.tgz", + "integrity": "sha512-NUPRluOfOiTKBKvWPtSD4PhFvWCqOi0BGStNWs57X9js7XGTprSmFoz5F0tWhR4WPjNeR9jXqdC7/UpSJTnlRg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, "node_modules/chalk": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", @@ -4487,6 +4758,16 @@ "resolved": "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz", "integrity": "sha512-W9pAhw0ja1Edb5GVdIF1mjZw/ASI0AlShXM83UUGe2DVr5TdAPEA1OA8m/g8zWp9x6On7gqufY+FatDbC3MDQg==" }, + "node_modules/component-emitter": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.3.1.tgz", + "integrity": "sha512-T0+barUSQRTUQASh8bx02dl+DhF54GtIDY13Y3m9oWTklKbb3Wv974meRpeZ3lp1JpLVECWWNHC4vaG2XHXouQ==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/compute-scroll-into-view": { "version": "1.0.20", "resolved": "https://registry.npmjs.org/compute-scroll-into-view/-/compute-scroll-into-view-1.0.20.tgz", @@ -4599,6 +4880,13 @@ "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.7.tgz", "integrity": "sha512-NXdYc3dLr47pBkpUCHtKSwIOQXLVn8dZEuywboCOJY/osA0wFSLlSawr3KN8qXJEyX66FcONTH8EIlVuK0yyFA==" }, + "node_modules/cookiejar": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/cookiejar/-/cookiejar-2.1.4.tgz", + "integrity": "sha512-LDx6oHrK+PhzLKJU9j5S7/Y3jM/mUHvD/DeI1WQmJn652iPC5Y4TBzC9l+5OMOXlyTTA+SmVUPm0HQUwpD5Jqw==", + "dev": true, + "license": "MIT" + }, "node_modules/core-util-is": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", @@ -5000,6 +5288,17 @@ "npm": "1.2.8000 || >= 1.4.16" } }, + "node_modules/dezalgo": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/dezalgo/-/dezalgo-1.0.4.tgz", + "integrity": "sha512-rXSP0bf+5n0Qonsb+SVVfNfIsimO4HEtmnIpPHY8Q1UCzKlQrDMfdobr8nJOOsRgWCyMRqeSBQzmWUMq7zvVig==", + "dev": true, + "license": "ISC", + "dependencies": { + "asap": "^2.0.0", + "wrappy": "1" + } + }, "node_modules/diff": { "version": "5.2.2", "resolved": "https://registry.npmjs.org/diff/-/diff-5.2.2.tgz", @@ -5420,6 +5719,13 @@ "node": ">= 0.4" } }, + "node_modules/es-module-lexer": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.7.0.tgz", + "integrity": "sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==", + "dev": true, + "license": "MIT" + }, "node_modules/es-object-atoms": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", @@ -5805,6 +6111,16 @@ "node": ">= 0.6" } }, + "node_modules/expect-type": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/expect-type/-/expect-type-1.3.0.tgz", + "integrity": "sha512-knvyeauYhqjOYvQ66MznSMs83wmHrCycNEN6Ao+2AeYEfxUIkuiVxdEa1qlGEPK+We3n0THiDciYSsCcgW/DoA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=12.0.0" + } + }, "node_modules/express": { "version": "4.22.1", "resolved": "https://registry.npmjs.org/express/-/express-4.22.1.tgz", @@ -6043,6 +6359,13 @@ "fastest-levenshtein": "^1.0.7" } }, + "node_modules/fast-safe-stringify": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/fast-safe-stringify/-/fast-safe-stringify-2.1.1.tgz", + "integrity": "sha512-W+KJc2dmILlPplD/H4K9l9LcAHAfPtP6BY84uVLXQ6Evcz9Lcg33Y2z1IVblT6xdY54PXYVHEv+0Wpq8Io6zkA==", + "dev": true, + "license": "MIT" + }, "node_modules/fast-uri": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.0.tgz", @@ -6355,6 +6678,24 @@ "node": ">= 6" } }, + "node_modules/formidable": { + "version": "3.5.4", + "resolved": "https://registry.npmjs.org/formidable/-/formidable-3.5.4.tgz", + "integrity": "sha512-YikH+7CUTOtP44ZTnUhR7Ic2UASBPOqmaRkRKxRbywPTe5VxF7RRCck4af9wutiZ/QKM5nME9Bie2fFaPz5Gug==", + "dev": true, + "license": "MIT", + "dependencies": { + "@paralleldrive/cuid2": "^2.2.2", + "dezalgo": "^1.0.4", + "once": "^1.4.0" + }, + "engines": { + "node": ">=14.0.0" + }, + "funding": { + "url": "https://ko-fi.com/tunnckoCore/commissions" + } + }, "node_modules/forwarded": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", @@ -6463,6 +6804,20 @@ "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", "dev": true }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, "node_modules/function-bind": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", @@ -6909,6 +7264,13 @@ "node": ">=18" } }, + "node_modules/html-escaper": { + "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" + }, "node_modules/html-to-json-parser": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/html-to-json-parser/-/html-to-json-parser-2.0.1.tgz", @@ -8181,6 +8543,58 @@ "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", "integrity": "sha512-Yljz7ffyPbrLpLngrMtZ7NduUgVvi6wG9RJ9IUcyCd59YQ911PBJphODUcbOVbqYfxe1wuYf/LJ8PauMRwsM/g==" }, + "node_modules/istanbul-lib-coverage": { + "version": "3.2.2", + "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", + "engines": { + "node": ">=8" + } + }, + "node_modules/istanbul-lib-report": { + "version": "3.0.1", + "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", + "dependencies": { + "istanbul-lib-coverage": "^3.0.0", + "make-dir": "^4.0.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/istanbul-lib-report/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/istanbul-reports": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.2.0.tgz", + "integrity": "sha512-HGYWWS/ehqTV3xN10i23tkPkpH46MLCIMFNCaaKNavAXTF1RkqxawEPtnjnGZ6XKSInBKkiOA5BKS+aZiY3AvA==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "html-escaper": "^2.0.0", + "istanbul-lib-report": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/jake": { "version": "10.9.4", "resolved": "https://registry.npmjs.org/jake/-/jake-10.9.4.tgz", @@ -9121,6 +9535,34 @@ "@jridgewell/sourcemap-codec": "^1.5.5" } }, + "node_modules/magicast": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/magicast/-/magicast-0.5.2.tgz", + "integrity": "sha512-E3ZJh4J3S9KfwdjZhe2afj6R9lGIN5Pher1pF39UGrXRqq/VDaGVIGN13BjHd2u8B61hArAGOnso7nBOouW3TQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.29.0", + "@babel/types": "^7.29.0", + "source-map-js": "^1.2.1" + } + }, + "node_modules/make-dir": { + "version": "4.0.0", + "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", + "dependencies": { + "semver": "^7.5.3" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/marked": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/marked/-/marked-4.3.0.tgz", @@ -9387,6 +9829,25 @@ "node": ">=8.0.0" } }, + "node_modules/nanoid": { + "version": "3.3.11", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", + "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, "node_modules/natural-compare": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", @@ -12080,6 +12541,17 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/obug": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/obug/-/obug-2.1.1.tgz", + "integrity": "sha512-uTqF9MuPraAQ+IsnPf366RG4cP9RtUi7MLO1N3KEc+wb0a6yKpeL0lmk2IB1jY5KHPAlTc6T/JRdC/YqxHNwkQ==", + "dev": true, + "funding": [ + "https://github.com/sponsors/sxzz", + "https://opencollective.com/debug" + ], + "license": "MIT" + }, "node_modules/omit-deep-lodash": { "version": "1.1.7", "resolved": "https://registry.npmjs.org/omit-deep-lodash/-/omit-deep-lodash-1.1.7.tgz", @@ -12519,6 +12991,13 @@ "node": ">=8" } }, + "node_modules/pathe": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz", + "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==", + "dev": true, + "license": "MIT" + }, "node_modules/php-serialize": { "version": "5.1.3", "resolved": "https://registry.npmjs.org/php-serialize/-/php-serialize-5.1.3.tgz", @@ -12618,6 +13097,35 @@ "node": ">= 0.4" } }, + "node_modules/postcss": { + "version": "8.5.6", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz", + "integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.11", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, "node_modules/prelude-ls": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", @@ -13725,6 +14233,13 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/siginfo": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/siginfo/-/siginfo-2.0.0.tgz", + "integrity": "sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==", + "dev": true, + "license": "ISC" + }, "node_modules/signal-exit": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", @@ -14003,6 +14518,16 @@ "node": ">=0.10.0" } }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/speedometer": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/speedometer/-/speedometer-1.0.0.tgz", @@ -14036,6 +14561,13 @@ "node": "*" } }, + "node_modules/stackback": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/stackback/-/stackback-0.0.2.tgz", + "integrity": "sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==", + "dev": true, + "license": "MIT" + }, "node_modules/statuses": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz", @@ -14044,6 +14576,13 @@ "node": ">= 0.8" } }, + "node_modules/std-env": { + "version": "3.10.0", + "resolved": "https://registry.npmjs.org/std-env/-/std-env-3.10.0.tgz", + "integrity": "sha512-5GS12FdOZNliM5mAOxFRg7Ir0pWz8MdpYm6AY6VPkGpbA7ZzmbzNcBJQ0GPvvyWgcY7QAhCgf9Uy89I03faLkg==", + "dev": true, + "license": "MIT" + }, "node_modules/steno": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/steno/-/steno-4.0.2.tgz", @@ -14187,6 +14726,82 @@ "integrity": "sha512-Orov6g6BB1sDfYgzWfTHDOxamtX1bE/zo104Dh9e6fqJ3PooipYyfJ0pUmrZO2wAvO8YbEyeFrkV91XTsGMSrw==", "dev": true }, + "node_modules/superagent": { + "version": "10.3.0", + "resolved": "https://registry.npmjs.org/superagent/-/superagent-10.3.0.tgz", + "integrity": "sha512-B+4Ik7ROgVKrQsXTV0Jwp2u+PXYLSlqtDAhYnkkD+zn3yg8s/zjA2MeGayPoY/KICrbitwneDHrjSotxKL+0XQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "component-emitter": "^1.3.1", + "cookiejar": "^2.1.4", + "debug": "^4.3.7", + "fast-safe-stringify": "^2.1.1", + "form-data": "^4.0.5", + "formidable": "^3.5.4", + "methods": "^1.1.2", + "mime": "2.6.0", + "qs": "^6.14.1" + }, + "engines": { + "node": ">=14.18.0" + } + }, + "node_modules/superagent/node_modules/form-data": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.5.tgz", + "integrity": "sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==", + "dev": true, + "license": "MIT", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "es-set-tostringtag": "^2.1.0", + "hasown": "^2.0.2", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/superagent/node_modules/mime": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-2.6.0.tgz", + "integrity": "sha512-USPkMeET31rOMiarsBNIHZKLGgvKc/LrjofAnBlOttf5ajRvqiRA8QsenbcooctK6d6Ts6aqZXBA+XbkKthiQg==", + "dev": true, + "license": "MIT", + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/supertest": { + "version": "7.2.2", + "resolved": "https://registry.npmjs.org/supertest/-/supertest-7.2.2.tgz", + "integrity": "sha512-oK8WG9diS3DlhdUkcFn4tkNIiIbBx9lI2ClF8K+b2/m8Eyv47LSawxUzZQSNKUrVb2KsqeTDCcjAAVPYaSLVTA==", + "dev": true, + "license": "MIT", + "dependencies": { + "cookie-signature": "^1.2.2", + "methods": "^1.1.2", + "superagent": "^10.3.0" + }, + "engines": { + "node": ">=14.18.0" + } + }, + "node_modules/supertest/node_modules/cookie-signature": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.2.2.tgz", + "integrity": "sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.6.0" + } + }, "node_modules/supports-color": { "version": "8.1.1", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", @@ -14293,6 +14908,23 @@ "resolved": "https://registry.npmjs.org/tiny-warning/-/tiny-warning-1.0.3.tgz", "integrity": "sha512-lBN9zLN/oAf68o3zNXYrdCt1kP8WsiGW8Oo2ka41b2IM5JL/S1CTyX1rW0mb/zSuJun0ZUrDxx4sqvYS2FWzPA==" }, + "node_modules/tinybench": { + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/tinybench/-/tinybench-2.9.0.tgz", + "integrity": "sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==", + "dev": true, + "license": "MIT" + }, + "node_modules/tinyexec": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-1.0.2.tgz", + "integrity": "sha512-W/KYk+NFhkmsYpuHq5JykngiOCnxeVL8v8dFnqxSD8qEEdRfXk1SDM6JzNqcERbcGYj9tMrDQBYV9cjgnunFIg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, "node_modules/tinyglobby": { "version": "0.2.15", "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz", @@ -14308,6 +14940,16 @@ "url": "https://github.com/sponsors/SuperchupuDev" } }, + "node_modules/tinyrainbow": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/tinyrainbow/-/tinyrainbow-3.0.3.tgz", + "integrity": "sha512-PSkbLUoxOFRzJYjjxHJt9xro7D+iilgMX/C9lawzVuYiIdcihh9DXmVibBe8lmcFrRi/VzlPjBxbN7rH24q8/Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, "node_modules/tmp": { "version": "0.2.5", "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.5.tgz", @@ -14795,6 +15437,214 @@ "node": ">= 0.8" } }, + "node_modules/vitest": { + "version": "4.0.18", + "resolved": "https://registry.npmjs.org/vitest/-/vitest-4.0.18.tgz", + "integrity": "sha512-hOQuK7h0FGKgBAas7v0mSAsnvrIgAvWmRFjmzpJ7SwFHH3g1k2u37JtYwOwmEKhK6ZO3v9ggDBBm0La1LCK4uQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/expect": "4.0.18", + "@vitest/mocker": "4.0.18", + "@vitest/pretty-format": "4.0.18", + "@vitest/runner": "4.0.18", + "@vitest/snapshot": "4.0.18", + "@vitest/spy": "4.0.18", + "@vitest/utils": "4.0.18", + "es-module-lexer": "^1.7.0", + "expect-type": "^1.2.2", + "magic-string": "^0.30.21", + "obug": "^2.1.1", + "pathe": "^2.0.3", + "picomatch": "^4.0.3", + "std-env": "^3.10.0", + "tinybench": "^2.9.0", + "tinyexec": "^1.0.2", + "tinyglobby": "^0.2.15", + "tinyrainbow": "^3.0.3", + "vite": "^6.0.0 || ^7.0.0", + "why-is-node-running": "^2.3.0" + }, + "bin": { + "vitest": "vitest.mjs" + }, + "engines": { + "node": "^20.0.0 || ^22.0.0 || >=24.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "@edge-runtime/vm": "*", + "@opentelemetry/api": "^1.9.0", + "@types/node": "^20.0.0 || ^22.0.0 || >=24.0.0", + "@vitest/browser-playwright": "4.0.18", + "@vitest/browser-preview": "4.0.18", + "@vitest/browser-webdriverio": "4.0.18", + "@vitest/ui": "4.0.18", + "happy-dom": "*", + "jsdom": "*" + }, + "peerDependenciesMeta": { + "@edge-runtime/vm": { + "optional": true + }, + "@opentelemetry/api": { + "optional": true + }, + "@types/node": { + "optional": true + }, + "@vitest/browser-playwright": { + "optional": true + }, + "@vitest/browser-preview": { + "optional": true + }, + "@vitest/browser-webdriverio": { + "optional": true + }, + "@vitest/ui": { + "optional": true + }, + "happy-dom": { + "optional": true + }, + "jsdom": { + "optional": true + } + } + }, + "node_modules/vitest/node_modules/@vitest/mocker": { + "version": "4.0.18", + "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-4.0.18.tgz", + "integrity": "sha512-HhVd0MDnzzsgevnOWCBj5Otnzobjy5wLBe4EdeeFGv8luMsGcYqDuFRMcttKWZA5vVO8RFjexVovXvAM4JoJDQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/spy": "4.0.18", + "estree-walker": "^3.0.3", + "magic-string": "^0.30.21" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "msw": "^2.4.9", + "vite": "^6.0.0 || ^7.0.0-0" + }, + "peerDependenciesMeta": { + "msw": { + "optional": true + }, + "vite": { + "optional": true + } + } + }, + "node_modules/vitest/node_modules/estree-walker": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz", + "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0" + } + }, + "node_modules/vitest/node_modules/vite": { + "version": "7.3.1", + "resolved": "https://registry.npmjs.org/vite/-/vite-7.3.1.tgz", + "integrity": "sha512-w+N7Hifpc3gRjZ63vYBXA56dvvRlNWRczTdmCBBa+CotUzAPf5b7YMdMR/8CQoeYE5LX3W4wj6RYTgonm1b9DA==", + "dev": true, + "license": "MIT", + "dependencies": { + "esbuild": "^0.27.0", + "fdir": "^6.5.0", + "picomatch": "^4.0.3", + "postcss": "^8.5.6", + "rollup": "^4.43.0", + "tinyglobby": "^0.2.15" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^20.19.0 || >=22.12.0", + "jiti": ">=1.21.0", + "less": "^4.0.0", + "lightningcss": "^1.21.0", + "sass": "^1.70.0", + "sass-embedded": "^1.70.0", + "stylus": ">=0.54.8", + "sugarss": "^5.0.0", + "terser": "^5.16.0", + "tsx": "^4.8.1", + "yaml": "^2.4.2" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "jiti": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { + "optional": true + } + } + }, + "node_modules/vitest/node_modules/yaml": { + "version": "2.8.2", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.8.2.tgz", + "integrity": "sha512-mplynKqc1C2hTVYxd0PU2xQAc22TI1vShAYGksCCfxbn/dFwnHTNi1bvYsBTkhdUNtGIf5xNOg938rrSSYvS9A==", + "dev": true, + "license": "ISC", + "optional": true, + "peer": true, + "bin": { + "yaml": "bin.mjs" + }, + "engines": { + "node": ">= 14.6" + }, + "funding": { + "url": "https://github.com/sponsors/eemeli" + } + }, "node_modules/void-elements": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/void-elements/-/void-elements-2.0.1.tgz", @@ -14973,6 +15823,23 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/why-is-node-running": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/why-is-node-running/-/why-is-node-running-2.3.0.tgz", + "integrity": "sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==", + "dev": true, + "license": "MIT", + "dependencies": { + "siginfo": "^2.0.0", + "stackback": "0.0.2" + }, + "bin": { + "why-is-node-running": "cli.js" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/widest-line": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/widest-line/-/widest-line-3.1.0.tgz", diff --git a/api/package.json b/api/package.json index fa47e995d..714ab14fe 100644 --- a/api/package.json +++ b/api/package.json @@ -11,7 +11,13 @@ "windev": "SET NODE_ENV=production&& tsx watch ./src/server.ts", "winstart": "SET NODE_ENV=production&& node dist/server.js", "lint:fix": "eslint --ext .ts --ignore-pattern './node_modules/' --ignore-pattern './dist/'", - "precommit": "npm run prettify && npm run lint:fix" + "precommit": "npm run prettify && npm run lint:fix", + "test": "vitest run", + "test:watch": "vitest", + "test:coverage": "vitest run --coverage", + "test:unit": "vitest run tests/unit", + "test:integration": "vitest run tests/integration", + "coverage:ui": "npx serve coverage -l 3939" }, "type": "module", "repository": { @@ -35,6 +41,7 @@ "chokidar": "^3.6.0", "cors": "^2.8.5", "dayjs": "^1.11.18", + "diff": "^5.2.2", "dotenv": "^16.3.1", "express": "^4.22.0", "express-validator": "^7.3.1", @@ -53,8 +60,7 @@ "php-serialize": "^5.1.3", "socket.io": "^4.7.5", "uuid": "^9.0.1", - "winston": "^3.11.0", - "diff": "^5.2.2" + "winston": "^3.11.0" }, "devDependencies": { "@types/cors": "^2.8.17", @@ -65,17 +71,21 @@ "@types/jsonwebtoken": "^9.0.5", "@types/lodash": "^4.17.0", "@types/node": "^20.10.4", + "@types/supertest": "^6.0.3", "@types/uuid": "^9.0.8", "@types/wordpress__block-library": "^2.6.3", "@types/wordpress__block-serialization-spec-parser": "^3.1.3", "@types/wordpress__blocks": "^12.5.18", "@typescript-eslint/eslint-plugin": "^6.15.0", "@typescript-eslint/parser": "^6.15.0", + "@vitest/coverage-v8": "^4.0.18", "eslint": "^8.56.0", "eslint-config-prettier": "^8.3.0", "prettier": "^2.4.1", + "supertest": "^7.2.2", "tsx": "^4.7.1", - "typescript": "^5.4.3" + "typescript": "^5.4.3", + "vitest": "^4.0.18" }, "overrides": { "qs": ">=6.14.2", diff --git a/api/tests/fixtures/auth.fixture.ts b/api/tests/fixtures/auth.fixture.ts new file mode 100644 index 000000000..0c953cd26 --- /dev/null +++ b/api/tests/fixtures/auth.fixture.ts @@ -0,0 +1,26 @@ +export const createMockJwtPayload = (overrides: Record = {}) => ({ + region: 'NA', + user_id: 'user-123', + iat: Math.floor(Date.now() / 1000), + exp: Math.floor(Date.now() / 1000) + 3600, + ...overrides, +}); + +export const createMockToken = () => 'mock.jwt.token'; + +export const createMockLoginBody = (overrides: Record = {}) => ({ + email: 'test@example.com', + password: 'password123', + region: 'NA', + ...overrides, +}); + +export const createMockAuthUser = (overrides: Record = {}) => ({ + user_id: 'user-123', + email: 'test@example.com', + region: 'NA', + authtoken: 'cs-auth-token-123', + created_at: new Date().toISOString(), + updated_at: new Date().toISOString(), + ...overrides, +}); diff --git a/api/tests/fixtures/contentMapper.fixture.ts b/api/tests/fixtures/contentMapper.fixture.ts new file mode 100644 index 000000000..293fb4cf3 --- /dev/null +++ b/api/tests/fixtures/contentMapper.fixture.ts @@ -0,0 +1,46 @@ +export const createMockContentType = (overrides: Record = {}) => ({ + id: 'ct-123', + projectId: 'a1b2c3d4-e5f6-7890-abcd-ef1234567890', + otherCmsTitle: 'Blog Post', + otherCmsUid: 'blog_post', + isUpdated: false, + updateAt: new Date(), + contentstackTitle: '', + contentstackUid: '', + status: 1, + fieldMapping: [], + type: 'content_type', + ...overrides, +}); + +export const createMockFieldMapper = (overrides: Record = {}) => ({ + id: 'fm-123', + projectId: 'a1b2c3d4-e5f6-7890-abcd-ef1234567890', + contentTypeId: 'ct-123', + uid: 'field-uid-1', + otherCmsField: 'title', + otherCmsType: 'text', + contentstackField: 'title', + contentstackFieldUid: 'title', + contentstackFieldType: 'text', + isDeleted: false, + backupFieldType: 'text', + backupFieldUid: 'title', + refrnceTo: { uid: '', title: '' }, + advanced: { + validationRegex: '', + mandatory: false, + multiple: false, + unique: false, + nonLocalizable: false, + embedObject: false, + embedObjects: null, + minChars: '', + maxChars: 0, + default_value: '', + description: '', + validationErrorMessage: '', + options: [], + }, + ...overrides, +}); diff --git a/api/tests/fixtures/project.fixture.ts b/api/tests/fixtures/project.fixture.ts new file mode 100644 index 000000000..c5031fcd5 --- /dev/null +++ b/api/tests/fixtures/project.fixture.ts @@ -0,0 +1,50 @@ +export const createMockProject = (overrides: Record = {}) => ({ + id: 'a1b2c3d4-e5f6-7890-abcd-ef1234567890', + region: 'NA', + org_id: 'org-123', + owner: 'user-123', + created_by: 'user-123', + updated_by: 'user-123', + former_owner_ids: [], + name: 'Test Project', + description: 'A test project', + status: 0, + current_step: 1, + destination_stack_id: '', + test_stacks: [], + current_test_stack_id: '', + legacy_cms: { + cms: '', + affix: '', + affix_confirmation: false, + file_format: '', + file_format_confirmation: false, + file: { id: '', name: '', size: 0, type: '', path: '' }, + awsDetails: { awsRegion: '', bucketName: '', bucketKey: '' }, + file_path: '', + is_fileValid: false, + is_localPath: false, + }, + content_mapper: [], + execution_log: [], + created_at: new Date().toISOString(), + updated_at: new Date().toISOString(), + isDeleted: false, + isNewStack: false, + newStackId: '', + stackDetails: [], + mapperKeys: {}, + extract_path: '', + isMigrationStarted: false, + isMigrationCompleted: false, + migration_execution: false, + ...overrides, +}); + +export const createMockProjectList = (count: number = 3) => + Array.from({ length: count }, (_, i) => + createMockProject({ + id: `proj-${i + 1}`, + name: `Project ${i + 1}`, + }) + ); diff --git a/api/tests/fixtures/user.fixture.ts b/api/tests/fixtures/user.fixture.ts new file mode 100644 index 000000000..cbbe37af4 --- /dev/null +++ b/api/tests/fixtures/user.fixture.ts @@ -0,0 +1,23 @@ +export const createMockUser = (overrides: Record = {}) => ({ + uid: 'user-123', + email: 'test@example.com', + first_name: 'Test', + last_name: 'User', + ...overrides, +}); + +export const createMockOrg = (overrides: Record = {}) => ({ + uid: 'org-123', + name: 'Test Organization', + org_roles: [{ admin: true }], + ...overrides, +}); + +export const createMockStack = (overrides: Record = {}) => ({ + api_key: 'stack-api-key-123', + name: 'Test Stack', + description: 'A test stack', + master_locale: 'en-us', + org_uid: 'org-123', + ...overrides, +}); diff --git a/api/tests/setup.ts b/api/tests/setup.ts new file mode 100644 index 000000000..c91770ea5 --- /dev/null +++ b/api/tests/setup.ts @@ -0,0 +1,20 @@ +import { vi, beforeAll, afterAll, afterEach } from 'vitest'; + +beforeAll(() => { + vi.stubEnv('NODE_ENV', 'production'); + vi.stubEnv('APP_TOKEN_KEY', 'test-secret-key'); + vi.stubEnv('PORT', '5001'); + vi.stubEnv('FILE_UPLOAD_KEY', 'test-upload-key'); + vi.stubEnv('MONGODB_URI', 'mongodb://localhost:27017/test-migration'); + vi.stubEnv('LOG_LEVEL', 'error'); + vi.stubEnv('DRUPAL_ASSETS_BASE_URL', 'http://localhost:8080'); + vi.stubEnv('DRUPAL_ASSETS_PUBLIC_PATH', '/sites/default/files'); +}); + +afterEach(() => { + vi.restoreAllMocks(); +}); + +afterAll(() => { + vi.unstubAllEnvs(); +}); diff --git a/api/tests/unit/config/index.config.test.ts b/api/tests/unit/config/index.config.test.ts new file mode 100644 index 000000000..63a1c6e9c --- /dev/null +++ b/api/tests/unit/config/index.config.test.ts @@ -0,0 +1,33 @@ +import { describe, it, expect, vi, beforeEach } from 'vitest'; + +describe('config/index', () => { + beforeEach(() => { + vi.resetModules(); + }); + + it('should export config with expected keys', async () => { + vi.stubEnv('NODE_ENV', 'production'); + vi.stubEnv('APP_TOKEN_KEY', 'my-secret'); + vi.stubEnv('PORT', '3000'); + vi.stubEnv('FILE_UPLOAD_KEY', 'upload-key'); + + const { config } = await import('../../../src/config/index.js'); + + expect(config).toBeDefined(); + expect(config.APP_TOKEN_EXP).toBe('2d'); + expect(config.CS_API).toBeDefined(); + expect(config.CS_URL).toBeDefined(); + }); + + it('should have APP_TOKEN_EXP set to 2d', async () => { + const { config } = await import('../../../src/config/index.js'); + expect(config.APP_TOKEN_EXP).toBe('2d'); + }); + + it('should have CS_API with region keys', async () => { + const { config } = await import('../../../src/config/index.js'); + expect(config.CS_API).toHaveProperty('NA'); + expect(config.CS_API).toHaveProperty('EU'); + expect(config.CS_API).toHaveProperty('AZURE_NA'); + }); +}); diff --git a/api/tests/unit/controllers/auth.controller.test.ts b/api/tests/unit/controllers/auth.controller.test.ts new file mode 100644 index 000000000..2849eb870 --- /dev/null +++ b/api/tests/unit/controllers/auth.controller.test.ts @@ -0,0 +1,84 @@ +import { describe, it, expect, vi, beforeEach } from 'vitest'; + +const { mockLogin, mockRequestSms } = vi.hoisted(() => ({ + mockLogin: vi.fn(), + mockRequestSms: vi.fn(), +})); + +vi.mock('../../../src/services/auth.service.js', () => ({ + authService: { + login: mockLogin, + requestSms: mockRequestSms, + }, +})); + +import { authController } from '../../../src/controllers/auth.controller.js'; + +describe('auth.controller', () => { + let req: any; + let res: any; + + beforeEach(() => { + vi.clearAllMocks(); + req = { body: {} }; + res = { + status: vi.fn().mockReturnThis(), + json: vi.fn().mockReturnThis(), + }; + }); + + describe('login', () => { + it('should return service response status and data on success', async () => { + mockLogin.mockResolvedValue({ status: 200, data: { app_token: 'token' } }); + await authController.login(req, res); + expect(res.status).toHaveBeenCalledWith(200); + expect(res.json).toHaveBeenCalledWith({ app_token: 'token' }); + }); + + it('should return error status and message on service throw', async () => { + mockLogin.mockRejectedValue({ statusCode: 400, message: 'Invalid credentials' }); + await authController.login(req, res); + expect(res.status).toHaveBeenCalledWith(400); + expect(res.json).toHaveBeenCalledWith({ message: 'Invalid credentials' }); + }); + + it('should default to 500 when error has no statusCode', async () => { + mockLogin.mockRejectedValue({ message: 'Unknown error' }); + await authController.login(req, res); + expect(res.status).toHaveBeenCalledWith(500); + }); + + it('should default to "Login failed" when error has no message', async () => { + mockLogin.mockRejectedValue({}); + await authController.login(req, res); + expect(res.json).toHaveBeenCalledWith({ message: 'Login failed' }); + }); + + it('should default to 500 when response has no status', async () => { + mockLogin.mockResolvedValue({ data: { ok: true } }); + await authController.login(req, res); + expect(res.status).toHaveBeenCalledWith(500); + }); + }); + + describe('RequestSms', () => { + it('should return service response on success', async () => { + mockRequestSms.mockResolvedValue({ status: 200, data: { message: 'SMS sent' } }); + await authController.RequestSms(req, res); + expect(res.status).toHaveBeenCalledWith(200); + expect(res.json).toHaveBeenCalledWith({ message: 'SMS sent' }); + }); + + it('should return error on failure', async () => { + mockRequestSms.mockRejectedValue({ statusCode: 429, message: 'Too many requests' }); + await authController.RequestSms(req, res); + expect(res.status).toHaveBeenCalledWith(429); + }); + + it('should default to 500 when no statusCode', async () => { + mockRequestSms.mockRejectedValue({ message: 'Fail' }); + await authController.RequestSms(req, res); + expect(res.status).toHaveBeenCalledWith(500); + }); + }); +}); diff --git a/api/tests/unit/controllers/migration.controller.test.ts b/api/tests/unit/controllers/migration.controller.test.ts new file mode 100644 index 000000000..7bcdee018 --- /dev/null +++ b/api/tests/unit/controllers/migration.controller.test.ts @@ -0,0 +1,104 @@ +import { describe, it, expect, vi, beforeEach } from 'vitest'; + +const { mockMigrationService } = vi.hoisted(() => ({ + mockMigrationService: { + createTestStack: vi.fn(), + deleteTestStack: vi.fn(), + startTestMigration: vi.fn(), + startMigration: vi.fn(), + getLogs: vi.fn(), + createSourceLocales: vi.fn(), + updateLocaleMapper: vi.fn(), + getAuditData: vi.fn(), + }, +})); + +vi.mock('../../../src/services/migration.service.js', () => ({ + migrationService: mockMigrationService, +})); + +import { migrationController } from '../../../src/controllers/migration.controller.js'; + +describe('migration.controller', () => { + let req: any; + let res: any; + + beforeEach(() => { + vi.clearAllMocks(); + req = { params: { projectId: 'proj-123' }, body: {} }; + res = { + status: vi.fn().mockReturnThis(), + json: vi.fn().mockReturnThis(), + }; + }); + + it('createTestStack should return awaited service response', async () => { + mockMigrationService.createTestStack.mockResolvedValue({ status: 200, data: { stack: {} } }); + + await migrationController.createTestStack(req, res); + + expect(res.status).toHaveBeenCalledWith(200); + }); + + it('getAuditData should return awaited service response', async () => { + mockMigrationService.getAuditData.mockResolvedValue({ status: 200, data: [] }); + + await migrationController.getAuditData(req, res); + + expect(res.status).toHaveBeenCalledWith(200); + }); + + it('deleteTestStack should return 200', async () => { + mockMigrationService.deleteTestStack.mockResolvedValue({ ok: true }); + + await migrationController.deleteTestStack(req, res); + + expect(res.status).toHaveBeenCalledWith(200); + }); + + it('getLogs should return 200', async () => { + mockMigrationService.getLogs.mockResolvedValue({ logs: [] }); + + await migrationController.getLogs(req, res); + + expect(res.status).toHaveBeenCalledWith(200); + }); + + it('saveLocales should return 200', async () => { + mockMigrationService.createSourceLocales.mockResolvedValue({ ok: true }); + + await migrationController.saveLocales(req, res); + + expect(res.status).toHaveBeenCalledWith(200); + }); + + it('saveMappedLocales should return 200', async () => { + mockMigrationService.updateLocaleMapper.mockResolvedValue({ ok: true }); + + await migrationController.saveMappedLocales(req, res); + + expect(res.status).toHaveBeenCalledWith(200); + }); + + describe('fire-and-forget methods', () => { + it('startTestMigration should return 200 immediately and call service', async () => { + const migrationPromise = Promise.resolve({ ok: true }); + mockMigrationService.startTestMigration.mockReturnValue(migrationPromise); + + await migrationController.startTestMigration(req, res); + + expect(res.status).toHaveBeenCalledWith(200); + expect(mockMigrationService.startTestMigration).toHaveBeenCalledWith(req); + }); + + it('startMigration should return 200 immediately and call service', async () => { + const migrationPromise = Promise.resolve({ ok: true }); + mockMigrationService.startMigration.mockReturnValue(migrationPromise); + + await migrationController.startMigration(req, res); + + expect(res.status).toHaveBeenCalledWith(200); + expect(mockMigrationService.startMigration).toHaveBeenCalledWith(req); + }); + }); +}); diff --git a/api/tests/unit/controllers/org.controller.test.ts b/api/tests/unit/controllers/org.controller.test.ts new file mode 100644 index 000000000..37b7d7b71 --- /dev/null +++ b/api/tests/unit/controllers/org.controller.test.ts @@ -0,0 +1,51 @@ +import { describe, it, expect, vi, beforeEach } from 'vitest'; + +const { mockOrgService } = vi.hoisted(() => ({ + mockOrgService: { + getAllStacks: vi.fn(), + createStack: vi.fn(), + getLocales: vi.fn(), + getStackStatus: vi.fn(), + getStackLocale: vi.fn(), + getOrgDetails: vi.fn(), + }, +})); + +vi.mock('../../../src/services/org.service.js', () => ({ + orgService: mockOrgService, +})); + +import { orgController } from '../../../src/controllers/org.controller.js'; + +describe('org.controller', () => { + let req: any; + let res: any; + + beforeEach(() => { + vi.clearAllMocks(); + req = { params: { orgId: 'org-123' }, body: { token_payload: { region: 'NA', user_id: 'user-123' } } }; + res = { + status: vi.fn().mockReturnThis(), + json: vi.fn().mockReturnThis(), + }; + }); + + const testControllerMethod = (methodName: keyof typeof orgController, serviceName: keyof typeof mockOrgService) => { + it(`${methodName} should delegate to service and return resp.status/resp.data`, async () => { + mockOrgService[serviceName].mockResolvedValue({ status: 200, data: { result: 'ok' } }); + + await orgController[methodName](req, res); + + expect(mockOrgService[serviceName]).toHaveBeenCalledWith(req); + expect(res.status).toHaveBeenCalledWith(200); + expect(res.json).toHaveBeenCalledWith({ result: 'ok' }); + }); + }; + + testControllerMethod('getAllStacks', 'getAllStacks'); + testControllerMethod('createStack', 'createStack'); + testControllerMethod('getLocales', 'getLocales'); + testControllerMethod('getStackStatus', 'getStackStatus'); + testControllerMethod('getStackLocale', 'getStackLocale'); + testControllerMethod('getOrgDetails', 'getOrgDetails'); +}); diff --git a/api/tests/unit/controllers/projects.contentMapper.controller.test.ts b/api/tests/unit/controllers/projects.contentMapper.controller.test.ts new file mode 100644 index 000000000..f27ea340b --- /dev/null +++ b/api/tests/unit/controllers/projects.contentMapper.controller.test.ts @@ -0,0 +1,101 @@ +import { describe, it, expect, vi, beforeEach } from 'vitest'; + +const { mockContentMapperService } = vi.hoisted(() => ({ + mockContentMapperService: { + putTestData: vi.fn(), + getContentTypes: vi.fn(), + getFieldMapping: vi.fn(), + getExistingContentTypes: vi.fn(), + getExistingGlobalFields: vi.fn(), + getExistingTaxonomies: vi.fn(), + updateContentType: vi.fn(), + resetToInitialMapping: vi.fn(), + removeContentMapper: vi.fn(), + getSingleContentTypes: vi.fn(), + getSingleGlobalField: vi.fn(), + updateContentMapper: vi.fn(), + }, +})); + +vi.mock('../../../src/services/contentMapper.service.js', () => ({ + contentMapperService: mockContentMapperService, +})); + +import { contentMapperController } from '../../../src/controllers/projects.contentMapper.controller.js'; + +describe('projects.contentMapper.controller', () => { + let req: any; + let res: any; + + beforeEach(() => { + vi.clearAllMocks(); + req = { params: { projectId: 'proj-123' }, body: {} }; + res = { + status: vi.fn().mockReturnThis(), + json: vi.fn().mockReturnThis(), + }; + }); + + const testDelegation = ( + controllerMethod: string, + serviceMethod: string, + expectedStatus?: number + ) => { + it(`${controllerMethod} should delegate to service`, async () => { + const serviceResp = { status: 200, data: { ok: true } }; + (mockContentMapperService as any)[serviceMethod].mockResolvedValue(serviceResp); + + await (contentMapperController as any)[controllerMethod](req, res); + + expect((mockContentMapperService as any)[serviceMethod]).toHaveBeenCalledWith(req); + }); + }; + + testDelegation('putTestData', 'putTestData'); + testDelegation('getContentTypes', 'getContentTypes'); + testDelegation('getFieldMapping', 'getFieldMapping'); + testDelegation('putContentTypeFields', 'updateContentType'); + testDelegation('resetContentType', 'resetToInitialMapping'); + testDelegation('removeContentMapper', 'removeContentMapper'); + testDelegation('updateContentMapper', 'updateContentMapper'); + + it('getExistingContentTypes should return 201', async () => { + mockContentMapperService.getExistingContentTypes.mockResolvedValue({ contentTypes: [] }); + + await contentMapperController.getExistingContentTypes(req, res); + + expect(res.status).toHaveBeenCalledWith(201); + }); + + it('getExistingGlobalFields should return 201', async () => { + mockContentMapperService.getExistingGlobalFields.mockResolvedValue({ globalFields: [] }); + + await contentMapperController.getExistingGlobalFields(req, res); + + expect(res.status).toHaveBeenCalledWith(201); + }); + + it('getExistingTaxonomies should return status from response or default 200', async () => { + mockContentMapperService.getExistingTaxonomies.mockResolvedValue({ status: 200, taxonomies: [] }); + + await contentMapperController.getExistingTaxonomies(req, res); + + expect(res.status).toHaveBeenCalledWith(200); + }); + + it('getSingleContentTypes should return 201', async () => { + mockContentMapperService.getSingleContentTypes.mockResolvedValue({ title: 'Blog' }); + + await contentMapperController.getSingleContentTypes(req, res); + + expect(res.status).toHaveBeenCalledWith(201); + }); + + it('getSingleGlobalField should return 201', async () => { + mockContentMapperService.getSingleGlobalField.mockResolvedValue({ title: 'SEO' }); + + await contentMapperController.getSingleGlobalField(req, res); + + expect(res.status).toHaveBeenCalledWith(201); + }); +}); diff --git a/api/tests/unit/controllers/projects.controller.test.ts b/api/tests/unit/controllers/projects.controller.test.ts new file mode 100644 index 000000000..42fc0136a --- /dev/null +++ b/api/tests/unit/controllers/projects.controller.test.ts @@ -0,0 +1,121 @@ +import { describe, it, expect, vi, beforeEach } from 'vitest'; + +const { mockProjectService } = vi.hoisted(() => ({ + mockProjectService: { + getAllProjects: vi.fn(), + getProject: vi.fn(), + createProject: vi.fn(), + updateProject: vi.fn(), + updateLegacyCMS: vi.fn(), + updateAffix: vi.fn(), + affixConfirmation: vi.fn(), + updateFileFormat: vi.fn(), + fileformatConfirmation: vi.fn(), + updateDestinationStack: vi.fn(), + updateCurrentStep: vi.fn(), + deleteProject: vi.fn(), + revertProject: vi.fn(), + updateStackDetails: vi.fn(), + updateMigrationExecution: vi.fn(), + getMigratedStacks: vi.fn(), + }, +})); + +vi.mock('../../../src/services/projects.service.js', () => ({ + projectService: mockProjectService, +})); + +import { projectController } from '../../../src/controllers/projects.controller.js'; + +describe('projects.controller', () => { + let req: any; + let res: any; + + beforeEach(() => { + vi.clearAllMocks(); + req = { + params: { orgId: 'org-123', projectId: 'proj-123' }, + body: { token_payload: { region: 'NA', user_id: 'user-123' } }, + }; + res = { + status: vi.fn().mockReturnThis(), + json: vi.fn().mockReturnThis(), + }; + }); + + it('getAllProjects should return 200 with projects array', async () => { + const projects = [{ id: '1' }, { id: '2' }]; + mockProjectService.getAllProjects.mockResolvedValue(projects); + + await projectController.getAllProjects(req, res); + + expect(res.status).toHaveBeenCalledWith(200); + expect(res.json).toHaveBeenCalledWith(projects); + }); + + it('getProject should return 200 with single project', async () => { + const project = { id: 'proj-123', name: 'Test' }; + mockProjectService.getProject.mockResolvedValue(project); + + await projectController.getProject(req, res); + + expect(res.status).toHaveBeenCalledWith(200); + expect(res.json).toHaveBeenCalledWith(project); + }); + + it('createProject should return 201 with created project', async () => { + const project = { id: 'new-proj', name: 'New Project' }; + mockProjectService.createProject.mockResolvedValue(project); + + await projectController.createProject(req, res); + + expect(res.status).toHaveBeenCalledWith(201); + expect(res.json).toHaveBeenCalledWith(project); + }); + + it('updateProject should return 200 with updated project', async () => { + const project = { id: 'proj-123', name: 'Updated' }; + mockProjectService.updateProject.mockResolvedValue(project); + + await projectController.updateProject(req, res); + + expect(res.status).toHaveBeenCalledWith(200); + expect(res.json).toHaveBeenCalledWith(project); + }); + + const testServiceDelegation = ( + controllerMethod: keyof typeof projectController, + serviceMethod: keyof typeof mockProjectService + ) => { + it(`${controllerMethod} should delegate to service and return response`, async () => { + mockProjectService[serviceMethod].mockResolvedValue({ status: 200, data: { ok: true } }); + + await projectController[controllerMethod](req, res); + + expect(mockProjectService[serviceMethod]).toHaveBeenCalledWith(req); + expect(res.status).toHaveBeenCalledWith(200); + }); + }; + + testServiceDelegation('updateLegacyCMS', 'updateLegacyCMS'); + testServiceDelegation('updateAffix', 'updateAffix'); + testServiceDelegation('affixConfirmation', 'affixConfirmation'); + testServiceDelegation('updateFileFormat', 'updateFileFormat'); + testServiceDelegation('fileformatConfirmation', 'fileformatConfirmation'); + testServiceDelegation('updateDestinationStack', 'updateDestinationStack'); + testServiceDelegation('deleteProject', 'deleteProject'); + testServiceDelegation('revertProject', 'revertProject'); + testServiceDelegation('updateStackDetails', 'updateStackDetails'); + testServiceDelegation('updateMigrationExecution', 'updateMigrationExecution'); + testServiceDelegation('getMigratedStacks', 'getMigratedStacks'); + + it('updateCurrentStep should return 200', async () => { + const project = { id: 'proj-123', current_step: 2 }; + mockProjectService.updateCurrentStep.mockResolvedValue(project); + + await projectController.updateCurrentStep(req, res); + + expect(res.status).toHaveBeenCalledWith(200); + expect(res.json).toHaveBeenCalledWith(project); + }); +}); diff --git a/api/tests/unit/controllers/user.controller.test.ts b/api/tests/unit/controllers/user.controller.test.ts new file mode 100644 index 000000000..154325b0e --- /dev/null +++ b/api/tests/unit/controllers/user.controller.test.ts @@ -0,0 +1,39 @@ +import { describe, it, expect, vi, beforeEach } from 'vitest'; + +const { mockGetUserProfile } = vi.hoisted(() => ({ mockGetUserProfile: vi.fn() })); + +vi.mock('../../../src/services/user.service.js', () => ({ + userService: { + getUserProfile: mockGetUserProfile, + }, +})); + +import { userController } from '../../../src/controllers/user.controller.js'; + +describe('user.controller', () => { + let req: any; + let res: any; + + beforeEach(() => { + vi.clearAllMocks(); + req = { body: { token_payload: { region: 'NA', user_id: 'user-123' } } }; + res = { + status: vi.fn().mockReturnThis(), + json: vi.fn().mockReturnThis(), + }; + }); + + describe('getUserProfile', () => { + it('should return resp.status and resp.data from service', async () => { + mockGetUserProfile.mockResolvedValue({ + status: 200, + data: { user: { email: 'test@example.com' } }, + }); + + await userController.getUserProfile(req, res); + + expect(res.status).toHaveBeenCalledWith(200); + expect(res.json).toHaveBeenCalledWith({ user: { email: 'test@example.com' } }); + }); + }); +}); diff --git a/api/tests/unit/helpers/index.test.ts b/api/tests/unit/helpers/index.test.ts new file mode 100644 index 000000000..ecd51a418 --- /dev/null +++ b/api/tests/unit/helpers/index.test.ts @@ -0,0 +1,118 @@ +import { describe, it, expect, vi, beforeEach } from 'vitest'; + +const { mockConnect, mockEnd, mockDestroy, mockCreateConnection } = vi.hoisted(() => { + const mockConnect = vi.fn(); + const mockEnd = vi.fn(); + const mockDestroy = vi.fn(); + const mockCreateConnection = vi.fn(() => ({ + connect: mockConnect, + end: mockEnd, + destroy: mockDestroy, + })); + return { mockConnect, mockEnd, mockDestroy, mockCreateConnection }; +}); + +vi.mock('mysql2', () => ({ + default: { + createConnection: mockCreateConnection, + }, +})); + +vi.mock('../../../src/utils/custom-logger.utils.js', () => ({ + default: vi.fn().mockResolvedValue(undefined), +})); + +import { createDbConnection, getDbConnection } from '../../../src/helper/index.js'; + +describe('helper/index', () => { + const dbConfig = { + host: 'localhost', + user: 'root', + password: 'password', + database: 'testdb', + port: 3306, + }; + + beforeEach(() => { + vi.clearAllMocks(); + vi.useFakeTimers(); + }); + + describe('createDbConnection', () => { + it('should create a MySQL connection successfully', async () => { + mockConnect.mockImplementation((cb: any) => cb(null)); + + const connectionPromise = createDbConnection(dbConfig, 'proj-1', 'stack-1'); + vi.runAllTimers(); + const connection = await connectionPromise; + + expect(connection).toBeTruthy(); + expect(mockCreateConnection).toHaveBeenCalledWith( + expect.objectContaining({ + host: 'localhost', + user: 'root', + password: 'password', + database: 'testdb', + port: 3306, + }) + ); + }); + + it('should reject on connection error', async () => { + const dbError = new Error('Access denied'); + mockConnect.mockImplementation((cb: any) => cb(dbError)); + mockEnd.mockImplementation((cb: any) => cb(null)); + + const connectionPromise = createDbConnection(dbConfig); + vi.runAllTimers(); + + await expect(connectionPromise).rejects.toThrow('Access denied'); + }); + + it('should reject on connection timeout', async () => { + mockConnect.mockImplementation(() => { + // Never calls callback, simulating hang + }); + + const connectionPromise = createDbConnection(dbConfig, '', '', 100); + vi.advanceTimersByTime(150); + + await expect(connectionPromise).rejects.toThrow('timed out'); + expect(mockDestroy).toHaveBeenCalled(); + }); + + it('should return null on synchronous createConnection error', async () => { + mockCreateConnection.mockImplementation(() => { + throw new Error('Invalid config'); + }); + + const result = await createDbConnection(dbConfig); + expect(result).toBeNull(); + }); + }); + + describe('getDbConnection', () => { + it('should return connection when createDbConnection succeeds', async () => { + mockConnect.mockImplementation((cb: any) => cb(null)); + mockCreateConnection.mockReturnValue({ + connect: mockConnect, + end: mockEnd, + destroy: mockDestroy, + }); + + const connectionPromise = getDbConnection(dbConfig, 'proj-1', 'stack-1'); + vi.runAllTimers(); + const connection = await connectionPromise; + + expect(connection).toBeTruthy(); + }); + + it('should throw when connection is null', async () => { + mockCreateConnection.mockImplementation(() => { + throw new Error('Cannot connect'); + }); + + await expect(getDbConnection(dbConfig)).rejects.toThrow(); + }); + }); +}); diff --git a/api/tests/unit/middlewares/auth.middleware.test.ts b/api/tests/unit/middlewares/auth.middleware.test.ts new file mode 100644 index 000000000..248e596a3 --- /dev/null +++ b/api/tests/unit/middlewares/auth.middleware.test.ts @@ -0,0 +1,63 @@ +import { describe, it, expect, vi, beforeEach } from 'vitest'; +import jwt from 'jsonwebtoken'; + +vi.mock('../../../src/config/index.js', () => ({ + config: { APP_TOKEN_KEY: 'test-secret-key' }, +})); + +import { authenticateUser } from '../../../src/middlewares/auth.middleware.js'; + +describe('auth.middleware', () => { + let req: any; + let res: any; + let next: any; + + beforeEach(() => { + req = { + get: vi.fn(), + body: {}, + }; + res = { + status: vi.fn().mockReturnThis(), + json: vi.fn().mockReturnThis(), + }; + next = vi.fn(); + }); + + it('should return 401 when app_token header is missing', () => { + req.get.mockReturnValue(undefined); + + authenticateUser(req, res, next); + + expect(res.status).toHaveBeenCalledWith(401); + expect(res.json).toHaveBeenCalledWith( + expect.objectContaining({ message: 'Unauthorized - Token missing' }) + ); + expect(next).not.toHaveBeenCalled(); + }); + + it('should return 401 when JWT verification fails', () => { + req.get.mockReturnValue('invalid-token'); + + authenticateUser(req, res, next); + + expect(res.status).toHaveBeenCalledWith(401); + expect(res.json).toHaveBeenCalledWith( + expect.objectContaining({ message: 'Unauthorized - Invalid token' }) + ); + expect(next).not.toHaveBeenCalled(); + }); + + it('should call next and set token_payload on valid token', () => { + const payload = { region: 'NA', user_id: 'user-123' }; + const token = jwt.sign(payload, 'test-secret-key'); + req.get.mockReturnValue(token); + + authenticateUser(req, res, next); + + expect(next).toHaveBeenCalled(); + expect(req.body.token_payload).toBeDefined(); + expect(req.body.token_payload.region).toBe('NA'); + expect(req.body.token_payload.user_id).toBe('user-123'); + }); +}); diff --git a/api/tests/unit/middlewares/auth.uploadService.middleware.test.ts b/api/tests/unit/middlewares/auth.uploadService.middleware.test.ts new file mode 100644 index 000000000..636f248a6 --- /dev/null +++ b/api/tests/unit/middlewares/auth.uploadService.middleware.test.ts @@ -0,0 +1,52 @@ +import { describe, it, expect, vi, beforeEach } from 'vitest'; + +vi.mock('../../../src/config/index.js', () => ({ + config: { FILE_UPLOAD_KEY: 'valid-upload-key' }, +})); + +import { authenticateUploadService } from '../../../src/middlewares/auth.uploadService.middleware.js'; + +describe('auth.uploadService.middleware', () => { + let req: any; + let res: any; + let next: any; + + beforeEach(() => { + req = { get: vi.fn() }; + res = { + status: vi.fn().mockReturnThis(), + json: vi.fn().mockReturnThis(), + }; + next = vi.fn(); + }); + + it('should return 401 when secret_key header is missing', () => { + req.get.mockReturnValue(undefined); + + authenticateUploadService(req, res, next); + + expect(res.status).toHaveBeenCalledWith(401); + expect(res.json).toHaveBeenCalledWith( + expect.objectContaining({ message: 'Unauthorized - Please provide a valid key' }) + ); + expect(next).not.toHaveBeenCalled(); + }); + + it('should return 401 when secret_key does not match', () => { + req.get.mockReturnValue('wrong-key'); + + authenticateUploadService(req, res, next); + + expect(res.status).toHaveBeenCalledWith(401); + expect(next).not.toHaveBeenCalled(); + }); + + it('should call next when secret_key matches', () => { + req.get.mockReturnValue('valid-upload-key'); + + authenticateUploadService(req, res, next); + + expect(next).toHaveBeenCalled(); + expect(res.status).not.toHaveBeenCalled(); + }); +}); diff --git a/api/tests/unit/middlewares/error.middleware.test.ts b/api/tests/unit/middlewares/error.middleware.test.ts new file mode 100644 index 000000000..7a26128f4 --- /dev/null +++ b/api/tests/unit/middlewares/error.middleware.test.ts @@ -0,0 +1,66 @@ +import { describe, it, expect, vi } from 'vitest'; + +vi.mock('../../../src/utils/logger.js', () => ({ + default: { error: vi.fn(), info: vi.fn(), warn: vi.fn() }, +})); + +import { errorMiddleware } from '../../../src/middlewares/error.middleware.js'; +import { AppError, BadRequestError, NotFoundError } from '../../../src/utils/custom-errors.utils.js'; + +describe('error.middleware', () => { + const req = {} as any; + const next = vi.fn(); + + const createRes = () => ({ + status: vi.fn().mockReturnThis(), + json: vi.fn().mockReturnThis(), + }); + + it('should return statusCode and message for AppError instances', () => { + const res = createRes(); + const error = new BadRequestError('Invalid input'); + + errorMiddleware(error, req, res as any, next); + + expect(res.status).toHaveBeenCalledWith(400); + expect(res.json).toHaveBeenCalledWith({ + error: { code: 400, message: 'Invalid input' }, + }); + }); + + it('should return 404 for NotFoundError', () => { + const res = createRes(); + const error = new NotFoundError('Resource not found'); + + errorMiddleware(error, req, res as any, next); + + expect(res.status).toHaveBeenCalledWith(404); + expect(res.json).toHaveBeenCalledWith({ + error: { code: 404, message: 'Resource not found' }, + }); + }); + + it('should return 500 for generic errors', () => { + const res = createRes(); + const error = new Error('Something broke'); + + errorMiddleware(error, req, res as any, next); + + expect(res.status).toHaveBeenCalledWith(500); + expect(res.json).toHaveBeenCalledWith({ + error: { code: 500, message: 'Internal Server Error' }, + }); + }); + + it('should handle AppError with custom statusCode', () => { + const res = createRes(); + const error = new AppError(503, 'Service unavailable'); + + errorMiddleware(error, req, res as any, next); + + expect(res.status).toHaveBeenCalledWith(503); + expect(res.json).toHaveBeenCalledWith({ + error: { code: 503, message: 'Service unavailable' }, + }); + }); +}); diff --git a/api/tests/unit/middlewares/req-headers.middleware.test.ts b/api/tests/unit/middlewares/req-headers.middleware.test.ts new file mode 100644 index 000000000..77d380deb --- /dev/null +++ b/api/tests/unit/middlewares/req-headers.middleware.test.ts @@ -0,0 +1,51 @@ +import { describe, it, expect, vi } from 'vitest'; +import { requestHeadersMiddleware } from '../../../src/middlewares/req-headers.middleware.js'; + +describe('req-headers.middleware', () => { + const createRes = () => ({ + header: vi.fn(), + status: vi.fn().mockReturnThis(), + json: vi.fn().mockReturnThis(), + }); + + it('should set CORS headers on all requests', () => { + const req = { method: 'GET' } as any; + const res = createRes(); + const next = vi.fn(); + + requestHeadersMiddleware(req, res as any, next); + + expect(res.header).toHaveBeenCalledWith('Access-Control-Allow-Origin', '*'); + expect(res.header).toHaveBeenCalledWith( + 'Access-Control-Allow-Headers', + 'Origin, Content-Type, Accept, app_token' + ); + }); + + it('should return 200 with empty JSON for OPTIONS requests', () => { + const req = { method: 'OPTIONS' } as any; + const res = createRes(); + const next = vi.fn(); + + requestHeadersMiddleware(req, res as any, next); + + expect(res.header).toHaveBeenCalledWith( + 'Access-Control-Allow-Methods', + 'GET, POST, PUT, DELETE' + ); + expect(res.status).toHaveBeenCalledWith(200); + expect(res.json).toHaveBeenCalledWith({}); + expect(next).not.toHaveBeenCalled(); + }); + + it('should call next for non-OPTIONS requests', () => { + const req = { method: 'POST' } as any; + const res = createRes(); + const next = vi.fn(); + + requestHeadersMiddleware(req, res as any, next); + + expect(next).toHaveBeenCalled(); + expect(res.status).not.toHaveBeenCalled(); + }); +}); diff --git a/api/tests/unit/middlewares/unmatched-routes.middleware.test.ts b/api/tests/unit/middlewares/unmatched-routes.middleware.test.ts new file mode 100644 index 000000000..f682aebd3 --- /dev/null +++ b/api/tests/unit/middlewares/unmatched-routes.middleware.test.ts @@ -0,0 +1,22 @@ +import { describe, it, expect, vi } from 'vitest'; +import { unmatchedRoutesMiddleware } from '../../../src/middlewares/unmatched-routes.middleware.js'; + +describe('unmatched-routes.middleware', () => { + it('should return 404 with route error message', () => { + const req = {} as any; + const res = { + status: vi.fn().mockReturnThis(), + json: vi.fn().mockReturnThis(), + }; + + unmatchedRoutesMiddleware(req, res as any); + + expect(res.status).toHaveBeenCalledWith(404); + expect(res.json).toHaveBeenCalledWith({ + error: { + code: 404, + message: 'Sorry, the requested resource is not available.', + }, + }); + }); +}); diff --git a/api/tests/unit/models/FieldMapper.model.test.ts b/api/tests/unit/models/FieldMapper.model.test.ts new file mode 100644 index 000000000..96efda500 --- /dev/null +++ b/api/tests/unit/models/FieldMapper.model.test.ts @@ -0,0 +1,43 @@ +import { describe, it, expect, vi, beforeEach } from 'vitest'; + +vi.mock('lowdb/node', () => ({ + JSONFile: vi.fn().mockImplementation(function (this: unknown) { + return {}; + }), +})); + +vi.mock('../../../src/utils/lowdb-lodash.utils.js', () => ({ + default: vi.fn().mockImplementation(function ( + _adapter: unknown, + defaultData: { field_mapper: unknown[] } + ) { + return { + data: defaultData, + chain: {}, + }; + }), +})); + +describe('FieldMapper model', () => { + beforeEach(() => { + vi.resetModules(); + }); + + it('should export db with field_mapper array in default data', async () => { + const fieldMapperDb = (await import('../../../src/models/FieldMapper.js')).default; + + expect(fieldMapperDb).toBeDefined(); + expect(fieldMapperDb.data).toBeDefined(); + expect(fieldMapperDb.data).toHaveProperty('field_mapper'); + expect(Array.isArray(fieldMapperDb.data.field_mapper)).toBe(true); + expect(fieldMapperDb.data.field_mapper).toEqual([]); + }); + + it('should have correct default structure for FieldMapper', async () => { + const fieldMapperDb = (await import('../../../src/models/FieldMapper.js')).default; + + expect(fieldMapperDb.data).toMatchObject({ + field_mapper: [], + }); + }); +}); diff --git a/api/tests/unit/models/authentication.model.test.ts b/api/tests/unit/models/authentication.model.test.ts new file mode 100644 index 000000000..e7f6d4062 --- /dev/null +++ b/api/tests/unit/models/authentication.model.test.ts @@ -0,0 +1,43 @@ +import { describe, it, expect, vi, beforeEach } from 'vitest'; + +vi.mock('lowdb/node', () => ({ + JSONFile: vi.fn().mockImplementation(function (this: unknown) { + return {}; + }), +})); + +vi.mock('../../../src/utils/lowdb-lodash.utils.js', () => ({ + default: vi.fn().mockImplementation(function ( + _adapter: unknown, + defaultData: { users: unknown[] } + ) { + return { + data: defaultData, + chain: {}, + }; + }), +})); + +describe('authentication model', () => { + beforeEach(() => { + vi.resetModules(); + }); + + it('should export db with users array in default data', async () => { + const authDb = (await import('../../../src/models/authentication.js')).default; + + expect(authDb).toBeDefined(); + expect(authDb.data).toBeDefined(); + expect(authDb.data).toHaveProperty('users'); + expect(Array.isArray(authDb.data.users)).toBe(true); + expect(authDb.data.users).toEqual([]); + }); + + it('should have correct default structure for AuthenticationDocument', async () => { + const authDb = (await import('../../../src/models/authentication.js')).default; + + expect(authDb.data).toMatchObject({ + users: [], + }); + }); +}); diff --git a/api/tests/unit/models/contentTypesMapper-lowdb.model.test.ts b/api/tests/unit/models/contentTypesMapper-lowdb.model.test.ts new file mode 100644 index 000000000..66ce8ce16 --- /dev/null +++ b/api/tests/unit/models/contentTypesMapper-lowdb.model.test.ts @@ -0,0 +1,43 @@ +import { describe, it, expect, vi, beforeEach } from 'vitest'; + +vi.mock('lowdb/node', () => ({ + JSONFile: vi.fn().mockImplementation(function (this: unknown) { + return {}; + }), +})); + +vi.mock('../../../src/utils/lowdb-lodash.utils.js', () => ({ + default: vi.fn().mockImplementation(function ( + _adapter: unknown, + defaultData: { ContentTypesMappers: unknown[] } + ) { + return { + data: defaultData, + chain: {}, + }; + }), +})); + +describe('contentTypesMapper-lowdb model', () => { + beforeEach(() => { + vi.resetModules(); + }); + + it('should export db with ContentTypesMappers array in default data', async () => { + const contentTypesDb = (await import('../../../src/models/contentTypesMapper-lowdb.js')).default; + + expect(contentTypesDb).toBeDefined(); + expect(contentTypesDb.data).toBeDefined(); + expect(contentTypesDb.data).toHaveProperty('ContentTypesMappers'); + expect(Array.isArray(contentTypesDb.data.ContentTypesMappers)).toBe(true); + expect(contentTypesDb.data.ContentTypesMappers).toEqual([]); + }); + + it('should have correct default structure for ContentTypeMapperDocument', async () => { + const contentTypesDb = (await import('../../../src/models/contentTypesMapper-lowdb.js')).default; + + expect(contentTypesDb.data).toMatchObject({ + ContentTypesMappers: [], + }); + }); +}); diff --git a/api/tests/unit/models/project-lowdb.model.test.ts b/api/tests/unit/models/project-lowdb.model.test.ts new file mode 100644 index 000000000..6716269fb --- /dev/null +++ b/api/tests/unit/models/project-lowdb.model.test.ts @@ -0,0 +1,43 @@ +import { describe, it, expect, vi, beforeEach } from 'vitest'; + +vi.mock('lowdb/node', () => ({ + JSONFile: vi.fn().mockImplementation(function (this: unknown) { + return {}; + }), +})); + +vi.mock('../../../src/utils/lowdb-lodash.utils.js', () => ({ + default: vi.fn().mockImplementation(function ( + _adapter: unknown, + defaultData: { projects: unknown[] } + ) { + return { + data: defaultData, + chain: {}, + }; + }), +})); + +describe('project-lowdb model', () => { + beforeEach(() => { + vi.resetModules(); + }); + + it('should export db with projects array in default data', async () => { + const projectDb = (await import('../../../src/models/project-lowdb.js')).default; + + expect(projectDb).toBeDefined(); + expect(projectDb.data).toBeDefined(); + expect(projectDb.data).toHaveProperty('projects'); + expect(Array.isArray(projectDb.data.projects)).toBe(true); + expect(projectDb.data.projects).toEqual([]); + }); + + it('should have correct default structure for ProjectDocument', async () => { + const projectDb = (await import('../../../src/models/project-lowdb.js')).default; + + expect(projectDb.data).toMatchObject({ + projects: [], + }); + }); +}); diff --git a/api/tests/unit/routes/auth.routes.test.ts b/api/tests/unit/routes/auth.routes.test.ts new file mode 100644 index 000000000..b8a37e5bb --- /dev/null +++ b/api/tests/unit/routes/auth.routes.test.ts @@ -0,0 +1,44 @@ +import { describe, it, expect, vi, beforeAll } from 'vitest'; + +vi.mock('../../../src/controllers/auth.controller.js', () => ({ + authController: { + login: vi.fn((_req: any, res: any) => res.status(200).json({ ok: true })), + RequestSms: vi.fn((_req: any, res: any) => res.status(200).json({ ok: true })), + }, +})); + +vi.mock('../../../src/validators/index.js', () => ({ + default: () => (_req: any, _res: any, next: any) => next(), +})); + +vi.mock('../../../src/utils/async-router.utils.js', () => ({ + asyncRouter: (fn: any) => fn, +})); + +describe('auth.routes', () => { + let router: any; + + beforeAll(async () => { + const mod = await import('../../../src/routes/auth.routes.js'); + router = mod.default; + }); + + it('should export an Express router', () => { + expect(router).toBeDefined(); + expect(typeof router).toBe('function'); + }); + + it('should register POST /user-session', () => { + const postRoutes = router.stack + .filter((layer: any) => layer.route?.methods?.post) + .map((layer: any) => layer.route.path); + expect(postRoutes).toContain('/user-session'); + }); + + it('should register POST /request-token-sms', () => { + const postRoutes = router.stack + .filter((layer: any) => layer.route?.methods?.post) + .map((layer: any) => layer.route.path); + expect(postRoutes).toContain('/request-token-sms'); + }); +}); diff --git a/api/tests/unit/routes/contentMapper.routes.test.ts b/api/tests/unit/routes/contentMapper.routes.test.ts new file mode 100644 index 000000000..5b9474f8b --- /dev/null +++ b/api/tests/unit/routes/contentMapper.routes.test.ts @@ -0,0 +1,104 @@ +import { describe, it, expect, vi, beforeAll } from 'vitest'; + +vi.mock('../../../src/controllers/projects.contentMapper.controller.js', () => ({ + contentMapperController: { + putTestData: vi.fn((_req: any, res: any) => res.status(200).json({})), + getContentTypes: vi.fn((_req: any, res: any) => res.status(200).json({})), + getFieldMapping: vi.fn((_req: any, res: any) => res.status(200).json({})), + getExistingContentTypes: vi.fn((_req: any, res: any) => res.status(200).json({})), + getExistingGlobalFields: vi.fn((_req: any, res: any) => res.status(200).json({})), + getExistingTaxonomies: vi.fn((_req: any, res: any) => res.status(200).json({})), + putContentTypeFields: vi.fn((_req: any, res: any) => res.status(200).json({})), + resetContentType: vi.fn((_req: any, res: any) => res.status(200).json({})), + removeContentMapper: vi.fn((_req: any, res: any) => res.status(200).json({})), + updateContentMapper: vi.fn((_req: any, res: any) => res.status(200).json({})), + }, +})); + +vi.mock('../../../src/utils/async-router.utils.js', () => ({ + asyncRouter: (fn: any) => fn, +})); + +describe('contentMapper.routes', () => { + let router: any; + + beforeAll(async () => { + const mod = await import('../../../src/routes/contentMapper.routes.js'); + router = mod.default; + }); + + it('should export an Express router', () => { + expect(router).toBeDefined(); + expect(typeof router).toBe('function'); + }); + + it('should register POST /createDummyData/:projectId', () => { + const routes = router.stack + .filter((layer: any) => layer.route?.methods?.post) + .map((layer: any) => layer.route.path); + expect(routes).toContain('/createDummyData/:projectId'); + }); + + it('should register GET /contentTypes/:projectId/:skip/:limit/:searchText?', () => { + const routes = router.stack + .filter((layer: any) => layer.route?.methods?.get) + .map((layer: any) => layer.route.path); + expect(routes).toContain('/contentTypes/:projectId/:skip/:limit/:searchText?'); + }); + + it('should register GET /fieldMapping/:projectId/:contentTypeId/:skip/:limit/:searchText?', () => { + const routes = router.stack + .filter((layer: any) => layer.route?.methods?.get) + .map((layer: any) => layer.route.path); + expect(routes).toContain('/fieldMapping/:projectId/:contentTypeId/:skip/:limit/:searchText?'); + }); + + it('should register GET /:projectId/contentTypes/:contentTypeUid?', () => { + const routes = router.stack + .filter((layer: any) => layer.route?.methods?.get) + .map((layer: any) => layer.route.path); + expect(routes).toContain('/:projectId/contentTypes/:contentTypeUid?'); + }); + + it('should register GET /:projectId/globalFields/:globalFieldUid?', () => { + const routes = router.stack + .filter((layer: any) => layer.route?.methods?.get) + .map((layer: any) => layer.route.path); + expect(routes).toContain('/:projectId/globalFields/:globalFieldUid?'); + }); + + it('should register GET /:projectId/taxonomies', () => { + const routes = router.stack + .filter((layer: any) => layer.route?.methods?.get) + .map((layer: any) => layer.route.path); + expect(routes).toContain('/:projectId/taxonomies'); + }); + + it('should register PUT /contentTypes/:orgId/:projectId/:contentTypeId', () => { + const routes = router.stack + .filter((layer: any) => layer.route?.methods?.put) + .map((layer: any) => layer.route.path); + expect(routes).toContain('/contentTypes/:orgId/:projectId/:contentTypeId'); + }); + + it('should register PUT /resetFields/:orgId/:projectId/:contentTypeId', () => { + const routes = router.stack + .filter((layer: any) => layer.route?.methods?.put) + .map((layer: any) => layer.route.path); + expect(routes).toContain('/resetFields/:orgId/:projectId/:contentTypeId'); + }); + + it('should register GET /:orgId/:projectId/content-mapper', () => { + const routes = router.stack + .filter((layer: any) => layer.route?.methods?.get) + .map((layer: any) => layer.route.path); + expect(routes).toContain('/:orgId/:projectId/content-mapper'); + }); + + it('should register PATCH /:orgId/:projectId/mapper_keys', () => { + const routes = router.stack + .filter((layer: any) => layer.route?.methods?.patch) + .map((layer: any) => layer.route.path); + expect(routes).toContain('/:orgId/:projectId/mapper_keys'); + }); +}); diff --git a/api/tests/unit/routes/migration.routes.test.ts b/api/tests/unit/routes/migration.routes.test.ts new file mode 100644 index 000000000..85ce2f391 --- /dev/null +++ b/api/tests/unit/routes/migration.routes.test.ts @@ -0,0 +1,92 @@ +import { describe, it, expect, vi, beforeAll } from 'vitest'; + +vi.mock('../../../src/controllers/migration.controller.js', () => ({ + migrationController: { + startTestMigration: vi.fn((_req: any, res: any) => res.status(200).json({})), + deleteTestStack: vi.fn((_req: any, res: any) => res.status(200).json({})), + createTestStack: vi.fn((_req: any, res: any) => res.status(200).json({})), + startMigration: vi.fn((_req: any, res: any) => res.status(200).json({})), + getLogs: vi.fn((_req: any, res: any) => res.status(200).json({})), + getAuditData: vi.fn((_req: any, res: any) => res.status(200).json({})), + saveLocales: vi.fn((_req: any, res: any) => res.status(200).json({})), + saveMappedLocales: vi.fn((_req: any, res: any) => res.status(200).json({})), + }, +})); + +vi.mock('../../../src/utils/async-router.utils.js', () => ({ + asyncRouter: (fn: any) => fn, +})); + +describe('migration.routes', () => { + let router: any; + + beforeAll(async () => { + const mod = await import('../../../src/routes/migration.routes.js'); + router = mod.default; + }); + + it('should export an Express router', () => { + expect(router).toBeDefined(); + expect(typeof router).toBe('function'); + }); + + it('should register POST /test-stack/:orgId/:projectId (startTestMigration)', () => { + const routes = router.stack + .filter((layer: any) => layer.route?.methods?.post) + .map((layer: any) => layer.route.path); + expect(routes).toContain('/test-stack/:orgId/:projectId'); + }); + + it('should register POST /test-stack/:projectId (deleteTestStack)', () => { + const routes = router.stack + .filter((layer: any) => layer.route?.methods?.post) + .map((layer: any) => layer.route.path); + expect(routes).toContain('/test-stack/:projectId'); + }); + + it('should register POST /create-test-stack/:orgId/:projectId', () => { + const routes = router.stack + .filter((layer: any) => layer.route?.methods?.post) + .map((layer: any) => layer.route.path); + expect(routes).toContain('/create-test-stack/:orgId/:projectId'); + }); + + it('should register POST /start/:orgId/:projectId', () => { + const routes = router.stack + .filter((layer: any) => layer.route?.methods?.post) + .map((layer: any) => layer.route.path); + expect(routes).toContain('/start/:orgId/:projectId'); + }); + + it('should register GET /get_migration_logs/...', () => { + const routes = router.stack + .filter((layer: any) => layer.route?.methods?.get) + .map((layer: any) => layer.route.path); + expect(routes).toContain( + '/get_migration_logs/:orgId/:projectId/:stackId/:skip/:limit/:startIndex/:stopIndex/:searchText/:filter' + ); + }); + + it('should register GET /get_audit_data/...', () => { + const routes = router.stack + .filter((layer: any) => layer.route?.methods?.get) + .map((layer: any) => layer.route.path); + expect(routes).toContain( + '/get_audit_data/:orgId/:projectId/:stackId/:moduleName/:skip/:limit/:startIndex/:stopIndex/:searchText/:filter' + ); + }); + + it('should register POST /localeMapper/:projectId', () => { + const routes = router.stack + .filter((layer: any) => layer.route?.methods?.post) + .map((layer: any) => layer.route.path); + expect(routes).toContain('/localeMapper/:projectId'); + }); + + it('should register POST /updateLocales/:projectId', () => { + const routes = router.stack + .filter((layer: any) => layer.route?.methods?.post) + .map((layer: any) => layer.route.path); + expect(routes).toContain('/updateLocales/:projectId'); + }); +}); diff --git a/api/tests/unit/routes/org.routes.test.ts b/api/tests/unit/routes/org.routes.test.ts new file mode 100644 index 000000000..30ef54187 --- /dev/null +++ b/api/tests/unit/routes/org.routes.test.ts @@ -0,0 +1,76 @@ +import { describe, it, expect, vi, beforeAll } from 'vitest'; + +vi.mock('../../../src/controllers/org.controller.js', () => ({ + orgController: { + getAllStacks: vi.fn((_req: any, res: any) => res.status(200).json([])), + createStack: vi.fn((_req: any, res: any) => res.status(201).json({})), + getLocales: vi.fn((_req: any, res: any) => res.status(200).json([])), + getStackStatus: vi.fn((_req: any, res: any) => res.status(200).json({})), + getStackLocale: vi.fn((_req: any, res: any) => res.status(200).json([])), + getOrgDetails: vi.fn((_req: any, res: any) => res.status(200).json({})), + }, +})); + +vi.mock('../../../src/validators/index.js', () => ({ + default: () => (_req: any, _res: any, next: any) => next(), +})); + +vi.mock('../../../src/utils/async-router.utils.js', () => ({ + asyncRouter: (fn: any) => fn, +})); + +describe('org.routes', () => { + let router: any; + + beforeAll(async () => { + const mod = await import('../../../src/routes/org.routes.js'); + router = mod.default; + }); + + it('should export an Express router', () => { + expect(router).toBeDefined(); + expect(typeof router).toBe('function'); + }); + + it('should register GET /stacks/:searchText?', () => { + const getRoutes = router.stack + .filter((layer: any) => layer.route?.methods?.get) + .map((layer: any) => layer.route.path); + expect(getRoutes).toContain('/stacks/:searchText?'); + }); + + it('should register POST /stacks', () => { + const postRoutes = router.stack + .filter((layer: any) => layer.route?.methods?.post) + .map((layer: any) => layer.route.path); + expect(postRoutes).toContain('/stacks'); + }); + + it('should register GET /locales', () => { + const getRoutes = router.stack + .filter((layer: any) => layer.route?.methods?.get) + .map((layer: any) => layer.route.path); + expect(getRoutes).toContain('/locales'); + }); + + it('should register POST /stack_status', () => { + const postRoutes = router.stack + .filter((layer: any) => layer.route?.methods?.post) + .map((layer: any) => layer.route.path); + expect(postRoutes).toContain('/stack_status'); + }); + + it('should register GET /get_stack_locales', () => { + const getRoutes = router.stack + .filter((layer: any) => layer.route?.methods?.get) + .map((layer: any) => layer.route.path); + expect(getRoutes).toContain('/get_stack_locales'); + }); + + it('should register GET /get_org_details', () => { + const getRoutes = router.stack + .filter((layer: any) => layer.route?.methods?.get) + .map((layer: any) => layer.route.path); + expect(getRoutes).toContain('/get_org_details'); + }); +}); diff --git a/api/tests/unit/routes/projects.routes.test.ts b/api/tests/unit/routes/projects.routes.test.ts new file mode 100644 index 000000000..94425b4fc --- /dev/null +++ b/api/tests/unit/routes/projects.routes.test.ts @@ -0,0 +1,156 @@ +import { describe, it, expect, vi, beforeAll } from 'vitest'; + +vi.mock('../../../src/controllers/projects.controller.js', () => ({ + projectController: { + getAllProjects: vi.fn((_req: any, res: any) => res.status(200).json([])), + getProject: vi.fn((_req: any, res: any) => res.status(200).json({})), + createProject: vi.fn((_req: any, res: any) => res.status(201).json({})), + updateProject: vi.fn((_req: any, res: any) => res.status(200).json({})), + updateLegacyCMS: vi.fn((_req: any, res: any) => res.status(200).json({})), + updateAffix: vi.fn((_req: any, res: any) => res.status(200).json({})), + affixConfirmation: vi.fn((_req: any, res: any) => res.status(200).json({})), + updateFileFormat: vi.fn((_req: any, res: any) => res.status(200).json({})), + fileformatConfirmation: vi.fn((_req: any, res: any) => res.status(200).json({})), + updateDestinationStack: vi.fn((_req: any, res: any) => res.status(200).json({})), + updateCurrentStep: vi.fn((_req: any, res: any) => res.status(200).json({})), + deleteProject: vi.fn((_req: any, res: any) => res.status(200).json({})), + revertProject: vi.fn((_req: any, res: any) => res.status(200).json({})), + updateStackDetails: vi.fn((_req: any, res: any) => res.status(200).json({})), + updateMigrationExecution: vi.fn((_req: any, res: any) => res.status(200).json({})), + getMigratedStacks: vi.fn((_req: any, res: any) => res.status(200).json({})), + }, +})); + +vi.mock('../../../src/validators/index.js', () => ({ + default: () => (_req: any, _res: any, next: any) => next(), +})); + +vi.mock('../../../src/utils/async-router.utils.js', () => ({ + asyncRouter: (fn: any) => fn, +})); + +describe('projects.routes', () => { + let router: any; + + beforeAll(async () => { + const mod = await import('../../../src/routes/projects.routes.js'); + router = mod.default; + }); + + it('should export an Express router', () => { + expect(router).toBeDefined(); + expect(typeof router).toBe('function'); + }); + + it('should register GET /', () => { + const routes = router.stack + .filter((layer: any) => layer.route?.methods?.get && layer.route.path === '/') + .map((layer: any) => layer.route.path); + expect(routes).toContain('/'); + }); + + it('should register GET /:projectId', () => { + const routes = router.stack + .filter((layer: any) => layer.route?.methods?.get) + .map((layer: any) => layer.route.path); + expect(routes).toContain('/:projectId'); + }); + + it('should register POST /', () => { + const routes = router.stack + .filter((layer: any) => layer.route?.methods?.post && layer.route.path === '/') + .map((layer: any) => layer.route.path); + expect(routes).toContain('/'); + }); + + it('should register PUT /:projectId', () => { + const routes = router.stack + .filter((layer: any) => layer.route?.methods?.put && layer.route.path === '/:projectId') + .map((layer: any) => layer.route.path); + expect(routes).toContain('/:projectId'); + }); + + it('should register PUT /:projectId/legacy-cms', () => { + const routes = router.stack + .filter((layer: any) => layer.route?.methods?.put) + .map((layer: any) => layer.route.path); + expect(routes).toContain('/:projectId/legacy-cms'); + }); + + it('should register PUT /:projectId/affix', () => { + const routes = router.stack + .filter((layer: any) => layer.route?.methods?.put) + .map((layer: any) => layer.route.path); + expect(routes).toContain('/:projectId/affix'); + }); + + it('should register PUT /:projectId/affix_confirmation', () => { + const routes = router.stack + .filter((layer: any) => layer.route?.methods?.put) + .map((layer: any) => layer.route.path); + expect(routes).toContain('/:projectId/affix_confirmation'); + }); + + it('should register PUT /:projectId/file-format', () => { + const routes = router.stack + .filter((layer: any) => layer.route?.methods?.put) + .map((layer: any) => layer.route.path); + expect(routes).toContain('/:projectId/file-format'); + }); + + it('should register PUT /:projectId/fileformat_confirmation', () => { + const routes = router.stack + .filter((layer: any) => layer.route?.methods?.put) + .map((layer: any) => layer.route.path); + expect(routes).toContain('/:projectId/fileformat_confirmation'); + }); + + it('should register PUT /:projectId/destination-stack', () => { + const routes = router.stack + .filter((layer: any) => layer.route?.methods?.put) + .map((layer: any) => layer.route.path); + expect(routes).toContain('/:projectId/destination-stack'); + }); + + it('should register PUT /:projectId/current-step', () => { + const routes = router.stack + .filter((layer: any) => layer.route?.methods?.put) + .map((layer: any) => layer.route.path); + expect(routes).toContain('/:projectId/current-step'); + }); + + it('should register DELETE /:projectId', () => { + const routes = router.stack + .filter((layer: any) => layer.route?.methods?.delete) + .map((layer: any) => layer.route.path); + expect(routes).toContain('/:projectId'); + }); + + it('should register PATCH /:projectId', () => { + const routes = router.stack + .filter((layer: any) => layer.route?.methods?.patch) + .map((layer: any) => layer.route.path); + expect(routes).toContain('/:projectId'); + }); + + it('should register PATCH /:projectId/stack-details', () => { + const routes = router.stack + .filter((layer: any) => layer.route?.methods?.patch) + .map((layer: any) => layer.route.path); + expect(routes).toContain('/:projectId/stack-details'); + }); + + it('should register PUT /:projectId/migration-excution', () => { + const routes = router.stack + .filter((layer: any) => layer.route?.methods?.put) + .map((layer: any) => layer.route.path); + expect(routes).toContain('/:projectId/migration-excution'); + }); + + it('should register GET /:projectId/get-migrated-stacks', () => { + const routes = router.stack + .filter((layer: any) => layer.route?.methods?.get) + .map((layer: any) => layer.route.path); + expect(routes).toContain('/:projectId/get-migrated-stacks'); + }); +}); diff --git a/api/tests/unit/routes/user.routes.test.ts b/api/tests/unit/routes/user.routes.test.ts new file mode 100644 index 000000000..be61b2516 --- /dev/null +++ b/api/tests/unit/routes/user.routes.test.ts @@ -0,0 +1,32 @@ +import { describe, it, expect, vi, beforeAll } from 'vitest'; + +vi.mock('../../../src/controllers/user.controller.js', () => ({ + userController: { + getUserProfile: vi.fn((_req: any, res: any) => res.status(200).json({ ok: true })), + }, +})); + +vi.mock('../../../src/utils/async-router.utils.js', () => ({ + asyncRouter: (fn: any) => fn, +})); + +describe('user.routes', () => { + let router: any; + + beforeAll(async () => { + const mod = await import('../../../src/routes/user.routes.js'); + router = mod.default; + }); + + it('should export an Express router', () => { + expect(router).toBeDefined(); + expect(typeof router).toBe('function'); + }); + + it('should register GET /profile', () => { + const getRoutes = router.stack + .filter((layer: any) => layer.route?.methods?.get) + .map((layer: any) => layer.route.path); + expect(getRoutes).toContain('/profile'); + }); +}); diff --git a/api/tests/unit/services/auth.service.test.ts b/api/tests/unit/services/auth.service.test.ts new file mode 100644 index 000000000..a1654b287 --- /dev/null +++ b/api/tests/unit/services/auth.service.test.ts @@ -0,0 +1,228 @@ +import { describe, it, expect, vi, beforeEach } from 'vitest'; + +const { mockHttps, mockGenerateToken, mockAuthModelRead, mockAuthModelUpdate, mockChainValue } = vi.hoisted(() => ({ + mockHttps: vi.fn(), + mockGenerateToken: vi.fn(), + mockAuthModelRead: vi.fn(), + mockAuthModelUpdate: vi.fn(), + mockChainValue: vi.fn(), +})); + +vi.mock('../../../src/utils/https.utils.js', () => ({ default: mockHttps })); +vi.mock('../../../src/utils/jwt.utils.js', () => ({ generateToken: mockGenerateToken })); +vi.mock('../../../src/utils/logger.js', () => ({ + default: { error: vi.fn(), info: vi.fn(), warn: vi.fn() }, +})); +vi.mock('../../../src/config/index.js', () => ({ + config: { + CS_API: { NA: 'https://api.contentstack.io/v3', EU: 'https://eu-api.contentstack.io/v3' }, + APP_TOKEN_KEY: 'test-secret', + APP_TOKEN_EXP: '2d', + }, +})); +vi.mock('../../../src/models/authentication.js', () => ({ + default: { + read: mockAuthModelRead, + update: mockAuthModelUpdate, + chain: { + get: vi.fn().mockReturnValue({ + findIndex: vi.fn().mockReturnValue({ value: mockChainValue }), + }), + }, + }, +})); + +import { authService } from '../../../src/services/auth.service.js'; + +describe('auth.service', () => { + beforeEach(() => { + vi.clearAllMocks(); + mockAuthModelRead.mockResolvedValue(undefined); + mockAuthModelUpdate.mockImplementation((fn: any) => fn({ users: [] })); + mockChainValue.mockReturnValue(-1); + }); + + describe('login', () => { + const createReq = (body: any = {}) => ({ + body: { + email: 'test@example.com', + password: 'password123', + region: 'NA', + ...body, + }, + }); + + it('should return app_token on successful login with admin org', async () => { + mockHttps.mockResolvedValue({ + status: 200, + data: { + user: { + uid: 'user-123', + email: 'test@example.com', + authtoken: 'cs-token', + organizations: [ + { uid: 'org-1', name: 'Org 1', org_roles: [{ admin: true }], is_owner: false }, + ], + }, + }, + }); + mockGenerateToken.mockReturnValue('jwt-token'); + + const result = await authService.login(createReq() as any); + + expect(result.status).toBe(200); + expect(result.data.app_token).toBe('jwt-token'); + expect(result.data.message).toBe('Login Successful.'); + expect(mockGenerateToken).toHaveBeenCalledWith({ region: 'NA', user_id: 'user-123' }); + }); + + it('should return app_token for owner org', async () => { + mockHttps.mockResolvedValue({ + status: 200, + data: { + user: { + uid: 'user-123', + email: 'test@example.com', + authtoken: 'cs-token', + organizations: [ + { uid: 'org-1', name: 'Org 1', org_roles: [], is_owner: true }, + ], + }, + }, + }); + mockGenerateToken.mockReturnValue('jwt-token'); + + const result = await authService.login(createReq() as any); + + expect(result.status).toBe(200); + expect(result.data.app_token).toBe('jwt-token'); + }); + + it('should throw BadRequestError for non-admin/non-owner user', async () => { + mockHttps.mockResolvedValue({ + status: 200, + data: { + user: { + uid: 'user-123', + email: 'test@example.com', + authtoken: 'cs-token', + organizations: [ + { uid: 'org-1', name: 'Org 1', org_roles: [{ admin: false }], is_owner: false }, + ], + }, + }, + }); + + await expect(authService.login(createReq() as any)).rejects.toThrow( + "Sorry, You Don't have admin access in any of the Organisation" + ); + }); + + it('should return error data when CS API returns error', async () => { + mockHttps.mockRejectedValue({ + response: { data: { error_message: 'Invalid credentials' }, status: 401 }, + }); + + const result = await authService.login(createReq() as any); + + expect(result.status).toBe(401); + expect(result.data.error_message).toBe('Invalid credentials'); + }); + + it('should handle SUPPORT_DOC status response', async () => { + mockHttps.mockResolvedValue({ + status: 294, + data: { notice: 'Support doc needed' }, + }); + + const result = await authService.login(createReq() as any); + + expect(result.status).toBe(294); + expect(result.data.notice).toBe('Support doc needed'); + }); + + it('should handle 2FA token flow', async () => { + mockHttps.mockResolvedValue({ + status: 200, + data: { + user: { + uid: 'user-123', + email: 'test@example.com', + authtoken: 'cs-token', + organizations: [ + { uid: 'org-1', name: 'Org 1', org_roles: [{ admin: true }], is_owner: false }, + ], + }, + }, + }); + mockGenerateToken.mockReturnValue('jwt-2fa'); + + const result = await authService.login( + createReq({ tfa_token: '123456' }) as any + ); + + expect(result.status).toBe(200); + expect(result.data.app_token).toBe('jwt-2fa'); + expect(mockHttps).toHaveBeenCalledWith( + expect.objectContaining({ + data: expect.objectContaining({ + user: expect.objectContaining({ tfa_token: '123456' }), + }), + }) + ); + }); + + it('should return raw response when organizations is undefined', async () => { + mockHttps.mockResolvedValue({ + status: 200, + data: { user: { uid: 'u1' } }, + }); + + const result = await authService.login(createReq() as any); + + expect(result.status).toBe(200); + expect(result.data).toEqual({ user: { uid: 'u1' } }); + }); + }); + + describe('requestSms', () => { + const createReq = (body: any = {}) => ({ + body: { + email: 'test@example.com', + password: 'password123', + region: 'NA', + ...body, + }, + }); + + it('should return success response on successful SMS request', async () => { + mockHttps.mockResolvedValue({ + status: 200, + data: { message: 'SMS sent' }, + }); + + const result = await authService.requestSms(createReq() as any); + + expect(result.status).toBe(200); + expect(result.data.message).toBe('SMS sent'); + }); + + it('should return error data when CS API returns error', async () => { + mockHttps.mockRejectedValue({ + response: { data: { error_message: 'Rate limited' }, status: 429 }, + }); + + const result = await authService.requestSms(createReq() as any); + + expect(result.status).toBe(429); + }); + + it('should throw InternalServerError on unexpected exception', async () => { + mockHttps.mockImplementation(() => { + throw new Error('Unexpected'); + }); + + await expect(authService.requestSms(createReq() as any)).rejects.toThrow(); + }); + }); +}); diff --git a/api/tests/unit/services/contentMapper.service.test.ts b/api/tests/unit/services/contentMapper.service.test.ts new file mode 100644 index 000000000..d48502d5f --- /dev/null +++ b/api/tests/unit/services/contentMapper.service.test.ts @@ -0,0 +1,820 @@ +import { describe, it, expect, vi, beforeEach } from 'vitest'; + +const { + mockHttps, + mockGetAuthToken, + mockGetProjectUtil, + mockFetchAllPaginatedData, + mockProjectRead, + mockProjectUpdate, + mockProjectWrite, + mockContentTypesMapperRead, + mockContentTypesMapperUpdate, + mockFieldMapperRead, + mockFieldMapperUpdate, + mockUuidv4, + mockFsPromises, +} = vi.hoisted(() => ({ + mockHttps: vi.fn(), + mockGetAuthToken: vi.fn(), + mockGetProjectUtil: vi.fn(), + mockFetchAllPaginatedData: vi.fn(), + mockProjectRead: vi.fn(), + mockProjectUpdate: vi.fn(), + mockProjectWrite: vi.fn(), + mockContentTypesMapperRead: vi.fn(), + mockContentTypesMapperUpdate: vi.fn(), + mockFieldMapperRead: vi.fn(), + mockFieldMapperUpdate: vi.fn(), + mockUuidv4: vi.fn(() => 'uuid-123'), + mockFsPromises: { lstat: vi.fn(), readFile: vi.fn(), realpath: vi.fn() }, +})); + +vi.mock('../../../src/utils/https.utils.js', () => ({ default: mockHttps })); +vi.mock('../../../src/utils/auth.utils.js', () => ({ default: mockGetAuthToken })); +vi.mock('../../../src/utils/get-project.utils.js', () => ({ default: mockGetProjectUtil })); +vi.mock('../../../src/utils/pagination.utils.js', () => ({ default: mockFetchAllPaginatedData })); +vi.mock('../../../src/utils/logger.js', () => ({ + default: { error: vi.fn(), info: vi.fn(), warn: vi.fn() }, +})); +vi.mock('../../../src/config/index.js', () => ({ + config: { CS_API: { NA: 'https://api.contentstack.io/v3' } }, +})); +vi.mock('uuid', () => ({ v4: mockUuidv4 })); + +vi.mock('../../../src/models/project-lowdb.js', () => { + const mockChainGet = vi.fn(); + return { + default: { + read: mockProjectRead, + update: mockProjectUpdate, + write: mockProjectWrite, + chain: { get: mockChainGet }, + data: { projects: [] }, + }, + }; +}); + +vi.mock('../../../src/models/contentTypesMapper-lowdb.js', () => { + const mockChainGet = vi.fn(); + return { + default: { + read: mockContentTypesMapperRead, + update: mockContentTypesMapperUpdate, + write: vi.fn(), + chain: { get: mockChainGet }, + data: { ContentTypesMappers: [] }, + }, + ContentTypesMapper: {}, + }; +}); + +vi.mock('../../../src/models/FieldMapper.js', () => { + const mockChainGet = vi.fn(); + return { + default: { + read: mockFieldMapperRead, + update: mockFieldMapperUpdate, + write: vi.fn(), + chain: { get: mockChainGet }, + data: { field_mapper: [] }, + }, + }; +}); + +vi.mock('fs', () => ({ + default: { promises: mockFsPromises }, + promises: mockFsPromises, +})); + +import { contentMapperService } from '../../../src/services/contentMapper.service.js'; +import ProjectModelLowdb from '../../../src/models/project-lowdb.js'; +import ContentTypesMapperModelLowdb from '../../../src/models/contentTypesMapper-lowdb.js'; +import FieldMapperModel from '../../../src/models/FieldMapper.js'; + +const createChain = (opts: { + find?: unknown; + findIndex?: number; + value?: unknown; +}) => { + const findValue = opts.find !== undefined ? opts.find : null; + const findIndexValue = opts.findIndex !== undefined ? opts.findIndex : -1; + return { + find: vi.fn().mockReturnValue({ value: vi.fn().mockReturnValue(findValue) }), + findIndex: vi.fn().mockReturnValue({ value: vi.fn().mockReturnValue(findIndexValue) }), + filter: vi.fn().mockReturnValue({ value: vi.fn().mockReturnValue([]) }), + }; +}; + +describe('contentMapper.service', () => { + beforeEach(() => { + vi.clearAllMocks(); + mockGetAuthToken.mockResolvedValue('cs-auth-token'); + mockProjectRead.mockResolvedValue(undefined); + mockContentTypesMapperRead.mockResolvedValue(undefined); + mockFieldMapperRead.mockResolvedValue(undefined); + mockProjectUpdate.mockImplementation(async (fn: (d: any) => void) => { + const data = ProjectModelLowdb.data as any; + if (!data.projects) data.projects = []; + while (data.projects.length < 2) data.projects.push({}); + fn(data); + }); + mockContentTypesMapperUpdate.mockImplementation(async (fn: (d: any) => void) => { + const data = ContentTypesMapperModelLowdb.data as any; + if (!data.ContentTypesMappers) data.ContentTypesMappers = []; + while (data.ContentTypesMappers.length < 2) data.ContentTypesMappers.push({}); + fn(data); + }); + mockFieldMapperUpdate.mockImplementation(async (fn: (d: any) => void) => { + const data = FieldMapperModel.data as any; + if (!data.field_mapper) data.field_mapper = []; + fn(data); + }); + mockFetchAllPaginatedData.mockResolvedValue([]); + ProjectModelLowdb.data = { projects: [] }; + ContentTypesMapperModelLowdb.data = { ContentTypesMappers: [] }; + FieldMapperModel.data = { field_mapper: [] }; + (ProjectModelLowdb.chain.get as ReturnType).mockReturnValue(createChain({ find: null, findIndex: -1 })); + (ContentTypesMapperModelLowdb.chain.get as ReturnType).mockReturnValue(createChain({ find: null, findIndex: -1 })); + (FieldMapperModel.chain.get as ReturnType).mockReturnValue(createChain({ find: null, findIndex: -1 })); + }); + + describe('putTestData', () => { + it('should throw BadRequestError when contentTypes is not an array', async () => { + const req = { + params: { projectId: 'proj-1' }, + body: { contentTypes: 'invalid' }, + } as any; + + await expect(contentMapperService.putTestData(req)).rejects.toThrow('Invalid contentTypes: Expected an array.'); + }); + + it('should throw when project not found', async () => { + const req = { + params: { projectId: 'proj-999' }, + body: { + contentTypes: [ + { id: 'ct-1', otherCmsTitle: 'Blog', fieldMapping: [] }, + ], + }, + } as any; + + (ProjectModelLowdb.chain.get as ReturnType).mockReturnValue( + createChain({ findIndex: -1, find: null }) + ); + + await expect(contentMapperService.putTestData(req)).rejects.toThrow(); + }); + + it('should create content mappers successfully', async () => { + const project = { id: 'proj-1', content_mapper: [], destination_stack_id: 'stack-1' }; + ProjectModelLowdb.data.projects = [project]; + mockProjectWrite.mockResolvedValue(undefined); + + (ProjectModelLowdb.chain.get as ReturnType) + .mockReturnValueOnce(createChain({ findIndex: 0 })) + .mockReturnValueOnce(createChain({ find: project })); + + const req = { + params: { projectId: 'proj-1' }, + body: { + contentTypes: [ + { id: 'ct-1', otherCmsTitle: 'Blog', fieldMapping: [{ id: 'f1', otherCmsField: 'title' }] }, + ], + }, + } as any; + + const result = await contentMapperService.putTestData(req); + + expect(result.status).toBe(200); + expect(result.data).toBeDefined(); + }); + }); + + describe('getContentTypes', () => { + it('should throw when project not found', async () => { + const req = { + params: { projectId: 'proj-1', skip: 0, limit: 10 }, + } as any; + + (ProjectModelLowdb.chain.get as ReturnType).mockReturnValue( + createChain({ find: null }) + ); + + await expect(contentMapperService.getContentTypes(req)).rejects.toThrow('Sorry, the requested project does not exists.'); + }); + + it('should return content types when project has content mappers', async () => { + const project = { id: 'proj-1', content_mapper: ['ct-1'] }; + const contentMapper = { id: 'ct-1', projectId: 'proj-1', otherCmsTitle: 'Blog' }; + + (ProjectModelLowdb.chain.get as ReturnType).mockReturnValue(createChain({ find: project })); + (ContentTypesMapperModelLowdb.chain.get as ReturnType).mockReturnValue( + createChain({ find: contentMapper }) + ); + + const req = { + params: { projectId: 'proj-1', skip: 0, limit: 10 }, + } as any; + + const result = await contentMapperService.getContentTypes(req); + + expect(result.status).toBe(200); + expect(result.count).toBe(1); + expect(result.contentTypes).toHaveLength(1); + }); + + it('should filter by search when searchText provided', async () => { + const project = { id: 'proj-1', content_mapper: ['ct-1'] }; + const contentMapper = { id: 'ct-1', projectId: 'proj-1', otherCmsTitle: 'Blog' }; + + (ProjectModelLowdb.chain.get as ReturnType).mockReturnValue(createChain({ find: project })); + (ContentTypesMapperModelLowdb.chain.get as ReturnType).mockReturnValue( + createChain({ find: contentMapper }) + ); + + const req = { + params: { projectId: 'proj-1', skip: 0, limit: 10, searchText: 'blog' }, + } as any; + + const result = await contentMapperService.getContentTypes(req); + + expect(result.status).toBe(200); + expect(result.contentTypes).toBeDefined(); + }); + }); + + describe('getFieldMapping', () => { + it('should throw when content type not found', async () => { + (ContentTypesMapperModelLowdb.chain.get as ReturnType).mockReturnValue( + createChain({ find: null }) + ); + + const req = { + params: { projectId: 'proj-1', contentTypeId: 'ct-1', skip: 0, limit: 10 }, + } as any; + + await expect(contentMapperService.getFieldMapping(req)).rejects.toThrow('ContentType does not exist'); + }); + + it('should return field mapping when content type exists', async () => { + const contentType = { id: 'ct-1', projectId: 'proj-1', fieldMapping: ['f1'] }; + const fieldData = { id: 'f1', otherCmsField: 'title', contentstackField: 'title' }; + + (ContentTypesMapperModelLowdb.chain.get as ReturnType) + .mockReturnValue(createChain({ find: contentType })); + + (FieldMapperModel.chain.get as ReturnType) + .mockReturnValue(createChain({ find: fieldData })); + + const req = { + params: { projectId: 'proj-1', contentTypeId: 'ct-1', skip: 0, limit: 10 }, + } as any; + + const result = await contentMapperService.getFieldMapping(req); + + expect(result.status).toBe(200); + expect(result.fieldMapping).toBeDefined(); + }); + + it('should filter by search when searchText provided', async () => { + const contentType = { id: 'ct-1', projectId: 'proj-1', fieldMapping: ['f1'] }; + const fieldData = { id: 'f1', otherCmsField: 'Title', contentstackField: 'title' }; + + (ContentTypesMapperModelLowdb.chain.get as ReturnType) + .mockReturnValue(createChain({ find: contentType })); + (FieldMapperModel.chain.get as ReturnType) + .mockReturnValue(createChain({ find: fieldData })); + + const req = { + params: { projectId: 'proj-1', contentTypeId: 'ct-1', skip: 0, limit: 10, searchText: 'title' }, + } as any; + + const result = await contentMapperService.getFieldMapping(req); + expect(result.status).toBe(200); + }); + }); + + describe('getExistingContentTypes', () => { + it('should return content types successfully', async () => { + const project = { id: 'proj-1', destination_stack_id: 'stack-1' }; + (ProjectModelLowdb.chain.get as ReturnType).mockReturnValue( + createChain({ find: project }) + ); + mockFetchAllPaginatedData.mockResolvedValue([ + { uid: 'ct-1', title: 'Blog', schema: {} }, + ]); + + const req = { + params: { projectId: 'proj-1' }, + body: { token_payload: { region: 'NA', user_id: 'user-1' } }, + } as any; + + const result = await contentMapperService.getExistingContentTypes(req); + + expect(result.contentTypes).toHaveLength(1); + expect(result.contentTypes[0].uid).toBe('ct-1'); + }); + + it('should fetch selected content type when contentTypeUid provided', async () => { + const project = { id: 'proj-1', destination_stack_id: 'stack-1' }; + (ProjectModelLowdb.chain.get as ReturnType).mockReturnValue( + createChain({ find: project }) + ); + mockFetchAllPaginatedData.mockResolvedValue([]); + mockHttps.mockResolvedValue({ + data: { content_type: { uid: 'ct-1', title: 'Blog', schema: {} } }, + }); + + const req = { + params: { projectId: 'proj-1', contentTypeUid: 'ct-1' }, + body: { token_payload: { region: 'NA', user_id: 'user-1' } }, + } as any; + + const result = await contentMapperService.getExistingContentTypes(req); + + expect(result.contentTypes).toBeDefined(); + expect(result.selectedContentType).toBeDefined(); + }); + }); + + describe('getExistingGlobalFields', () => { + it('should return 400 when projectId is missing', async () => { + const req = { + params: {}, + body: { token_payload: { region: 'NA', user_id: 'user-1' } }, + } as any; + + const result = await contentMapperService.getExistingGlobalFields(req); + + expect(result.status).toBe(400); + expect(result.data).toContain('Project ID'); + }); + + it('should return 400 when token payload is missing', async () => { + const req = { + params: { projectId: 'proj-1' }, + body: {}, + } as any; + + const result = await contentMapperService.getExistingGlobalFields(req); + + expect(result.status).toBe(400); + expect(result.data).toContain('Token payload'); + }); + + it('should return 404 when project not found', async () => { + (ProjectModelLowdb.chain.get as ReturnType).mockReturnValue( + createChain({ find: null }) + ); + + const req = { + params: { projectId: 'proj-1' }, + body: { token_payload: { region: 'NA', user_id: 'user-1' } }, + } as any; + + const result = await contentMapperService.getExistingGlobalFields(req); + + expect(result.status).toBe(404); + expect(result.data).toBe('Project not found'); + }); + + it('should return 400 when stackId is missing', async () => { + const project = { id: 'proj-1', destination_stack_id: null }; + (ProjectModelLowdb.chain.get as ReturnType).mockReturnValue( + createChain({ find: project }) + ); + + const req = { + params: { projectId: 'proj-1' }, + body: { token_payload: { region: 'NA', user_id: 'user-1' } }, + } as any; + + const result = await contentMapperService.getExistingGlobalFields(req); + + expect(result.status).toBe(400); + expect(result.data).toContain('Destination stack ID'); + }); + + it('should return global fields successfully', async () => { + const project = { id: 'proj-1', destination_stack_id: 'stack-1' }; + (ProjectModelLowdb.chain.get as ReturnType).mockReturnValue( + createChain({ find: project }) + ); + mockFetchAllPaginatedData.mockResolvedValue([ + { uid: 'gf-1', title: 'SEO', schema: {} }, + ]); + + const req = { + params: { projectId: 'proj-1' }, + body: { token_payload: { region: 'NA', user_id: 'user-1' } }, + } as any; + + const result = await contentMapperService.getExistingGlobalFields(req); + + expect(result.globalFields).toHaveLength(1); + expect(result.globalFields[0].uid).toBe('gf-1'); + }); + }); + + describe('updateContentType', () => { + it('should return 400 when status prevents update', async () => { + mockGetProjectUtil.mockResolvedValue(0); + ProjectModelLowdb.data.projects = [ + { status: 5, current_step: 2 }, + ]; + + const req = { + params: { orgId: 'org-1', projectId: 'proj-1', contentTypeId: 'ct-1' }, + body: { + token_payload: { region: 'NA', user_id: 'user-1' }, + contentTypeData: { otherCmsTitle: 'Blog' }, + }, + } as any; + + const result = await contentMapperService.updateContentType(req); + + expect(result.status).toBe(400); + expect(result.message).toContain('content mapping is restricted'); + }); + + it('should return 400 when contentTypeData is empty', async () => { + mockGetProjectUtil.mockResolvedValue(0); + ProjectModelLowdb.data.projects = [ + { status: 1, current_step: 3 }, + ]; + + const req = { + params: { orgId: 'org-1', projectId: 'proj-1', contentTypeId: 'ct-1' }, + body: { + token_payload: { region: 'NA', user_id: 'user-1' }, + contentTypeData: null, + }, + } as any; + + const result = await contentMapperService.updateContentType(req); + + expect(result.status).toBe(400); + expect(result.message).toContain('valid ContentType'); + }); + + it('should return 400 when field has invalid contentstackFieldType', async () => { + mockGetProjectUtil.mockResolvedValue(0); + ProjectModelLowdb.data.projects = [{ status: 1, current_step: 3 }]; + ContentTypesMapperModelLowdb.data.ContentTypesMappers = [{ id: 'ct-1', status: 1 }]; + + (ContentTypesMapperModelLowdb.chain.get as ReturnType) + .mockReturnValueOnce(createChain({ findIndex: 0 })) + .mockReturnValue(createChain({ find: { id: 'ct-1', projectId: 'proj-1', status: 1 } })); + + const req = { + params: { orgId: 'org-1', projectId: 'proj-1', contentTypeId: 'ct-1' }, + body: { + token_payload: { region: 'NA', user_id: 'user-1' }, + contentTypeData: { + otherCmsTitle: 'Blog', + fieldMapping: [{ id: 'f1', contentstackFieldType: '', contentstackFieldUid: '' }], + }, + }, + } as any; + + const result = await contentMapperService.updateContentType(req); + + expect(result.status).toBe(400); + }); + + it('should update content type successfully', async () => { + mockGetProjectUtil.mockResolvedValue(0); + ProjectModelLowdb.data.projects = [{ status: 1, current_step: 3 }]; + ContentTypesMapperModelLowdb.data.ContentTypesMappers = [{ id: 'ct-1', projectId: 'proj-1', status: 1 }]; + FieldMapperModel.data.field_mapper = [ + { id: 'f1', contentTypeId: 'ct-1', contentstackFieldType: 'text', contentstackFieldUid: 'f1' }, + ]; + + (ContentTypesMapperModelLowdb.chain.get as ReturnType) + .mockReturnValueOnce(createChain({ findIndex: 0 })) + .mockReturnValue(createChain({ find: { id: 'ct-1', projectId: 'proj-1', status: 1 } })); + + const req = { + params: { orgId: 'org-1', projectId: 'proj-1', contentTypeId: 'ct-1' }, + body: { + token_payload: { region: 'NA', user_id: 'user-1' }, + contentTypeData: { + otherCmsTitle: 'Blog', + contentstackTitle: 'Blog', + contentstackUid: 'ct-1', + fieldMapping: [ + { id: 'f1', contentTypeId: 'ct-1', contentstackFieldType: 'text', contentstackFieldUid: 'f1' }, + ], + }, + }, + } as any; + + const result = await contentMapperService.updateContentType(req); + + expect(result.status).toBe(200); + expect(result.data).toBeDefined(); + }); + }); + + describe('resetToInitialMapping', () => { + it('should throw when status prevents reset', async () => { + mockGetProjectUtil.mockResolvedValue(0); + ProjectModelLowdb.data.projects = [ + { status: 0, current_step: 2 }, + ]; + + const req = { + params: { orgId: 'org-1', projectId: 'proj-1', contentTypeId: 'ct-1' }, + body: { token_payload: { region: 'NA', user_id: 'user-1' } }, + } as any; + + await expect(contentMapperService.resetToInitialMapping(req)).rejects.toThrow( + 'Reseting the content mapping is restricted' + ); + }); + + it('should reset successfully', async () => { + mockGetProjectUtil.mockResolvedValue(0); + ProjectModelLowdb.data.projects = [{ status: 1, current_step: 3 }]; + const contentTypeData = { + id: 'ct-1', + projectId: 'proj-1', + fieldMapping: ['f1'], + }; + const fieldData = { + id: 'f1', + otherCmsField: 'title', + backupFieldUid: 'buid', + backupFieldType: 'text', + advanced: { initial: {} }, + }; + + ContentTypesMapperModelLowdb.data.ContentTypesMappers = [contentTypeData]; + FieldMapperModel.data.field_mapper = [fieldData]; + + (ContentTypesMapperModelLowdb.chain.get as ReturnType) + .mockReturnValueOnce(createChain({ find: contentTypeData })) + .mockReturnValueOnce(createChain({ findIndex: 0 })); + (FieldMapperModel.chain.get as ReturnType).mockReturnValue(createChain({ find: fieldData })); + + const req = { + params: { orgId: 'org-1', projectId: 'proj-1', contentTypeId: 'ct-1' }, + body: { token_payload: { region: 'NA', user_id: 'user-1' } }, + } as any; + + const result = await contentMapperService.resetToInitialMapping(req); + + expect(result.status).toBe(200); + expect(result.message).toContain('restored to its initial mapping'); + }); + }); + + describe('resetAllContentTypesMapping', () => { + it('should throw when content mapper is empty', async () => { + const project = { id: 'proj-1', content_mapper: [] }; + (ProjectModelLowdb.chain.get as ReturnType).mockReturnValue( + createChain({ find: project }) + ); + + await expect( + contentMapperService.resetAllContentTypesMapping('proj-1') + ).rejects.toThrow('content mapper id does not exists'); + }); + + it('should reset all content types successfully', async () => { + const project = { id: 'proj-1', content_mapper: ['ct-1'] }; + const contentType = { + id: 'ct-1', + projectId: 'proj-1', + fieldMapping: ['f1'], + }; + const fieldData = { id: 'f1', projectId: 'proj-1', backupFieldType: 'text' }; + + ContentTypesMapperModelLowdb.data.ContentTypesMappers = [{ ...contentType, contentstackTitle: 'Old' }]; + FieldMapperModel.data.field_mapper = [fieldData]; + + (ProjectModelLowdb.chain.get as ReturnType).mockReturnValue(createChain({ find: project })); + (ContentTypesMapperModelLowdb.chain.get as ReturnType) + .mockReturnValueOnce(createChain({ find: contentType })) + .mockReturnValueOnce(createChain({ findIndex: 0 })); + (FieldMapperModel.chain.get as ReturnType) + .mockReturnValueOnce(createChain({ find: fieldData })) + .mockReturnValueOnce(createChain({ findIndex: 0 })); + + const result = await contentMapperService.resetAllContentTypesMapping('proj-1'); + + expect(result).toEqual(project); + }); + }); + + describe('removeMapping', () => { + it('should throw when project not found', async () => { + (ProjectModelLowdb.chain.get as ReturnType).mockReturnValue( + createChain({ find: null }) + ); + + await expect(contentMapperService.removeMapping('proj-999')).rejects.toThrow( + 'Sorry, the requested project does not exists.' + ); + }); + + it('should remove mapping successfully', async () => { + const project = { id: 'proj-1', content_mapper: ['ct-1'] }; + const contentType = { id: 'ct-1', projectId: 'proj-1', fieldMapping: ['f1'] }; + + (ProjectModelLowdb.chain.get as ReturnType) + .mockReturnValueOnce(createChain({ find: project })) + .mockReturnValueOnce(createChain({ findIndex: 0 })); + (ContentTypesMapperModelLowdb.chain.get as ReturnType) + .mockReturnValueOnce(createChain({ find: contentType })) + .mockReturnValueOnce(createChain({ findIndex: 0 })); + (FieldMapperModel.chain.get as ReturnType).mockReturnValue( + createChain({ findIndex: 0 }) + ); + + const result = await contentMapperService.removeMapping('proj-1'); + + expect(result).toEqual(project); + }); + }); + + describe('removeContentMapper', () => { + it('should throw when project not found', async () => { + (ProjectModelLowdb.chain.get as ReturnType).mockReturnValue( + createChain({ find: null }) + ); + + const req = { params: { projectId: 'proj-999' } } as any; + + await expect(contentMapperService.removeContentMapper(req)).rejects.toThrow( + 'Sorry, the requested project does not exists.' + ); + }); + + it('should remove content mappers successfully', async () => { + const project = { id: 'proj-1', content_mapper: ['ct-1'] }; + const contentType = { id: 'ct-1', projectId: 'proj-1', fieldMapping: ['f1'] }; + + (ProjectModelLowdb.chain.get as ReturnType) + .mockReturnValueOnce(createChain({ find: project })) + .mockReturnValueOnce(createChain({ findIndex: 0 })); + (ContentTypesMapperModelLowdb.chain.get as ReturnType) + .mockReturnValueOnce(createChain({ find: contentType })) + .mockReturnValueOnce(createChain({ findIndex: 0 })); + (FieldMapperModel.chain.get as ReturnType).mockReturnValue( + createChain({ findIndex: 0 }) + ); + + const req = { params: { projectId: 'proj-1' } } as any; + + const result = await contentMapperService.removeContentMapper(req); + + expect(result).toEqual(project); + }); + }); + + describe('getSingleContentTypes', () => { + it('should return content type successfully', async () => { + const project = { id: 'proj-1', destination_stack_id: 'stack-1' }; + (ProjectModelLowdb.chain.get as ReturnType).mockReturnValue( + createChain({ find: project }) + ); + mockHttps.mockResolvedValue({ + data: { content_type: { title: 'Blog', uid: 'ct-1', schema: {} } }, + }); + + const req = { + params: { projectId: 'proj-1', contentTypeUid: 'ct-1' }, + body: { token_payload: { region: 'NA', user_id: 'user-1' } }, + } as any; + + const result = await contentMapperService.getSingleContentTypes(req); + + expect(result.title).toBe('Blog'); + expect(result.uid).toBe('ct-1'); + }); + + it('should return error when https fails', async () => { + const project = { id: 'proj-1', destination_stack_id: 'stack-1' }; + (ProjectModelLowdb.chain.get as ReturnType).mockReturnValue( + createChain({ find: project }) + ); + mockHttps.mockRejectedValue({ response: { data: 'Error', status: 404 } }); + + const req = { + params: { projectId: 'proj-1', contentTypeUid: 'ct-1' }, + body: { token_payload: { region: 'NA', user_id: 'user-1' } }, + } as any; + + const result = await contentMapperService.getSingleContentTypes(req); + + expect(result.status).toBe(404); + }); + }); + + describe('getSingleGlobalField', () => { + it('should return global field successfully', async () => { + const project = { id: 'proj-1', destination_stack_id: 'stack-1' }; + (ProjectModelLowdb.chain.get as ReturnType).mockReturnValue( + createChain({ find: project }) + ); + mockHttps.mockResolvedValue({ + data: { global_field: { title: 'SEO', uid: 'gf-1', schema: {} } }, + }); + + const req = { + params: { projectId: 'proj-1', globalFieldUid: 'gf-1' }, + body: { token_payload: { region: 'NA', user_id: 'user-1' } }, + } as any; + + const result = await contentMapperService.getSingleGlobalField(req); + + expect(result.title).toBe('SEO'); + expect(result.uid).toBe('gf-1'); + }); + + it('should return error when https fails', async () => { + const project = { id: 'proj-1', destination_stack_id: 'stack-1' }; + (ProjectModelLowdb.chain.get as ReturnType).mockReturnValue( + createChain({ find: project }) + ); + mockHttps.mockRejectedValue({ response: { data: 'Error', status: 500 } }); + + const req = { + params: { projectId: 'proj-1', globalFieldUid: 'gf-1' }, + body: { token_payload: { region: 'NA', user_id: 'user-1' } }, + } as any; + + const result = await contentMapperService.getSingleGlobalField(req); + + expect(result.status).toBe(500); + }); + }); + + describe('updateContentMapper', () => { + it('should update content mapper successfully', async () => { + mockGetProjectUtil.mockResolvedValue(0); + const project = { id: 'proj-1', mapperKeys: undefined }; + ProjectModelLowdb.data.projects = [project]; + + const req = { + params: { orgId: 'org-1', projectId: 'proj-1' }, + body: { + token_payload: { region: 'NA', user_id: 'user-1' }, + content_mapper: { key: 'value' }, + }, + } as any; + + const result = await contentMapperService.updateContentMapper(req); + + expect(result.status).toBe(200); + expect(result.data.message).toContain('content mapping updated'); + }); + }); + + describe('getExistingTaxonomies', () => { + it('should return source and destination taxonomies when project has taxonomies', async () => { + const project = { + id: 'proj-1', + destination_stack_id: 'stack-1', + taxonomies: [{ uid: 'tax-1', name: 'Category', description: '' }], + }; + (ProjectModelLowdb.chain.get as ReturnType).mockReturnValue( + createChain({ find: project }) + ); + mockFetchAllPaginatedData.mockResolvedValue([ + { uid: 'cs-tax-1', name: 'Category', description: '' }, + ]); + + const req = { + params: { projectId: 'proj-1' }, + body: { token_payload: { region: 'NA', user_id: 'user-1' } }, + } as any; + + const result = await contentMapperService.getExistingTaxonomies(req); + + expect(result.status).toBe(200); + expect(result.sourceTaxonomies).toHaveLength(1); + expect(result.sourceTaxonomies[0].uid).toBe('tax-1'); + expect(result.destinationTaxonomies).toHaveLength(1); + expect(result.destinationTaxonomies[0].uid).toBe('cs-tax-1'); + }); + + it('should return 404 when project not found', async () => { + (ProjectModelLowdb.chain.get as ReturnType).mockReturnValue( + createChain({ find: null }) + ); + + const req = { + params: { projectId: 'proj-999' }, + body: { token_payload: { region: 'NA', user_id: 'user-1' } }, + } as any; + + const result = await contentMapperService.getExistingTaxonomies(req); + + expect(result.status).toBe(404); + expect(result.data).toBe('Project not found'); + }); + }); +}); diff --git a/api/tests/unit/services/extension.service.test.ts b/api/tests/unit/services/extension.service.test.ts new file mode 100644 index 000000000..18ad83cf6 --- /dev/null +++ b/api/tests/unit/services/extension.service.test.ts @@ -0,0 +1,158 @@ +import { describe, it, expect, vi, beforeEach } from 'vitest'; + +const { + mockFsPromises, + mockPathJoin, +} = vi.hoisted(() => ({ + mockFsPromises: { + access: vi.fn(), + mkdir: vi.fn(), + writeFile: vi.fn(), + readFile: vi.fn(), + }, + mockPathJoin: vi.fn((...args: string[]) => args.join('/')), +})); + +vi.mock('fs', () => ({ + default: { promises: mockFsPromises }, +})); +vi.mock('path', () => ({ default: { join: mockPathJoin } })); +vi.mock('../../../src/constants/index.js', () => ({ + MIGRATION_DATA_CONFIG: { + DATA: './cmsMigrationData', + EXTENSION_APPS_DIR_NAME: 'extensions', + EXTENSION_APPS_FILE_NAME: 'extensions.json', + CUSTOM_MAPPER_FILE_NAME: 'custmon-mapper.json', + }, + LIST_EXTENSION_UID: 'blt0000000000000000', +})); + +vi.stubGlobal('process', { + ...process, + cwd: vi.fn(() => '/test/cwd'), +}); + +import { extensionService } from '../../../src/services/extension.service.js'; + +describe('extension.service', () => { + beforeEach(() => { + vi.clearAllMocks(); + mockFsPromises.access.mockResolvedValue(undefined); + mockFsPromises.mkdir.mockResolvedValue(undefined); + mockFsPromises.writeFile.mockResolvedValue(undefined); + mockFsPromises.readFile.mockResolvedValue(undefined); + mockPathJoin.mockImplementation((...args: string[]) => args.join('/')); + }); + + describe('createExtension', () => { + it('should create extension file when custom mapper has LIST_EXTENSION_UID', async () => { + const customMapperContent = JSON.stringify([ + { extensionUid: 'blt0000000000000000' }, + ]); + mockFsPromises.readFile.mockResolvedValue(customMapperContent); + + await extensionService.createExtension({ + destinationStackId: 'stack-123', + }); + + expect(mockFsPromises.readFile).toHaveBeenCalled(); + expect(mockFsPromises.writeFile).toHaveBeenCalledWith( + expect.stringContaining('extensions'), + expect.stringContaining('blt0000000000000000') + ); + }); + + it('should create extension file with unique extension UIDs only', async () => { + const customMapperContent = JSON.stringify([ + { extensionUid: 'blt0000000000000000' }, + { extensionUid: 'blt0000000000000000' }, + ]); + mockFsPromises.readFile.mockResolvedValue(customMapperContent); + + await extensionService.createExtension({ + destinationStackId: 'stack-456', + }); + + expect(mockFsPromises.writeFile).toHaveBeenCalled(); + const writeCall = mockFsPromises.writeFile.mock.calls[0]; + const writtenData = JSON.parse(writeCall[1]); + expect(Object.keys(writtenData)).toHaveLength(1); + expect(writtenData['blt0000000000000000']).toBeDefined(); + }); + + it('should create directory when it does not exist', async () => { + mockFsPromises.access.mockRejectedValue(new Error('ENOENT')); + mockFsPromises.readFile.mockResolvedValue( + JSON.stringify([{ extensionUid: 'blt0000000000000000' }]) + ); + + await extensionService.createExtension({ + destinationStackId: 'stack-789', + }); + + expect(mockFsPromises.mkdir).toHaveBeenCalledWith( + expect.any(String), + { recursive: true } + ); + }); + + it('should not write file when custom mapper is undefined (file not found)', async () => { + mockFsPromises.readFile.mockRejectedValue(new Error('ENOENT')); + + await extensionService.createExtension({ + destinationStackId: 'stack-999', + }); + + expect(mockFsPromises.writeFile).not.toHaveBeenCalled(); + }); + + it('should skip extensions not matching LIST_EXTENSION_UID', async () => { + const customMapperContent = JSON.stringify([ + { extensionUid: 'unknown-extension-uid' }, + ]); + mockFsPromises.readFile.mockResolvedValue(customMapperContent); + + await extensionService.createExtension({ + destinationStackId: 'stack-abc', + }); + + const writeCall = mockFsPromises.writeFile.mock.calls[0]; + if (writeCall) { + const writtenData = JSON.parse(writeCall[1]); + expect(Object.keys(writtenData)).toHaveLength(0); + } + }); + + it('should handle writeFile error gracefully', async () => { + const consoleSpy = vi.spyOn(console, 'error').mockImplementation(() => {}); + mockFsPromises.readFile.mockResolvedValue( + JSON.stringify([{ extensionUid: 'blt0000000000000000' }]) + ); + mockFsPromises.writeFile.mockRejectedValue(new Error('Write failed')); + + await extensionService.createExtension({ + destinationStackId: 'stack-err', + }); + + expect(consoleSpy).toHaveBeenCalled(); + consoleSpy.mockRestore(); + }); + + it('should handle mkdir error gracefully', async () => { + const consoleSpy = vi.spyOn(console, 'error').mockImplementation(() => {}); + mockFsPromises.access.mockRejectedValue(new Error('ENOENT')); + mockFsPromises.mkdir.mockRejectedValue(new Error('Mkdir failed')); + mockFsPromises.readFile.mockResolvedValue( + JSON.stringify([{ extensionUid: 'blt0000000000000000' }]) + ); + + await extensionService.createExtension({ + destinationStackId: 'stack-mkdir-err', + }); + + expect(consoleSpy).toHaveBeenCalled(); + expect(mockFsPromises.writeFile).not.toHaveBeenCalled(); + consoleSpy.mockRestore(); + }); + }); +}); diff --git a/api/tests/unit/services/globalField.service.test.ts b/api/tests/unit/services/globalField.service.test.ts new file mode 100644 index 000000000..c865e5dad --- /dev/null +++ b/api/tests/unit/services/globalField.service.test.ts @@ -0,0 +1,172 @@ +import { describe, it, expect, vi, beforeEach } from 'vitest'; + +const { + mockHttps, + mockGetAuthToken, + mockFsExistsSync, + mockFsMkdirSync, + mockFsPromisesReadFile, + mockFsPromisesMkdir, + mockFsPromisesWriteFile, + mockPathJoin, + mockPathDirname, +} = vi.hoisted(() => ({ + mockHttps: vi.fn(), + mockGetAuthToken: vi.fn(), + mockFsExistsSync: vi.fn(), + mockFsMkdirSync: vi.fn(), + mockFsPromisesReadFile: vi.fn(), + mockFsPromisesMkdir: vi.fn(), + mockFsPromisesWriteFile: vi.fn(), + mockPathJoin: vi.fn((...args: string[]) => args.join('/')), + mockPathDirname: vi.fn((p: string) => p.split('/').slice(0, -1).join('/')), +})); + +vi.mock('../../../src/utils/https.utils.js', () => ({ default: mockHttps })); +vi.mock('../../../src/utils/auth.utils.js', () => ({ default: mockGetAuthToken })); +vi.mock('../../../src/utils/logger.js', () => ({ + default: { error: vi.fn(), info: vi.fn(), warn: vi.fn() }, +})); +vi.mock('../../../src/config/index.js', () => ({ + config: { CS_API: { NA: 'https://api.contentstack.io/v3', EU: 'https://eu-api.contentstack.com/v3' } }, +})); +vi.mock('fs', () => ({ + default: { + existsSync: mockFsExistsSync, + mkdirSync: mockFsMkdirSync, + promises: { + readFile: mockFsPromisesReadFile, + mkdir: mockFsPromisesMkdir, + writeFile: mockFsPromisesWriteFile, + }, + }, +})); +vi.mock('path', () => ({ + default: { join: mockPathJoin, dirname: mockPathDirname }, +})); + +import { globalFieldServie } from '../../../src/services/globalField.service.js'; + +describe('globalField.service', () => { + beforeEach(() => { + vi.clearAllMocks(); + mockGetAuthToken.mockResolvedValue('cs-auth-token'); + mockHttps.mockResolvedValue({ + status: 200, + data: { + global_fields: [ + { uid: 'gf-1', title: 'SEO', schema: {} }, + { uid: 'gf-2', title: 'Meta', schema: {} }, + ], + }, + }); + mockFsExistsSync.mockReturnValue(false); + mockFsPromisesReadFile.mockResolvedValue('[]'); + mockFsPromisesMkdir.mockResolvedValue(undefined); + mockFsPromisesWriteFile.mockResolvedValue(undefined); + }); + + describe('createGlobalField', () => { + it('should fetch global fields from CS API and write to file', async () => { + await globalFieldServie.createGlobalField({ + region: 'NA', + user_id: 'user-123', + stackId: 'stack-abc', + current_test_stack_id: 'test-stack-1', + }); + + expect(mockGetAuthToken).toHaveBeenCalledWith('NA', 'user-123'); + expect(mockHttps).toHaveBeenCalledWith( + expect.objectContaining({ + method: 'GET', + url: expect.stringContaining('global_fields'), + headers: expect.objectContaining({ + api_key: 'stack-abc', + authtoken: 'cs-auth-token', + }), + }) + ); + expect(mockFsPromisesWriteFile).toHaveBeenCalled(); + const [, writtenData] = mockFsPromisesWriteFile.mock.calls[0]; + const parsed = JSON.parse(writtenData); + expect(parsed).toHaveLength(2); + expect(parsed[0].uid).toBe('gf-1'); + }); + + it('should merge new global fields with existing file data', async () => { + mockFsExistsSync.mockReturnValue(true); + mockFsPromisesReadFile.mockResolvedValue( + JSON.stringify([{ uid: 'gf-existing', title: 'Existing' }]) + ); + + await globalFieldServie.createGlobalField({ + region: 'NA', + user_id: 'user-123', + stackId: 'stack-abc', + current_test_stack_id: 'test-stack-2', + }); + + const [, writtenData] = mockFsPromisesWriteFile.mock.calls[0]; + const parsed = JSON.parse(writtenData); + expect(parsed).toHaveLength(3); + const uids = parsed.map((gf: { uid: string }) => gf.uid); + expect(uids).toContain('gf-1'); + expect(uids).toContain('gf-2'); + expect(uids).toContain('gf-existing'); + }); + + it('should create directory when it does not exist', async () => { + mockFsExistsSync.mockReturnValue(false); + + await globalFieldServie.createGlobalField({ + region: 'NA', + user_id: 'user-123', + stackId: 'stack-abc', + current_test_stack_id: 'test-stack-3', + }); + + expect(mockFsMkdirSync).toHaveBeenCalledWith(expect.any(String), { recursive: true }); + }); + + it('should return error object when writeFile fails', async () => { + mockFsPromisesWriteFile.mockRejectedValue(new Error('Write failed')); + + const result = await globalFieldServie.createGlobalField({ + region: 'NA', + user_id: 'user-123', + stackId: 'stack-abc', + current_test_stack_id: 'test-stack-4', + }); + + expect(result).toEqual({ + data: expect.any(Error), + status: 500, + }); + }); + + it('should handle invalid JSON in existing file', async () => { + mockFsExistsSync.mockReturnValue(true); + mockFsPromisesReadFile.mockResolvedValue('invalid json {'); + + await globalFieldServie.createGlobalField({ + region: 'NA', + user_id: 'user-123', + stackId: 'stack-abc', + current_test_stack_id: 'test-stack-5', + }); + + expect(mockFsPromisesWriteFile).toHaveBeenCalled(); + }); + + it('should use current_test_stack_id when provided', async () => { + await globalFieldServie.createGlobalField({ + region: 'EU', + user_id: 'user-456', + stackId: 'stack-eu', + current_test_stack_id: 'eu-test-stack', + }); + + expect(mockFsPromisesWriteFile).toHaveBeenCalled(); + }); + }); +}); diff --git a/api/tests/unit/services/marketplace.service.test.ts b/api/tests/unit/services/marketplace.service.test.ts new file mode 100644 index 000000000..6e873f1ae --- /dev/null +++ b/api/tests/unit/services/marketplace.service.test.ts @@ -0,0 +1,159 @@ +import { describe, it, expect, vi, beforeEach } from 'vitest'; + +const { + mockGetAuthToken, + mockGetAppManifestAndAppConfig, + mockFsPromisesAccess, + mockFsPromisesMkdir, + mockFsPromisesWriteFile, + mockFsPromisesReadFile, + mockPathJoin, +} = vi.hoisted(() => ({ + mockGetAuthToken: vi.fn(), + mockGetAppManifestAndAppConfig: vi.fn(), + mockFsPromisesAccess: vi.fn(), + mockFsPromisesMkdir: vi.fn(), + mockFsPromisesWriteFile: vi.fn(), + mockFsPromisesReadFile: vi.fn(), + mockPathJoin: vi.fn((...args: string[]) => args.join('/')), +})); + +vi.mock('../../../src/utils/auth.utils.js', () => ({ default: mockGetAuthToken })); +vi.mock('../../../src/utils/market-app.utils.js', () => ({ + getAppManifestAndAppConfig: mockGetAppManifestAndAppConfig, +})); +vi.mock('fs', () => ({ + default: { + promises: { + access: mockFsPromisesAccess, + mkdir: mockFsPromisesMkdir, + writeFile: mockFsPromisesWriteFile, + readFile: mockFsPromisesReadFile, + }, + }, +})); +vi.mock('path', () => ({ default: { join: mockPathJoin } })); +vi.mock('../../../src/constants/index.js', () => ({ + MIGRATION_DATA_CONFIG: { + DATA: './cmsMigrationData', + EXTENSIONS_MAPPER_DIR_NAME: 'extension-mapper.json', + MARKETPLACE_APPS_DIR_NAME: 'marketplace_apps', + MARKETPLACE_APPS_FILE_NAME: 'marketplace_apps.json', + }, + KEYTOREMOVE: ['update', 'fetch', 'delete'], +})); + +vi.stubGlobal('process', { ...process, cwd: vi.fn(() => '/test/cwd') }); + +import { marketPlaceAppService } from '../../../src/services/marketplace.service.js'; + +describe('marketplace.service', () => { + beforeEach(() => { + vi.clearAllMocks(); + mockGetAuthToken.mockResolvedValue('cs-auth-token'); + mockFsPromisesAccess.mockResolvedValue(undefined); + mockFsPromisesReadFile.mockResolvedValue( + JSON.stringify([ + { appUid: 'app-1', extensionUid: 'ext-1-field' }, + { appUid: 'app-1', extensionUid: 'ext-2-widget' }, + ]) + ); + mockGetAppManifestAndAppConfig.mockResolvedValue({ + uid: 'app-1', + name: 'Test App', + ui_location: { + locations: [ + { type: 'field', meta: [{ extension_uid: 'ext-1' }] }, + { type: 'widget', meta: [{ extension_uid: 'ext-2' }] }, + { type: 'cs.cm.stack.config', meta: [{}] }, + ], + }, + }); + }); + + describe('createAppManifest', () => { + it('should create app manifest and write to file', async () => { + await marketPlaceAppService.createAppManifest({ + destinationStackId: 'stack-123', + region: 'NA', + userId: 'user-1', + orgId: 'org-1', + }); + + expect(mockGetAuthToken).toHaveBeenCalledWith('NA', 'user-1'); + expect(mockFsPromisesReadFile).toHaveBeenCalled(); + expect(mockGetAppManifestAndAppConfig).toHaveBeenCalledWith( + expect.objectContaining({ + organizationUid: 'org-1', + authtoken: 'cs-auth-token', + region: 'NA', + manifestUid: 'app-1', + }) + ); + expect(mockFsPromisesWriteFile).toHaveBeenCalled(); + }); + + it('should create directory when it does not exist', async () => { + mockFsPromisesAccess.mockRejectedValue(new Error('ENOENT')); + + await marketPlaceAppService.createAppManifest({ + destinationStackId: 'stack-456', + region: 'NA', + userId: 'user-2', + orgId: 'org-2', + }); + + expect(mockFsPromisesMkdir).toHaveBeenCalledWith(expect.any(String), { recursive: true }); + }); + + it('should not process when extension mapper file is not found', async () => { + mockFsPromisesReadFile.mockRejectedValue(new Error('ENOENT')); + + await marketPlaceAppService.createAppManifest({ + destinationStackId: 'stack-789', + region: 'NA', + userId: 'user-3', + orgId: 'org-3', + }); + + expect(mockGetAppManifestAndAppConfig).not.toHaveBeenCalled(); + expect(mockFsPromisesWriteFile).not.toHaveBeenCalled(); + }); + + it('should remove KEYTOREMOVE keys from manifest data', async () => { + mockGetAppManifestAndAppConfig.mockResolvedValue({ + uid: 'app-1', + update: 'should-be-removed', + fetch: 'should-be-removed', + ui_location: { locations: [{ type: 'field', meta: [{}] }] }, + }); + + await marketPlaceAppService.createAppManifest({ + destinationStackId: 'stack-abc', + region: 'NA', + userId: 'user-4', + orgId: 'org-4', + }); + + const [, writtenData] = mockFsPromisesWriteFile.mock.calls[0]; + const parsed = JSON.parse(writtenData); + expect(parsed[0]).not.toHaveProperty('update'); + expect(parsed[0]).not.toHaveProperty('fetch'); + }); + + it('should handle writeFile error gracefully', async () => { + const consoleSpy = vi.spyOn(console, 'error').mockImplementation(() => {}); + mockFsPromisesWriteFile.mockRejectedValue(new Error('Write failed')); + + await marketPlaceAppService.createAppManifest({ + destinationStackId: 'stack-err', + region: 'NA', + userId: 'user-5', + orgId: 'org-5', + }); + + expect(consoleSpy).toHaveBeenCalled(); + consoleSpy.mockRestore(); + }); + }); +}); diff --git a/api/tests/unit/services/migration.service.test.ts b/api/tests/unit/services/migration.service.test.ts new file mode 100644 index 000000000..a5ebf2a72 --- /dev/null +++ b/api/tests/unit/services/migration.service.test.ts @@ -0,0 +1,1068 @@ +import { describe, it, expect, vi, beforeEach } from 'vitest'; + +const { + mockHttps, + mockGetAuthToken, + mockProjectRead, + mockProjectUpdate, + mockChainGet, + mockProjects, + mockFsExistsSync, + mockFsReadDirSync, + mockFsPromisesReadFile, + mockFsPromisesAppendFile, + mockFsPromisesRealpath, +} = vi.hoisted(() => { + const projects = [ + { + id: 'proj-1', + org_id: 'org-123', + test_stacks: [] as any[], + stackDetails: { master_locale: 'en-us' }, + legacy_cms: { cms: 'wordpress' }, + current_test_stack_id: '', + destination_stack_id: '', + current_step: 1, + }, + ]; + return { + mockHttps: vi.fn(), + mockGetAuthToken: vi.fn(), + mockProjectRead: vi.fn(), + mockProjectUpdate: vi.fn(), + mockChainGet: vi.fn(), + mockProjects: projects, + mockFsExistsSync: vi.fn(), + mockFsReadDirSync: vi.fn(), + mockFsPromisesReadFile: vi.fn(), + mockFsPromisesAppendFile: vi.fn(), + mockFsPromisesRealpath: vi.fn(), + }; +}); + +vi.mock('../../../src/utils/https.utils.js', () => ({ default: mockHttps })); +vi.mock('../../../src/utils/auth.utils.js', () => ({ default: mockGetAuthToken })); +vi.mock('../../../src/utils/logger.js', () => ({ + default: { error: vi.fn(), info: vi.fn(), warn: vi.fn() }, +})); +vi.mock('../../../src/utils/custom-logger.utils.js', () => ({ + default: vi.fn().mockResolvedValue(undefined), +})); +vi.mock('../../../src/config/index.js', () => ({ + config: { + CS_API: { NA: 'https://api.contentstack.io/v3' }, + CS_URL: { NA: 'https://app.contentstack.com' }, + LOG_FILE_PATH: '/tmp/test.log', + }, +})); + +vi.mock('../../../src/models/project-lowdb.js', () => ({ + default: { + read: mockProjectRead, + update: mockProjectUpdate, + chain: { + get: (...args: unknown[]) => { + const chain = mockChainGet(...args); + return chain; + }, + }, + data: { projects: mockProjects }, + }, +})); + +vi.mock('../../../src/services/sitecore.service.js', () => ({ + siteCoreService: { + createEntry: vi.fn().mockResolvedValue(undefined), + createLocale: vi.fn().mockResolvedValue(undefined), + createEnvironment: vi.fn().mockResolvedValue(undefined), + createVersionFile: vi.fn().mockResolvedValue(undefined), + }, +})); +vi.mock('../../../src/services/drupal.service.js', () => ({ + drupalService: { + createQuery: vi.fn().mockResolvedValue(undefined), + generateContentTypeSchemas: vi.fn().mockResolvedValue(undefined), + createAssets: vi.fn().mockResolvedValue(undefined), + createRefrence: vi.fn().mockResolvedValue(undefined), + createTaxonomy: vi.fn().mockResolvedValue(undefined), + createEntry: vi.fn().mockResolvedValue(undefined), + createLocale: vi.fn().mockResolvedValue(undefined), + createVersionFile: vi.fn().mockResolvedValue(undefined), + }, +})); +vi.mock('../../../src/services/wordpress.service.js', () => ({ + wordpressService: { + getAllAssets: vi.fn().mockResolvedValue(undefined), + createTaxonomy: vi.fn().mockResolvedValue(undefined), + createEntry: vi.fn().mockResolvedValue(undefined), + createLocale: vi.fn().mockResolvedValue(undefined), + createVersionFile: vi.fn().mockResolvedValue(undefined), + }, +})); +vi.mock('../../../src/services/contentful.service.js', () => ({ + contentfulService: { + createLocale: vi.fn().mockResolvedValue(undefined), + createRefrence: vi.fn().mockResolvedValue(undefined), + createWebhooks: vi.fn().mockResolvedValue(undefined), + createEnvironment: vi.fn().mockResolvedValue(undefined), + createAssets: vi.fn().mockResolvedValue(undefined), + createEntry: vi.fn().mockResolvedValue(undefined), + createVersionFile: vi.fn().mockResolvedValue(undefined), + }, +})); +vi.mock('../../../src/services/aem.service.js', () => ({ + aemService: { + createAssets: vi.fn().mockResolvedValue(undefined), + createEntry: vi.fn().mockResolvedValue(undefined), + createLocale: vi.fn().mockResolvedValue(undefined), + createVersionFile: vi.fn().mockResolvedValue(undefined), + }, +})); +vi.mock('../../../src/services/marketplace.service.js', () => ({ + marketPlaceAppService: { createAppManifest: vi.fn().mockResolvedValue(undefined) }, +})); +vi.mock('../../../src/services/extension.service.js', () => ({ + extensionService: { createExtension: vi.fn().mockResolvedValue(undefined) }, +})); +vi.mock('../../../src/services/globalField.service.js', () => ({ + globalFieldServie: { createGlobalField: vi.fn().mockResolvedValue(undefined) }, +})); +vi.mock('../../../src/services/taxonomy.service.js', () => ({ + taxonomyService: { createTaxonomy: vi.fn().mockResolvedValue(undefined) }, +})); +vi.mock('../../../src/services/runCli.service.js', () => ({ + utilsCli: { runCli: vi.fn().mockResolvedValue(undefined) }, +})); +vi.mock('../../../src/utils/field-attacher.utils.js', () => ({ + fieldAttacher: vi.fn().mockResolvedValue([]), +})); +vi.mock('../../../src/utils/test-folder-creator.utils.js', () => ({ + testFolderCreator: vi.fn().mockResolvedValue(undefined), +})); +vi.mock('../../../src/server.js', () => ({ setLogFilePath: vi.fn() })); + +vi.mock('fs', () => ({ + default: { + existsSync: (...args: unknown[]) => mockFsExistsSync(...args), + readdirSync: (...args: unknown[]) => mockFsReadDirSync(...args), + promises: { + readFile: (...args: unknown[]) => mockFsPromisesReadFile(...args), + }, + }, +})); + +vi.mock('fs/promises', () => ({ + default: { + readFile: mockFsPromisesReadFile, + appendFile: mockFsPromisesAppendFile, + realpath: mockFsPromisesRealpath, + }, +})); + +vi.mock('../../../src/utils/sanitize-path.utils.js', async (importOriginal) => { + const actual = await importOriginal(); + return { + ...actual, + getSafePath: vi.fn((p: string) => p), + }; +}); + +import { migrationService } from '../../../src/services/migration.service.js'; + +const createMockReq = (overrides: Record = {}) => + ({ + params: { orgId: 'org-123', projectId: 'proj-1' }, + body: { token_payload: { region: 'NA', user_id: 'user-123' } }, + ...overrides, + }) as any; + +describe('migration.service', () => { + beforeEach(() => { + vi.clearAllMocks(); + mockGetAuthToken.mockResolvedValue('cs-auth-token'); + mockProjectRead.mockResolvedValue(undefined); + mockProjectUpdate.mockImplementation((fn: (data: any) => void) => { + fn({ projects: [...mockProjects] }); + }); + mockChainGet.mockReturnValue({ + find: vi.fn().mockReturnValue({ value: vi.fn().mockReturnValue(mockProjects[0]) }), + findIndex: vi.fn().mockReturnValue({ value: vi.fn().mockReturnValue(0) }), + }); + mockFsPromisesRealpath.mockRejectedValue(new Error('File not found')); + }); + + describe('createTestStack', () => { + it('should create test stack and update project on success', async () => { + mockHttps.mockResolvedValue({ + status: 201, + data: { stack: { api_key: 'test-stack-1', name: 'MyStack-Test-1' } }, + }); + + const req = createMockReq({ + params: { orgId: 'org-123', projectId: 'proj-1' }, + body: { + token_payload: { region: 'NA', user_id: 'user-123' }, + name: 'MyStack', + }, + }); + + const result = await migrationService.createTestStack(req); + + expect(result.status).toBe(201); + expect(result.data.data.stack.api_key).toBe('test-stack-1'); + expect(result.data.url).toContain('test-stack-1'); + expect(mockProjectUpdate).toHaveBeenCalled(); + }); + + it('should return error when create stack API fails', async () => { + vi.spyOn( + await import('../../../src/utils/index.js'), + 'safePromise' + ).mockImplementation((p: Promise) => + p.then(() => [ + { response: { status: 400, data: { error: 'Bad request' } } }, + null, + ] as any) + ); + + mockHttps.mockResolvedValue({ status: 201, data: {} }); + + const req = createMockReq({ + params: { orgId: 'org-123', projectId: 'proj-1' }, + body: { token_payload: { region: 'NA', user_id: 'user-123' }, name: 'Test' }, + }); + + const result = await migrationService.createTestStack(req); + + expect(result.status).toBe(400); + expect(result.data).toEqual({ error: 'Bad request' }); + }); + + it('should throw when getAuthtoken or ProjectModelLowdb fails', async () => { + mockGetAuthToken.mockRejectedValue(new Error('Auth failed')); + + const req = createMockReq({ + params: { orgId: 'org-123', projectId: 'proj-1' }, + body: { + token_payload: { region: 'NA', user_id: 'user-123' }, + name: 'MyStack', + }, + }); + + await expect(migrationService.createTestStack(req)).rejects.toThrow(); + }); + + it('should create Drupal test stack and generate queries when CMS is Drupal', async () => { + const drupalProject = { + ...mockProjects[0], + legacy_cms: { + cms: 'drupal', + mySQLDetails: { + host: 'localhost', + user: 'root', + password: '', + database: 'drupal', + port: 3306, + }, + }, + test_stacks: [], + }; + + mockChainGet.mockReturnValue({ + find: vi.fn().mockReturnValue({ value: vi.fn().mockReturnValue(drupalProject) }), + findIndex: vi.fn().mockReturnValue({ value: vi.fn().mockReturnValue(0) }), + }); + + mockHttps.mockResolvedValue({ + status: 201, + data: { stack: { api_key: 'drupal-test-stack', name: 'Drupal-Test-1' } }, + }); + + const req = createMockReq({ + params: { orgId: 'org-123', projectId: 'proj-1' }, + body: { + token_payload: { region: 'NA', user_id: 'user-123' }, + name: 'Drupal', + }, + }); + + const result = await migrationService.createTestStack(req); + + expect(result.status).toBe(201); + expect(mockProjectUpdate).toHaveBeenCalled(); + }); + }); + + describe('deleteTestStack', () => { + it('should delete test stack and remove from project on success', async () => { + mockHttps.mockResolvedValue({ status: 200, data: {} }); + + const req = createMockReq({ + params: { projectId: 'proj-1' }, + body: { + token_payload: { region: 'NA', user_id: 'user-123' }, + stack_key: 'test-stack-1', + }, + }); + + const result = await migrationService.deleteTestStack(req); + + expect(result.status).toBe(200); + expect(mockProjectUpdate).toHaveBeenCalled(); + }); + + it('should return error when delete API fails', async () => { + vi.spyOn( + await import('../../../src/utils/index.js'), + 'safePromise' + ).mockImplementation((p: Promise) => + p.then(() => [ + { response: { status: 404, data: { error: 'Not found' } } }, + null, + ] as any) + ); + + mockHttps.mockResolvedValue({ status: 200, data: {} }); + + const req = createMockReq({ + params: { projectId: 'proj-1' }, + body: { + token_payload: { region: 'NA', user_id: 'user-123' }, + stack_key: 'test-stack-1', + }, + }); + + const result = await migrationService.deleteTestStack(req); + + expect(result.status).toBe(404); + expect(result.data).toEqual({ error: 'Not found' }); + }); + + it('should still return success when index is -1 (stack not in project)', async () => { + mockHttps.mockResolvedValue({ status: 200, data: {} }); + mockChainGet.mockReturnValue({ + find: vi.fn().mockReturnValue({ value: vi.fn().mockReturnValue(mockProjects[0]) }), + findIndex: vi.fn().mockReturnValue({ value: vi.fn().mockReturnValue(-1) }), + }); + + const req = createMockReq({ + params: { projectId: 'proj-1' }, + body: { + token_payload: { region: 'NA', user_id: 'user-123' }, + stack_key: 'test-stack-1', + }, + }); + + const result = await migrationService.deleteTestStack(req); + + expect(result.status).toBe(200); + expect(mockProjectUpdate).not.toHaveBeenCalled(); + }); + + it('should throw when getAuthtoken fails', async () => { + mockGetAuthToken.mockRejectedValue(new Error('Token error')); + + const req = createMockReq({ + params: { projectId: 'proj-1' }, + body: { + token_payload: { region: 'NA', user_id: 'user-123' }, + stack_key: 'test-stack-1', + }, + }); + + await expect(migrationService.deleteTestStack(req)).rejects.toThrow(); + }); + }); + + describe('startTestMigration', () => { + it('should run without throwing when project has current_test_stack_id (WordPress)', async () => { + const projectWithTestStack = { + ...mockProjects[0], + current_test_stack_id: 'test-stack-1', + extract_path: '/tmp/extract', + legacy_cms: { cms: 'wordpress', file_path: '/tmp/wp' }, + stackDetails: { master_locale: 'en-us' }, + mapperKeys: {}, + }; + + mockChainGet.mockReturnValue({ + find: vi.fn().mockReturnValue({ value: vi.fn().mockReturnValue(projectWithTestStack) }), + findIndex: vi.fn().mockReturnValue({ value: vi.fn().mockReturnValue(0) }), + }); + + const req = createMockReq({ + params: { orgId: 'org-123', projectId: 'proj-1' }, + body: { token_payload: { region: 'NA', user_id: 'user-123' } }, + }); + + await expect(migrationService.startTestMigration(req)).resolves.not.toThrow(); + }); + + it('should run for Sitecore CMS when project has current_test_stack_id', async () => { + const projectWithTestStack = { + ...mockProjects[0], + current_test_stack_id: 'test-stack-1', + extract_path: '/tmp/extract', + legacy_cms: { cms: 'sitecore v9', file_path: '/tmp/sc' }, + stackDetails: { master_locale: 'en-us' }, + mapperKeys: {}, + }; + + mockChainGet.mockReturnValue({ + find: vi.fn().mockReturnValue({ value: vi.fn().mockReturnValue(projectWithTestStack) }), + findIndex: vi.fn().mockReturnValue({ value: vi.fn().mockReturnValue(0) }), + }); + + const req = createMockReq({ + params: { orgId: 'org-123', projectId: 'proj-1' }, + body: { token_payload: { region: 'NA', user_id: 'user-123' } }, + }); + + await expect(migrationService.startTestMigration(req)).resolves.not.toThrow(); + }); + + it('should run for Contentful CMS when project has current_test_stack_id', async () => { + const projectWithTestStack = { + ...mockProjects[0], + current_test_stack_id: 'test-stack-1', + extract_path: '/tmp/extract', + legacy_cms: { cms: 'contentful', file_path: '/tmp/cf/' }, + stackDetails: { master_locale: 'en-us' }, + mapperKeys: {}, + }; + + mockChainGet.mockReturnValue({ + find: vi.fn().mockReturnValue({ value: vi.fn().mockReturnValue(projectWithTestStack) }), + findIndex: vi.fn().mockReturnValue({ value: vi.fn().mockReturnValue(0) }), + }); + + const req = createMockReq({ + params: { orgId: 'org-123', projectId: 'proj-1' }, + body: { token_payload: { region: 'NA', user_id: 'user-123' } }, + }); + + await expect(migrationService.startTestMigration(req)).resolves.not.toThrow(); + }); + + it('should run for AEM CMS when project has current_test_stack_id', async () => { + const projectWithTestStack = { + ...mockProjects[0], + current_test_stack_id: 'test-stack-1', + extract_path: '/tmp/extract', + legacy_cms: { cms: 'aem', file_path: '/tmp/aem' }, + stackDetails: { master_locale: 'en-us' }, + mapperKeys: {}, + }; + + mockChainGet.mockReturnValue({ + find: vi.fn().mockReturnValue({ value: vi.fn().mockReturnValue(projectWithTestStack) }), + findIndex: vi.fn().mockReturnValue({ value: vi.fn().mockReturnValue(0) }), + }); + + const req = createMockReq({ + params: { orgId: 'org-123', projectId: 'proj-1' }, + body: { token_payload: { region: 'NA', user_id: 'user-123' } }, + }); + + await expect(migrationService.startTestMigration(req)).resolves.not.toThrow(); + }); + + it('should run for Drupal CMS when project has current_test_stack_id', async () => { + const projectWithTestStack = { + ...mockProjects[0], + current_test_stack_id: 'test-stack-1', + extract_path: '/tmp/extract', + legacy_cms: { + cms: 'drupal', + file_path: '/tmp/drupal', + mySQLDetails: { + host: 'localhost', + user: 'root', + password: '', + database: 'drupal', + port: 3306, + }, + }, + stackDetails: { master_locale: 'en-us' }, + mapperKeys: {}, + content_mapper: [], + }; + + mockChainGet.mockReturnValue({ + find: vi.fn().mockReturnValue({ value: vi.fn().mockReturnValue(projectWithTestStack) }), + findIndex: vi.fn().mockReturnValue({ value: vi.fn().mockReturnValue(0) }), + }); + + const req = createMockReq({ + params: { orgId: 'org-123', projectId: 'proj-1' }, + body: { token_payload: { region: 'NA', user_id: 'user-123' } }, + }); + + await expect(migrationService.startTestMigration(req)).resolves.not.toThrow(); + }); + + it('should do nothing when project has no current_test_stack_id', async () => { + const projectNoTestStack = { + ...mockProjects[0], + current_test_stack_id: '', + extract_path: '/tmp/extract', + }; + + mockChainGet.mockReturnValue({ + find: vi.fn().mockReturnValue({ value: vi.fn().mockReturnValue(projectNoTestStack) }), + findIndex: vi.fn().mockReturnValue({ value: vi.fn().mockReturnValue(-1) }), + }); + + const req = createMockReq({ + params: { orgId: 'org-123', projectId: 'proj-1' }, + body: { token_payload: { region: 'NA', user_id: 'user-123' } }, + }); + + await expect(migrationService.startTestMigration(req)).resolves.not.toThrow(); + }); + }); + + describe('startMigration', () => { + it('should run without throwing when project has destination_stack_id', async () => { + const projectWithDest = { + ...mockProjects[0], + destination_stack_id: 'dest-stack-1', + extract_path: '/tmp/extract', + legacy_cms: { cms: 'wordpress', file_path: '/tmp/wp' }, + stackDetails: { master_locale: 'en-us' }, + mapperKeys: {}, + }; + + mockChainGet.mockReturnValue({ + find: vi.fn().mockReturnValue({ value: vi.fn().mockReturnValue(projectWithDest) }), + findIndex: vi.fn().mockReturnValue({ value: vi.fn().mockReturnValue(0) }), + }); + + const req = createMockReq({ + params: { orgId: 'org-123', projectId: 'proj-1' }, + body: { token_payload: { region: 'NA', user_id: 'user-123' } }, + }); + + await expect(migrationService.startMigration(req)).resolves.not.toThrow(); + expect(mockProjectUpdate).toHaveBeenCalled(); + }); + + it('should do nothing when project has no destination_stack_id', async () => { + const projectNoDest = { + ...mockProjects[0], + destination_stack_id: '', + extract_path: '/tmp/extract', + }; + + mockChainGet.mockReturnValue({ + find: vi.fn().mockReturnValue({ value: vi.fn().mockReturnValue(projectNoDest) }), + findIndex: vi.fn().mockReturnValue({ value: vi.fn().mockReturnValue(0) }), + }); + + const req = createMockReq({ + params: { orgId: 'org-123', projectId: 'proj-1' }, + body: { token_payload: { region: 'NA', user_id: 'user-123' } }, + }); + + await migrationService.startMigration(req); + + expect(mockProjectUpdate).toHaveBeenCalled(); + }); + }); + + describe('getLogs', () => { + it('should return logs when file exists with valid logs', async () => { + mockFsExistsSync.mockReturnValue(true); + const logLine1 = JSON.stringify({ level: 'info', message: 'test', id: 0 }); + const logLine2 = JSON.stringify({ level: 'error', message: 'test2', id: 1 }); + mockFsPromisesReadFile.mockResolvedValue(logLine1 + '\n' + logLine2 + '\n'); + + const req = createMockReq({ + params: { + projectId: 'proj-1', + stackId: 'stack-1', + limit: '10', + startIndex: '0', + searchText: 'null', + filter: 'all', + }, + }); + + const result = await migrationService.getLogs(req); + + expect(result.status).toBe(200); + expect(result.logs).toBeDefined(); + expect(result.total).toBeDefined(); + expect(result.filterOptions).toBeDefined(); + expect(Array.isArray(result.logs)).toBe(true); + }); + + it('should return empty logs when file has no valid log entries', async () => { + mockFsExistsSync.mockReturnValue(true); + mockFsPromisesReadFile.mockResolvedValue('invalid\nnotjson\n'); + + const req = createMockReq({ + params: { + projectId: 'proj-1', + stackId: 'stack-1', + limit: '10', + startIndex: '0', + }, + }); + + const result = await migrationService.getLogs(req); + + expect(result.status).toBe(200); + expect(result.logs).toEqual([]); + expect(result.total).toBe(0); + }); + + it('should throw BadRequestError when projectId contains ..', async () => { + const req = createMockReq({ + params: { + projectId: '..', + stackId: 'stack-1', + limit: '10', + startIndex: '0', + }, + }); + + await expect(migrationService.getLogs(req)).rejects.toThrow('Invalid projectId or stackId'); + }); + + it('should throw BadRequestError when stackId contains ..', async () => { + const req = createMockReq({ + params: { + projectId: 'proj-1', + stackId: '../..', + limit: '10', + startIndex: '0', + }, + }); + + await expect(migrationService.getLogs(req)).rejects.toThrow('Invalid projectId or stackId'); + }); + + it('should throw BadRequestError when projectId is missing', async () => { + const req = createMockReq({ + params: { + projectId: '', + stackId: 'stack-1', + limit: '10', + startIndex: '0', + }, + }); + + await expect(migrationService.getLogs(req)).rejects.toThrow('Invalid projectId or stackId'); + }); + + it('should throw BadRequestError when stackId is missing', async () => { + const req = createMockReq({ + params: { + projectId: 'proj-1', + stackId: '', + limit: '10', + startIndex: '0', + }, + }); + + await expect(migrationService.getLogs(req)).rejects.toThrow('Invalid projectId or stackId'); + }); + + it('should throw BadRequestError when log file does not exist', async () => { + mockFsExistsSync.mockReturnValue(false); + + const req = createMockReq({ + params: { + projectId: 'proj-1', + stackId: 'stack-1', + limit: '10', + startIndex: '0', + }, + }); + + await expect(migrationService.getLogs(req)).rejects.toThrow(); + }); + + it('should apply filter when filter is not "all"', async () => { + mockFsExistsSync.mockReturnValue(true); + const logLine1 = JSON.stringify({ level: 'info', message: 'info msg', id: 0 }); + const logLine2 = JSON.stringify({ level: 'error', message: 'error msg', id: 1 }); + mockFsPromisesReadFile.mockResolvedValue(logLine1 + '\n' + logLine2 + '\n'); + + const req = createMockReq({ + params: { + projectId: 'proj-1', + stackId: 'stack-1', + limit: '10', + startIndex: '0', + filter: 'error', + }, + }); + + const result = await migrationService.getLogs(req); + + expect(result.status).toBe(200); + expect(result.logs).toBeDefined(); + }); + + it('should apply searchText when provided', async () => { + mockFsExistsSync.mockReturnValue(true); + const logLine1 = JSON.stringify({ + level: 'info', + message: 'Starting audit process', + methodName: 'audit', + timestamp: '2024-01-01', + id: 0, + }); + mockFsPromisesReadFile.mockResolvedValue(logLine1 + '\n'); + + const req = createMockReq({ + params: { + projectId: 'proj-1', + stackId: 'stack-1', + limit: '10', + startIndex: '0', + searchText: 'audit', + }, + }); + + const result = await migrationService.getLogs(req); + + expect(result.status).toBe(200); + expect(result.logs).toBeDefined(); + }); + + it('should use default limit and startIndex when not provided', async () => { + mockFsExistsSync.mockReturnValue(true); + mockFsPromisesReadFile.mockResolvedValue( + JSON.stringify({ level: 'info', message: 'test', id: 0 }) + '\n' + ); + + const req = createMockReq({ + params: { + projectId: 'proj-1', + stackId: 'stack-1', + }, + }); + + const result = await migrationService.getLogs(req); + + expect(result.status).toBe(200); + expect(result.logs).toBeDefined(); + }); + }); + + describe('createSourceLocales', () => { + it('should update project source locales when project exists', async () => { + const req = createMockReq({ + params: { projectId: 'proj-1' }, + body: { + token_payload: { region: 'NA', user_id: 'user-123' }, + locale: [{ code: 'en-us', name: 'English' }], + }, + }); + + await expect(migrationService.createSourceLocales(req)).resolves.not.toThrow(); + expect(mockProjectUpdate).toHaveBeenCalled(); + }); + + it('should not throw when project index is -1', async () => { + mockChainGet.mockReturnValue({ + find: vi.fn().mockReturnValue({ value: vi.fn().mockReturnValue(null) }), + findIndex: vi.fn().mockReturnValue({ value: vi.fn().mockReturnValue(-1) }), + }); + + const req = createMockReq({ + params: { projectId: 'nonexistent' }, + body: { locale: [] }, + }); + + await expect(migrationService.createSourceLocales(req)).resolves.not.toThrow(); + expect(mockProjectUpdate).not.toHaveBeenCalled(); + }); + + it('should throw when ProjectModelLowdb.read fails', async () => { + mockProjectRead.mockRejectedValue(new Error('DB read failed')); + + const req = createMockReq({ + params: { projectId: 'proj-1' }, + body: { locale: [{ code: 'en-us', name: 'English' }] }, + }); + + await expect(migrationService.createSourceLocales(req)).rejects.toThrow(); + }); + }); + + describe('updateLocaleMapper', () => { + it('should update master_locale and locales when project exists', async () => { + const req = createMockReq({ + params: { projectId: 'proj-1' }, + body: { + master_locale: 'en-us', + locales: [{ code: 'fr', name: 'French' }], + }, + }); + + await expect(migrationService.updateLocaleMapper(req)).resolves.not.toThrow(); + expect(mockProjectUpdate).toHaveBeenCalled(); + }); + + it('should not throw when project index is -1', async () => { + mockChainGet.mockReturnValue({ + find: vi.fn().mockReturnValue({ value: vi.fn().mockReturnValue(null) }), + findIndex: vi.fn().mockReturnValue({ value: vi.fn().mockReturnValue(-1) }), + }); + + const req = createMockReq({ + params: { projectId: 'nonexistent' }, + body: { master_locale: 'en-us', locales: [] }, + }); + + await expect(migrationService.updateLocaleMapper(req)).resolves.not.toThrow(); + expect(mockProjectUpdate).not.toHaveBeenCalled(); + }); + + it('should throw when ProjectModelLowdb.read fails', async () => { + mockProjectRead.mockRejectedValue(new Error('DB read failed')); + + const req = createMockReq({ + params: { projectId: 'proj-1' }, + body: { master_locale: 'en-us', locales: [] }, + }); + + await expect(migrationService.updateLocaleMapper(req)).rejects.toThrow(); + }); + }); + + describe('getAuditData', () => { + it('should throw BadRequestError when projectId contains ..', async () => { + const req = createMockReq({ + params: { + projectId: '..bad', + stackId: 'stack-1', + moduleName: 'entries', + limit: '10', + startIndex: '0', + searchText: '', + filter: 'all', + }, + }); + + await expect(migrationService.getAuditData(req)).rejects.toThrow( + 'Invalid projectId, stackId, or moduleName' + ); + }); + + it('should throw BadRequestError when stackId contains ..', async () => { + const req = createMockReq({ + params: { + projectId: 'proj-1', + stackId: '..stack', + moduleName: 'entries', + limit: '10', + startIndex: '0', + searchText: '', + filter: 'all', + }, + }); + + await expect(migrationService.getAuditData(req)).rejects.toThrow( + 'Invalid projectId, stackId, or moduleName' + ); + }); + + it('should throw BadRequestError when moduleName contains ..', async () => { + const req = createMockReq({ + params: { + projectId: 'proj-1', + stackId: 'stack-1', + moduleName: '..entries', + limit: '10', + startIndex: '0', + searchText: '', + filter: 'all', + }, + }); + + await expect(migrationService.getAuditData(req)).rejects.toThrow( + 'Invalid projectId, stackId, or moduleName' + ); + }); + + it('should throw when stack folder not found in migration-data', async () => { + mockFsReadDirSync.mockReturnValue(['other-stack-folder']); + + const req = createMockReq({ + params: { + projectId: 'proj-1', + stackId: 'stack-1', + moduleName: 'entries', + limit: '10', + startIndex: '0', + searchText: '', + filter: 'all', + }, + }); + + await expect(migrationService.getAuditData(req)).rejects.toThrow( + 'Migration data not found for this stack' + ); + }); + + it('should throw when audit log path does not exist', async () => { + mockFsReadDirSync.mockReturnValue(['stack-1-abc']); + mockFsExistsSync.mockReturnValue(false); + + const req = createMockReq({ + params: { + projectId: 'proj-1', + stackId: 'stack-1', + moduleName: 'entries', + limit: '10', + startIndex: '0', + searchText: '', + filter: 'all', + }, + }); + + await expect(migrationService.getAuditData(req)).rejects.toThrow( + 'Audit log path not found' + ); + }); + + it('should return audit data when files exist', async () => { + mockFsReadDirSync.mockReturnValue(['stack-1-abc']); + mockFsExistsSync + .mockReturnValueOnce(true) + .mockReturnValueOnce(true); + + mockFsPromisesReadFile.mockResolvedValue( + JSON.stringify([{ uid: 'item-1', title: 'Test', data_type: 'entry' }]) + ); + + const req = createMockReq({ + params: { + projectId: 'proj-1', + stackId: 'stack-1', + moduleName: 'entries', + limit: '10', + startIndex: '0', + searchText: '', + filter: 'all', + }, + }); + + const result = await migrationService.getAuditData(req); + + expect(result.status).toBe(200); + expect(result.data).toBeDefined(); + expect(result.totalCount).toBeDefined(); + expect(Array.isArray(result.data)).toBe(true); + }); + + it('should throw when no audit data found for module', async () => { + mockFsReadDirSync.mockReturnValue(['stack-1-abc']); + mockFsExistsSync.mockReturnValue(false); + + const req = createMockReq({ + params: { + projectId: 'proj-1', + stackId: 'stack-1', + moduleName: 'nonexistent', + limit: '10', + startIndex: '0', + searchText: '', + filter: 'all', + }, + }); + + await expect(migrationService.getAuditData(req)).rejects.toThrow(); + }); + + it('should apply filter when filter is not "all"', async () => { + mockFsReadDirSync.mockReturnValue(['stack-1-abc']); + mockFsExistsSync.mockReturnValue(true); + mockFsPromisesReadFile.mockResolvedValue( + JSON.stringify([ + { uid: '1', data_type: 'entry', title: 'Entry 1' }, + { uid: '2', data_type: 'asset', title: 'Asset 1' }, + ]) + ); + + const req = createMockReq({ + params: { + projectId: 'proj-1', + stackId: 'stack-1', + moduleName: 'entries', + limit: '10', + startIndex: '0', + searchText: '', + filter: 'entry-asset', + }, + }); + + const result = await migrationService.getAuditData(req); + + expect(result.status).toBe(200); + expect(result.data).toBeDefined(); + }); + + it('should throw on invalid JSON in audit file', async () => { + mockFsReadDirSync.mockReturnValue(['stack-1-abc']); + mockFsExistsSync.mockReturnValue(true); + mockFsPromisesReadFile.mockResolvedValue('invalid json {'); + + const req = createMockReq({ + params: { + projectId: 'proj-1', + stackId: 'stack-1', + moduleName: 'entries', + limit: '10', + startIndex: '0', + searchText: '', + filter: 'all', + }, + }); + + await expect(migrationService.getAuditData(req)).rejects.toThrow( + 'Invalid JSON format in audit file' + ); + }); + + it('should apply searchText for Entries_Select_feild module', async () => { + mockFsReadDirSync.mockReturnValue(['stack-1-abc']); + mockFsExistsSync + .mockReturnValueOnce(true) + .mockReturnValueOnce(true); + + mockFsPromisesReadFile.mockResolvedValue( + JSON.stringify([ + { + uid: '1', + data_type: 'entry', + title: 'Hello World', + display_type: 'entry', + }, + ]) + ); + + const req = createMockReq({ + params: { + projectId: 'proj-1', + stackId: 'stack-1', + moduleName: 'Entries_Select_feild', + limit: '10', + startIndex: '0', + searchText: 'Hello', + filter: 'all', + }, + }); + + const result = await migrationService.getAuditData(req); + + expect(result.status).toBe(200); + expect(result.data).toBeDefined(); + }); + }); +}); diff --git a/api/tests/unit/services/org.service.test.ts b/api/tests/unit/services/org.service.test.ts new file mode 100644 index 000000000..f5c4267fb --- /dev/null +++ b/api/tests/unit/services/org.service.test.ts @@ -0,0 +1,339 @@ +import { describe, it, expect, vi, beforeEach } from 'vitest'; + +const { mockHttps, mockGetAuthToken } = vi.hoisted(() => ({ + mockHttps: vi.fn(), + mockGetAuthToken: vi.fn(), +})); + +vi.mock('../../../src/utils/https.utils.js', () => ({ default: mockHttps })); +vi.mock('../../../src/utils/auth.utils.js', () => ({ default: mockGetAuthToken })); +vi.mock('../../../src/utils/logger.js', () => ({ + default: { error: vi.fn(), info: vi.fn(), warn: vi.fn() }, +})); +vi.mock('../../../src/config/index.js', () => ({ + config: { + CS_API: { NA: 'https://api.contentstack.io/v3' }, + }, +})); + +const { mockProjectRead, mockChainGet } = vi.hoisted(() => ({ + mockProjectRead: vi.fn(), + mockChainGet: vi.fn(), +})); + +vi.mock('../../../src/models/project-lowdb.js', () => ({ + default: { + read: mockProjectRead, + chain: { + get: (...args: unknown[]) => mockChainGet(...args), + }, + data: { projects: [] }, + }, +})); + +import { orgService } from '../../../src/services/org.service.js'; + +const createMockReq = (overrides: Record = {}) => + ({ + params: { orgId: 'org-123' }, + body: { token_payload: { region: 'NA', user_id: 'user-123', org_uid: 'org-123' } }, + ...overrides, + }) as any; + +describe('org.service', () => { + beforeEach(() => { + vi.clearAllMocks(); + mockGetAuthToken.mockResolvedValue('cs-auth-token'); + mockProjectRead.mockResolvedValue(undefined); + }); + + describe('getAllStacks', () => { + it('should return stacks from CS API', async () => { + mockHttps.mockResolvedValue({ + status: 200, + data: { + stacks: [ + { api_key: 'stack-1', name: 'Stack 1', description: 'Desc 1' }, + { api_key: 'stack-2', name: 'Stack 2', description: 'Desc 2' }, + ], + }, + }); + mockChainGet.mockReturnValue({ + flatMap: vi.fn().mockReturnValue({ value: vi.fn().mockReturnValue([]) }), + }); + + const req = createMockReq({ params: { orgId: 'org-123' } }); + const result = await orgService.getAllStacks(req); + + expect(result.status).toBe(200); + expect(result.data.stacks).toHaveLength(2); + }); + + it('should filter stacks by searchText', async () => { + mockHttps.mockResolvedValue({ + status: 200, + data: { + stacks: [ + { api_key: 's1', name: 'Foo Stack', description: 'A' }, + { api_key: 's2', name: 'Bar Stack', description: 'B' }, + { api_key: 's3', name: 'Other', description: 'Foo bar' }, + ], + }, + }); + mockChainGet.mockReturnValue({ + flatMap: vi.fn().mockReturnValue({ value: vi.fn().mockReturnValue([]) }), + }); + + const req = createMockReq({ + params: { orgId: 'org-123', searchText: 'foo' }, + }); + const result = await orgService.getAllStacks(req); + + expect(result.data.stacks).toHaveLength(2); + }); + + it('should exclude test stacks from results', async () => { + mockHttps.mockResolvedValue({ + status: 200, + data: { + stacks: [ + { api_key: 'stack-1', name: 'Stack 1' }, + { api_key: 'stack-2', name: 'Stack 2' }, + ], + }, + }); + mockChainGet.mockReturnValue({ + flatMap: vi.fn().mockReturnValue({ + value: vi.fn().mockReturnValue([{ stackUid: 'stack-1' }]), + }), + }); + + const req = createMockReq(); + const result = await orgService.getAllStacks(req); + + expect(result.data.stacks).toHaveLength(1); + expect(result.data.stacks[0].api_key).toBe('stack-2'); + }); + + it('should return error when https returns error tuple', async () => { + const safePromiseModule = await import('../../../src/utils/index.js'); + const origSafePromise = safePromiseModule.safePromise; + vi.spyOn(safePromiseModule, 'safePromise').mockImplementation((p: Promise) => + p.then(() => [{ response: { status: 403, data: { error: 'Forbidden' } } }, null] as any) + ); + + mockHttps.mockResolvedValue({ status: 200, data: { stacks: [] } }); + mockChainGet.mockReturnValue({ + flatMap: vi.fn().mockReturnValue({ value: vi.fn().mockReturnValue([]) }), + }); + + const req = createMockReq(); + const result = await orgService.getAllStacks(req); + expect(result.status).toBe(403); + expect(result.data).toEqual({ error: 'Forbidden' }); + }); + }); + + describe('createStack', () => { + it('should create a stack via CS API', async () => { + mockHttps.mockResolvedValue({ + status: 201, + data: { stack: { api_key: 'new-stack', name: 'New Stack' } }, + }); + + const req = createMockReq({ + body: { + token_payload: { region: 'NA', user_id: 'user-123' }, + name: 'New Stack', + description: 'Test stack', + master_locale: 'en-us', + }, + }); + const result = await orgService.createStack(req); + + expect(result.status).toBe(201); + expect(result.data.stack.api_key).toBe('new-stack'); + }); + + it('should return error response when create stack API returns error', async () => { + const safePromiseModule = await import('../../../src/utils/index.js'); + vi.spyOn(safePromiseModule, 'safePromise').mockImplementation((p: Promise) => + p.then(() => [{ response: { status: 400, data: { error: 'Bad request' } } }, null] as any) + ); + + mockHttps.mockResolvedValue({ status: 201, data: {} }); + + const req = createMockReq({ + body: { + token_payload: { region: 'NA', user_id: 'user-123' }, + name: 'New Stack', + description: 'Test', + master_locale: 'en-us', + }, + }); + const result = await orgService.createStack(req); + + expect(result.status).toBe(400); + }); + }); + + describe('getLocales', () => { + it('should return locales from CS API', async () => { + mockHttps.mockResolvedValue({ + status: 200, + data: { locales: [{ code: 'en-us', name: 'English' }] }, + }); + + const req = createMockReq(); + const result = await orgService.getLocales(req); + + expect(result.status).toBe(200); + expect(result.data.locales).toHaveLength(1); + }); + + it('should return error when get locales API returns error', async () => { + const safePromiseModule = await import('../../../src/utils/index.js'); + vi.spyOn(safePromiseModule, 'safePromise').mockImplementation((p: Promise) => + p.then(() => [{ response: { status: 401, data: { error: 'Unauthorized' } } }, null] as any) + ); + + mockHttps.mockResolvedValue({ status: 200, data: {} }); + + const req = createMockReq(); + const result = await orgService.getLocales(req); + + expect(result.status).toBe(401); + }); + }); + + describe('getStackStatus', () => { + it('should return stack status with content type count', async () => { + mockHttps + .mockResolvedValueOnce({ + status: 200, + data: { stacks: [{ api_key: 'stack-1', name: 'Stack 1' }] }, + }) + .mockResolvedValueOnce({ + status: 200, + data: { count: 5 }, + }); + + const req = createMockReq({ + params: { orgId: 'org-123' }, + body: { + token_payload: { region: 'NA', user_id: 'user-123' }, + stack_api_key: 'stack-1', + }, + }); + const result = await orgService.getStackStatus(req); + + expect(result.status).toBe(200); + expect(result.data.contenttype_count).toBe(5); + }); + + it('should return error when stacks fetch fails', async () => { + const safePromiseModule = await import('../../../src/utils/index.js'); + vi.spyOn(safePromiseModule, 'safePromise').mockImplementation((p: Promise) => + p.then(() => [{ response: { status: 500, data: {} } }, null] as any) + ); + + mockHttps.mockResolvedValue({ status: 200, data: { stacks: [] } }); + + const req = createMockReq({ + params: { orgId: 'org-123' }, + body: { + token_payload: { region: 'NA', user_id: 'user-123' }, + stack_api_key: 'stack-1', + }, + }); + const result = await orgService.getStackStatus(req); + + expect(result.status).toBe(500); + expect(result.data.message).toBeDefined(); + }); + + it('should throw when stack not found', async () => { + mockHttps.mockResolvedValueOnce({ + status: 200, + data: { stacks: [{ api_key: 'other-stack' }] }, + }); + + const req = createMockReq({ + params: { orgId: 'org-123' }, + body: { + token_payload: { region: 'NA', user_id: 'user-123' }, + stack_api_key: 'stack-1', + }, + }); + await expect(orgService.getStackStatus(req)).rejects.toThrow(); + }); + }); + + describe('getStackLocale', () => { + it('should return stack locales', async () => { + mockHttps.mockResolvedValue({ + status: 200, + data: { locales: [{ code: 'en-us', name: 'English' }] }, + }); + + const req = createMockReq({ + body: { + token_payload: { region: 'NA', user_id: 'user-123' }, + stack_api_key: 'stack-1', + }, + }); + const result = await orgService.getStackLocale(req); + + expect(result.status).toBe(200); + expect(result.data.locales).toHaveLength(1); + }); + + it('should return error when get stack locale fails', async () => { + const safePromiseModule = await import('../../../src/utils/index.js'); + vi.spyOn(safePromiseModule, 'safePromise').mockImplementation((p: Promise) => + p.then(() => [{ response: { status: 404, data: {} } }, null] as any) + ); + + mockHttps.mockResolvedValue({ status: 200, data: {} }); + + const req = createMockReq({ + body: { + token_payload: { region: 'NA', user_id: 'user-123' }, + stack_api_key: 'stack-1', + }, + }); + const result = await orgService.getStackLocale(req); + + expect(result.status).toBe(404); + }); + }); + + describe('getOrgDetails', () => { + it('should return org details with plan', async () => { + mockHttps.mockResolvedValue({ + status: 200, + data: { organization: { uid: 'org-123', name: 'Test Org' } }, + }); + + const req = createMockReq({ params: { orgId: 'org-123' } }); + const result = await orgService.getOrgDetails(req); + + expect(result.status).toBe(200); + expect(result.data.organization.uid).toBe('org-123'); + }); + + it('should return error when get org details fails', async () => { + const safePromiseModule = await import('../../../src/utils/index.js'); + vi.spyOn(safePromiseModule, 'safePromise').mockImplementation((p: Promise) => + p.then(() => [{ response: { status: 404, data: {} } }, null] as any) + ); + + mockHttps.mockResolvedValue({ status: 200, data: {} }); + + const req = createMockReq({ params: { orgId: 'org-123' } }); + const result = await orgService.getOrgDetails(req); + + expect(result.status).toBe(404); + }); + }); +}); diff --git a/api/tests/unit/services/projects.service.test.ts b/api/tests/unit/services/projects.service.test.ts new file mode 100644 index 000000000..55ea2e419 --- /dev/null +++ b/api/tests/unit/services/projects.service.test.ts @@ -0,0 +1,701 @@ +import { describe, it, expect, vi, beforeEach } from 'vitest'; +import { createMockProject } from '../../fixtures/project.fixture.js'; + +const { + mockProjectRead, + mockProjectUpdate, + mockProjectWrite, + mockFindValue, + mockFilterValue, + mockGetProjectUtil, + mockHttps, + mockGetAuthToken, + mockFindIndexValue, +} = vi.hoisted(() => ({ + mockProjectRead: vi.fn(), + mockProjectUpdate: vi.fn(), + mockProjectWrite: vi.fn(), + mockFindValue: vi.fn(), + mockFilterValue: vi.fn(), + mockGetProjectUtil: vi.fn(), + mockHttps: vi.fn(), + mockGetAuthToken: vi.fn(), + mockFindIndexValue: vi.fn(), +})); + +vi.mock('../../../src/models/project-lowdb.js', () => ({ + default: { + read: mockProjectRead, + update: mockProjectUpdate, + write: mockProjectWrite, + chain: { + get: vi.fn().mockReturnValue({ + filter: vi.fn().mockReturnValue({ value: mockFilterValue }), + find: vi.fn().mockReturnValue({ value: mockFindValue }), + findIndex: vi.fn().mockReturnValue({ value: mockFindIndexValue }), + }), + }, + data: { projects: [] }, + }, +})); + +vi.mock('../../../src/utils/get-project.utils.js', () => ({ default: mockGetProjectUtil })); +vi.mock('../../../src/utils/https.utils.js', () => ({ default: mockHttps })); +vi.mock('../../../src/utils/auth.utils.js', () => ({ default: mockGetAuthToken })); +vi.mock('../../../src/utils/logger.js', () => ({ + default: { error: vi.fn(), info: vi.fn(), warn: vi.fn() }, +})); +vi.mock('../../../src/utils/custom-logger.utils.js', () => ({ + default: vi.fn().mockResolvedValue(undefined), +})); +vi.mock('../../../src/config/index.js', () => ({ + config: { + CS_API: { NA: 'https://api.contentstack.io/v3' }, + }, +})); +vi.mock('../../../src/models/contentTypesMapper-lowdb.js', () => ({ + default: { + read: vi.fn().mockResolvedValue(undefined), + update: vi.fn(), + write: vi.fn(), + chain: { + get: vi.fn().mockReturnValue({ + filter: vi.fn().mockReturnValue({ value: vi.fn().mockReturnValue([]) }), + find: vi.fn().mockReturnValue({ value: vi.fn().mockReturnValue(null) }), + findIndex: vi.fn().mockReturnValue({ value: vi.fn().mockReturnValue(-1) }), + }), + }, + data: { ContentTypesMappers: [] }, + }, +})); +vi.mock('../../../src/models/FieldMapper.js', () => ({ + default: { + read: vi.fn().mockResolvedValue(undefined), + update: vi.fn(), + write: vi.fn(), + chain: { + get: vi.fn().mockReturnValue({ + filter: vi.fn().mockReturnValue({ value: vi.fn().mockReturnValue([]) }), + find: vi.fn().mockReturnValue({ value: vi.fn().mockReturnValue(null) }), + findIndex: vi.fn().mockReturnValue({ value: vi.fn().mockReturnValue(-1) }), + }), + }, + data: { field_mapper: [] }, + }, +})); +vi.mock('../../../src/services/contentMapper.service.js', () => ({ + contentMapperService: { + removeMapping: vi.fn().mockResolvedValue(undefined), + resetAllContentTypesMapping: vi.fn().mockResolvedValue(undefined), + }, +})); + +import { projectService } from '../../../src/services/projects.service.js'; + +const makeReq = (params: any = {}, body: any = {}) => + ({ params, body } as any); + +const tokenPayload = { region: 'NA', user_id: 'user-123' }; + +describe('projects.service', () => { + beforeEach(() => { + vi.clearAllMocks(); + mockProjectRead.mockResolvedValue(undefined); + mockGetAuthToken.mockResolvedValue('cs-auth-token'); + }); + + describe('getAllProjects', () => { + it('should return filtered projects', async () => { + const projects = [createMockProject(), createMockProject({ id: 'proj-2' })]; + mockFilterValue.mockReturnValue(projects); + const result = await projectService.getAllProjects( + makeReq({ orgId: 'org-123' }, { token_payload: tokenPayload }) + ); + expect(result).toEqual(projects); + }); + + it('should throw NotFoundError when projects is null', async () => { + mockFilterValue.mockReturnValue(null); + await expect( + projectService.getAllProjects(makeReq({ orgId: 'org-123' }, { token_payload: tokenPayload })) + ).rejects.toThrow(); + }); + + it('should return empty array when no projects match', async () => { + mockFilterValue.mockReturnValue([]); + const result = await projectService.getAllProjects( + makeReq({ orgId: 'org-123' }, { token_payload: tokenPayload }) + ); + expect(result).toEqual([]); + }); + + it('should throw BadRequestError when orgId is missing', async () => { + await expect( + projectService.getAllProjects(makeReq({}, { token_payload: tokenPayload })) + ).rejects.toThrow('Organization ID is required'); + }); + + it('should throw BadRequestError when token_payload is missing', async () => { + await expect( + projectService.getAllProjects(makeReq({ orgId: 'org-123' }, {})) + ).rejects.toThrow('Token payload is required'); + }); + }); + + describe('getProject', () => { + it('should return project by ID', async () => { + const project = createMockProject(); + mockGetProjectUtil.mockResolvedValue(project); + const result = await projectService.getProject( + makeReq({ orgId: 'org-123', projectId: project.id }, { token_payload: tokenPayload }) + ); + expect(result).toEqual(project); + }); + + it('should throw BadRequestError when params missing', async () => { + await expect( + projectService.getProject(makeReq({}, { token_payload: tokenPayload })) + ).rejects.toThrow('Organization ID and Project ID are required'); + }); + }); + + describe('createProject', () => { + it('should create project and return success', async () => { + mockProjectUpdate.mockImplementation((fn: any) => { + const data = { projects: [] }; + fn(data); + return data; + }); + const result = await projectService.createProject( + makeReq({ orgId: 'org-123' }, { token_payload: tokenPayload, name: 'New', description: 'Desc' }) + ); + expect(result.status).toBe('success'); + expect(result.project.name).toBe('New'); + }); + + it('should throw BadRequestError when name is missing', async () => { + await expect( + projectService.createProject(makeReq({ orgId: 'org-123' }, { token_payload: tokenPayload })) + ).rejects.toThrow('Project name is required'); + }); + }); + + describe('updateProject', () => { + it('should update project and return success', async () => { + mockGetProjectUtil.mockResolvedValue(0); + const project = createMockProject(); + mockProjectUpdate.mockImplementation(async (fn: any) => { + const data = { projects: [project] }; + fn(data); + }); + const result = await projectService.updateProject( + makeReq( + { orgId: 'org-123', projectId: project.id }, + { token_payload: tokenPayload, name: 'Updated', description: 'Updated desc' } + ) + ); + expect(result.status).toBe('success'); + }); + }); + + describe('updateLegacyCMS', () => { + it('should update legacy CMS successfully', async () => { + mockGetProjectUtil.mockResolvedValue(0); + const project = createMockProject({ status: 0, legacy_cms: {} }); + const mockModel = await import('../../../src/models/project-lowdb.js'); + (mockModel.default as any).data = { projects: [project] }; + mockProjectUpdate.mockImplementation(async (fn: any) => fn({ projects: [project] })); + + const result = await projectService.updateLegacyCMS( + makeReq( + { orgId: 'org-123', projectId: project.id }, + { token_payload: tokenPayload, legacy_cms: 'wordpress' } + ) + ); + expect(result.status).toBe(200); + }); + + it('should throw BadRequestError when project status is migration completed', async () => { + mockGetProjectUtil.mockResolvedValue(0); + const project = createMockProject({ status: 5 }); + const mockModel = await import('../../../src/models/project-lowdb.js'); + (mockModel.default as any).data = { projects: [project] }; + + await expect( + projectService.updateLegacyCMS( + makeReq( + { orgId: 'org-123', projectId: project.id }, + { token_payload: tokenPayload, legacy_cms: 'wordpress' } + ) + ) + ).rejects.toThrow(); + }); + + it('should throw BadRequestError when legacy_cms is missing', async () => { + await expect( + projectService.updateLegacyCMS( + makeReq({ orgId: 'org-123', projectId: 'p1' }, { token_payload: tokenPayload }) + ) + ).rejects.toThrow('Legacy CMS data is required'); + }); + }); + + describe('updateAffix', () => { + it('should update affix successfully', async () => { + mockGetProjectUtil.mockResolvedValue(0); + const project = createMockProject(); + mockProjectUpdate.mockImplementation(async (fn: any) => fn({ projects: [project] })); + + const result = await projectService.updateAffix( + makeReq( + { orgId: 'org-123', projectId: project.id }, + { token_payload: tokenPayload, affix: 'pre' } + ) + ); + expect(result.status).toBe(200); + }); + + it('should throw BadRequestError when affix is empty', async () => { + await expect( + projectService.updateAffix( + makeReq({ orgId: 'org-123', projectId: 'p1' }, { token_payload: tokenPayload, affix: '' }) + ) + ).rejects.toThrow('Affix is required'); + }); + }); + + describe('affixConfirmation', () => { + it('should update affix confirmation', async () => { + mockGetProjectUtil.mockResolvedValue(0); + const project = createMockProject(); + mockProjectUpdate.mockImplementation(async (fn: any) => fn({ projects: [project] })); + + const result = await projectService.affixConfirmation( + makeReq( + { orgId: 'org-123', projectId: project.id }, + { token_payload: tokenPayload, affix_confirmation: true } + ) + ); + expect(result.status).toBe(200); + }); + }); + + describe('updateFileFormat', () => { + it('should update file format successfully', async () => { + mockGetProjectUtil.mockResolvedValue(0); + const project = createMockProject({ status: 0, legacy_cms: {} }); + const mockModel = await import('../../../src/models/project-lowdb.js'); + (mockModel.default as any).data = { projects: [project] }; + mockProjectUpdate.mockImplementation(async (fn: any) => fn({ projects: [project] })); + + const result = await projectService.updateFileFormat( + makeReq( + { orgId: 'org-123', projectId: project.id }, + { token_payload: tokenPayload, file_format: 'json', file_path: '/path', is_localPath: true, is_fileValid: true } + ) + ); + expect(result.status).toBe(200); + }); + + it('should update file format with awsDetails', async () => { + mockGetProjectUtil.mockResolvedValue(0); + const project = createMockProject({ status: 0, legacy_cms: {} }); + const mockModel = await import('../../../src/models/project-lowdb.js'); + (mockModel.default as any).data = { projects: [project] }; + mockProjectUpdate.mockImplementation(async (fn: any) => fn({ projects: [project] })); + + const result = await projectService.updateFileFormat( + makeReq( + { orgId: 'org-123', projectId: project.id }, + { + token_payload: tokenPayload, + file_format: 'json', + file_path: '/path', + is_localPath: false, + is_fileValid: true, + awsDetails: { awsRegion: 'us-east-1', bucketName: 'bucket', bucketKey: 'key' }, + } + ) + ); + expect(result.status).toBe(200); + }); + + it('should throw when project status is migration completed', async () => { + mockGetProjectUtil.mockResolvedValue(0); + const project = createMockProject({ status: 5 }); + const mockModel = await import('../../../src/models/project-lowdb.js'); + (mockModel.default as any).data = { projects: [project] }; + + await expect( + projectService.updateFileFormat( + makeReq( + { orgId: 'org-123', projectId: project.id }, + { token_payload: tokenPayload, file_format: 'json' } + ) + ) + ).rejects.toThrow(); + }); + }); + + describe('fileformatConfirmation', () => { + it('should update fileformat confirmation', async () => { + mockGetProjectUtil.mockResolvedValue(0); + const project = createMockProject(); + mockProjectUpdate.mockImplementation(async (fn: any) => fn({ projects: [project] })); + + const result = await projectService.fileformatConfirmation( + makeReq( + { orgId: 'org-123', projectId: project.id }, + { token_payload: tokenPayload, fileformat_confirmation: true } + ) + ); + expect(result.status).toBe(200); + }); + + it('should skip update when fileformat_confirmation is undefined', async () => { + mockGetProjectUtil.mockResolvedValue(0); + const result = await projectService.fileformatConfirmation( + makeReq( + { orgId: 'org-123', projectId: 'p1' }, + { token_payload: tokenPayload } + ) + ); + expect(result.status).toBe(200); + expect(mockProjectUpdate).not.toHaveBeenCalled(); + }); + }); + + describe('updateDestinationStack', () => { + it('should update destination stack when stack is found', async () => { + mockGetProjectUtil.mockResolvedValue(0); + const project = createMockProject({ status: 0, current_step: 2 }); + const mockModel = await import('../../../src/models/project-lowdb.js'); + (mockModel.default as any).data = { projects: [project] }; + mockHttps.mockResolvedValue({ + data: { stacks: [{ api_key: 'stack-key' }] }, + status: 200, + }); + mockProjectUpdate.mockImplementation(async (fn: any) => fn({ projects: [project] })); + + const result = await projectService.updateDestinationStack( + makeReq( + { orgId: 'org-123', projectId: project.id }, + { token_payload: tokenPayload, stack_api_key: 'stack-key' } + ) + ); + expect(result.status).toBe(200); + }); + + it('should throw when stack not found in org stacks', async () => { + mockGetProjectUtil.mockResolvedValue(0); + const project = createMockProject({ status: 0, current_step: 2 }); + const mockModel = await import('../../../src/models/project-lowdb.js'); + (mockModel.default as any).data = { projects: [project] }; + mockHttps.mockResolvedValue({ + data: { stacks: [{ api_key: 'other-stack' }] }, + status: 200, + }); + + await expect( + projectService.updateDestinationStack( + makeReq( + { orgId: 'org-123', projectId: project.id }, + { token_payload: tokenPayload, stack_api_key: 'stack-key' } + ) + ) + ).rejects.toThrow(); + }); + + it('should throw when project status blocks update', async () => { + mockGetProjectUtil.mockResolvedValue(0); + const project = createMockProject({ status: 5, current_step: 2 }); + const mockModel = await import('../../../src/models/project-lowdb.js'); + (mockModel.default as any).data = { projects: [project] }; + + await expect( + projectService.updateDestinationStack( + makeReq( + { orgId: 'org-123', projectId: project.id }, + { token_payload: tokenPayload, stack_api_key: 'stack-key' } + ) + ) + ).rejects.toThrow(); + }); + + it('should return error when CS API fails', async () => { + mockGetProjectUtil.mockResolvedValue(0); + const project = createMockProject({ status: 0, current_step: 2 }); + const mockModel = await import('../../../src/models/project-lowdb.js'); + (mockModel.default as any).data = { projects: [project] }; + mockHttps.mockRejectedValue({ response: { data: 'error', status: 500 } }); + + const result = await projectService.updateDestinationStack( + makeReq( + { orgId: 'org-123', projectId: project.id }, + { token_payload: tokenPayload, stack_api_key: 'stack-key' } + ) + ); + expect(result.status).toBe(500); + }); + }); + + describe('updateCurrentStep', () => { + it('should advance from LEGACY_CMS to DESTINATION_STACK', async () => { + mockGetProjectUtil.mockResolvedValue(0); + const project = createMockProject({ + status: 0, + current_step: 1, + legacy_cms: { cms: 'wordpress', file_format: 'json' }, + }); + const mockModel = await import('../../../src/models/project-lowdb.js'); + (mockModel.default as any).data = { projects: [project] }; + mockProjectUpdate.mockImplementation(async (fn: any) => fn({ projects: [project] })); + + const result = await projectService.updateCurrentStep( + makeReq({ orgId: 'org-123', projectId: project.id }, { token_payload: tokenPayload }) + ); + expect(result).toBeDefined(); + }); + + it('should advance from DESTINATION_STACK to CONTENT_MAPPING', async () => { + mockGetProjectUtil.mockResolvedValue(0); + const project = createMockProject({ + status: 0, + current_step: 2, + legacy_cms: { cms: 'wordpress', file_format: 'json' }, + destination_stack_id: 'stack-1', + }); + const mockModel = await import('../../../src/models/project-lowdb.js'); + (mockModel.default as any).data = { projects: [project] }; + mockProjectUpdate.mockImplementation(async (fn: any) => fn({ projects: [project] })); + + const result = await projectService.updateCurrentStep( + makeReq({ orgId: 'org-123', projectId: project.id }, { token_payload: tokenPayload }) + ); + expect(result).toBeDefined(); + }); + + it('should advance from CONTENT_MAPPING to TESTING', async () => { + mockGetProjectUtil.mockResolvedValue(0); + const project = createMockProject({ + status: 3, + current_step: 3, + legacy_cms: { cms: 'wordpress', file_format: 'json' }, + destination_stack_id: 'stack-1', + content_mapper: ['ct-1'], + }); + const mockModel = await import('../../../src/models/project-lowdb.js'); + (mockModel.default as any).data = { projects: [project] }; + mockProjectUpdate.mockImplementation(async (fn: any) => fn({ projects: [project] })); + + const result = await projectService.updateCurrentStep( + makeReq({ orgId: 'org-123', projectId: project.id }, { token_payload: tokenPayload }) + ); + expect(result).toBeDefined(); + }); + + it('should advance from TESTING to MIGRATION', async () => { + mockGetProjectUtil.mockResolvedValue(0); + const project = createMockProject({ + status: 4, + current_step: 4, + legacy_cms: { cms: 'wordpress', file_format: 'json' }, + destination_stack_id: 'stack-1', + content_mapper: ['ct-1'], + current_test_stack_id: 'test-stack-1', + migration_execution: true, + }); + const mockModel = await import('../../../src/models/project-lowdb.js'); + (mockModel.default as any).data = { projects: [project] }; + mockProjectUpdate.mockImplementation(async (fn: any) => fn({ projects: [project] })); + + const result = await projectService.updateCurrentStep( + makeReq({ orgId: 'org-123', projectId: project.id }, { token_payload: tokenPayload }) + ); + expect(result).toBeDefined(); + }); + + it('should complete MIGRATION step', async () => { + mockGetProjectUtil.mockResolvedValue(0); + const project = createMockProject({ + status: 4, + current_step: 5, + legacy_cms: { cms: 'wordpress', file_format: 'json' }, + destination_stack_id: 'stack-1', + content_mapper: ['ct-1'], + current_test_stack_id: 'test-stack-1', + isMigrationCompleted: true, + }); + const mockModel = await import('../../../src/models/project-lowdb.js'); + (mockModel.default as any).data = { projects: [project] }; + mockProjectUpdate.mockImplementation(async (fn: any) => fn({ projects: [project] })); + + const result = await projectService.updateCurrentStep( + makeReq({ orgId: 'org-123', projectId: project.id }, { token_payload: tokenPayload }) + ); + expect(result).toBeDefined(); + }); + + it('should throw when LEGACY_CMS step is incomplete', async () => { + mockGetProjectUtil.mockResolvedValue(0); + const project = createMockProject({ status: 0, current_step: 1, legacy_cms: {} }); + const mockModel = await import('../../../src/models/project-lowdb.js'); + (mockModel.default as any).data = { projects: [project] }; + + await expect( + projectService.updateCurrentStep( + makeReq({ orgId: 'org-123', projectId: project.id }, { token_payload: tokenPayload }) + ) + ).rejects.toThrow(); + }); + }); + + describe('deleteProject', () => { + it('should soft delete project when status is not completed', async () => { + const project = createMockProject({ status: 0 }); + mockGetProjectUtil.mockResolvedValue(0); + const mockModel = await import('../../../src/models/project-lowdb.js'); + (mockModel.default as any).data = { projects: [project] }; + mockProjectUpdate.mockImplementation(async (fn: any) => fn({ projects: [project] })); + + const result = await projectService.deleteProject( + makeReq({ orgId: 'org-123', projectId: project.id }, { token_payload: tokenPayload }) + ); + expect(result.status).toBe(200); + }); + + it('should hard delete project with content mappers when status is 5', async () => { + const project = createMockProject({ status: 5, content_mapper: ['ct-1'] }); + mockGetProjectUtil.mockResolvedValue(0); + const mockModel = await import('../../../src/models/project-lowdb.js'); + (mockModel.default as any).data = { projects: [project] }; + + const ctMock = await import('../../../src/models/contentTypesMapper-lowdb.js'); + (ctMock.default as any).chain.get.mockReturnValue({ + find: vi.fn().mockReturnValue({ value: vi.fn().mockReturnValue({ id: 'ct-1', fieldMapping: [] }) }), + findIndex: vi.fn().mockReturnValue({ value: vi.fn().mockReturnValue(0) }), + }); + mockProjectUpdate.mockImplementation(async (fn: any) => fn({ projects: [project] })); + + const result = await projectService.deleteProject( + makeReq({ orgId: 'org-123', projectId: project.id }, { token_payload: tokenPayload }) + ); + expect(result.status).toBe(200); + }); + }); + + describe('revertProject', () => { + it('should set isDeleted to false', async () => { + const project = createMockProject({ isDeleted: true }); + mockGetProjectUtil.mockResolvedValue(0); + const mockModel = await import('../../../src/models/project-lowdb.js'); + (mockModel.default as any).data = { projects: [project] }; + mockProjectUpdate.mockImplementation(async (fn: any) => fn({ projects: [project] })); + + const result = await projectService.revertProject( + makeReq({ orgId: 'org-123', projectId: project.id }, { token_payload: tokenPayload }) + ); + expect(result.status).toBe(200); + }); + + it('should throw NotFoundError when project not found', async () => { + mockGetProjectUtil.mockResolvedValue(0); + const mockModel = await import('../../../src/models/project-lowdb.js'); + (mockModel.default as any).data = { projects: [undefined] }; + + await expect( + projectService.revertProject( + makeReq({ orgId: 'org-123', projectId: 'p1' }, { token_payload: tokenPayload }) + ) + ).rejects.toThrow(); + }); + }); + + describe('updateStackDetails', () => { + it('should update stack details successfully', async () => { + mockGetProjectUtil.mockResolvedValue(0); + const project = createMockProject(); + mockProjectUpdate.mockImplementation(async (fn: any) => fn({ projects: [project] })); + + const result = await projectService.updateStackDetails( + makeReq( + { orgId: 'org-123', projectId: project.id }, + { token_payload: tokenPayload, stack_details: { uid: 's1', label: 'Stack' } } + ) + ); + expect(result.status).toBe(200); + }); + }); + + describe('updateContentMapper', () => { + it('should update content mapper keys', async () => { + mockGetProjectUtil.mockResolvedValue(0); + const project = createMockProject(); + mockProjectUpdate.mockImplementation(async (fn: any) => fn({ projects: [project] })); + + const result = await projectService.updateContentMapper( + makeReq( + { orgId: 'org-123', projectId: project.id }, + { token_payload: tokenPayload, content_mapper: { key: 'value' } } + ) + ); + expect(result.status).toBe(200); + }); + }); + + describe('updateMigrationExecution', () => { + it('should set migration_execution to true', async () => { + mockGetProjectUtil.mockResolvedValue(0); + const project = createMockProject(); + mockProjectUpdate.mockImplementation(async (fn: any) => fn({ projects: [project] })); + + const result = await projectService.updateMigrationExecution( + makeReq( + { orgId: 'org-123', projectId: project.id }, + { token_payload: tokenPayload } + ) + ); + expect(result.status).toBe(200); + }); + + it('should throw BadRequestError when params missing', async () => { + await expect( + projectService.updateMigrationExecution(makeReq({}, { token_payload: tokenPayload })) + ).rejects.toThrow('Organization ID and Project ID are required'); + }); + }); + + describe('getMigratedStacks', () => { + it('should return destination stacks of completed projects', async () => { + const mockModel = await import('../../../src/models/project-lowdb.js'); + (mockModel.default as any).data = { + projects: [ + { status: 5, current_step: 5, destination_stack_id: 'stack-1' }, + { status: 0, current_step: 1, destination_stack_id: '' }, + ], + }; + + const result = await projectService.getMigratedStacks( + makeReq({}, { token_payload: tokenPayload }) + ); + expect(result.status).toBe(200); + expect(result.destinationStacks).toEqual(['stack-1']); + }); + + it('should return empty array when no completed projects', async () => { + const mockModel = await import('../../../src/models/project-lowdb.js'); + (mockModel.default as any).data = { projects: [] }; + + const result = await projectService.getMigratedStacks( + makeReq({}, { token_payload: tokenPayload }) + ); + expect(result.destinationStacks).toEqual([]); + }); + + it('should throw BadRequestError when token_payload missing', async () => { + await expect( + projectService.getMigratedStacks(makeReq({}, {})) + ).rejects.toThrow('Token payload is required'); + }); + }); +}); diff --git a/api/tests/unit/services/taxonomy.service.test.ts b/api/tests/unit/services/taxonomy.service.test.ts new file mode 100644 index 000000000..40efa9d97 --- /dev/null +++ b/api/tests/unit/services/taxonomy.service.test.ts @@ -0,0 +1,204 @@ +import { describe, it, expect, vi, beforeEach } from 'vitest'; + +const { + mockHttps, + mockGetAuthToken, + mockFsPromisesMkdir, + mockFsPromisesWriteFile, + mockPathJoin, +} = vi.hoisted(() => ({ + mockHttps: vi.fn(), + mockGetAuthToken: vi.fn(), + mockFsPromisesMkdir: vi.fn(), + mockFsPromisesWriteFile: vi.fn(), + mockPathJoin: vi.fn((...args: string[]) => args.join('/')), +})); + +vi.mock('../../../src/utils/https.utils.js', () => ({ default: mockHttps })); +vi.mock('../../../src/utils/auth.utils.js', () => ({ default: mockGetAuthToken })); +vi.mock('../../../src/utils/logger.js', () => ({ + default: { error: vi.fn(), info: vi.fn(), warn: vi.fn() }, +})); +vi.mock('../../../src/config/index.js', () => ({ + config: { CS_API: { NA: 'https://api.contentstack.io/v3', EU: 'https://eu-api.contentstack.com/v3' } }, +})); +vi.mock('fs', () => ({ + default: { + promises: { + mkdir: mockFsPromisesMkdir, + writeFile: mockFsPromisesWriteFile, + }, + }, +})); +vi.mock('path', () => ({ + default: { join: mockPathJoin }, +})); +vi.mock('../../../src/constants/index.js', () => ({ + MIGRATION_DATA_CONFIG: { + DATA: './cmsMigrationData', + TAXONOMIES_DIR_NAME: 'taxonomies', + TAXONOMIES_FILE_NAME: 'taxonomies.json', + }, + HTTP_TEXTS: { CS_ERROR: 'Contentstack API error' }, +})); + +import { taxonomyService } from '../../../src/services/taxonomy.service.js'; + +describe('taxonomy.service', () => { + beforeEach(() => { + vi.clearAllMocks(); + mockGetAuthToken.mockResolvedValue('cs-auth-token'); + mockFsPromisesMkdir.mockResolvedValue(undefined); + mockFsPromisesWriteFile.mockResolvedValue(undefined); + mockHttps + .mockResolvedValueOnce({ + status: 200, + data: { + taxonomies: [ + { uid: 'tax-1', name: 'Category', description: 'Cat taxonomy' }, + ], + }, + }) + .mockResolvedValue({ + status: 200, + data: { + terms: [ + { uid: 'term-1', name: 'Root', parent_uid: null, children_count: 0 }, + ], + }, + }); + }); + + describe('createTaxonomy', () => { + it('should fetch taxonomies and create term files', async () => { + await taxonomyService.createTaxonomy({ + stackId: 'stack-123', + region: 'NA', + userId: 'user-1', + current_test_stack_id: 'test-stack-1', + orgId: 'org-1', + projectId: 'proj-1', + }); + + expect(mockGetAuthToken).toHaveBeenCalledWith('NA', 'user-1'); + expect(mockHttps).toHaveBeenCalledWith( + expect.objectContaining({ + method: 'GET', + url: expect.stringContaining('taxonomies'), + headers: expect.objectContaining({ + api_key: 'stack-123', + authtoken: 'cs-auth-token', + }), + }) + ); + expect(mockFsPromisesMkdir).toHaveBeenCalled(); + expect(mockFsPromisesWriteFile).toHaveBeenCalled(); + }); + + it('should return error object when taxonomies API fails', async () => { + mockHttps + .mockReset() + .mockRejectedValue({ response: { status: 500, data: { message: 'Error' } } }); + + const result = await taxonomyService.createTaxonomy({ + stackId: 'stack-456', + region: 'NA', + userId: 'user-2', + current_test_stack_id: 'test-stack-2', + orgId: 'org-2', + projectId: 'proj-2', + }); + + expect(result).toEqual({ + data: { message: 'Error' }, + status: 500, + }); + }); + + it('should create taxonomy JSON file with correct structure', async () => { + mockHttps + .mockReset() + .mockResolvedValueOnce({ + status: 200, + data: { taxonomies: [{ uid: 'tax-1', name: 'Tags', description: 'Tags taxonomy' }] }, + }) + .mockResolvedValue({ + status: 200, + data: { terms: [{ uid: 't1', name: 'Tag1', parent_uid: null, children_count: 0 }] }, + }); + + await taxonomyService.createTaxonomy({ + stackId: 'stack-789', + region: 'NA', + userId: 'user-3', + current_test_stack_id: 'test-stack-3', + orgId: 'org-3', + projectId: 'proj-3', + }); + + const writeCalls = mockFsPromisesWriteFile.mock.calls; + expect(writeCalls.length).toBeGreaterThan(0); + const taxonomiesFileCall = writeCalls.find((c) => c[0].includes('taxonomies.json')); + expect(taxonomiesFileCall).toBeDefined(); + if (taxonomiesFileCall) { + const parsed = JSON.parse(taxonomiesFileCall[1]); + expect(parsed['tax-1']).toEqual({ + uid: 'tax-1', + name: 'Tags', + description: 'Tags taxonomy', + }); + } + }); + + it('should recursively fetch descendant terms', async () => { + mockHttps + .mockReset() + .mockResolvedValueOnce({ + status: 200, + data: { + taxonomies: [{ uid: 'tax-2', name: 'Nested', description: '' }], + }, + }) + .mockResolvedValueOnce({ + status: 200, + data: { + terms: [ + { uid: 'root', name: 'Root', parent_uid: null, children_count: 1 }, + ], + }, + }) + .mockResolvedValue({ + status: 200, + data: { + terms: [{ uid: 'child', name: 'Child', parent_uid: 'root', children_count: 0 }], + }, + }); + + await taxonomyService.createTaxonomy({ + stackId: 'stack-nested', + region: 'NA', + userId: 'user-4', + current_test_stack_id: 'test-stack-4', + orgId: 'org-4', + projectId: 'proj-4', + }); + + expect(mockHttps.mock.calls.length).toBeGreaterThan(2); + }); + + it('should throw when getAuthtoken fails', async () => { + mockGetAuthToken.mockRejectedValue(new Error('Network failure')); + + await expect( + taxonomyService.createTaxonomy({ + stackId: 'stack-err', + region: 'NA', + userId: 'user-5', + current_test_stack_id: 'test-stack-5', + orgId: 'org-5', + projectId: 'proj-5', + }) + ).rejects.toThrow('Network failure'); + }); + }); +}); diff --git a/api/tests/unit/services/user.service.test.ts b/api/tests/unit/services/user.service.test.ts new file mode 100644 index 000000000..6903c04b5 --- /dev/null +++ b/api/tests/unit/services/user.service.test.ts @@ -0,0 +1,100 @@ +import { describe, it, expect, vi, beforeEach } from 'vitest'; + +const { mockHttps, mockAuthModelRead, mockChainValue } = vi.hoisted(() => ({ + mockHttps: vi.fn(), + mockAuthModelRead: vi.fn(), + mockChainValue: vi.fn(), +})); + +vi.mock('../../../src/utils/https.utils.js', () => ({ default: mockHttps })); +vi.mock('../../../src/utils/logger.js', () => ({ + default: { error: vi.fn(), info: vi.fn(), warn: vi.fn() }, +})); +vi.mock('../../../src/config/index.js', () => ({ + config: { + CS_API: { NA: 'https://api.contentstack.io/v3' }, + }, +})); +vi.mock('../../../src/models/authentication.js', () => ({ + default: { + read: mockAuthModelRead, + chain: { + get: vi.fn().mockReturnValue({ + findIndex: vi.fn().mockReturnValue({ value: mockChainValue }), + }), + }, + data: { + users: [{ user_id: 'user-123', region: 'NA', authtoken: 'cs-token' }], + }, + }, +})); + +import { userService } from '../../../src/services/user.service.js'; + +describe('user.service', () => { + beforeEach(() => { + vi.clearAllMocks(); + mockAuthModelRead.mockResolvedValue(undefined); + }); + + describe('getUserProfile', () => { + const createReq = () => ({ + body: { token_payload: { region: 'NA', user_id: 'user-123' } }, + }); + + it('should return user profile with orgs', async () => { + mockChainValue.mockReturnValue(0); + mockHttps.mockResolvedValue({ + status: 200, + data: { + user: { + email: 'test@example.com', + first_name: 'Test', + last_name: 'User', + organizations: [ + { uid: 'org-1', name: 'Org 1', org_roles: [{ admin: true }], is_owner: false }, + { uid: 'org-2', name: 'Org 2', org_roles: [], is_owner: true }, + ], + }, + }, + }); + + const result = await userService.getUserProfile(createReq() as any); + + expect(result.status).toBe(200); + expect(result.data.user.email).toBe('test@example.com'); + expect(result.data.user.orgs).toHaveLength(2); + }); + + it('should throw when user not found in AuthenticationModel', async () => { + mockChainValue.mockReturnValue(-1); + + await expect( + userService.getUserProfile(createReq() as any) + ).rejects.toThrow(); + }); + + it('should return error response when CS API fails', async () => { + mockChainValue.mockReturnValue(0); + mockHttps.mockRejectedValue({ + response: { data: { error: 'Token expired' }, status: 401 }, + }); + + const result = await userService.getUserProfile(createReq() as any); + + expect(result.status).toBe(401); + }); + + it('should throw when CS API returns no user', async () => { + mockChainValue.mockReturnValue(0); + mockHttps.mockResolvedValue({ + status: 200, + data: {}, + }); + + await expect( + userService.getUserProfile(createReq() as any) + ).rejects.toThrow(); + }); + }); +}); diff --git a/api/tests/unit/utils/async-router.utils.test.ts b/api/tests/unit/utils/async-router.utils.test.ts new file mode 100644 index 000000000..ab2f7eb68 --- /dev/null +++ b/api/tests/unit/utils/async-router.utils.test.ts @@ -0,0 +1,37 @@ +import { describe, it, expect, vi } from 'vitest'; +import { asyncRouter } from '../../../src/utils/async-router.utils.js'; + +describe('async-router.utils', () => { + const mockReq = {} as any; + const mockRes = {} as any; + const mockNext = vi.fn(); + + it('should call the wrapped function with req, res, next', async () => { + const handler = vi.fn().mockResolvedValue(undefined); + const wrapped = asyncRouter(handler); + + await wrapped(mockReq, mockRes, mockNext); + + expect(handler).toHaveBeenCalledWith(mockReq, mockRes, mockNext); + }); + + it('should catch rejected promises and pass error to next', async () => { + const error = new Error('Async failure'); + const handler = vi.fn().mockRejectedValue(error); + const wrapped = asyncRouter(handler); + + await wrapped(mockReq, mockRes, mockNext); + + expect(mockNext).toHaveBeenCalledWith(error); + }); + + it('should not call next on success if handler does not call it', async () => { + const handler = vi.fn().mockResolvedValue(undefined); + const next = vi.fn(); + const wrapped = asyncRouter(handler); + + await wrapped(mockReq, mockRes, next); + + expect(next).not.toHaveBeenCalled(); + }); +}); diff --git a/api/tests/unit/utils/auth.utils.test.ts b/api/tests/unit/utils/auth.utils.test.ts new file mode 100644 index 000000000..26a006ed9 --- /dev/null +++ b/api/tests/unit/utils/auth.utils.test.ts @@ -0,0 +1,63 @@ +import { describe, it, expect, vi, beforeEach } from 'vitest'; + +const { mockRead, mockChain } = vi.hoisted(() => { + const mockChain = { + get: vi.fn().mockReturnThis(), + findIndex: vi.fn().mockReturnThis(), + value: vi.fn(), + }; + return { + mockRead: vi.fn(), + mockChain, + }; +}); + +vi.mock('../../../src/models/authentication.js', () => ({ + default: { + read: mockRead, + chain: mockChain, + data: { users: [] }, + }, +})); + +vi.mock('../../../src/utils/custom-errors.utils.js', async (importOriginal) => { + const actual = await importOriginal(); + return actual; +}); + +import getAuthToken from '../../../src/utils/auth.utils.js'; +import AuthenticationModel from '../../../src/models/authentication.js'; + +describe('auth.utils', () => { + beforeEach(() => { + vi.clearAllMocks(); + mockRead.mockResolvedValue(undefined); + }); + + it('should return authtoken for a valid user', async () => { + mockChain.value.mockReturnValue(0); + (AuthenticationModel as any).data = { + users: [{ region: 'NA', user_id: 'user-123', authtoken: 'valid-token' }], + }; + + const token = await getAuthToken('NA', 'user-123'); + expect(token).toBe('valid-token'); + expect(mockRead).toHaveBeenCalled(); + }); + + it('should throw UnauthorizedError when user is not found', async () => { + mockChain.value.mockReturnValue(-1); + (AuthenticationModel as any).data = { users: [] }; + + await expect(getAuthToken('NA', 'unknown-user')).rejects.toThrow(); + }); + + it('should throw UnauthorizedError when authtoken is missing', async () => { + mockChain.value.mockReturnValue(0); + (AuthenticationModel as any).data = { + users: [{ region: 'NA', user_id: 'user-123', authtoken: '' }], + }; + + await expect(getAuthToken('NA', 'user-123')).rejects.toThrow(); + }); +}); diff --git a/api/tests/unit/utils/batch-processor.utils.test.ts b/api/tests/unit/utils/batch-processor.utils.test.ts new file mode 100644 index 000000000..65855d596 --- /dev/null +++ b/api/tests/unit/utils/batch-processor.utils.test.ts @@ -0,0 +1,100 @@ +import { describe, it, expect, vi } from 'vitest'; +import { BatchProcessor, processBatches } from '../../../src/utils/batch-processor.utils.js'; + +describe('batch-processor.utils', () => { + describe('BatchProcessor', () => { + it('should process all items in correct batch sizes', async () => { + const processor = new BatchProcessor({ + batchSize: 2, + concurrency: 2, + delayBetweenBatches: 0, + }); + + const items = [1, 2, 3, 4, 5]; + const results = await processor.processBatches( + items, + async (item) => item * 2 + ); + + expect(results).toEqual([2, 4, 6, 8, 10]); + }); + + it('should handle empty arrays', async () => { + const processor = new BatchProcessor({ + batchSize: 5, + concurrency: 2, + delayBetweenBatches: 0, + }); + + const results = await processor.processBatches([], async (item) => item); + expect(results).toEqual([]); + }); + + it('should respect concurrency limit', async () => { + let maxConcurrent = 0; + let currentConcurrent = 0; + + const processor = new BatchProcessor({ + batchSize: 10, + concurrency: 2, + delayBetweenBatches: 0, + }); + + const items = [1, 2, 3, 4]; + await processor.processBatches(items, async (item) => { + currentConcurrent++; + maxConcurrent = Math.max(maxConcurrent, currentConcurrent); + await new Promise((r) => setTimeout(r, 10)); + currentConcurrent--; + return item; + }); + + expect(maxConcurrent).toBeLessThanOrEqual(2); + }); + + it('should call onBatchComplete callback', async () => { + const processor = new BatchProcessor({ + batchSize: 2, + concurrency: 2, + delayBetweenBatches: 0, + }); + + const callback = vi.fn(); + await processor.processBatches( + [1, 2, 3, 4], + async (item) => item, + callback + ); + + expect(callback).toHaveBeenCalledTimes(2); + expect(callback).toHaveBeenCalledWith(1, 2, [1, 2]); + expect(callback).toHaveBeenCalledWith(2, 2, [3, 4]); + }); + + it('should apply delay between batches', async () => { + const processor = new BatchProcessor({ + batchSize: 1, + concurrency: 1, + delayBetweenBatches: 50, + }); + + const start = Date.now(); + await processor.processBatches([1, 2, 3], async (item) => item); + const elapsed = Date.now() - start; + + expect(elapsed).toBeGreaterThanOrEqual(80); + }); + }); + + describe('processBatches utility function', () => { + it('should process items using the utility function', async () => { + const results = await processBatches( + [1, 2, 3], + async (item) => item * 3, + { batchSize: 2, concurrency: 1, delayBetweenBatches: 0 } + ); + + expect(results).toEqual([3, 6, 9]); + }); + }); +}); diff --git a/api/tests/unit/utils/custom-errors.utils.test.ts b/api/tests/unit/utils/custom-errors.utils.test.ts new file mode 100644 index 000000000..2b4c45a36 --- /dev/null +++ b/api/tests/unit/utils/custom-errors.utils.test.ts @@ -0,0 +1,110 @@ +import { describe, it, expect } from 'vitest'; +import { + AppError, + NotFoundError, + BadRequestError, + DatabaseError, + ValidationError, + InternalServerError, + UnauthorizedError, + S3Error, + ExceptionFunction, +} from '../../../src/utils/custom-errors.utils.js'; + +describe('Custom Error Classes', () => { + describe('AppError', () => { + it('should create an error with statusCode and message', () => { + const error = new AppError(418, 'I am a teapot'); + expect(error.statusCode).toBe(418); + expect(error.message).toBe('I am a teapot'); + expect(error).toBeInstanceOf(Error); + expect(error).toBeInstanceOf(AppError); + }); + }); + + describe('NotFoundError', () => { + it('should default to 404 and "Not Found"', () => { + const error = new NotFoundError(); + expect(error.statusCode).toBe(404); + expect(error.message).toBe('Not Found'); + expect(error).toBeInstanceOf(AppError); + }); + + it('should accept a custom message', () => { + const error = new NotFoundError('Resource missing'); + expect(error.statusCode).toBe(404); + expect(error.message).toBe('Resource missing'); + }); + }); + + describe('BadRequestError', () => { + it('should default to 400 and "Bad Request"', () => { + const error = new BadRequestError(); + expect(error.statusCode).toBe(400); + expect(error.message).toBe('Bad Request'); + expect(error).toBeInstanceOf(AppError); + }); + + it('should accept a custom message', () => { + const error = new BadRequestError('Invalid input'); + expect(error.message).toBe('Invalid input'); + }); + }); + + describe('DatabaseError', () => { + it('should default to 500 and "DB error"', () => { + const error = new DatabaseError(); + expect(error.statusCode).toBe(500); + expect(error.message).toBe('DB error'); + expect(error).toBeInstanceOf(AppError); + }); + }); + + describe('ValidationError', () => { + it('should default to 422 and "User validation error"', () => { + const error = new ValidationError(); + expect(error.statusCode).toBe(422); + expect(error.message).toBe('User validation error'); + expect(error).toBeInstanceOf(AppError); + }); + }); + + describe('InternalServerError', () => { + it('should default to 500 with internal error message', () => { + const error = new InternalServerError(); + expect(error.statusCode).toBe(500); + expect(error.message).toBeTruthy(); + expect(error).toBeInstanceOf(AppError); + }); + }); + + describe('UnauthorizedError', () => { + it('should default to 401', () => { + const error = new UnauthorizedError(); + expect(error.statusCode).toBe(401); + expect(error).toBeInstanceOf(AppError); + }); + + it('should accept a custom message', () => { + const error = new UnauthorizedError('Token expired'); + expect(error.message).toBe('Token expired'); + }); + }); + + describe('S3Error', () => { + it('should default to 500', () => { + const error = new S3Error(); + expect(error.statusCode).toBe(500); + expect(error).toBeInstanceOf(AppError); + }); + }); + + describe('ExceptionFunction', () => { + it('should accept custom message and status', () => { + const error = new ExceptionFunction('Custom error', 503); + expect(error.statusCode).toBe(503); + expect(error.message).toBe('Custom error'); + expect(error).toBeInstanceOf(AppError); + }); + }); +}); diff --git a/api/tests/unit/utils/field-attacher.utils.test.ts b/api/tests/unit/utils/field-attacher.utils.test.ts new file mode 100644 index 000000000..c4fcc32b9 --- /dev/null +++ b/api/tests/unit/utils/field-attacher.utils.test.ts @@ -0,0 +1,224 @@ +import { describe, it, expect, vi, beforeEach } from 'vitest'; + +const { + mockProjectRead, + mockContentTypesRead, + mockFieldMapperRead, + mockProjectChain, + mockContentTypesChain, + mockFieldMapperChain, + mockContenTypeMaker, +} = vi.hoisted(() => { + const mockProjectChain = { + get: vi.fn().mockReturnThis(), + find: vi.fn().mockReturnThis(), + value: vi.fn(), + }; + const mockContentTypesChain = { + get: vi.fn().mockReturnThis(), + find: vi.fn().mockReturnThis(), + value: vi.fn(), + }; + const mockFieldMapperChain = { + get: vi.fn().mockReturnThis(), + find: vi.fn().mockReturnThis(), + value: vi.fn(), + }; + return { + mockProjectRead: vi.fn(), + mockContentTypesRead: vi.fn(), + mockFieldMapperRead: vi.fn(), + mockProjectChain, + mockContentTypesChain, + mockFieldMapperChain, + mockContenTypeMaker: vi.fn(), + }; +}); + +vi.mock('../../../src/models/project-lowdb.js', () => ({ + default: { + read: mockProjectRead, + chain: mockProjectChain, + }, +})); + +vi.mock('../../../src/models/contentTypesMapper-lowdb.js', () => ({ + default: { + read: mockContentTypesRead, + chain: mockContentTypesChain, + }, +})); + +vi.mock('../../../src/models/FieldMapper.js', () => ({ + default: { + read: mockFieldMapperRead, + chain: mockFieldMapperChain, + }, +})); + +vi.mock('../../../src/utils/content-type-creator.utils.js', () => ({ + contenTypeMaker: (...args: any[]) => mockContenTypeMaker(...args), +})); + +import { fieldAttacher } from '../../../src/utils/field-attacher.utils.js'; + +describe('field-attacher.utils', () => { + beforeEach(() => { + vi.clearAllMocks(); + mockProjectRead.mockResolvedValue(undefined); + mockContentTypesRead.mockResolvedValue(undefined); + mockFieldMapperRead.mockResolvedValue(undefined); + mockContenTypeMaker.mockResolvedValue(undefined); + }); + + it('should return empty array when project has no content_mapper', async () => { + mockProjectChain.value.mockReturnValue({ + id: 'proj-1', + org_id: 'org-1', + content_mapper: undefined, + }); + + const result = await fieldAttacher({ + projectId: 'proj-1', + orgId: 'org-1', + destinationStackId: 'stack-1', + region: 'NA', + user_id: 'user-1', + }); + + expect(result).toEqual([]); + expect(mockProjectRead).toHaveBeenCalled(); + expect(mockContentTypesRead).toHaveBeenCalled(); + expect(mockFieldMapperRead).toHaveBeenCalled(); + expect(mockContenTypeMaker).not.toHaveBeenCalled(); + }); + + it('should return empty array when project has empty content_mapper', async () => { + mockProjectChain.value.mockReturnValue({ + id: 'proj-1', + org_id: 'org-1', + content_mapper: [], + }); + + const result = await fieldAttacher({ + projectId: 'proj-1', + orgId: 'org-1', + destinationStackId: 'stack-1', + region: 'NA', + user_id: 'user-1', + }); + + expect(result).toEqual([]); + expect(mockContenTypeMaker).not.toHaveBeenCalled(); + }); + + it('should call contenTypeMaker for each content type and return contentTypes', async () => { + const contentType1 = { + id: 'ct-1', + fieldMapping: ['field-1'], + }; + const field1 = { id: 'field-1', display_name: 'Title' }; + + mockProjectChain.value.mockReturnValue({ + id: 'proj-1', + org_id: 'org-1', + content_mapper: ['ct-1'], + stackDetails: { isNewStack: false }, + mapperKeys: {}, + }); + + mockContentTypesChain.value.mockReturnValue(contentType1); + mockFieldMapperChain.value.mockReturnValue(field1); + + const result = await fieldAttacher({ + projectId: 'proj-1', + orgId: 'org-1', + destinationStackId: 'stack-1', + region: 'NA', + user_id: 'user-1', + }); + + expect(mockContenTypeMaker).toHaveBeenCalledWith( + expect.objectContaining({ + contentType: expect.objectContaining({ + id: 'ct-1', + fieldMapping: [field1], + }), + destinationStackId: 'stack-1', + projectId: 'proj-1', + newStack: false, + keyMapper: {}, + region: 'NA', + user_id: 'user-1', + }), + ); + expect(result).toHaveLength(1); + }); + + it('should handle content type with no fieldMapping', async () => { + const contentType = { id: 'ct-1', fieldMapping: undefined }; + + mockProjectChain.value.mockReturnValue({ + id: 'proj-1', + org_id: 'org-1', + content_mapper: ['ct-1'], + stackDetails: { isNewStack: true }, + mapperKeys: {}, + }); + + mockContentTypesChain.value.mockReturnValue(contentType); + + const result = await fieldAttacher({ + projectId: 'proj-1', + orgId: 'org-1', + destinationStackId: 'stack-1', + region: 'NA', + user_id: 'user-1', + }); + + expect(mockContenTypeMaker).toHaveBeenCalled(); + expect(result).toHaveLength(1); + }); + + it('should return empty array when project is not found', async () => { + mockProjectChain.value.mockReturnValue(undefined); + + const result = await fieldAttacher({ + projectId: 'proj-999', + orgId: 'org-1', + destinationStackId: 'stack-1', + region: 'NA', + user_id: 'user-1', + }); + + expect(result).toEqual([]); + }); + + it('should handle multiple content types in content_mapper', async () => { + const contentType1 = { id: 'ct-1', fieldMapping: [] }; + const contentType2 = { id: 'ct-2', fieldMapping: [] }; + + mockProjectChain.value.mockReturnValue({ + id: 'proj-1', + org_id: 'org-1', + content_mapper: ['ct-1', 'ct-2'], + stackDetails: {}, + mapperKeys: {}, + }); + + mockContentTypesChain.value + .mockReturnValueOnce(contentType1) + .mockReturnValueOnce(contentType2); + + const result = await fieldAttacher({ + projectId: 'proj-1', + orgId: 'org-1', + destinationStackId: 'stack-1', + region: 'NA', + user_id: 'user-1', + }); + + expect(mockContenTypeMaker).toHaveBeenCalledTimes(2); + expect(result).toHaveLength(2); + }); +}); diff --git a/api/tests/unit/utils/get-project.utils.test.ts b/api/tests/unit/utils/get-project.utils.test.ts new file mode 100644 index 000000000..e8224cf60 --- /dev/null +++ b/api/tests/unit/utils/get-project.utils.test.ts @@ -0,0 +1,90 @@ +import { describe, it, expect, vi, beforeEach } from 'vitest'; + +const { mockRead, mockFind, mockFindIndex, mockChain } = vi.hoisted(() => { + const mockFind = vi.fn(); + const mockFindIndex = vi.fn(); + const mockChain = { + get: vi.fn().mockReturnValue({ + find: mockFind, + findIndex: mockFindIndex, + }), + }; + return { + mockRead: vi.fn(), + mockFind, + mockFindIndex, + mockChain, + }; +}); + +vi.mock('../../../src/models/project-lowdb.js', () => ({ + default: { + read: mockRead, + chain: mockChain, + }, +})); + +vi.mock('../../../src/utils/logger.js', () => ({ + default: { error: vi.fn(), info: vi.fn(), warn: vi.fn() }, +})); + +vi.mock('../../../src/utils/custom-logger.utils.js', () => ({ + default: vi.fn().mockResolvedValue(undefined), +})); + +import getProjectUtil from '../../../src/utils/get-project.utils.js'; + +describe('get-project.utils', () => { + const validUuid = 'f47ac10b-58cc-4372-a567-0e02b2c3d479'; + const mockQuery = { id: validUuid, org_id: 'org-123', region: 'NA', owner: 'user-123' }; + + beforeEach(() => { + vi.clearAllMocks(); + mockRead.mockResolvedValue(undefined); + }); + + it('should throw BadRequestError for invalid UUID', async () => { + await expect(getProjectUtil('invalid-id', mockQuery)).rejects.toThrow( + 'Provided project ID is invalid.' + ); + }); + + it('should return project when found', async () => { + const mockProject = { id: validUuid, name: 'Test' }; + mockFind.mockReturnValue({ value: () => mockProject }); + mockChain.get.mockReturnValue({ find: () => ({ value: () => mockProject }), findIndex: mockFindIndex }); + + const result = await getProjectUtil(validUuid, mockQuery); + expect(result).toEqual(mockProject); + }); + + it('should throw when project is not found', async () => { + mockChain.get.mockReturnValue({ + find: () => ({ value: () => null }), + findIndex: mockFindIndex, + }); + + await expect(getProjectUtil(validUuid, mockQuery)).rejects.toThrow(); + }); + + it('should support isIndex mode', async () => { + mockChain.get.mockReturnValue({ + find: mockFind, + findIndex: () => ({ value: () => 0 }), + }); + + const result = await getProjectUtil(validUuid, mockQuery, 'test', true); + expect(result).toBe(0); + }); + + it('should throw when isIndex returns -1', async () => { + mockChain.get.mockReturnValue({ + find: mockFind, + findIndex: () => ({ value: () => -1 }), + }); + + await expect( + getProjectUtil(validUuid, mockQuery, 'test', true) + ).rejects.toThrow(); + }); +}); diff --git a/api/tests/unit/utils/https.utils.test.ts b/api/tests/unit/utils/https.utils.test.ts new file mode 100644 index 000000000..bfc884b2f --- /dev/null +++ b/api/tests/unit/utils/https.utils.test.ts @@ -0,0 +1,95 @@ +import { describe, it, expect, vi, beforeEach } from 'vitest'; +import axios from 'axios'; + +vi.mock('axios', () => ({ + default: vi.fn(), +})); + +vi.mock('../../../src/constants/index.js', () => ({ + AXIOS_TIMEOUT: 60000, + METHODS_TO_INCLUDE_DATA_IN_AXIOS: ['PUT', 'POST', 'DELETE', 'PATCH'], +})); + +import https from '../../../src/utils/https.utils.js'; + +describe('https.utils', () => { + beforeEach(() => { + vi.clearAllMocks(); + }); + + it('should send a GET request and return headers, status, data', async () => { + const mockResponse = { + headers: { 'content-type': 'application/json' }, + status: 200, + data: { message: 'ok' }, + }; + vi.mocked(axios).mockResolvedValue(mockResponse); + + const result = await https({ + url: 'https://api.example.com/test', + method: 'GET', + headers: { Authorization: 'Bearer token' }, + }); + + expect(result).toEqual({ + headers: mockResponse.headers, + status: 200, + data: { message: 'ok' }, + }); + expect(axios).toHaveBeenCalledWith('https://api.example.com/test', expect.objectContaining({ + method: 'GET', + timeout: 60000, + })); + }); + + it('should include data for POST requests', async () => { + vi.mocked(axios).mockResolvedValue({ headers: {}, status: 201, data: {} }); + + await https({ + url: 'https://api.example.com/test', + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + data: { name: 'test' }, + }); + + expect(axios).toHaveBeenCalledWith('https://api.example.com/test', expect.objectContaining({ + method: 'POST', + data: { name: 'test' }, + })); + }); + + it('should not include data for GET requests', async () => { + vi.mocked(axios).mockResolvedValue({ headers: {}, status: 200, data: {} }); + + await https({ + url: 'https://api.example.com/test', + method: 'GET', + data: { shouldNotAppear: true }, + }); + + const callArgs = vi.mocked(axios).mock.calls[0][1] as any; + expect(callArgs.data).toBeUndefined(); + }); + + it('should use custom timeout when provided', async () => { + vi.mocked(axios).mockResolvedValue({ headers: {}, status: 200, data: {} }); + + await https({ + url: 'https://api.example.com/test', + method: 'GET', + timeout: 5000, + }); + + expect(axios).toHaveBeenCalledWith('https://api.example.com/test', expect.objectContaining({ + timeout: 5000, + })); + }); + + it('should propagate axios errors', async () => { + vi.mocked(axios).mockRejectedValue(new Error('Network Error')); + + await expect( + https({ url: 'https://api.example.com/test', method: 'GET' }) + ).rejects.toThrow('Network Error'); + }); +}); diff --git a/api/tests/unit/utils/index-extra.test.ts b/api/tests/unit/utils/index-extra.test.ts new file mode 100644 index 000000000..1787f3f16 --- /dev/null +++ b/api/tests/unit/utils/index-extra.test.ts @@ -0,0 +1,103 @@ +import { describe, it, expect, vi, beforeEach } from 'vitest'; + +const mockEnsureDir = vi.fn(); +const mockCopy = vi.fn(); +const mockExistsSync = vi.fn(); +const mockReadFile = vi.fn(); +const mockWriteFile = vi.fn(); + +vi.mock('fs-extra', () => ({ + default: { + ensureDir: (...args: any[]) => mockEnsureDir(...args), + copy: (...args: any[]) => mockCopy(...args), + existsSync: (...args: any[]) => mockExistsSync(...args), + promises: { + readFile: (...args: any[]) => mockReadFile(...args), + writeFile: (...args: any[]) => mockWriteFile(...args), + }, + }, +})); + +const mockMkdirp = vi.fn(); +vi.mock('mkdirp', () => ({ + mkdirp: (...args: any[]) => mockMkdirp(...args), +})); + +const mockHttps = vi.fn(); +vi.mock('../../../src/utils/https.utils.js', () => ({ + default: (...args: any[]) => mockHttps(...args), +})); + +vi.mock('../../../src/config/index.js', () => ({ + config: { CS_API: { NA: 'https://api.contentstack.io/v3' } }, +})); + +describe('utils/index - copyDirectory, createDirectoryAndFile, getAllLocales', () => { + let copyDirectory: any; + let createDirectoryAndFile: any; + let getAllLocales: any; + + beforeEach(async () => { + vi.clearAllMocks(); + const mod = await import('../../../src/utils/index.js'); + copyDirectory = mod.copyDirectory; + createDirectoryAndFile = mod.createDirectoryAndFile; + getAllLocales = mod.getAllLocales; + }); + + describe('copyDirectory', () => { + it('should copy directory from src to dest', async () => { + mockEnsureDir.mockResolvedValue(undefined); + mockCopy.mockResolvedValue(undefined); + await copyDirectory('/src', '/dest'); + expect(mockEnsureDir).toHaveBeenCalledWith('/dest'); + expect(mockCopy).toHaveBeenCalledWith('/src', '/dest'); + }); + + it('should handle errors gracefully', async () => { + mockEnsureDir.mockRejectedValue(new Error('fail')); + await copyDirectory('/src', '/dest'); + }); + }); + + describe('createDirectoryAndFile', () => { + it('should create directory and file when file does not exist', async () => { + mockMkdirp.mockResolvedValue(undefined); + mockExistsSync.mockReturnValue(false); + mockReadFile.mockResolvedValue('content'); + mockWriteFile.mockResolvedValue(undefined); + await createDirectoryAndFile('/dir/file.txt', '/source.txt'); + expect(mockMkdirp).toHaveBeenCalled(); + expect(mockReadFile).toHaveBeenCalledWith('/source.txt', 'utf8'); + expect(mockWriteFile).toHaveBeenCalled(); + }); + + it('should skip file creation when file already exists', async () => { + mockMkdirp.mockResolvedValue(undefined); + mockExistsSync.mockReturnValue(true); + await createDirectoryAndFile('/dir/file.txt', '/source.txt'); + expect(mockReadFile).not.toHaveBeenCalled(); + }); + + it('should handle errors gracefully', async () => { + mockMkdirp.mockRejectedValue(new Error('fail')); + await createDirectoryAndFile('/dir/file.txt', '/source.txt'); + }); + }); + + describe('getAllLocales', () => { + it('should return locales on success', async () => { + mockHttps.mockResolvedValue({ data: { locales: [{ code: 'en-us' }] } }); + const [err, locales] = await getAllLocales(); + expect(err).toBeNull(); + expect(locales).toEqual([{ code: 'en-us' }]); + }); + + it('should return error on failure', async () => { + mockHttps.mockRejectedValue(new Error('network error')); + const [err, locales] = await getAllLocales(); + expect(err).toBeDefined(); + expect(locales).toBeUndefined(); + }); + }); +}); diff --git a/api/tests/unit/utils/index.test.ts b/api/tests/unit/utils/index.test.ts new file mode 100644 index 000000000..eefac615f --- /dev/null +++ b/api/tests/unit/utils/index.test.ts @@ -0,0 +1,96 @@ +import { describe, it, expect, vi } from 'vitest'; +import { throwError, isEmpty, safePromise, getLogMessage } from '../../../src/utils/index.js'; + +describe('utils/index', () => { + describe('throwError', () => { + it('should throw an error with the given message and statusCode', () => { + expect(() => throwError('Not found', 404)).toThrow('Not found'); + try { + throwError('Server error', 500); + } catch (e: any) { + expect(e.statusCode).toBe(500); + expect(e.message).toBe('Server error'); + } + }); + }); + + describe('isEmpty', () => { + it('should return true for undefined', () => { + expect(isEmpty(undefined)).toBe(true); + }); + + it('should return true for null', () => { + expect(isEmpty(null)).toBe(true); + }); + + it('should return true for empty object', () => { + expect(isEmpty({})).toBe(true); + }); + + it('should return true for empty string', () => { + expect(isEmpty('')).toBe(true); + }); + + it('should return true for whitespace-only string', () => { + expect(isEmpty(' ')).toBe(true); + }); + + it('should return false for non-empty string', () => { + expect(isEmpty('hello')).toBe(false); + }); + + it('should return false for non-empty object', () => { + expect(isEmpty({ key: 'value' })).toBe(false); + }); + + it('should return false for numbers', () => { + expect(isEmpty(0)).toBe(false); + expect(isEmpty(42)).toBe(false); + }); + + it('should return false for boolean', () => { + expect(isEmpty(false)).toBe(false); + }); + }); + + describe('safePromise', () => { + it('should resolve to [null, result] on success', async () => { + const result = await safePromise(Promise.resolve('data')); + expect(result).toEqual([null, 'data']); + }); + + it('should resolve to [error] on failure', async () => { + const error = new Error('fail'); + const result = await safePromise(Promise.reject(error)); + expect(result).toEqual([error]); + }); + }); + + describe('getLogMessage', () => { + it('should return correct log object shape', () => { + const log = getLogMessage('testMethod', 'test message'); + expect(log).toEqual({ + methodName: 'testMethod', + message: 'test message', + user: {}, + }); + }); + + it('should include user when provided', () => { + const user = { id: '123' }; + const log = getLogMessage('testMethod', 'test message', user); + expect(log.user).toEqual(user); + }); + + it('should include error when provided', () => { + const error = new Error('test error'); + const log = getLogMessage('testMethod', 'test message', {}, error); + expect(log.error).toBe(error); + }); + + it('should not include error key when error is not provided', () => { + const log = getLogMessage('testMethod', 'test message'); + expect(log).not.toHaveProperty('error'); + }); + }); +}); diff --git a/api/tests/unit/utils/jwt.utils.test.ts b/api/tests/unit/utils/jwt.utils.test.ts new file mode 100644 index 000000000..a8519f669 --- /dev/null +++ b/api/tests/unit/utils/jwt.utils.test.ts @@ -0,0 +1,43 @@ +import { describe, it, expect, vi } from 'vitest'; +import jwt from 'jsonwebtoken'; + +vi.mock('../../../src/config/index.js', () => ({ + config: { + APP_TOKEN_KEY: 'test-secret-key', + APP_TOKEN_EXP: '2d', + }, +})); + +import { generateToken } from '../../../src/utils/jwt.utils.js'; + +describe('jwt.utils', () => { + describe('generateToken', () => { + it('should return a signed JWT string', () => { + const payload = { region: 'NA', user_id: 'user-123' }; + const token = generateToken(payload); + + expect(typeof token).toBe('string'); + expect(token.split('.')).toHaveLength(3); + }); + + it('should encode the payload correctly', () => { + const payload = { region: 'EU', user_id: 'user-456' }; + const token = generateToken(payload); + const decoded = jwt.verify(token, 'test-secret-key') as any; + + expect(decoded.region).toBe('EU'); + expect(decoded.user_id).toBe('user-456'); + expect(decoded.exp).toBeDefined(); + expect(decoded.iat).toBeDefined(); + }); + + it('should set expiration from config', () => { + const payload = { region: 'NA', user_id: 'user-123' }; + const token = generateToken(payload); + const decoded = jwt.decode(token) as any; + + const twoDaysInSeconds = 2 * 24 * 60 * 60; + expect(decoded.exp - decoded.iat).toBe(twoDaysInSeconds); + }); + }); +}); diff --git a/api/tests/unit/utils/market-app.utils.test.ts b/api/tests/unit/utils/market-app.utils.test.ts new file mode 100644 index 000000000..646a30064 --- /dev/null +++ b/api/tests/unit/utils/market-app.utils.test.ts @@ -0,0 +1,134 @@ +import { describe, it, expect, vi, beforeEach } from 'vitest'; + +const mockClient = { + marketplace: vi.fn(), +}; + +vi.mock('@contentstack/marketplace-sdk', () => ({ + default: { + client: vi.fn(() => mockClient), + }, +})); + +vi.mock('../../../src/constants/index.js', () => ({ + DEVURLS: { + NA: 'developerhub-api.contentstack.com', + EU: 'eu-developerhub-api.contentstack.com', + }, +})); + +import contentstack from '@contentstack/marketplace-sdk'; +import { + getAllApps, + getAppManifestAndAppConfig, +} from '../../../src/utils/market-app.utils.js'; + +describe('market-app.utils', () => { + const originalConsoleInfo = console.info; + + beforeEach(() => { + vi.clearAllMocks(); + mockClient.marketplace.mockReturnValue({ + findAllApps: vi.fn(), + app: vi.fn(), + }); + }); + + describe('getAllApps', () => { + it('should return items when findAllApps succeeds', async () => { + const mockItems = [{ uid: 'app-1' }, { uid: 'app-2' }]; + mockClient.marketplace.mockReturnValue({ + findAllApps: vi.fn().mockResolvedValue({ items: mockItems }), + }); + + const result = await getAllApps({ + organizationUid: 'org-123', + authtoken: 'token-xyz', + region: 'NA', + }); + + expect(result).toEqual(mockItems); + expect(contentstack.client).toHaveBeenCalledWith({ + authtoken: 'token-xyz', + host: 'developerhub-api.contentstack.com', + }); + expect(mockClient.marketplace).toHaveBeenCalledWith('org-123'); + }); + + it('should use EU host when region is EU', async () => { + mockClient.marketplace.mockReturnValue({ + findAllApps: vi.fn().mockResolvedValue({ items: [] }), + }); + + await getAllApps({ + organizationUid: 'org-123', + authtoken: 'token', + region: 'EU', + }); + + expect(contentstack.client).toHaveBeenCalledWith({ + authtoken: 'token', + host: 'eu-developerhub-api.contentstack.com', + }); + }); + + it('should return undefined and log when error occurs', async () => { + const consoleSpy = vi.spyOn(console, 'info').mockImplementation(() => {}); + mockClient.marketplace.mockReturnValue({ + findAllApps: vi.fn().mockRejectedValue(new Error('API Error')), + }); + + const result = await getAllApps({ + organizationUid: 'org-123', + authtoken: 'token', + region: 'NA', + }); + + expect(result).toBeUndefined(); + expect(consoleSpy).toHaveBeenCalled(); + consoleSpy.mockRestore(); + }); + }); + + describe('getAppManifestAndAppConfig', () => { + it('should return app data when fetch succeeds', async () => { + const mockAppData = { uid: 'manifest-1', title: 'Test App' }; + mockClient.marketplace.mockReturnValue({ + app: vi.fn().mockReturnValue({ + fetch: vi.fn().mockResolvedValue(mockAppData), + }), + }); + + const result = await getAppManifestAndAppConfig({ + organizationUid: 'org-123', + authtoken: 'token', + region: 'NA', + manifestUid: 'manifest-1', + }); + + expect(result).toEqual(mockAppData); + const appFn = mockClient.marketplace().app; + expect(appFn).toHaveBeenCalledWith('manifest-1'); + }); + + it('should return undefined and log when error occurs', async () => { + const consoleSpy = vi.spyOn(console, 'info').mockImplementation(() => {}); + mockClient.marketplace.mockReturnValue({ + app: vi.fn().mockReturnValue({ + fetch: vi.fn().mockRejectedValue(new Error('Not found')), + }), + }); + + const result = await getAppManifestAndAppConfig({ + organizationUid: 'org-123', + authtoken: 'token', + region: 'NA', + manifestUid: 'invalid', + }); + + expect(result).toBeUndefined(); + expect(consoleSpy).toHaveBeenCalled(); + consoleSpy.mockRestore(); + }); + }); +}); diff --git a/api/tests/unit/utils/mimeTypes.test.ts b/api/tests/unit/utils/mimeTypes.test.ts new file mode 100644 index 000000000..befee79cc --- /dev/null +++ b/api/tests/unit/utils/mimeTypes.test.ts @@ -0,0 +1,75 @@ +import { describe, it, expect, vi } from 'vitest'; + +import { + EXT_TO_MIME_MAP, + getMimeTypeFromExtension, + default as defaultExport, +} from '../../../src/utils/mimeTypes.js'; + +describe('mimeTypes', () => { + describe('getMimeTypeFromExtension', () => { + it('should return correct MIME type for known image extensions', () => { + expect(getMimeTypeFromExtension('jpg')).toBe('image/jpeg'); + expect(getMimeTypeFromExtension('jpeg')).toBe('image/jpeg'); + expect(getMimeTypeFromExtension('png')).toBe('image/png'); + expect(getMimeTypeFromExtension('gif')).toBe('image/gif'); + expect(getMimeTypeFromExtension('webp')).toBe('image/webp'); + expect(getMimeTypeFromExtension('svg')).toBe('image/svg+xml'); + }); + + it('should return correct MIME type for video extensions', () => { + expect(getMimeTypeFromExtension('mp4')).toBe('video/mp4'); + expect(getMimeTypeFromExtension('webm')).toBe('video/webm'); + expect(getMimeTypeFromExtension('mov')).toBe('video/quicktime'); + }); + + it('should return correct MIME type for audio extensions', () => { + expect(getMimeTypeFromExtension('mp3')).toBe('audio/mpeg'); + expect(getMimeTypeFromExtension('wav')).toBe('audio/wav'); + }); + + it('should return correct MIME type for document extensions', () => { + expect(getMimeTypeFromExtension('pdf')).toBe('application/pdf'); + expect(getMimeTypeFromExtension('json')).toBe('application/json'); + expect(getMimeTypeFromExtension('txt')).toBe('text/plain'); + }); + + it('should be case-insensitive', () => { + expect(getMimeTypeFromExtension('JPG')).toBe('image/jpeg'); + expect(getMimeTypeFromExtension('PNG')).toBe('image/png'); + expect(getMimeTypeFromExtension('PDF')).toBe('application/pdf'); + }); + + it('should return undefined for unknown extension', () => { + expect(getMimeTypeFromExtension('unknown')).toBeUndefined(); + expect(getMimeTypeFromExtension('xyz')).toBeUndefined(); + expect(getMimeTypeFromExtension('')).toBeUndefined(); + }); + + it('should handle empty string', () => { + expect(getMimeTypeFromExtension('')).toBeUndefined(); + }); + }); + + describe('EXT_TO_MIME_MAP', () => { + it('should export expected MIME type mappings', () => { + expect(EXT_TO_MIME_MAP).toBeDefined(); + expect(typeof EXT_TO_MIME_MAP).toBe('object'); + expect(Object.keys(EXT_TO_MIME_MAP).length).toBeGreaterThan(0); + }); + + it('should have valid MIME type format for entries', () => { + for (const [ext, mime] of Object.entries(EXT_TO_MIME_MAP)) { + expect(typeof ext).toBe('string'); + expect(typeof mime).toBe('string'); + expect(mime).toMatch(/.+\/.+/); + } + }); + }); + + describe('default export', () => { + it('should export EXT_TO_MIME_MAP as default', () => { + expect(defaultExport).toBe(EXT_TO_MIME_MAP); + }); + }); +}); diff --git a/api/tests/unit/utils/pagination.utils.test.ts b/api/tests/unit/utils/pagination.utils.test.ts new file mode 100644 index 000000000..27ba63429 --- /dev/null +++ b/api/tests/unit/utils/pagination.utils.test.ts @@ -0,0 +1,83 @@ +import { describe, it, expect, vi, beforeEach } from 'vitest'; + +const { mockHttps } = vi.hoisted(() => ({ mockHttps: vi.fn() })); + +vi.mock('../../../src/utils/https.utils.js', () => ({ + default: mockHttps, +})); + +vi.mock('../../../src/utils/index.js', async (importOriginal) => { + const actual = await importOriginal(); + return { + ...actual, + safePromise: (promise: Promise) => + promise.then((res: any) => [null, res]).catch((err: any) => [err]), + }; +}); + +import fetchAllPaginatedData from '../../../src/utils/pagination.utils.js'; + +describe('pagination.utils', () => { + beforeEach(() => { + vi.clearAllMocks(); + }); + + it('should fetch a single page of data', async () => { + mockHttps.mockResolvedValue({ + data: { items: [{ id: 1 }, { id: 2 }] }, + }); + + const result = await fetchAllPaginatedData( + 'https://api.example.com/data', + { Authorization: 'Bearer token' }, + 100, + 'testFunc', + 'items' + ); + + expect(result).toEqual([{ id: 1 }, { id: 2 }]); + expect(mockHttps).toHaveBeenCalledTimes(1); + }); + + it('should fetch multiple pages of data', async () => { + mockHttps + .mockResolvedValueOnce({ + data: { items: Array.from({ length: 2 }, (_, i) => ({ id: i })) }, + }) + .mockResolvedValueOnce({ + data: { items: [{ id: 2 }] }, + }); + + const result = await fetchAllPaginatedData( + 'https://api.example.com/data', + {}, + 2, + 'testFunc', + 'items' + ); + + expect(result).toHaveLength(3); + expect(mockHttps).toHaveBeenCalledTimes(2); + }); + + it('should throw on API error', async () => { + const apiError = Object.assign(new Error('API Error'), { + response: { data: 'Bad Request' }, + }); + mockHttps.mockRejectedValue(apiError); + + await expect( + fetchAllPaginatedData('https://api.example.com/data', {}, 100, 'testFunc', 'items') + ).rejects.toThrow('Error in testFunc'); + }); + + it('should throw when responseKey is not iterable', async () => { + mockHttps.mockResolvedValue({ + data: { items: 'not-an-array' }, + }); + + await expect( + fetchAllPaginatedData('https://api.example.com/data', {}, 100, 'testFunc', 'items') + ).rejects.toThrow('is not iterable'); + }); +}); diff --git a/api/tests/unit/utils/sanitize-path.utils.test.ts b/api/tests/unit/utils/sanitize-path.utils.test.ts new file mode 100644 index 000000000..bb60825f7 --- /dev/null +++ b/api/tests/unit/utils/sanitize-path.utils.test.ts @@ -0,0 +1,90 @@ +import { describe, it, expect } from 'vitest'; +import { sanitizeStackId, getSafePath } from '../../../src/utils/sanitize-path.utils.js'; +import path from 'path'; + +describe('sanitize-path.utils', () => { + describe('sanitizeStackId', () => { + it('should return the same string for valid alphanumeric input', () => { + expect(sanitizeStackId('blt1234abcd')).toBe('blt1234abcd'); + }); + + it('should allow dots, hyphens, and underscores', () => { + expect(sanitizeStackId('stack-id_v2.0')).toBe('stack-id_v2.0'); + }); + + it('should return null for null input', () => { + expect(sanitizeStackId(null)).toBeNull(); + }); + + it('should return null for undefined input', () => { + expect(sanitizeStackId(undefined)).toBeNull(); + }); + + it('should return null for empty string', () => { + expect(sanitizeStackId('')).toBeNull(); + }); + + it('should return null for path traversal with ../', () => { + expect(sanitizeStackId('../etc/passwd')).toBeNull(); + }); + + it('should return null for backslash path traversal', () => { + expect(sanitizeStackId('..\\windows\\system32')).toBeNull(); + }); + + it('should return null for null bytes', () => { + expect(sanitizeStackId('valid\0malicious')).toBeNull(); + }); + + it('should return null for forward slashes', () => { + expect(sanitizeStackId('path/to/file')).toBeNull(); + }); + + it('should return null for strings longer than 256 characters', () => { + const longString = 'a'.repeat(257); + expect(sanitizeStackId(longString)).toBeNull(); + }); + + it('should accept strings of exactly 256 characters', () => { + const maxString = 'a'.repeat(256); + expect(sanitizeStackId(maxString)).toBe(maxString); + }); + + it('should return null for non-string input', () => { + expect(sanitizeStackId(123 as any)).toBeNull(); + }); + + it('should return null for special characters', () => { + expect(sanitizeStackId('stack@id!')).toBeNull(); + }); + }); + + describe('getSafePath', () => { + it('should resolve an absolute path', () => { + const result = getSafePath('/tmp/test.log'); + expect(path.isAbsolute(result)).toBe(true); + }); + + it('should sanitize the filename', () => { + const result = getSafePath('/tmp/test@file!.log'); + expect(result).not.toContain('@'); + expect(result).not.toContain('!'); + }); + + it('should prevent directory escape when baseDir is provided', () => { + const result = getSafePath('../../etc/passwd', '/tmp/logs'); + expect(result).toContain('/tmp/logs'); + }); + + it('should return default.log on error with baseDir', () => { + const result = getSafePath('', '/tmp/logs'); + expect(path.isAbsolute(result)).toBe(true); + }); + + it('should handle relative paths with baseDir', () => { + const result = getSafePath('subdir/file.log', '/tmp/logs'); + expect(result).toContain('file.log'); + expect(path.isAbsolute(result)).toBe(true); + }); + }); +}); diff --git a/api/tests/unit/utils/search.util.test.ts b/api/tests/unit/utils/search.util.test.ts new file mode 100644 index 000000000..2c725b0b6 --- /dev/null +++ b/api/tests/unit/utils/search.util.test.ts @@ -0,0 +1,52 @@ +import { describe, it, expect } from 'vitest'; +import { matchesSearchText } from '../../../src/utils/search.util.js'; + +describe('search.util', () => { + describe('matchesSearchText', () => { + const mockLog: any = { + level: 'error', + message: 'Something failed in migration', + methodName: 'startTestMigration', + timestamp: '2025-01-15T10:30:00.000Z', + }; + + it('should return true when searchText is empty', () => { + expect(matchesSearchText(mockLog, '')).toBe(true); + }); + + it('should return true when searchText is "null"', () => { + expect(matchesSearchText(mockLog, 'null')).toBe(true); + }); + + it('should match on level field', () => { + expect(matchesSearchText(mockLog, 'error')).toBe(true); + }); + + it('should match on message field', () => { + expect(matchesSearchText(mockLog, 'migration')).toBe(true); + }); + + it('should match on methodName field', () => { + expect(matchesSearchText(mockLog, 'startTest')).toBe(true); + }); + + it('should match on timestamp field', () => { + expect(matchesSearchText(mockLog, '2025-01')).toBe(true); + }); + + it('should be case-insensitive', () => { + expect(matchesSearchText(mockLog, 'ERROR')).toBe(true); + expect(matchesSearchText(mockLog, 'Migration')).toBe(true); + }); + + it('should return false when no fields match', () => { + expect(matchesSearchText(mockLog, 'nonexistent')).toBe(false); + }); + + it('should handle log with missing fields gracefully', () => { + const partialLog: any = { level: 'info' }; + expect(matchesSearchText(partialLog, 'info')).toBe(true); + expect(matchesSearchText(partialLog, 'missing')).toBe(false); + }); + }); +}); diff --git a/api/tests/unit/validators/affix-confirmation.validator.test.ts b/api/tests/unit/validators/affix-confirmation.validator.test.ts new file mode 100644 index 000000000..2da88917d --- /dev/null +++ b/api/tests/unit/validators/affix-confirmation.validator.test.ts @@ -0,0 +1,39 @@ +import { describe, it, expect, beforeAll } from 'vitest'; +import { validationResult } from 'express-validator'; + +const mockReq = (body: any) => ({ body, query: {}, params: {}, headers: {}, get: () => undefined }); + +async function runValidation(schema: any, body: any) { + const req = mockReq(body); + await schema.run(req); + return validationResult(req); +} + +describe('affix-confirmation.validator', () => { + let validator: any; + + beforeAll(async () => { + const mod = await import('../../../src/validators/affix-confirmation.validator.js'); + validator = mod.default; + }); + + it('should accept true', async () => { + const result = await runValidation(validator, { affix_confirmation: true }); + expect(result.isEmpty()).toBe(true); + }); + + it('should accept false', async () => { + const result = await runValidation(validator, { affix_confirmation: false }); + expect(result.isEmpty()).toBe(true); + }); + + it('should reject missing field', async () => { + const result = await runValidation(validator, {}); + expect(result.isEmpty()).toBe(false); + }); + + it('should reject non-boolean value', async () => { + const result = await runValidation(validator, { affix_confirmation: 123 }); + expect(result.isEmpty()).toBe(false); + }); +}); diff --git a/api/tests/unit/validators/affix.validator.test.ts b/api/tests/unit/validators/affix.validator.test.ts new file mode 100644 index 000000000..b2c64c87f --- /dev/null +++ b/api/tests/unit/validators/affix.validator.test.ts @@ -0,0 +1,49 @@ +import { describe, it, expect, beforeAll } from 'vitest'; +import { validationResult } from 'express-validator'; + +const mockReq = (body: any) => ({ body, query: {}, params: {}, headers: {}, get: () => undefined }); + +async function runValidation(schema: any, body: any) { + const req = mockReq(body); + await schema.run(req); + return validationResult(req); +} + +describe('affix.validator', () => { + let validator: any; + + beforeAll(async () => { + const mod = await import('../../../src/validators/affix.validator.js'); + validator = mod.default; + }); + + it('should accept valid affix', async () => { + const result = await runValidation(validator, { affix: 'abc12' }); + expect(result.isEmpty()).toBe(true); + }); + + it('should reject missing affix', async () => { + const result = await runValidation(validator, {}); + expect(result.isEmpty()).toBe(false); + }); + + it('should reject non-string affix', async () => { + const result = await runValidation(validator, { affix: 123 }); + expect(result.isEmpty()).toBe(false); + }); + + it('should reject affix starting with number', async () => { + const result = await runValidation(validator, { affix: '1abc' }); + expect(result.isEmpty()).toBe(false); + }); + + it('should reject affix with special characters', async () => { + const result = await runValidation(validator, { affix: 'ab-c' }); + expect(result.isEmpty()).toBe(false); + }); + + it('should reject affix exceeding 5 chars', async () => { + const result = await runValidation(validator, { affix: 'abcdef' }); + expect(result.isEmpty()).toBe(false); + }); +}); diff --git a/api/tests/unit/validators/auth.validator.test.ts b/api/tests/unit/validators/auth.validator.test.ts new file mode 100644 index 000000000..4178daccf --- /dev/null +++ b/api/tests/unit/validators/auth.validator.test.ts @@ -0,0 +1,91 @@ +import { describe, it, expect, beforeAll } from 'vitest'; +import { checkSchema, validationResult } from 'express-validator'; + +const mockReq = (body: any) => ({ + body, + query: {}, + params: {}, + headers: {}, + get: () => undefined, +}); + +async function runValidation(schema: any, body: any) { + const req = mockReq(body); + const validations = schema; + await validations.run(req); + return validationResult(req); +} + +describe('auth.validator', () => { + let authValidator: any; + + beforeAll(async () => { + const mod = await import('../../../src/validators/auth.validator.js'); + authValidator = mod.default; + }); + + it('should accept valid auth body', async () => { + const result = await runValidation(authValidator, { + email: 'test@example.com', + password: 'password123', + region: 'NA', + }); + expect(result.isEmpty()).toBe(true); + }); + + it('should reject missing email', async () => { + const result = await runValidation(authValidator, { + password: 'password123', + region: 'NA', + }); + expect(result.isEmpty()).toBe(false); + }); + + it('should reject invalid email', async () => { + const result = await runValidation(authValidator, { + email: 'not-an-email', + password: 'password123', + region: 'NA', + }); + expect(result.isEmpty()).toBe(false); + }); + + it('should reject non-string password', async () => { + const result = await runValidation(authValidator, { + email: 'test@example.com', + password: 12345, + region: 'NA', + }); + expect(result.isEmpty()).toBe(false); + }); + + it('should reject invalid region', async () => { + const result = await runValidation(authValidator, { + email: 'test@example.com', + password: 'password123', + region: 'INVALID', + }); + expect(result.isEmpty()).toBe(false); + }); + + it('should accept valid regions', async () => { + for (const region of ['NA', 'EU', 'AZURE_NA', 'AZURE_EU', 'GCP_NA', 'AU', 'GCP_EU']) { + const result = await runValidation(authValidator, { + email: 'test@example.com', + password: 'password123', + region, + }); + expect(result.isEmpty()).toBe(true); + } + }); + + it('should accept optional tfa_token', async () => { + const result = await runValidation(authValidator, { + email: 'test@example.com', + password: 'password123', + region: 'NA', + tfa_token: '123456', + }); + expect(result.isEmpty()).toBe(true); + }); +}); diff --git a/api/tests/unit/validators/cms.validator.test.ts b/api/tests/unit/validators/cms.validator.test.ts new file mode 100644 index 000000000..2cad3ff97 --- /dev/null +++ b/api/tests/unit/validators/cms.validator.test.ts @@ -0,0 +1,39 @@ +import { describe, it, expect, beforeAll } from 'vitest'; +import { validationResult } from 'express-validator'; + +const mockReq = (body: any) => ({ body, query: {}, params: {}, headers: {}, get: () => undefined }); + +async function runValidation(schema: any, body: any) { + const req = mockReq(body); + await schema.run(req); + return validationResult(req); +} + +describe('cms.validator', () => { + let validator: any; + + beforeAll(async () => { + const mod = await import('../../../src/validators/cms.validator.js'); + validator = mod.default; + }); + + it('should accept valid legacy_cms', async () => { + const result = await runValidation(validator, { legacy_cms: 'wordpress' }); + expect(result.isEmpty()).toBe(true); + }); + + it('should reject missing legacy_cms', async () => { + const result = await runValidation(validator, {}); + expect(result.isEmpty()).toBe(false); + }); + + it('should reject non-string legacy_cms', async () => { + const result = await runValidation(validator, { legacy_cms: 123 }); + expect(result.isEmpty()).toBe(false); + }); + + it('should reject legacy_cms exceeding max length', async () => { + const result = await runValidation(validator, { legacy_cms: 'a'.repeat(201) }); + expect(result.isEmpty()).toBe(false); + }); +}); diff --git a/api/tests/unit/validators/destination-stack.validator.test.ts b/api/tests/unit/validators/destination-stack.validator.test.ts new file mode 100644 index 000000000..f46243a00 --- /dev/null +++ b/api/tests/unit/validators/destination-stack.validator.test.ts @@ -0,0 +1,39 @@ +import { describe, it, expect, beforeAll } from 'vitest'; +import { validationResult } from 'express-validator'; + +const mockReq = (body: any) => ({ body, query: {}, params: {}, headers: {}, get: () => undefined }); + +async function runValidation(schema: any, body: any) { + const req = mockReq(body); + await schema.run(req); + return validationResult(req); +} + +describe('destination-stack.validator', () => { + let validator: any; + + beforeAll(async () => { + const mod = await import('../../../src/validators/destination-stack.validator.js'); + validator = mod.default; + }); + + it('should accept valid stack_api_key', async () => { + const result = await runValidation(validator, { stack_api_key: 'blt0000000000000000' }); + expect(result.isEmpty()).toBe(true); + }); + + it('should reject missing stack_api_key', async () => { + const result = await runValidation(validator, {}); + expect(result.isEmpty()).toBe(false); + }); + + it('should reject non-string stack_api_key', async () => { + const result = await runValidation(validator, { stack_api_key: 123 }); + expect(result.isEmpty()).toBe(false); + }); + + it('should reject stack_api_key exceeding max length', async () => { + const result = await runValidation(validator, { stack_api_key: 'a'.repeat(201) }); + expect(result.isEmpty()).toBe(false); + }); +}); diff --git a/api/tests/unit/validators/file-format.validator.test.ts b/api/tests/unit/validators/file-format.validator.test.ts new file mode 100644 index 000000000..735ab7ff4 --- /dev/null +++ b/api/tests/unit/validators/file-format.validator.test.ts @@ -0,0 +1,44 @@ +import { describe, it, expect, beforeAll } from 'vitest'; +import { validationResult } from 'express-validator'; + +const mockReq = (body: any) => ({ body, query: {}, params: {}, headers: {}, get: () => undefined }); + +async function runValidation(schema: any, body: any) { + const req = mockReq(body); + await schema.run(req); + return validationResult(req); +} + +describe('file-format.validator', () => { + let validator: any; + + beforeAll(async () => { + const mod = await import('../../../src/validators/file-format.validator.js'); + validator = mod.default; + }); + + it('should accept valid file_format', async () => { + const result = await runValidation(validator, { file_format: 'json' }); + expect(result.isEmpty()).toBe(true); + }); + + it('should reject missing file_format', async () => { + const result = await runValidation(validator, {}); + expect(result.isEmpty()).toBe(false); + }); + + it('should reject non-string', async () => { + const result = await runValidation(validator, { file_format: 123 }); + expect(result.isEmpty()).toBe(false); + }); + + it('should reject empty string', async () => { + const result = await runValidation(validator, { file_format: '' }); + expect(result.isEmpty()).toBe(false); + }); + + it('should reject string exceeding 200 chars', async () => { + const result = await runValidation(validator, { file_format: 'a'.repeat(201) }); + expect(result.isEmpty()).toBe(false); + }); +}); diff --git a/api/tests/unit/validators/fileformat-confirmation.validator.test.ts b/api/tests/unit/validators/fileformat-confirmation.validator.test.ts new file mode 100644 index 000000000..5e40e9efd --- /dev/null +++ b/api/tests/unit/validators/fileformat-confirmation.validator.test.ts @@ -0,0 +1,39 @@ +import { describe, it, expect, beforeAll } from 'vitest'; +import { validationResult } from 'express-validator'; + +const mockReq = (body: any) => ({ body, query: {}, params: {}, headers: {}, get: () => undefined }); + +async function runValidation(schema: any, body: any) { + const req = mockReq(body); + await schema.run(req); + return validationResult(req); +} + +describe('fileformat-confirmation.validator', () => { + let validator: any; + + beforeAll(async () => { + const mod = await import('../../../src/validators/fileformat-confirmation.validator.js'); + validator = mod.default; + }); + + it('should accept true', async () => { + const result = await runValidation(validator, { fileformat_confirmation: true }); + expect(result.isEmpty()).toBe(true); + }); + + it('should accept false', async () => { + const result = await runValidation(validator, { fileformat_confirmation: false }); + expect(result.isEmpty()).toBe(true); + }); + + it('should reject missing field', async () => { + const result = await runValidation(validator, {}); + expect(result.isEmpty()).toBe(false); + }); + + it('should reject non-boolean value', async () => { + const result = await runValidation(validator, { fileformat_confirmation: 123 }); + expect(result.isEmpty()).toBe(false); + }); +}); diff --git a/api/tests/unit/validators/index.validator.test.ts b/api/tests/unit/validators/index.validator.test.ts new file mode 100644 index 000000000..46e8a7c90 --- /dev/null +++ b/api/tests/unit/validators/index.validator.test.ts @@ -0,0 +1,82 @@ +import { describe, it, expect, vi, beforeAll } from 'vitest'; + +function flushPromises() { + return new Promise((resolve) => setTimeout(resolve, 50)); +} + +describe('validators/index', () => { + let validatorFactory: any; + + beforeAll(async () => { + const mod = await import('../../../src/validators/index.js'); + validatorFactory = mod.default; + }); + + it('should export a function', () => { + expect(typeof validatorFactory).toBe('function'); + }); + + it('should return a middleware function for each supported route', () => { + const routes = ['auth', 'project', 'cms', 'file_format', 'destination_stack', 'affix', 'affix_confirmation_validator', 'fileformat_confirmation_validator', 'stack']; + for (const route of routes) { + const middleware = validatorFactory(route); + expect(typeof middleware).toBe('function'); + } + }); + + it('should call next() when auth validation passes', async () => { + const middleware = validatorFactory('auth'); + const req = { + body: { email: 'test@example.com', password: 'pass123', region: 'NA' }, + query: {}, params: {}, headers: {}, get: () => undefined, + }; + const res = {}; + const next = vi.fn(); + middleware(req, res, next); + await flushPromises(); + expect(next).toHaveBeenCalledTimes(1); + expect(next.mock.calls[0][0]).toBeUndefined(); + }); + + it('should call next with error when auth validation fails', async () => { + const middleware = validatorFactory('auth'); + const req = { + body: {}, + query: {}, params: {}, headers: {}, get: () => undefined, + }; + const res = {}; + const next = vi.fn(); + middleware(req, res, next); + await flushPromises(); + expect(next).toHaveBeenCalledTimes(1); + expect(next.mock.calls[0][0]).toBeDefined(); + }); + + it('should call next() when cms validation passes', async () => { + const middleware = validatorFactory('cms'); + const req = { + body: { legacy_cms: 'wordpress' }, + query: {}, params: {}, headers: {}, get: () => undefined, + }; + const res = {}; + const next = vi.fn(); + middleware(req, res, next); + await flushPromises(); + expect(next).toHaveBeenCalledTimes(1); + expect(next.mock.calls[0][0]).toBeUndefined(); + }); + + it('should call next with error when cms validation fails', async () => { + const middleware = validatorFactory('cms'); + const req = { + body: {}, + query: {}, params: {}, headers: {}, get: () => undefined, + }; + const res = {}; + const next = vi.fn(); + middleware(req, res, next); + await flushPromises(); + expect(next).toHaveBeenCalledTimes(1); + expect(next.mock.calls[0][0]).toBeDefined(); + }); +}); diff --git a/api/tests/unit/validators/project.validator.test.ts b/api/tests/unit/validators/project.validator.test.ts new file mode 100644 index 000000000..e6815ea15 --- /dev/null +++ b/api/tests/unit/validators/project.validator.test.ts @@ -0,0 +1,71 @@ +import { describe, it, expect, beforeAll } from 'vitest'; +import { validationResult } from 'express-validator'; + +const mockReq = (body: any) => ({ + body, + query: {}, + params: {}, + headers: {}, + get: () => undefined, +}); + +async function runValidation(schema: any, body: any) { + const req = mockReq(body); + await schema.run(req); + return validationResult(req); +} + +describe('project.validator', () => { + let projectValidator: any; + + beforeAll(async () => { + const mod = await import('../../../src/validators/project.validator.js'); + projectValidator = mod.default; + }); + + it('should accept valid project body', async () => { + const result = await runValidation(projectValidator, { + name: 'My Project', + description: 'A test project', + }); + expect(result.isEmpty()).toBe(true); + }); + + it('should reject missing name', async () => { + const result = await runValidation(projectValidator, { + description: 'A test project', + }); + expect(result.isEmpty()).toBe(false); + }); + + it('should reject name longer than 200 chars', async () => { + const result = await runValidation(projectValidator, { + name: 'a'.repeat(201), + description: 'A test project', + }); + expect(result.isEmpty()).toBe(false); + }); + + it('should reject non-string name', async () => { + const result = await runValidation(projectValidator, { + name: 12345, + description: 'A test project', + }); + expect(result.isEmpty()).toBe(false); + }); + + it('should reject missing description', async () => { + const result = await runValidation(projectValidator, { + name: 'My Project', + }); + expect(result.isEmpty()).toBe(false); + }); + + it('should reject non-string description', async () => { + const result = await runValidation(projectValidator, { + name: 'My Project', + description: 12345, + }); + expect(result.isEmpty()).toBe(false); + }); +}); diff --git a/api/tests/unit/validators/stack.validator.test.ts b/api/tests/unit/validators/stack.validator.test.ts new file mode 100644 index 000000000..1f53c6a18 --- /dev/null +++ b/api/tests/unit/validators/stack.validator.test.ts @@ -0,0 +1,54 @@ +import { describe, it, expect, beforeAll } from 'vitest'; +import { validationResult } from 'express-validator'; + +const mockReq = (body: any) => ({ body, query: {}, params: {}, headers: {}, get: () => undefined }); + +async function runValidation(schema: any, body: any) { + const req = mockReq(body); + await schema.run(req); + return validationResult(req); +} + +describe('stack.validator', () => { + let validator: any; + + beforeAll(async () => { + const mod = await import('../../../src/validators/stack.validator.js'); + validator = mod.default; + }); + + it('should accept valid name and description', async () => { + const result = await runValidation(validator, { name: 'My Stack', description: 'A test stack' }); + expect(result.isEmpty()).toBe(true); + }); + + it('should reject missing name', async () => { + const result = await runValidation(validator, { description: 'A test stack' }); + expect(result.isEmpty()).toBe(false); + }); + + it('should reject non-string name', async () => { + const result = await runValidation(validator, { name: 123, description: 'A test stack' }); + expect(result.isEmpty()).toBe(false); + }); + + it('should reject name exceeding 255 chars', async () => { + const result = await runValidation(validator, { name: 'a'.repeat(256), description: 'A test stack' }); + expect(result.isEmpty()).toBe(false); + }); + + it('should reject missing description', async () => { + const result = await runValidation(validator, { name: 'My Stack' }); + expect(result.isEmpty()).toBe(false); + }); + + it('should reject description exceeding 512 chars', async () => { + const result = await runValidation(validator, { name: 'My Stack', description: 'a'.repeat(513) }); + expect(result.isEmpty()).toBe(false); + }); + + it('should accept empty description', async () => { + const result = await runValidation(validator, { name: 'My Stack', description: '' }); + expect(result.isEmpty()).toBe(true); + }); +}); diff --git a/api/vitest.config.ts b/api/vitest.config.ts new file mode 100644 index 000000000..501a0ea7d --- /dev/null +++ b/api/vitest.config.ts @@ -0,0 +1,45 @@ +import { defineConfig } from 'vitest/config'; + +export default defineConfig({ + test: { + globals: true, + environment: 'node', + setupFiles: ['./tests/setup.ts'], + coverage: { + provider: 'v8', + reporter: ['text', 'lcov', 'html'], + include: ['src/**/*.ts'], + exclude: [ + '**/node_modules/**', + '**/tests/**', + 'src/server.ts', + 'src/database.ts', + 'src/config/**', + 'src/services/wordpress.service.ts', + 'src/services/aem.service.ts', + 'src/services/contentful.service.ts', + 'src/services/sitecore.service.ts', + 'src/services/drupal.service.ts', + 'src/services/drupal/**', + 'src/services/contentful/**', + 'src/services/runCli.service.ts', + 'src/utils/content-type-creator.utils.ts', + 'src/utils/entries-field-creator.utils.ts', + 'src/utils/test-folder-creator.utils.ts', + 'src/utils/optimized-query-builder.utils.ts', + 'src/utils/custom-logger.utils.ts', + 'src/utils/wordpressParseUtil.ts', + 'src/utils/watch.utils.ts', + 'src/utils/logger.ts', + 'src/utils/lowdb-lodash.utils.ts', + 'src/models/types.ts', + ], + thresholds: { + lines: 80, + functions: 80, + branches: 60, + statements: 80, + }, + }, + }, +}); diff --git a/ui/package-lock.json b/ui/package-lock.json index a95618229..246c062c6 100644 --- a/ui/package-lock.json +++ b/ui/package-lock.json @@ -11,7 +11,6 @@ "@contentstack/json-rte-serializer": "^3.0.5", "@contentstack/venus-components": "^3.0.3", "@reduxjs/toolkit": "^2.8.2", - "@testing-library/jest-dom": "^6.0.0", "@types/react": "^18.3.21", "@types/react-dom": "^18.2.13", "@types/react-redux": "^7.1.33", @@ -34,24 +33,75 @@ "vite-tsconfig-paths": "^6.1.1" }, "devDependencies": { + "@testing-library/jest-dom": "^6.9.1", + "@testing-library/react": "^16.3.2", + "@testing-library/user-event": "^14.6.1", + "@vitest/coverage-v8": "^4.0.18", + "@vitest/ui": "^4.0.18", "eslint": "^8.51.0", "eslint-plugin-react": "^7.33.2", "eslint-plugin-react-hooks": "^4.6.0", - "prettier": "^3.3.3" + "jsdom": "^28.1.0", + "prettier": "^3.3.3", + "vitest": "^4.0.18" } }, + "node_modules/@acemir/cssom": { + "version": "0.9.31", + "resolved": "https://registry.npmjs.org/@acemir/cssom/-/cssom-0.9.31.tgz", + "integrity": "sha512-ZnR3GSaH+/vJ0YlHau21FjfLYjMpYVIzTD8M8vIEQvIGxeOXyXdzCI140rrCY862p/C/BbzWsjc1dgnM9mkoTA==", + "dev": true, + "license": "MIT" + }, "node_modules/@adobe/css-tools": { "version": "4.4.4", "resolved": "https://registry.npmjs.org/@adobe/css-tools/-/css-tools-4.4.4.tgz", "integrity": "sha512-Elp+iwUx5rN5+Y8xLt5/GRoG20WGoDCQ/1Fb+1LiGtvwbDavuSk0jhD/eZdckHAuzcDzccnkv+rEjyWfRx18gg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@asamuzakjp/css-color": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/@asamuzakjp/css-color/-/css-color-4.1.2.tgz", + "integrity": "sha512-NfBUvBaYgKIuq6E/RBLY1m0IohzNHAYyaJGuTK79Z23uNwmz2jl1mPsC5ZxCCxylinKhT1Amn5oNTlx1wN8cQg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@csstools/css-calc": "^3.0.0", + "@csstools/css-color-parser": "^4.0.1", + "@csstools/css-parser-algorithms": "^4.0.0", + "@csstools/css-tokenizer": "^4.0.0", + "lru-cache": "^11.2.5" + } + }, + "node_modules/@asamuzakjp/dom-selector": { + "version": "6.8.1", + "resolved": "https://registry.npmjs.org/@asamuzakjp/dom-selector/-/dom-selector-6.8.1.tgz", + "integrity": "sha512-MvRz1nCqW0fsy8Qz4dnLIvhOlMzqDVBabZx6lH+YywFDdjXhMY37SmpV1XFX3JzG5GWHn63j6HX6QPr3lZXHvQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@asamuzakjp/nwsapi": "^2.3.9", + "bidi-js": "^1.0.3", + "css-tree": "^3.1.0", + "is-potential-custom-element-name": "^1.0.1", + "lru-cache": "^11.2.6" + } + }, + "node_modules/@asamuzakjp/nwsapi": { + "version": "2.3.9", + "resolved": "https://registry.npmjs.org/@asamuzakjp/nwsapi/-/nwsapi-2.3.9.tgz", + "integrity": "sha512-n8GuYSrI9bF7FFZ/SjhwevlHc8xaVlb/7HmHelnc/PZXBD2ZR49NnN9sMMuDdEGPeeRQ5d0hqlSlEpgCX3Wl0Q==", + "dev": true, "license": "MIT" }, "node_modules/@babel/code-frame": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.27.1.tgz", - "integrity": "sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==", + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.0.tgz", + "integrity": "sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw==", + "license": "MIT", "dependencies": { - "@babel/helper-validator-identifier": "^7.27.1", + "@babel/helper-validator-identifier": "^7.28.5", "js-tokens": "^4.0.0", "picocolors": "^1.1.1" }, @@ -60,12 +110,13 @@ } }, "node_modules/@babel/generator": { - "version": "7.28.5", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.28.5.tgz", - "integrity": "sha512-3EwLFhZ38J4VyIP6WNtt2kUdW9dokXA9Cr4IVIFHuCpZ3H8/YFOl5JjZHisrn1fATPBmKKqXzDFvh9fUwHz6CQ==", + "version": "7.29.1", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.29.1.tgz", + "integrity": "sha512-qsaF+9Qcm2Qv8SRIMMscAvG4O3lJ0F1GuMo5HR/Bp02LopNgnZBC/EkbevHFeGs4ls/oPz9v+Bsmzbkbe+0dUw==", + "license": "MIT", "dependencies": { - "@babel/parser": "^7.28.5", - "@babel/types": "^7.28.5", + "@babel/parser": "^7.29.0", + "@babel/types": "^7.29.0", "@jridgewell/gen-mapping": "^0.3.12", "@jridgewell/trace-mapping": "^0.3.28", "jsesc": "^3.0.2" @@ -78,17 +129,19 @@ "version": "7.28.0", "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.28.0.tgz", "integrity": "sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==", + "license": "MIT", "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-module-imports": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.27.1.tgz", - "integrity": "sha512-0gSFWUPNXNopqtIPQvlD5WgXYI5GY2kP2cCvoT8kczjbfcfuIljTbcWrulD1CIPIX2gt1wghbDy08yE1p+/r3w==", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.28.6.tgz", + "integrity": "sha512-l5XkZK7r7wa9LucGw9LwZyyCUscb4x37JWTPz7swwFE/0FMQAGpiWUZn8u9DzkSBWEcK25jmvubfpw2dnAMdbw==", + "license": "MIT", "dependencies": { - "@babel/traverse": "^7.27.1", - "@babel/types": "^7.27.1" + "@babel/traverse": "^7.28.6", + "@babel/types": "^7.28.6" }, "engines": { "node": ">=6.9.0" @@ -98,6 +151,7 @@ "version": "7.27.1", "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==", + "license": "MIT", "engines": { "node": ">=6.9.0" } @@ -106,16 +160,18 @@ "version": "7.28.5", "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz", "integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==", + "license": "MIT", "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/parser": { - "version": "7.28.5", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.28.5.tgz", - "integrity": "sha512-KKBU1VGYR7ORr3At5HAtUQ+TV3SzRCXmA/8OdDZiLDBIZxVyzXuztPjfLd3BV1PRAQGCMWWSHYhL0F8d5uHBDQ==", + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.0.tgz", + "integrity": "sha512-IyDgFV5GeDUVX4YdF/3CPULtVGSXXMLh1xVIgdCgxApktqnQV0r7/8Nqthg+8YLGaAtdyIlo2qIdZrbCv4+7ww==", + "license": "MIT", "dependencies": { - "@babel/types": "^7.28.5" + "@babel/types": "^7.29.0" }, "bin": { "parser": "bin/babel-parser.js" @@ -125,37 +181,40 @@ } }, "node_modules/@babel/runtime": { - "version": "7.28.4", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.28.4.tgz", - "integrity": "sha512-Q/N6JNWvIvPnLDvjlE1OUBLPQHH6l3CltCEsHIujp45zQUSSh8K+gHnaEX45yAT1nyngnINhvWtzN+Nb9D8RAQ==", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.28.6.tgz", + "integrity": "sha512-05WQkdpL9COIMz4LjTxGpPNCdlpyimKppYNoJ5Di5EUObifl8t4tuLuUBBZEpoLYOmfvIWrsp9fCl0HoPRVTdA==", + "license": "MIT", "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/template": { - "version": "7.27.2", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.27.2.tgz", - "integrity": "sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw==", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.28.6.tgz", + "integrity": "sha512-YA6Ma2KsCdGb+WC6UpBVFJGXL58MDA6oyONbjyF/+5sBgxY/dwkhLogbMT2GXXyU84/IhRw/2D1Os1B/giz+BQ==", + "license": "MIT", "dependencies": { - "@babel/code-frame": "^7.27.1", - "@babel/parser": "^7.27.2", - "@babel/types": "^7.27.1" + "@babel/code-frame": "^7.28.6", + "@babel/parser": "^7.28.6", + "@babel/types": "^7.28.6" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/traverse": { - "version": "7.28.5", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.28.5.tgz", - "integrity": "sha512-TCCj4t55U90khlYkVV/0TfkJkAkUg3jZFA3Neb7unZT8CPok7iiRfaX0F+WnqWqt7OxhOn0uBKXCw4lbL8W0aQ==", + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.29.0.tgz", + "integrity": "sha512-4HPiQr0X7+waHfyXPZpWPfWL/J7dcN1mx9gL6WdQVMbPnF3+ZhSMs8tCxN7oHddJE9fhNE7+lxdnlyemKfJRuA==", + "license": "MIT", "dependencies": { - "@babel/code-frame": "^7.27.1", - "@babel/generator": "^7.28.5", + "@babel/code-frame": "^7.29.0", + "@babel/generator": "^7.29.0", "@babel/helper-globals": "^7.28.0", - "@babel/parser": "^7.28.5", - "@babel/template": "^7.27.2", - "@babel/types": "^7.28.5", + "@babel/parser": "^7.29.0", + "@babel/template": "^7.28.6", + "@babel/types": "^7.29.0", "debug": "^4.3.1" }, "engines": { @@ -163,9 +222,10 @@ } }, "node_modules/@babel/types": { - "version": "7.28.5", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.28.5.tgz", - "integrity": "sha512-qQ5m48eI/MFLQ5PxQj4PFaprjyCTLI37ElWMmNs0K8Lk3dVeOdNpB3ks8jc7yM5CDmVC73eMVk/trk3fgmrUpA==", + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.0.tgz", + "integrity": "sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==", + "license": "MIT", "dependencies": { "@babel/helper-string-parser": "^7.27.1", "@babel/helper-validator-identifier": "^7.28.5" @@ -174,6 +234,29 @@ "node": ">=6.9.0" } }, + "node_modules/@bcoe/v8-coverage": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-1.0.2.tgz", + "integrity": "sha512-6zABk/ECA/QYSCQ1NGiVwwbQerUCZ+TQbp64Q3AgmfNvurHH0j8TtXa1qbShXA6qqkpAj4V5W8pP6mLe1mcMqA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/@bramus/specificity": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/@bramus/specificity/-/specificity-2.4.2.tgz", + "integrity": "sha512-ctxtJ/eA+t+6q2++vj5j7FYX3nRu311q1wfYH3xjlLOsczhlhxAg2FWNUXhpGvAw3BWo1xBcvOV6/YLc2r5FJw==", + "dev": true, + "license": "MIT", + "dependencies": { + "css-tree": "^3.0.0" + }, + "bin": { + "specificity": "bin/cli.js" + } + }, "node_modules/@contentstack/json-rte-serializer": { "version": "3.0.5", "resolved": "https://registry.npmjs.org/@contentstack/json-rte-serializer/-/json-rte-serializer-3.0.5.tgz", @@ -276,191 +359,27 @@ "react-dom": "^16.8.6 || ^17 || ^18 || ^19" } }, - "node_modules/@contentstack/venus-components/node_modules/@babel/runtime": { - "version": "7.26.10", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.26.10.tgz", - "integrity": "sha512-2WJMeRQPHKSPemqk/awGrAiuFfzBmOIPXKizAsVhWH9YJqLZ0H+HS4c8loHGgW6utJ3E/ejXQUsiGaQy2NZ9Fw==", - "license": "MIT", - "dependencies": { - "regenerator-runtime": "^0.14.0" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@contentstack/venus-components/node_modules/@emotion/cache": { - "version": "10.0.9", - "resolved": "https://registry.npmjs.org/@emotion/cache/-/cache-10.0.9.tgz", - "integrity": "sha512-f7MblpE2xoimC4fCMZ9pivmsIn7hyWRIvY75owMDi8pdOSeh+w5tH3r4hBJv/LLrwiMM7cTQURqTPcYoL5pWnw==", - "dependencies": { - "@emotion/sheet": "0.9.2", - "@emotion/stylis": "0.8.3", - "@emotion/utils": "0.11.1", - "@emotion/weak-memoize": "0.2.2" - } - }, - "node_modules/@contentstack/venus-components/node_modules/@emotion/core": { - "version": "10.0.9", - "resolved": "https://registry.npmjs.org/@emotion/core/-/core-10.0.9.tgz", - "integrity": "sha512-v5a77dV+uWGoy9w6R3MXZG01lqHcXgoy/jGmJqPDGhPjmpWg26LWXAphYZxpZffFUwDUlDdYDiX5HtaKphvJnQ==", - "dependencies": { - "@emotion/cache": "^10.0.9", - "@emotion/css": "^10.0.9", - "@emotion/serialize": "^0.11.6", - "@emotion/sheet": "0.9.2", - "@emotion/utils": "0.11.1" - }, - "peerDependencies": { - "react": ">=16.3.0" - } - }, - "node_modules/@contentstack/venus-components/node_modules/@emotion/core/node_modules/@emotion/css": { - "version": "10.0.27", - "resolved": "https://registry.npmjs.org/@emotion/css/-/css-10.0.27.tgz", - "integrity": "sha512-6wZjsvYeBhyZQYNrGoR5yPMYbMBNEnanDrqmsqS1mzDm1cOTu12shvl2j4QHNS36UaTE0USIJawCH9C8oW34Zw==", - "dependencies": { - "@emotion/serialize": "^0.11.15", - "@emotion/utils": "0.11.3", - "babel-plugin-emotion": "^10.0.27" - } - }, - "node_modules/@contentstack/venus-components/node_modules/@emotion/core/node_modules/@emotion/css/node_modules/@emotion/utils": { - "version": "0.11.3", - "resolved": "https://registry.npmjs.org/@emotion/utils/-/utils-0.11.3.tgz", - "integrity": "sha512-0o4l6pZC+hI88+bzuaX/6BgOvQVhbt2PfmxauVaYOGgbsAw14wdKyvMCZXnsnsHys94iadcF+RG/wZyx6+ZZBw==" - }, - "node_modules/@contentstack/venus-components/node_modules/@emotion/hash": { - "version": "0.8.0", - "resolved": "https://registry.npmjs.org/@emotion/hash/-/hash-0.8.0.tgz", - "integrity": "sha512-kBJtf7PH6aWwZ6fka3zQ0p6SBYzx4fl1LoZXE2RrnYST9Xljm7WfKJrU4g/Xr3Beg72MLrp1AWNUmuYJTL7Cow==" - }, - "node_modules/@contentstack/venus-components/node_modules/@emotion/memoize": { - "version": "0.7.4", - "resolved": "https://registry.npmjs.org/@emotion/memoize/-/memoize-0.7.4.tgz", - "integrity": "sha512-Ja/Vfqe3HpuzRsG1oBtWTHk2PGZ7GR+2Vz5iYGelAw8dx32K0y7PjVuxK6z1nMpZOqAFsRUPCkK1YjJ56qJlgw==" - }, - "node_modules/@contentstack/venus-components/node_modules/@emotion/serialize": { - "version": "0.11.16", - "resolved": "https://registry.npmjs.org/@emotion/serialize/-/serialize-0.11.16.tgz", - "integrity": "sha512-G3J4o8by0VRrO+PFeSc3js2myYNOXVJ3Ya+RGVxnshRYgsvErfAOglKAiy1Eo1vhzxqtUvjCyS5gtewzkmvSSg==", - "dependencies": { - "@emotion/hash": "0.8.0", - "@emotion/memoize": "0.7.4", - "@emotion/unitless": "0.7.5", - "@emotion/utils": "0.11.3", - "csstype": "^2.5.7" - } - }, - "node_modules/@contentstack/venus-components/node_modules/@emotion/serialize/node_modules/@emotion/utils": { - "version": "0.11.3", - "resolved": "https://registry.npmjs.org/@emotion/utils/-/utils-0.11.3.tgz", - "integrity": "sha512-0o4l6pZC+hI88+bzuaX/6BgOvQVhbt2PfmxauVaYOGgbsAw14wdKyvMCZXnsnsHys94iadcF+RG/wZyx6+ZZBw==" - }, - "node_modules/@contentstack/venus-components/node_modules/@emotion/serialize/node_modules/csstype": { - "version": "2.6.21", - "resolved": "https://registry.npmjs.org/csstype/-/csstype-2.6.21.tgz", - "integrity": "sha512-Z1PhmomIfypOpoMjRQB70jfvy/wxT50qW08YXO5lMIJkrdq4yOTR+AW7FqutScmB9NkLwxo+jU+kZLbofZZq/w==" - }, - "node_modules/@contentstack/venus-components/node_modules/@emotion/sheet": { - "version": "0.9.2", - "resolved": "https://registry.npmjs.org/@emotion/sheet/-/sheet-0.9.2.tgz", - "integrity": "sha512-pVBLzIbC/QCHDKJF2E82V2H/W/B004mDFQZiyo/MSR+VC4pV5JLG0TF/zgQDFvP3fZL/5RTPGEmXlYJBMUuJ+A==" - }, - "node_modules/@contentstack/venus-components/node_modules/@emotion/unitless": { - "version": "0.7.5", - "resolved": "https://registry.npmjs.org/@emotion/unitless/-/unitless-0.7.5.tgz", - "integrity": "sha512-OWORNpfjMsSSUBVrRBVGECkhWcULOAJz9ZW8uK9qgxD+87M7jHRcvh/A96XXNhXTLmKcoYSQtBEX7lHMO7YRwg==" - }, - "node_modules/@contentstack/venus-components/node_modules/@emotion/utils": { - "version": "0.11.1", - "resolved": "https://registry.npmjs.org/@emotion/utils/-/utils-0.11.1.tgz", - "integrity": "sha512-8M3VN0hetwhsJ8dH8VkVy7xo5/1VoBsDOk/T4SJOeXwTO1c4uIqVNx2qyecLFnnUWD5vvUqHQ1gASSeUN6zcTg==" - }, - "node_modules/@contentstack/venus-components/node_modules/@emotion/weak-memoize": { - "version": "0.2.2", - "resolved": "https://registry.npmjs.org/@emotion/weak-memoize/-/weak-memoize-0.2.2.tgz", - "integrity": "sha512-n/VQ4mbfr81aqkx/XmVicOLjviMuy02eenSdJY33SVA7S2J42EU0P1H0mOogfYedb3wXA0d/LVtBrgTSm04WEA==" - }, - "node_modules/@contentstack/venus-components/node_modules/@testing-library/react-hooks": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/@testing-library/react-hooks/-/react-hooks-8.0.1.tgz", - "integrity": "sha512-Aqhl2IVmLt8IovEVarNDFuJDVWVvhnr9/GCU6UUnrYXwgDFF9h2L2o2P9KBni1AST5sT6riAyoukFLyjQUgD/g==", - "dependencies": { - "@babel/runtime": "^7.12.5", - "react-error-boundary": "^3.1.0" - }, - "engines": { - "node": ">=12" - }, - "peerDependencies": { - "@types/react": "^16.9.0 || ^17.0.0", - "react": "^16.9.0 || ^17.0.0", - "react-dom": "^16.9.0 || ^17.0.0", - "react-test-renderer": "^16.9.0 || ^17.0.0" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "react-dom": { - "optional": true - }, - "react-test-renderer": { - "optional": true - } - } - }, - "node_modules/@contentstack/venus-components/node_modules/@types/react": { - "version": "17.0.91", - "resolved": "https://registry.npmjs.org/@types/react/-/react-17.0.91.tgz", - "integrity": "sha512-xauZca6qMeCU3Moy0KxCM9jtf1vyk6qRYK39Ryf3afUqwgNUjRIGoDdS9BcGWgAMGSg1hvP4XcmlYrM66PtqeA==", - "optional": true, - "peer": true, - "dependencies": { - "@types/prop-types": "*", - "@types/scheduler": "^0.16", - "csstype": "^3.2.2" - } - }, "node_modules/@contentstack/venus-components/node_modules/immer": { "version": "9.0.21", "resolved": "https://registry.npmjs.org/immer/-/immer-9.0.21.tgz", "integrity": "sha512-bc4NBHqOqSfRW7POMkHd51LvClaeMXpm8dx0e8oE2GORbq5aRK7Bxl4FyzVLdGtLmvLKL7BTDBG5ACQm4HWjTA==", + "license": "MIT", "funding": { "type": "opencollective", "url": "https://opencollective.com/immer" } }, - "node_modules/@contentstack/venus-components/node_modules/memoize-one": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/memoize-one/-/memoize-one-5.0.0.tgz", - "integrity": "sha512-7g0+ejkOaI9w5x6LvQwmj68kUj6rxROywPSCqmclG/HBacmFnZqhVscQ8kovkn9FBCNJmOz6SY42+jnvZzDWdw==" - }, - "node_modules/@contentstack/venus-components/node_modules/react-ace": { - "version": "9.5.0", - "resolved": "https://registry.npmjs.org/react-ace/-/react-ace-9.5.0.tgz", - "integrity": "sha512-4l5FgwGh6K7A0yWVMQlPIXDItM4Q9zzXRqOae8KkCl6MkOob7sC1CzHxZdOGvV+QioKWbX2p5HcdOVUv6cAdSg==", - "dependencies": { - "ace-builds": "^1.4.13", - "diff-match-patch": "^1.0.5", - "lodash.get": "^4.4.2", - "lodash.isequal": "^4.5.0", - "prop-types": "^15.7.2" - }, - "peerDependencies": { - "react": "^0.13.0 || ^0.14.0 || ^15.0.1 || ^16.0.0 || ^17.0.0", - "react-dom": "^0.13.0 || ^0.14.0 || ^15.0.1 || ^16.0.0 || ^17.0.0" - } - }, "node_modules/@contentstack/venus-components/node_modules/react-is": { "version": "17.0.2", "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", - "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==" + "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==", + "license": "MIT" }, "node_modules/@contentstack/venus-components/node_modules/react-redux": { "version": "7.2.9", "resolved": "https://registry.npmjs.org/react-redux/-/react-redux-7.2.9.tgz", "integrity": "sha512-Gx4L3uM182jEEayZfRbI/G11ZpYdNAnBs70lFVMNdHJI76XYtR+7m0MN+eAs7UHBPhWXcnFPaS+9owSCJQHNpQ==", + "license": "MIT", "dependencies": { "@babel/runtime": "^7.15.4", "@types/react-redux": "^7.1.20", @@ -481,204 +400,163 @@ } } }, - "node_modules/@contentstack/venus-components/node_modules/react-select": { - "name": "@contentstack/react-select", - "version": "3.2.8", - "resolved": "https://registry.npmjs.org/@contentstack/react-select/-/react-select-3.2.8.tgz", - "integrity": "sha512-ffc5PKxXgOS0oc1UDUIhZNFZ+XNymw9sk9USRaemzFlCEI5ywl68uJRICiRySO1YMxZIN96aG0cUXTXKfXE4qg==", + "node_modules/@contentstack/venus-components/node_modules/redux": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/redux/-/redux-4.2.1.tgz", + "integrity": "sha512-LAUYz4lc+Do8/g7aeRa8JkyDErK6ekstQaqWQrNRW//MY1TvCEpMtpTWvlQ+FPbWCx+Xixu/6SHt5N0HR+SB4w==", "license": "MIT", "dependencies": { - "@babel/runtime": "7.26.10", - "@emotion/cache": "10.0.9", - "@emotion/core": "10.0.9", - "@emotion/css": "10.0.9", - "memoize-one": "5.0.0", - "react-input-autosize": "3.0.0", - "react-transition-group": "4.3.0" - }, - "peerDependencies": { - "react": "^16.8.0 || ^17.0.0", - "react-dom": "^16.8.0 || ^17.0.0" - } - }, - "node_modules/@contentstack/venus-components/node_modules/react-select-async-paginate": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/react-select-async-paginate/-/react-select-async-paginate-0.4.1.tgz", - "integrity": "sha512-zWeaN9C9PVQej4bz1+OvU6/ylHE6rHscDYcP+KiWdBedVQ5j2vXBjf/5RWLEvobvtUUHBOTbUF8+m2HDoeIcvQ==", - "dependencies": { - "@babel/runtime": "^7.11.2", - "@seznam/compose-react-refs": "^1.0.4", - "react-is-mounted-hook": "^1.0.3", - "sleep-promise": "^8.0.1" - }, - "peerDependencies": { - "react": "^16.8.0", - "react-select": "^2.0.0 || ^3.0.0" - } - }, - "node_modules/@contentstack/venus-components/node_modules/react-select-async-paginate/node_modules/react-dom": { - "version": "17.0.2", - "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-17.0.2.tgz", - "integrity": "sha512-s4h96KtLDUQlsENhMn1ar8t2bEa+q/YAtj8pPPdIjPDGBDIVNsrD9aXNWqspUe6AzKCIG0C1HZZLqLV7qpOBGA==", - "peer": true, - "dependencies": { - "loose-envify": "^1.1.0", - "object-assign": "^4.1.1", - "scheduler": "^0.20.2" - }, - "peerDependencies": { - "react": "17.0.2" - } - }, - "node_modules/@contentstack/venus-components/node_modules/react-select-async-paginate/node_modules/react-is-mounted-hook": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/react-is-mounted-hook/-/react-is-mounted-hook-1.1.2.tgz", - "integrity": "sha512-yjq3Tj34CiFcdVOS/h6JerWLOLdJqEGKMNpTHc4kWebzz2YtIpgqMRrqxdmQhewM1KJREojdAV2tsNvBsUWyhA==", - "peerDependencies": { - "react": "^16.8.6 || ^17", - "react-dom": "^16.8.6 || ^17" + "@babel/runtime": "^7.9.2" } }, - "node_modules/@contentstack/venus-components/node_modules/react-select/node_modules/@emotion/css": { - "version": "10.0.9", - "resolved": "https://registry.npmjs.org/@emotion/css/-/css-10.0.9.tgz", - "integrity": "sha512-jtHhUSWw+L7yxYgNtC+KJ3Ory90/jiAtpG1qT+gTQQ/RR5AMiigs9/lDHu/vnwljaq2S48FoKb/FZZMlJcC4bw==", + "node_modules/@contentstack/venus-components/node_modules/slate": { + "version": "0.77.2", + "resolved": "https://registry.npmjs.org/slate/-/slate-0.77.2.tgz", + "integrity": "sha512-raG/eyYyRDThz+r/opUD+wxWxwXQi4fH0ViZh41x8qBu8uOPMTBYl10RA7KsF8IK48DS207uPrvWGGBsnMCLwg==", + "license": "MIT", "dependencies": { - "@emotion/serialize": "^0.11.6", - "@emotion/utils": "0.11.1", - "babel-plugin-emotion": "^10.0.9" + "immer": "^9.0.6", + "is-plain-object": "^5.0.0", + "tiny-warning": "^1.0.3" } }, - "node_modules/@contentstack/venus-components/node_modules/react-select/node_modules/react-input-autosize": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/react-input-autosize/-/react-input-autosize-3.0.0.tgz", - "integrity": "sha512-nL9uS7jEs/zu8sqwFE5MAPx6pPkNAriACQ2rGLlqmKr2sPGtN7TXTyDdQt4lbNXVx7Uzadb40x8qotIuru6Rhg==", - "dependencies": { - "prop-types": "^15.5.8" - }, - "peerDependencies": { - "react": "^16.3.0 || ^17.0.0" + "node_modules/@csstools/color-helpers": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/@csstools/color-helpers/-/color-helpers-6.0.1.tgz", + "integrity": "sha512-NmXRccUJMk2AWA5A7e5a//3bCIMyOu2hAtdRYrhPPHjDxINuCwX1w6rnIZ4xjLcp0ayv6h8Pc3X0eJUGiAAXHQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "engines": { + "node": ">=20.19.0" } }, - "node_modules/@contentstack/venus-components/node_modules/react-sortable-hoc": { - "version": "1.11.0", - "resolved": "https://registry.npmjs.org/react-sortable-hoc/-/react-sortable-hoc-1.11.0.tgz", - "integrity": "sha512-v1CDCvdfoR3zLGNp6qsBa4J1BWMEVH25+UKxF/RvQRh+mrB+emqtVHMgZ+WreUiKJoEaiwYoScaueIKhMVBHUg==", - "dependencies": { - "@babel/runtime": "^7.2.0", - "invariant": "^2.2.4", - "prop-types": "^15.5.7" + "node_modules/@csstools/css-calc": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/@csstools/css-calc/-/css-calc-3.1.1.tgz", + "integrity": "sha512-HJ26Z/vmsZQqs/o3a6bgKslXGFAungXGbinULZO3eMsOyNJHeBBZfup5FiZInOghgoM4Hwnmw+OgbJCNg1wwUQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT", + "engines": { + "node": ">=20.19.0" }, "peerDependencies": { - "prop-types": "^15.5.7", - "react": "^0.14.0 || ^15.0.0 || ^16.0.0", - "react-dom": "^0.14.0 || ^15.0.0 || ^16.0.0" + "@csstools/css-parser-algorithms": "^4.0.0", + "@csstools/css-tokenizer": "^4.0.0" } }, - "node_modules/@contentstack/venus-components/node_modules/react-transition-group": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-4.3.0.tgz", - "integrity": "sha512-1qRV1ZuVSdxPlPf4O8t7inxUGpdyO5zG9IoNfJxSO0ImU2A1YWkEQvFPuIPZmMLkg5hYs7vv5mMOyfgSkvAwvw==", + "node_modules/@csstools/css-color-parser": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@csstools/css-color-parser/-/css-color-parser-4.0.1.tgz", + "integrity": "sha512-vYwO15eRBEkeF6xjAno/KQ61HacNhfQuuU/eGwH67DplL0zD5ZixUa563phQvUelA07yDczIXdtmYojCphKJcw==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT", "dependencies": { - "@babel/runtime": "^7.5.5", - "dom-helpers": "^5.0.1", - "loose-envify": "^1.4.0", - "prop-types": "^15.6.2" + "@csstools/color-helpers": "^6.0.1", + "@csstools/css-calc": "^3.0.0" }, - "peerDependencies": { - "react": ">=16.6.0", - "react-dom": ">=16.6.0" - } - }, - "node_modules/@contentstack/venus-components/node_modules/react-virtualized-auto-sizer": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/react-virtualized-auto-sizer/-/react-virtualized-auto-sizer-1.0.5.tgz", - "integrity": "sha512-kivjYVWX15TX2IUrm8F1jaCEX8EXrpy3DD+u41WGqJ1ZqbljWpiwscV+VxOM1l7sSIM1jwi2LADjhhAJkJ9dxA==", "engines": { - "node": ">8.0.0" + "node": ">=20.19.0" }, "peerDependencies": { - "react": "^15.3.0 || ^16.0.0-alpha || ^17.0.0", - "react-dom": "^15.3.0 || ^16.0.0-alpha || ^17.0.0" + "@csstools/css-parser-algorithms": "^4.0.0", + "@csstools/css-tokenizer": "^4.0.0" } }, - "node_modules/@contentstack/venus-components/node_modules/reactjs-social-embed": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/reactjs-social-embed/-/reactjs-social-embed-1.2.0.tgz", - "integrity": "sha512-Avi6yyQJ5rbLmAzLkJJHBL+86+3if40QGgOUEu0/GxpARWyMd6w4R7C4AAufQqGC2x7V+HFpMkUovdk4/HjHSg==", - "dependencies": { - "jsonp-p": "^2.0.0" - }, + "node_modules/@csstools/css-parser-algorithms": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@csstools/css-parser-algorithms/-/css-parser-algorithms-4.0.0.tgz", + "integrity": "sha512-+B87qS7fIG3L5h3qwJ/IFbjoVoOe/bpOdh9hAjXbvx0o8ImEmUsGXN0inFOnk2ChCFgqkkGFQ+TpM5rbhkKe4w==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT", "engines": { - "node": ">=8", - "npm": ">=5" + "node": ">=20.19.0" }, "peerDependencies": { - "prop-types": "^15.5.4", - "react": "^15.0.0 || ^16.0.0", - "react-dom": "^15.0.0 || ^16.0.0" - } - }, - "node_modules/@contentstack/venus-components/node_modules/redux": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/redux/-/redux-4.2.1.tgz", - "integrity": "sha512-LAUYz4lc+Do8/g7aeRa8JkyDErK6ekstQaqWQrNRW//MY1TvCEpMtpTWvlQ+FPbWCx+Xixu/6SHt5N0HR+SB4w==", - "dependencies": { - "@babel/runtime": "^7.9.2" - } - }, - "node_modules/@contentstack/venus-components/node_modules/regenerator-runtime": { - "version": "0.14.1", - "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz", - "integrity": "sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==", - "license": "MIT" - }, - "node_modules/@contentstack/venus-components/node_modules/scheduler": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.20.2.tgz", - "integrity": "sha512-2eWfGgAqqWFGqtdMmcL5zCMK1U8KlXv8SQFGglL3CEtd0aDVDWgeF/YoCmvln55m5zSk3J/20hTaSBeSObsQDQ==", - "peer": true, - "dependencies": { - "loose-envify": "^1.1.0", - "object-assign": "^4.1.1" + "@csstools/css-tokenizer": "^4.0.0" } }, - "node_modules/@contentstack/venus-components/node_modules/slate": { - "version": "0.77.2", - "resolved": "https://registry.npmjs.org/slate/-/slate-0.77.2.tgz", - "integrity": "sha512-raG/eyYyRDThz+r/opUD+wxWxwXQi4fH0ViZh41x8qBu8uOPMTBYl10RA7KsF8IK48DS207uPrvWGGBsnMCLwg==", - "dependencies": { - "immer": "^9.0.6", - "is-plain-object": "^5.0.0", - "tiny-warning": "^1.0.3" - } + "node_modules/@csstools/css-syntax-patches-for-csstree": { + "version": "1.0.27", + "resolved": "https://registry.npmjs.org/@csstools/css-syntax-patches-for-csstree/-/css-syntax-patches-for-csstree-1.0.27.tgz", + "integrity": "sha512-sxP33Jwg1bviSUXAV43cVYdmjt2TLnLXNqCWl9xmxHawWVjGz/kEbdkr7F9pxJNBN2Mh+dq0crgItbW6tQvyow==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0" }, - "node_modules/@contentstack/venus-components/node_modules/storybook-addon-whats-new": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/storybook-addon-whats-new/-/storybook-addon-whats-new-1.0.3.tgz", - "integrity": "sha512-5je0qJh2ahPbbS2pwOkgKKmUDJAT0aAn3cSmap0Hp3hzFlu4uaDPiY00T4mPF3BsavBrkPNmy7kKIHTXnmRu5A==", - "peerDependencies": { - "@storybook/addons": "^6.4.0", - "@storybook/api": "^6.4.0", - "@storybook/components": "^6.4.0", - "@storybook/core-events": "^6.4.0", - "@storybook/theming": "^6.4.0", - "react": "^16.8.0 || ^17.0.0", - "react-dom": "^16.8.0 || ^17.0.0" - }, - "peerDependenciesMeta": { - "react": { - "optional": true + "node_modules/@csstools/css-tokenizer": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@csstools/css-tokenizer/-/css-tokenizer-4.0.0.tgz", + "integrity": "sha512-QxULHAm7cNu72w97JUNCBFODFaXpbDg+dP8b/oWFAZ2MTRppA3U00Y2L1HqaS4J6yBqxwa/Y3nMBaxVKbB/NsA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" }, - "react-dom": { - "optional": true + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" } + ], + "license": "MIT", + "engines": { + "node": ">=20.19.0" } }, "node_modules/@emotion/babel-plugin": { "version": "11.13.5", "resolved": "https://registry.npmjs.org/@emotion/babel-plugin/-/babel-plugin-11.13.5.tgz", "integrity": "sha512-pxHCpT2ex+0q+HH91/zsdHkw/lXd468DIN2zvfvLtPKLLMo6gQj7oLObq8PhkrxOZb/gGCq03S3Z7PDhS8pduQ==", + "license": "MIT", "dependencies": { "@babel/helper-module-imports": "^7.16.7", "@babel/runtime": "^7.18.3", @@ -693,15 +571,11 @@ "stylis": "4.2.0" } }, - "node_modules/@emotion/babel-plugin/node_modules/convert-source-map": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.9.0.tgz", - "integrity": "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==" - }, "node_modules/@emotion/cache": { "version": "11.14.0", "resolved": "https://registry.npmjs.org/@emotion/cache/-/cache-11.14.0.tgz", "integrity": "sha512-L/B1lc/TViYk4DcpGxtAVbx0ZyiKM5ktoIyafGkH6zg/tj+mA+NE//aPYKG0k8kCHSHVJrpLpcAlOBEXQ3SavA==", + "license": "MIT", "dependencies": { "@emotion/memoize": "^0.9.0", "@emotion/sheet": "^1.4.0", @@ -711,16 +585,16 @@ } }, "node_modules/@emotion/core": { - "version": "10.3.1", - "resolved": "https://registry.npmjs.org/@emotion/core/-/core-10.3.1.tgz", - "integrity": "sha512-447aUEjPIm0MnE6QYIaFz9VQOHSXf4Iu6EWOIqq11EAPqinkSZmfymPTmlOE3QjLv846lH4JVZBUOtwGbuQoww==", + "version": "10.0.9", + "resolved": "https://registry.npmjs.org/@emotion/core/-/core-10.0.9.tgz", + "integrity": "sha512-v5a77dV+uWGoy9w6R3MXZG01lqHcXgoy/jGmJqPDGhPjmpWg26LWXAphYZxpZffFUwDUlDdYDiX5HtaKphvJnQ==", + "license": "MIT", "dependencies": { - "@babel/runtime": "^7.5.5", - "@emotion/cache": "^10.0.27", - "@emotion/css": "^10.0.27", - "@emotion/serialize": "^0.11.15", - "@emotion/sheet": "0.9.4", - "@emotion/utils": "0.11.3" + "@emotion/cache": "^10.0.9", + "@emotion/css": "^10.0.9", + "@emotion/serialize": "^0.11.6", + "@emotion/sheet": "0.9.2", + "@emotion/utils": "0.11.1" }, "peerDependencies": { "react": ">=16.3.0" @@ -730,6 +604,7 @@ "version": "10.0.29", "resolved": "https://registry.npmjs.org/@emotion/cache/-/cache-10.0.29.tgz", "integrity": "sha512-fU2VtSVlHiF27empSbxi1O2JFdNWZO+2NFHfwO0pxgTep6Xa3uGb+3pVKfLww2l/IBGLNEZl5Xf/++A4wAYDYQ==", + "license": "MIT", "dependencies": { "@emotion/sheet": "0.9.4", "@emotion/stylis": "0.8.5", @@ -737,30 +612,52 @@ "@emotion/weak-memoize": "0.2.5" } }, + "node_modules/@emotion/core/node_modules/@emotion/cache/node_modules/@emotion/sheet": { + "version": "0.9.4", + "resolved": "https://registry.npmjs.org/@emotion/sheet/-/sheet-0.9.4.tgz", + "integrity": "sha512-zM9PFmgVSqBw4zL101Q0HrBVTGmpAxFZH/pYx/cjJT5advXguvcgjHFTCaIO3enL/xr89vK2bh0Mfyj9aa0ANA==", + "license": "MIT" + }, + "node_modules/@emotion/core/node_modules/@emotion/cache/node_modules/@emotion/utils": { + "version": "0.11.3", + "resolved": "https://registry.npmjs.org/@emotion/utils/-/utils-0.11.3.tgz", + "integrity": "sha512-0o4l6pZC+hI88+bzuaX/6BgOvQVhbt2PfmxauVaYOGgbsAw14wdKyvMCZXnsnsHys94iadcF+RG/wZyx6+ZZBw==", + "license": "MIT" + }, "node_modules/@emotion/core/node_modules/@emotion/css": { "version": "10.0.27", "resolved": "https://registry.npmjs.org/@emotion/css/-/css-10.0.27.tgz", "integrity": "sha512-6wZjsvYeBhyZQYNrGoR5yPMYbMBNEnanDrqmsqS1mzDm1cOTu12shvl2j4QHNS36UaTE0USIJawCH9C8oW34Zw==", + "license": "MIT", "dependencies": { "@emotion/serialize": "^0.11.15", "@emotion/utils": "0.11.3", "babel-plugin-emotion": "^10.0.27" } }, + "node_modules/@emotion/core/node_modules/@emotion/css/node_modules/@emotion/utils": { + "version": "0.11.3", + "resolved": "https://registry.npmjs.org/@emotion/utils/-/utils-0.11.3.tgz", + "integrity": "sha512-0o4l6pZC+hI88+bzuaX/6BgOvQVhbt2PfmxauVaYOGgbsAw14wdKyvMCZXnsnsHys94iadcF+RG/wZyx6+ZZBw==", + "license": "MIT" + }, "node_modules/@emotion/core/node_modules/@emotion/hash": { "version": "0.8.0", "resolved": "https://registry.npmjs.org/@emotion/hash/-/hash-0.8.0.tgz", - "integrity": "sha512-kBJtf7PH6aWwZ6fka3zQ0p6SBYzx4fl1LoZXE2RrnYST9Xljm7WfKJrU4g/Xr3Beg72MLrp1AWNUmuYJTL7Cow==" + "integrity": "sha512-kBJtf7PH6aWwZ6fka3zQ0p6SBYzx4fl1LoZXE2RrnYST9Xljm7WfKJrU4g/Xr3Beg72MLrp1AWNUmuYJTL7Cow==", + "license": "MIT" }, "node_modules/@emotion/core/node_modules/@emotion/memoize": { "version": "0.7.4", "resolved": "https://registry.npmjs.org/@emotion/memoize/-/memoize-0.7.4.tgz", - "integrity": "sha512-Ja/Vfqe3HpuzRsG1oBtWTHk2PGZ7GR+2Vz5iYGelAw8dx32K0y7PjVuxK6z1nMpZOqAFsRUPCkK1YjJ56qJlgw==" + "integrity": "sha512-Ja/Vfqe3HpuzRsG1oBtWTHk2PGZ7GR+2Vz5iYGelAw8dx32K0y7PjVuxK6z1nMpZOqAFsRUPCkK1YjJ56qJlgw==", + "license": "MIT" }, "node_modules/@emotion/core/node_modules/@emotion/serialize": { "version": "0.11.16", "resolved": "https://registry.npmjs.org/@emotion/serialize/-/serialize-0.11.16.tgz", "integrity": "sha512-G3J4o8by0VRrO+PFeSc3js2myYNOXVJ3Ya+RGVxnshRYgsvErfAOglKAiy1Eo1vhzxqtUvjCyS5gtewzkmvSSg==", + "license": "MIT", "dependencies": { "@emotion/hash": "0.8.0", "@emotion/memoize": "0.7.4", @@ -769,40 +666,47 @@ "csstype": "^2.5.7" } }, - "node_modules/@emotion/core/node_modules/@emotion/sheet": { - "version": "0.9.4", - "resolved": "https://registry.npmjs.org/@emotion/sheet/-/sheet-0.9.4.tgz", - "integrity": "sha512-zM9PFmgVSqBw4zL101Q0HrBVTGmpAxFZH/pYx/cjJT5advXguvcgjHFTCaIO3enL/xr89vK2bh0Mfyj9aa0ANA==" + "node_modules/@emotion/core/node_modules/@emotion/serialize/node_modules/@emotion/utils": { + "version": "0.11.3", + "resolved": "https://registry.npmjs.org/@emotion/utils/-/utils-0.11.3.tgz", + "integrity": "sha512-0o4l6pZC+hI88+bzuaX/6BgOvQVhbt2PfmxauVaYOGgbsAw14wdKyvMCZXnsnsHys94iadcF+RG/wZyx6+ZZBw==", + "license": "MIT" }, - "node_modules/@emotion/core/node_modules/@emotion/stylis": { - "version": "0.8.5", - "resolved": "https://registry.npmjs.org/@emotion/stylis/-/stylis-0.8.5.tgz", - "integrity": "sha512-h6KtPihKFn3T9fuIrwvXXUOwlx3rfUvfZIcP5a6rh8Y7zjE3O06hT5Ss4S/YI1AYhuZ1kjaE/5EaOOI2NqSylQ==" + "node_modules/@emotion/core/node_modules/@emotion/sheet": { + "version": "0.9.2", + "resolved": "https://registry.npmjs.org/@emotion/sheet/-/sheet-0.9.2.tgz", + "integrity": "sha512-pVBLzIbC/QCHDKJF2E82V2H/W/B004mDFQZiyo/MSR+VC4pV5JLG0TF/zgQDFvP3fZL/5RTPGEmXlYJBMUuJ+A==", + "license": "MIT" }, "node_modules/@emotion/core/node_modules/@emotion/unitless": { "version": "0.7.5", "resolved": "https://registry.npmjs.org/@emotion/unitless/-/unitless-0.7.5.tgz", - "integrity": "sha512-OWORNpfjMsSSUBVrRBVGECkhWcULOAJz9ZW8uK9qgxD+87M7jHRcvh/A96XXNhXTLmKcoYSQtBEX7lHMO7YRwg==" + "integrity": "sha512-OWORNpfjMsSSUBVrRBVGECkhWcULOAJz9ZW8uK9qgxD+87M7jHRcvh/A96XXNhXTLmKcoYSQtBEX7lHMO7YRwg==", + "license": "MIT" }, "node_modules/@emotion/core/node_modules/@emotion/utils": { - "version": "0.11.3", - "resolved": "https://registry.npmjs.org/@emotion/utils/-/utils-0.11.3.tgz", - "integrity": "sha512-0o4l6pZC+hI88+bzuaX/6BgOvQVhbt2PfmxauVaYOGgbsAw14wdKyvMCZXnsnsHys94iadcF+RG/wZyx6+ZZBw==" + "version": "0.11.1", + "resolved": "https://registry.npmjs.org/@emotion/utils/-/utils-0.11.1.tgz", + "integrity": "sha512-8M3VN0hetwhsJ8dH8VkVy7xo5/1VoBsDOk/T4SJOeXwTO1c4uIqVNx2qyecLFnnUWD5vvUqHQ1gASSeUN6zcTg==", + "license": "MIT" }, "node_modules/@emotion/core/node_modules/@emotion/weak-memoize": { "version": "0.2.5", "resolved": "https://registry.npmjs.org/@emotion/weak-memoize/-/weak-memoize-0.2.5.tgz", - "integrity": "sha512-6U71C2Wp7r5XtFtQzYrW5iKFT67OixrSxjI4MptCHzdSVlgabczzqLe0ZSgnub/5Kp4hSbpDB1tMytZY9pwxxA==" + "integrity": "sha512-6U71C2Wp7r5XtFtQzYrW5iKFT67OixrSxjI4MptCHzdSVlgabczzqLe0ZSgnub/5Kp4hSbpDB1tMytZY9pwxxA==", + "license": "MIT" }, "node_modules/@emotion/core/node_modules/csstype": { "version": "2.6.21", "resolved": "https://registry.npmjs.org/csstype/-/csstype-2.6.21.tgz", - "integrity": "sha512-Z1PhmomIfypOpoMjRQB70jfvy/wxT50qW08YXO5lMIJkrdq4yOTR+AW7FqutScmB9NkLwxo+jU+kZLbofZZq/w==" + "integrity": "sha512-Z1PhmomIfypOpoMjRQB70jfvy/wxT50qW08YXO5lMIJkrdq4yOTR+AW7FqutScmB9NkLwxo+jU+kZLbofZZq/w==", + "license": "MIT" }, "node_modules/@emotion/css": { "version": "11.13.5", "resolved": "https://registry.npmjs.org/@emotion/css/-/css-11.13.5.tgz", "integrity": "sha512-wQdD0Xhkn3Qy2VNcIzbLP9MR8TafI0MJb7BEAXKp+w4+XqErksWR4OXomuDzPsN4InLdGhVe6EYcn2ZIUCpB8w==", + "license": "MIT", "dependencies": { "@emotion/babel-plugin": "^11.13.5", "@emotion/cache": "^11.13.5", @@ -814,12 +718,14 @@ "node_modules/@emotion/hash": { "version": "0.9.2", "resolved": "https://registry.npmjs.org/@emotion/hash/-/hash-0.9.2.tgz", - "integrity": "sha512-MyqliTZGuOm3+5ZRSaaBGP3USLw6+EGykkwZns2EPC5g8jJ4z9OrdZY9apkl3+UP9+sdz76YYkwCKP5gh8iY3g==" + "integrity": "sha512-MyqliTZGuOm3+5ZRSaaBGP3USLw6+EGykkwZns2EPC5g8jJ4z9OrdZY9apkl3+UP9+sdz76YYkwCKP5gh8iY3g==", + "license": "MIT" }, "node_modules/@emotion/is-prop-valid": { "version": "0.8.8", "resolved": "https://registry.npmjs.org/@emotion/is-prop-valid/-/is-prop-valid-0.8.8.tgz", "integrity": "sha512-u5WtneEAr5IDG2Wv65yhunPSMLIpuKsbuOktRojfrEiEvRyC85LgPMZI63cr7NUqT8ZIGdSVg8ZKGxIug4lXcA==", + "license": "MIT", "dependencies": { "@emotion/memoize": "0.7.4" } @@ -827,17 +733,20 @@ "node_modules/@emotion/is-prop-valid/node_modules/@emotion/memoize": { "version": "0.7.4", "resolved": "https://registry.npmjs.org/@emotion/memoize/-/memoize-0.7.4.tgz", - "integrity": "sha512-Ja/Vfqe3HpuzRsG1oBtWTHk2PGZ7GR+2Vz5iYGelAw8dx32K0y7PjVuxK6z1nMpZOqAFsRUPCkK1YjJ56qJlgw==" + "integrity": "sha512-Ja/Vfqe3HpuzRsG1oBtWTHk2PGZ7GR+2Vz5iYGelAw8dx32K0y7PjVuxK6z1nMpZOqAFsRUPCkK1YjJ56qJlgw==", + "license": "MIT" }, "node_modules/@emotion/memoize": { "version": "0.9.0", "resolved": "https://registry.npmjs.org/@emotion/memoize/-/memoize-0.9.0.tgz", - "integrity": "sha512-30FAj7/EoJ5mwVPOWhAyCX+FPfMDrVecJAM+Iw9NRoSl4BBAQeqj4cApHHUXOVvIPgLVDsCFoz/hGD+5QQD1GQ==" + "integrity": "sha512-30FAj7/EoJ5mwVPOWhAyCX+FPfMDrVecJAM+Iw9NRoSl4BBAQeqj4cApHHUXOVvIPgLVDsCFoz/hGD+5QQD1GQ==", + "license": "MIT" }, "node_modules/@emotion/serialize": { "version": "1.3.3", "resolved": "https://registry.npmjs.org/@emotion/serialize/-/serialize-1.3.3.tgz", "integrity": "sha512-EISGqt7sSNWHGI76hC7x1CksiXPahbxEOrC5RjmFRJTqLyEK9/9hZvBbiYn70dw4wuwMKiEMCUlR6ZXTSWQqxA==", + "license": "MIT", "dependencies": { "@emotion/hash": "^0.9.2", "@emotion/memoize": "^0.9.0", @@ -849,12 +758,14 @@ "node_modules/@emotion/sheet": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/@emotion/sheet/-/sheet-1.4.0.tgz", - "integrity": "sha512-fTBW9/8r2w3dXWYM4HCB1Rdp8NLibOw2+XELH5m5+AkWiL/KqYX6dc0kKYlaYyKjrQ6ds33MCdMPEwgs2z1rqg==" + "integrity": "sha512-fTBW9/8r2w3dXWYM4HCB1Rdp8NLibOw2+XELH5m5+AkWiL/KqYX6dc0kKYlaYyKjrQ6ds33MCdMPEwgs2z1rqg==", + "license": "MIT" }, "node_modules/@emotion/styled": { "version": "10.3.0", "resolved": "https://registry.npmjs.org/@emotion/styled/-/styled-10.3.0.tgz", "integrity": "sha512-GgcUpXBBEU5ido+/p/mCT2/Xx+Oqmp9JzQRuC+a4lYM4i4LBBn/dWvc0rQ19N9ObA8/T4NWMrPNe79kMBDJqoQ==", + "license": "MIT", "dependencies": { "@emotion/styled-base": "^10.3.0", "babel-plugin-emotion": "^10.0.27" @@ -868,6 +779,7 @@ "version": "10.3.0", "resolved": "https://registry.npmjs.org/@emotion/styled-base/-/styled-base-10.3.0.tgz", "integrity": "sha512-PBRqsVKR7QRNkmfH78hTSSwHWcwDpecH9W6heujWAcyp2wdz/64PP73s7fWS1dIPm8/Exc8JAzYS8dEWXjv60w==", + "license": "MIT", "dependencies": { "@babel/runtime": "^7.5.5", "@emotion/is-prop-valid": "0.8.8", @@ -882,17 +794,20 @@ "node_modules/@emotion/styled-base/node_modules/@emotion/hash": { "version": "0.8.0", "resolved": "https://registry.npmjs.org/@emotion/hash/-/hash-0.8.0.tgz", - "integrity": "sha512-kBJtf7PH6aWwZ6fka3zQ0p6SBYzx4fl1LoZXE2RrnYST9Xljm7WfKJrU4g/Xr3Beg72MLrp1AWNUmuYJTL7Cow==" + "integrity": "sha512-kBJtf7PH6aWwZ6fka3zQ0p6SBYzx4fl1LoZXE2RrnYST9Xljm7WfKJrU4g/Xr3Beg72MLrp1AWNUmuYJTL7Cow==", + "license": "MIT" }, "node_modules/@emotion/styled-base/node_modules/@emotion/memoize": { "version": "0.7.4", "resolved": "https://registry.npmjs.org/@emotion/memoize/-/memoize-0.7.4.tgz", - "integrity": "sha512-Ja/Vfqe3HpuzRsG1oBtWTHk2PGZ7GR+2Vz5iYGelAw8dx32K0y7PjVuxK6z1nMpZOqAFsRUPCkK1YjJ56qJlgw==" + "integrity": "sha512-Ja/Vfqe3HpuzRsG1oBtWTHk2PGZ7GR+2Vz5iYGelAw8dx32K0y7PjVuxK6z1nMpZOqAFsRUPCkK1YjJ56qJlgw==", + "license": "MIT" }, "node_modules/@emotion/styled-base/node_modules/@emotion/serialize": { "version": "0.11.16", "resolved": "https://registry.npmjs.org/@emotion/serialize/-/serialize-0.11.16.tgz", "integrity": "sha512-G3J4o8by0VRrO+PFeSc3js2myYNOXVJ3Ya+RGVxnshRYgsvErfAOglKAiy1Eo1vhzxqtUvjCyS5gtewzkmvSSg==", + "license": "MIT", "dependencies": { "@emotion/hash": "0.8.0", "@emotion/memoize": "0.7.4", @@ -904,37 +819,44 @@ "node_modules/@emotion/styled-base/node_modules/@emotion/unitless": { "version": "0.7.5", "resolved": "https://registry.npmjs.org/@emotion/unitless/-/unitless-0.7.5.tgz", - "integrity": "sha512-OWORNpfjMsSSUBVrRBVGECkhWcULOAJz9ZW8uK9qgxD+87M7jHRcvh/A96XXNhXTLmKcoYSQtBEX7lHMO7YRwg==" + "integrity": "sha512-OWORNpfjMsSSUBVrRBVGECkhWcULOAJz9ZW8uK9qgxD+87M7jHRcvh/A96XXNhXTLmKcoYSQtBEX7lHMO7YRwg==", + "license": "MIT" }, "node_modules/@emotion/styled-base/node_modules/@emotion/utils": { "version": "0.11.3", "resolved": "https://registry.npmjs.org/@emotion/utils/-/utils-0.11.3.tgz", - "integrity": "sha512-0o4l6pZC+hI88+bzuaX/6BgOvQVhbt2PfmxauVaYOGgbsAw14wdKyvMCZXnsnsHys94iadcF+RG/wZyx6+ZZBw==" + "integrity": "sha512-0o4l6pZC+hI88+bzuaX/6BgOvQVhbt2PfmxauVaYOGgbsAw14wdKyvMCZXnsnsHys94iadcF+RG/wZyx6+ZZBw==", + "license": "MIT" }, "node_modules/@emotion/styled-base/node_modules/csstype": { "version": "2.6.21", "resolved": "https://registry.npmjs.org/csstype/-/csstype-2.6.21.tgz", - "integrity": "sha512-Z1PhmomIfypOpoMjRQB70jfvy/wxT50qW08YXO5lMIJkrdq4yOTR+AW7FqutScmB9NkLwxo+jU+kZLbofZZq/w==" + "integrity": "sha512-Z1PhmomIfypOpoMjRQB70jfvy/wxT50qW08YXO5lMIJkrdq4yOTR+AW7FqutScmB9NkLwxo+jU+kZLbofZZq/w==", + "license": "MIT" }, "node_modules/@emotion/stylis": { - "version": "0.8.3", - "resolved": "https://registry.npmjs.org/@emotion/stylis/-/stylis-0.8.3.tgz", - "integrity": "sha512-M3nMfJ6ndJMYloSIbYEBq6G3eqoYD41BpDOxreE8j0cb4fzz/5qvmqU9Mb2hzsXcCnIlGlWhS03PCzVGvTAe0Q==" + "version": "0.8.5", + "resolved": "https://registry.npmjs.org/@emotion/stylis/-/stylis-0.8.5.tgz", + "integrity": "sha512-h6KtPihKFn3T9fuIrwvXXUOwlx3rfUvfZIcP5a6rh8Y7zjE3O06hT5Ss4S/YI1AYhuZ1kjaE/5EaOOI2NqSylQ==", + "license": "MIT" }, "node_modules/@emotion/unitless": { "version": "0.10.0", "resolved": "https://registry.npmjs.org/@emotion/unitless/-/unitless-0.10.0.tgz", - "integrity": "sha512-dFoMUuQA20zvtVTuxZww6OHoJYgrzfKM1t52mVySDJnMSEa08ruEvdYQbhvyu6soU+NeLVd3yKfTfT0NeV6qGg==" + "integrity": "sha512-dFoMUuQA20zvtVTuxZww6OHoJYgrzfKM1t52mVySDJnMSEa08ruEvdYQbhvyu6soU+NeLVd3yKfTfT0NeV6qGg==", + "license": "MIT" }, "node_modules/@emotion/utils": { "version": "1.4.2", "resolved": "https://registry.npmjs.org/@emotion/utils/-/utils-1.4.2.tgz", - "integrity": "sha512-3vLclRofFziIa3J2wDh9jjbkUz9qk5Vi3IZ/FSTKViB0k+ef0fPV7dYrUIugbgupYDx7v9ud/SjrtEP8Y4xLoA==" + "integrity": "sha512-3vLclRofFziIa3J2wDh9jjbkUz9qk5Vi3IZ/FSTKViB0k+ef0fPV7dYrUIugbgupYDx7v9ud/SjrtEP8Y4xLoA==", + "license": "MIT" }, "node_modules/@emotion/weak-memoize": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/@emotion/weak-memoize/-/weak-memoize-0.4.0.tgz", - "integrity": "sha512-snKqtPW01tN0ui7yu9rGv69aJXr/a/Ywvl11sUjNtEcRc+ng/mQriFL0wLXMef74iHa/EkftbDzU9F8iFbH+zg==" + "integrity": "sha512-snKqtPW01tN0ui7yu9rGv69aJXr/a/Ywvl11sUjNtEcRc+ng/mQriFL0wLXMef74iHa/EkftbDzU9F8iFbH+zg==", + "license": "MIT" }, "node_modules/@esbuild/aix-ppc64": { "version": "0.27.3", @@ -943,6 +865,7 @@ "cpu": [ "ppc64" ], + "license": "MIT", "optional": true, "os": [ "aix" @@ -958,6 +881,7 @@ "cpu": [ "arm" ], + "license": "MIT", "optional": true, "os": [ "android" @@ -973,6 +897,7 @@ "cpu": [ "arm64" ], + "license": "MIT", "optional": true, "os": [ "android" @@ -988,6 +913,7 @@ "cpu": [ "x64" ], + "license": "MIT", "optional": true, "os": [ "android" @@ -1003,6 +929,7 @@ "cpu": [ "arm64" ], + "license": "MIT", "optional": true, "os": [ "darwin" @@ -1018,6 +945,7 @@ "cpu": [ "x64" ], + "license": "MIT", "optional": true, "os": [ "darwin" @@ -1033,6 +961,7 @@ "cpu": [ "arm64" ], + "license": "MIT", "optional": true, "os": [ "freebsd" @@ -1048,6 +977,7 @@ "cpu": [ "x64" ], + "license": "MIT", "optional": true, "os": [ "freebsd" @@ -1063,6 +993,7 @@ "cpu": [ "arm" ], + "license": "MIT", "optional": true, "os": [ "linux" @@ -1078,6 +1009,7 @@ "cpu": [ "arm64" ], + "license": "MIT", "optional": true, "os": [ "linux" @@ -1093,6 +1025,7 @@ "cpu": [ "ia32" ], + "license": "MIT", "optional": true, "os": [ "linux" @@ -1108,6 +1041,7 @@ "cpu": [ "loong64" ], + "license": "MIT", "optional": true, "os": [ "linux" @@ -1123,6 +1057,7 @@ "cpu": [ "mips64el" ], + "license": "MIT", "optional": true, "os": [ "linux" @@ -1138,6 +1073,7 @@ "cpu": [ "ppc64" ], + "license": "MIT", "optional": true, "os": [ "linux" @@ -1153,6 +1089,7 @@ "cpu": [ "riscv64" ], + "license": "MIT", "optional": true, "os": [ "linux" @@ -1168,6 +1105,7 @@ "cpu": [ "s390x" ], + "license": "MIT", "optional": true, "os": [ "linux" @@ -1183,6 +1121,7 @@ "cpu": [ "x64" ], + "license": "MIT", "optional": true, "os": [ "linux" @@ -1198,6 +1137,7 @@ "cpu": [ "arm64" ], + "license": "MIT", "optional": true, "os": [ "netbsd" @@ -1213,6 +1153,7 @@ "cpu": [ "x64" ], + "license": "MIT", "optional": true, "os": [ "netbsd" @@ -1228,6 +1169,7 @@ "cpu": [ "arm64" ], + "license": "MIT", "optional": true, "os": [ "openbsd" @@ -1243,6 +1185,7 @@ "cpu": [ "x64" ], + "license": "MIT", "optional": true, "os": [ "openbsd" @@ -1258,6 +1201,7 @@ "cpu": [ "arm64" ], + "license": "MIT", "optional": true, "os": [ "openharmony" @@ -1273,6 +1217,7 @@ "cpu": [ "x64" ], + "license": "MIT", "optional": true, "os": [ "sunos" @@ -1288,6 +1233,7 @@ "cpu": [ "arm64" ], + "license": "MIT", "optional": true, "os": [ "win32" @@ -1303,6 +1249,7 @@ "cpu": [ "ia32" ], + "license": "MIT", "optional": true, "os": [ "win32" @@ -1318,6 +1265,7 @@ "cpu": [ "x64" ], + "license": "MIT", "optional": true, "os": [ "win32" @@ -1331,6 +1279,7 @@ "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.9.1.tgz", "integrity": "sha512-phrYmNiYppR7znFEdqgfWHXR6NCkZEK7hwWDHZUjit/2/U0r6XvkDl0SYnoM51Hq7FhCGdLDT6zxCCOY1hexsQ==", "dev": true, + "license": "MIT", "dependencies": { "eslint-visitor-keys": "^3.4.3" }, @@ -1349,6 +1298,7 @@ "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.2.tgz", "integrity": "sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==", "dev": true, + "license": "MIT", "engines": { "node": "^12.0.0 || ^14.0.0 || >=16.0.0" } @@ -1358,6 +1308,7 @@ "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.4.tgz", "integrity": "sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ==", "dev": true, + "license": "MIT", "dependencies": { "ajv": "^6.12.4", "debug": "^4.3.2", @@ -1381,16 +1332,36 @@ "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.57.1.tgz", "integrity": "sha512-d9zaMRSTIKDLhctzH12MtXvJKSSUhaHcjV+2Z+GK+EEY7XKpP5yR4x+N3TAcHTcu963nIr+TMcCb4DBCYX1z6Q==", "dev": true, + "license": "MIT", "engines": { "node": "^12.22.0 || ^14.17.0 || >=16.0.0" } }, + "node_modules/@exodus/bytes": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@exodus/bytes/-/bytes-1.14.1.tgz", + "integrity": "sha512-OhkBFWI6GcRMUroChZiopRiSp2iAMvEBK47NhJooDqz1RERO4QuZIZnjP63TXX8GAiLABkYmX+fuQsdJ1dd2QQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^20.19.0 || ^22.12.0 || >=24.0.0" + }, + "peerDependencies": { + "@noble/hashes": "^1.8.0 || ^2.0.0" + }, + "peerDependenciesMeta": { + "@noble/hashes": { + "optional": true + } + } + }, "node_modules/@humanwhocodes/config-array": { "version": "0.13.0", "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.13.0.tgz", "integrity": "sha512-DZLEEqFWQFiyK6h5YIeynKx7JlvCYWL0cImfSRXZ9l4Sg2efkFGTuFf6vzXjK1cq6IYkU+Eg/JizXw+TD2vRNw==", "deprecated": "Use @eslint/config-array instead", "dev": true, + "license": "Apache-2.0", "dependencies": { "@humanwhocodes/object-schema": "^2.0.3", "debug": "^4.3.1", @@ -1405,6 +1376,7 @@ "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", "dev": true, + "license": "Apache-2.0", "engines": { "node": ">=12.22" }, @@ -1418,12 +1390,14 @@ "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-2.0.3.tgz", "integrity": "sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA==", "deprecated": "Use @eslint/object-schema instead", - "dev": true + "dev": true, + "license": "BSD-3-Clause" }, "node_modules/@icons/material": { "version": "0.2.4", "resolved": "https://registry.npmjs.org/@icons/material/-/material-0.2.4.tgz", "integrity": "sha512-QPcGmICAPbGLGb6F/yNf/KzKqvFx8z5qx3D1yFqVAjoFmXK35EgyW+cJ57Te3CNsmzblwtzakLGFqHPqrfb4Tw==", + "license": "MIT", "peerDependencies": { "react": "*" } @@ -1432,6 +1406,7 @@ "version": "8.0.2", "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", + "license": "ISC", "dependencies": { "string-width": "^5.1.2", "string-width-cjs": "npm:string-width@^4.2.0", @@ -1448,6 +1423,7 @@ "version": "6.2.2", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz", "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==", + "license": "MIT", "engines": { "node": ">=12" }, @@ -1459,6 +1435,7 @@ "version": "7.1.2", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.2.tgz", "integrity": "sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA==", + "license": "MIT", "dependencies": { "ansi-regex": "^6.0.1" }, @@ -1473,6 +1450,7 @@ "version": "0.3.13", "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", + "license": "MIT", "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.0", "@jridgewell/trace-mapping": "^0.3.24" @@ -1482,6 +1460,7 @@ "version": "3.1.2", "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "license": "MIT", "engines": { "node": ">=6.0.0" } @@ -1489,12 +1468,14 @@ "node_modules/@jridgewell/sourcemap-codec": { "version": "1.5.5", "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", - "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==" + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", + "license": "MIT" }, "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==", + "license": "MIT", "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", "@jridgewell/sourcemap-codec": "^1.4.14" @@ -1504,6 +1485,7 @@ "version": "3.1.1", "resolved": "https://registry.npmjs.org/@mdx-js/react/-/react-3.1.1.tgz", "integrity": "sha512-f++rKLQgUVYDAtECQ6fn/is15GkEH9+nZPM3MS0RcxVqoTfawHvDlSCH7JbMhAM6uJ32v3eXLvLmLvjGu7PTQw==", + "license": "MIT", "dependencies": { "@types/mdx": "^2.0.0" }, @@ -1521,6 +1503,7 @@ "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", "dev": true, + "license": "MIT", "dependencies": { "@nodelib/fs.stat": "2.0.5", "run-parallel": "^1.1.9" @@ -1534,73 +1517,318 @@ "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", "dev": true, + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.walk": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", + "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@one-ini/wasm": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/@one-ini/wasm/-/wasm-0.1.1.tgz", + "integrity": "sha512-XuySG1E38YScSJoMlqovLru4KTUNSjgVTIjyh7qMX6aNN5HY5Ct5LhRJdxO79JtTzKfzV/bnWpz+zquYrISsvw==", + "license": "MIT" + }, + "node_modules/@parcel/watcher": { + "version": "2.5.6", + "resolved": "https://registry.npmjs.org/@parcel/watcher/-/watcher-2.5.6.tgz", + "integrity": "sha512-tmmZ3lQxAe/k/+rNnXQRawJ4NjxO2hqiOLTHvWchtGZULp4RyFeh6aU4XdOYBFe2KE1oShQTv4AblOs2iOrNnQ==", + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "dependencies": { + "detect-libc": "^2.0.3", + "is-glob": "^4.0.3", + "node-addon-api": "^7.0.0", + "picomatch": "^4.0.3" + }, + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + }, + "optionalDependencies": { + "@parcel/watcher-android-arm64": "2.5.6", + "@parcel/watcher-darwin-arm64": "2.5.6", + "@parcel/watcher-darwin-x64": "2.5.6", + "@parcel/watcher-freebsd-x64": "2.5.6", + "@parcel/watcher-linux-arm-glibc": "2.5.6", + "@parcel/watcher-linux-arm-musl": "2.5.6", + "@parcel/watcher-linux-arm64-glibc": "2.5.6", + "@parcel/watcher-linux-arm64-musl": "2.5.6", + "@parcel/watcher-linux-x64-glibc": "2.5.6", + "@parcel/watcher-linux-x64-musl": "2.5.6", + "@parcel/watcher-win32-arm64": "2.5.6", + "@parcel/watcher-win32-ia32": "2.5.6", + "@parcel/watcher-win32-x64": "2.5.6" + } + }, + "node_modules/@parcel/watcher-android-arm64": { + "version": "2.5.6", + "resolved": "https://registry.npmjs.org/@parcel/watcher-android-arm64/-/watcher-android-arm64-2.5.6.tgz", + "integrity": "sha512-YQxSS34tPF/6ZG7r/Ih9xy+kP/WwediEUsqmtf0cuCV5TPPKw/PQHRhueUo6JdeFJaqV3pyjm0GdYjZotbRt/A==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-darwin-arm64": { + "version": "2.5.6", + "resolved": "https://registry.npmjs.org/@parcel/watcher-darwin-arm64/-/watcher-darwin-arm64-2.5.6.tgz", + "integrity": "sha512-Z2ZdrnwyXvvvdtRHLmM4knydIdU9adO3D4n/0cVipF3rRiwP+3/sfzpAwA/qKFL6i1ModaabkU7IbpeMBgiVEA==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-darwin-x64": { + "version": "2.5.6", + "resolved": "https://registry.npmjs.org/@parcel/watcher-darwin-x64/-/watcher-darwin-x64-2.5.6.tgz", + "integrity": "sha512-HgvOf3W9dhithcwOWX9uDZyn1lW9R+7tPZ4sug+NGrGIo4Rk1hAXLEbcH1TQSqxts0NYXXlOWqVpvS1SFS4fRg==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-freebsd-x64": { + "version": "2.5.6", + "resolved": "https://registry.npmjs.org/@parcel/watcher-freebsd-x64/-/watcher-freebsd-x64-2.5.6.tgz", + "integrity": "sha512-vJVi8yd/qzJxEKHkeemh7w3YAn6RJCtYlE4HPMoVnCpIXEzSrxErBW5SJBgKLbXU3WdIpkjBTeUNtyBVn8TRng==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-linux-arm-glibc": { + "version": "2.5.6", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm-glibc/-/watcher-linux-arm-glibc-2.5.6.tgz", + "integrity": "sha512-9JiYfB6h6BgV50CCfasfLf/uvOcJskMSwcdH1PHH9rvS1IrNy8zad6IUVPVUfmXr+u+Km9IxcfMLzgdOudz9EQ==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-linux-arm-musl": { + "version": "2.5.6", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm-musl/-/watcher-linux-arm-musl-2.5.6.tgz", + "integrity": "sha512-Ve3gUCG57nuUUSyjBq/MAM0CzArtuIOxsBdQ+ftz6ho8n7s1i9E1Nmk/xmP323r2YL0SONs1EuwqBp2u1k5fxg==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-linux-arm64-glibc": { + "version": "2.5.6", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm64-glibc/-/watcher-linux-arm64-glibc-2.5.6.tgz", + "integrity": "sha512-f2g/DT3NhGPdBmMWYoxixqYr3v/UXcmLOYy16Bx0TM20Tchduwr4EaCbmxh1321TABqPGDpS8D/ggOTaljijOA==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-linux-arm64-musl": { + "version": "2.5.6", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm64-musl/-/watcher-linux-arm64-musl-2.5.6.tgz", + "integrity": "sha512-qb6naMDGlbCwdhLj6hgoVKJl2odL34z2sqkC7Z6kzir8b5W65WYDpLB6R06KabvZdgoHI/zxke4b3zR0wAbDTA==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-linux-x64-glibc": { + "version": "2.5.6", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-x64-glibc/-/watcher-linux-x64-glibc-2.5.6.tgz", + "integrity": "sha512-kbT5wvNQlx7NaGjzPFu8nVIW1rWqV780O7ZtkjuWaPUgpv2NMFpjYERVi0UYj1msZNyCzGlaCWEtzc+exjMGbQ==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-linux-x64-musl": { + "version": "2.5.6", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-x64-musl/-/watcher-linux-x64-musl-2.5.6.tgz", + "integrity": "sha512-1JRFeC+h7RdXwldHzTsmdtYR/Ku8SylLgTU/reMuqdVD7CtLwf0VR1FqeprZ0eHQkO0vqsbvFLXUmYm/uNKJBg==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": ">= 8" + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" } }, - "node_modules/@nodelib/fs.walk": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", - "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", - "dev": true, - "dependencies": { - "@nodelib/fs.scandir": "2.1.5", - "fastq": "^1.6.0" - }, + "node_modules/@parcel/watcher-win32-arm64": { + "version": "2.5.6", + "resolved": "https://registry.npmjs.org/@parcel/watcher-win32-arm64/-/watcher-win32-arm64-2.5.6.tgz", + "integrity": "sha512-3ukyebjc6eGlw9yRt678DxVF7rjXatWiHvTXqphZLvo7aC5NdEgFufVwjFfY51ijYEWpXbqF5jtrK275z52D4Q==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], "engines": { - "node": ">= 8" + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" } }, - "node_modules/@one-ini/wasm": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/@one-ini/wasm/-/wasm-0.1.1.tgz", - "integrity": "sha512-XuySG1E38YScSJoMlqovLru4KTUNSjgVTIjyh7qMX6aNN5HY5Ct5LhRJdxO79JtTzKfzV/bnWpz+zquYrISsvw==" - }, - "node_modules/@parcel/watcher": { - "version": "2.5.4", - "resolved": "https://registry.npmjs.org/@parcel/watcher/-/watcher-2.5.4.tgz", - "integrity": "sha512-WYa2tUVV5HiArWPB3ydlOc4R2ivq0IDrlqhMi3l7mVsFEXNcTfxYFPIHXHXIh/ca/y/V5N4E1zecyxdIBjYnkQ==", - "hasInstallScript": true, + "node_modules/@parcel/watcher-win32-ia32": { + "version": "2.5.6", + "resolved": "https://registry.npmjs.org/@parcel/watcher-win32-ia32/-/watcher-win32-ia32-2.5.6.tgz", + "integrity": "sha512-k35yLp1ZMwwee3Ez/pxBi5cf4AoBKYXj00CZ80jUz5h8prpiaQsiRPKQMxoLstNuqe2vR4RNPEAEcjEFzhEz/g==", + "cpu": [ + "ia32" + ], + "license": "MIT", "optional": true, - "dependencies": { - "detect-libc": "^2.0.3", - "is-glob": "^4.0.3", - "node-addon-api": "^7.0.0", - "picomatch": "^4.0.3" - }, + "os": [ + "win32" + ], "engines": { "node": ">= 10.0.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/parcel" - }, - "optionalDependencies": { - "@parcel/watcher-android-arm64": "2.5.4", - "@parcel/watcher-darwin-arm64": "2.5.4", - "@parcel/watcher-darwin-x64": "2.5.4", - "@parcel/watcher-freebsd-x64": "2.5.4", - "@parcel/watcher-linux-arm-glibc": "2.5.4", - "@parcel/watcher-linux-arm-musl": "2.5.4", - "@parcel/watcher-linux-arm64-glibc": "2.5.4", - "@parcel/watcher-linux-arm64-musl": "2.5.4", - "@parcel/watcher-linux-x64-glibc": "2.5.4", - "@parcel/watcher-linux-x64-musl": "2.5.4", - "@parcel/watcher-win32-arm64": "2.5.4", - "@parcel/watcher-win32-ia32": "2.5.4", - "@parcel/watcher-win32-x64": "2.5.4" } }, - "node_modules/@parcel/watcher-darwin-arm64": { - "version": "2.5.4", - "resolved": "https://registry.npmjs.org/@parcel/watcher-darwin-arm64/-/watcher-darwin-arm64-2.5.4.tgz", - "integrity": "sha512-kphKy377pZiWpAOyTgQYPE5/XEKVMaj6VUjKT5VkNyUJlr2qZAn8gIc7CPzx+kbhvqHDT9d7EqdOqRXT6vk0zw==", + "node_modules/@parcel/watcher-win32-x64": { + "version": "2.5.6", + "resolved": "https://registry.npmjs.org/@parcel/watcher-win32-x64/-/watcher-win32-x64-2.5.6.tgz", + "integrity": "sha512-hbQlYcCq5dlAX9Qx+kFb0FHue6vbjlf0FrNzSKdYK2APUf7tGfGxQCk2ihEREmbR6ZMc0MVAD5RIX/41gpUzTw==", "cpu": [ - "arm64" + "x64" ], + "license": "MIT", "optional": true, "os": [ - "darwin" + "win32" ], "engines": { "node": ">= 10.0.0" @@ -1614,15 +1842,24 @@ "version": "0.11.0", "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==", + "license": "MIT", "optional": true, "engines": { "node": ">=14" } }, + "node_modules/@polka/url": { + "version": "1.0.0-next.29", + "resolved": "https://registry.npmjs.org/@polka/url/-/url-1.0.0-next.29.tgz", + "integrity": "sha512-wwQAWhWSuHaag8c4q/KN/vCoeOJYshAIvMQwD4GpSb3OiZklFfvAgmj0VCBBImRpuF/aFgIRzllXlVX93Jevww==", + "dev": true, + "license": "MIT" + }, "node_modules/@popperjs/core": { "version": "2.11.8", "resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.8.tgz", "integrity": "sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A==", + "license": "MIT", "funding": { "type": "opencollective", "url": "https://opencollective.com/popperjs" @@ -1631,22 +1868,26 @@ "node_modules/@react-dnd/asap": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/@react-dnd/asap/-/asap-4.0.1.tgz", - "integrity": "sha512-kLy0PJDDwvwwTXxqTFNAAllPHD73AycE9ypWeln/IguoGBEbvFcPDbCV03G52bEcC5E+YgupBE0VzHGdC8SIXg==" + "integrity": "sha512-kLy0PJDDwvwwTXxqTFNAAllPHD73AycE9ypWeln/IguoGBEbvFcPDbCV03G52bEcC5E+YgupBE0VzHGdC8SIXg==", + "license": "MIT" }, "node_modules/@react-dnd/invariant": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/@react-dnd/invariant/-/invariant-2.0.0.tgz", - "integrity": "sha512-xL4RCQBCBDJ+GRwKTFhGUW8GXa4yoDfJrPbLblc3U09ciS+9ZJXJ3Qrcs/x2IODOdIE5kQxvMmE2UKyqUictUw==" + "integrity": "sha512-xL4RCQBCBDJ+GRwKTFhGUW8GXa4yoDfJrPbLblc3U09ciS+9ZJXJ3Qrcs/x2IODOdIE5kQxvMmE2UKyqUictUw==", + "license": "MIT" }, "node_modules/@react-dnd/shallowequal": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/@react-dnd/shallowequal/-/shallowequal-2.0.0.tgz", - "integrity": "sha512-Pc/AFTdwZwEKJxFJvlxrSmGe/di+aAOBn60sremrpLo6VI/6cmiUYNNwlI5KNYttg7uypzA3ILPMPgxB2GYZEg==" + "integrity": "sha512-Pc/AFTdwZwEKJxFJvlxrSmGe/di+aAOBn60sremrpLo6VI/6cmiUYNNwlI5KNYttg7uypzA3ILPMPgxB2GYZEg==", + "license": "MIT" }, "node_modules/@reduxjs/toolkit": { "version": "2.11.2", "resolved": "https://registry.npmjs.org/@reduxjs/toolkit/-/toolkit-2.11.2.tgz", "integrity": "sha512-Kd6kAHTA6/nUpp8mySPqj3en3dm0tdMIgbttnQ1xFMVpufoj+ADi8pXLBsd4xzTRHQa7t/Jv8W5UnCuW4kuWMQ==", + "license": "MIT", "dependencies": { "@standard-schema/spec": "^1.0.0", "@standard-schema/utils": "^0.3.0", @@ -1671,7 +1912,8 @@ "node_modules/@rolldown/pluginutils": { "version": "1.0.0-rc.2", "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-rc.2.tgz", - "integrity": "sha512-izyXV/v+cHiRfozX62W9htOAvwMo4/bXKDrQ+vom1L1qRuexPock/7VZDAhnpHCLNejd3NJ6hiab+tO0D44Rgw==" + "integrity": "sha512-izyXV/v+cHiRfozX62W9htOAvwMo4/bXKDrQ+vom1L1qRuexPock/7VZDAhnpHCLNejd3NJ6hiab+tO0D44Rgw==", + "license": "MIT" }, "node_modules/@rollup/rollup-android-arm-eabi": { "version": "4.57.1", @@ -1680,6 +1922,7 @@ "cpu": [ "arm" ], + "license": "MIT", "optional": true, "os": [ "android" @@ -1692,6 +1935,7 @@ "cpu": [ "arm64" ], + "license": "MIT", "optional": true, "os": [ "android" @@ -1704,6 +1948,7 @@ "cpu": [ "arm64" ], + "license": "MIT", "optional": true, "os": [ "darwin" @@ -1716,6 +1961,7 @@ "cpu": [ "x64" ], + "license": "MIT", "optional": true, "os": [ "darwin" @@ -1728,6 +1974,7 @@ "cpu": [ "arm64" ], + "license": "MIT", "optional": true, "os": [ "freebsd" @@ -1740,6 +1987,7 @@ "cpu": [ "x64" ], + "license": "MIT", "optional": true, "os": [ "freebsd" @@ -1752,6 +2000,7 @@ "cpu": [ "arm" ], + "license": "MIT", "optional": true, "os": [ "linux" @@ -1764,6 +2013,7 @@ "cpu": [ "arm" ], + "license": "MIT", "optional": true, "os": [ "linux" @@ -1776,6 +2026,7 @@ "cpu": [ "arm64" ], + "license": "MIT", "optional": true, "os": [ "linux" @@ -1788,6 +2039,7 @@ "cpu": [ "arm64" ], + "license": "MIT", "optional": true, "os": [ "linux" @@ -1800,6 +2052,7 @@ "cpu": [ "loong64" ], + "license": "MIT", "optional": true, "os": [ "linux" @@ -1812,6 +2065,7 @@ "cpu": [ "loong64" ], + "license": "MIT", "optional": true, "os": [ "linux" @@ -1824,6 +2078,7 @@ "cpu": [ "ppc64" ], + "license": "MIT", "optional": true, "os": [ "linux" @@ -1836,6 +2091,7 @@ "cpu": [ "ppc64" ], + "license": "MIT", "optional": true, "os": [ "linux" @@ -1848,6 +2104,7 @@ "cpu": [ "riscv64" ], + "license": "MIT", "optional": true, "os": [ "linux" @@ -1860,6 +2117,7 @@ "cpu": [ "riscv64" ], + "license": "MIT", "optional": true, "os": [ "linux" @@ -1872,6 +2130,7 @@ "cpu": [ "s390x" ], + "license": "MIT", "optional": true, "os": [ "linux" @@ -1884,6 +2143,7 @@ "cpu": [ "x64" ], + "license": "MIT", "optional": true, "os": [ "linux" @@ -1896,6 +2156,7 @@ "cpu": [ "x64" ], + "license": "MIT", "optional": true, "os": [ "linux" @@ -1908,6 +2169,7 @@ "cpu": [ "x64" ], + "license": "MIT", "optional": true, "os": [ "openbsd" @@ -1920,6 +2182,7 @@ "cpu": [ "arm64" ], + "license": "MIT", "optional": true, "os": [ "openharmony" @@ -1932,6 +2195,7 @@ "cpu": [ "arm64" ], + "license": "MIT", "optional": true, "os": [ "win32" @@ -1944,6 +2208,7 @@ "cpu": [ "ia32" ], + "license": "MIT", "optional": true, "os": [ "win32" @@ -1956,6 +2221,7 @@ "cpu": [ "x64" ], + "license": "MIT", "optional": true, "os": [ "win32" @@ -1968,6 +2234,7 @@ "cpu": [ "x64" ], + "license": "MIT", "optional": true, "os": [ "win32" @@ -1976,27 +2243,32 @@ "node_modules/@seznam/compose-react-refs": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/@seznam/compose-react-refs/-/compose-react-refs-1.0.6.tgz", - "integrity": "sha512-izzOXQfeQLonzrIQb8u6LQ8dk+ymz3WXTIXjvOlTXHq6sbzROg3NWU+9TTAOpEoK9Bth24/6F/XrfHJ5yR5n6Q==" + "integrity": "sha512-izzOXQfeQLonzrIQb8u6LQ8dk+ymz3WXTIXjvOlTXHq6sbzROg3NWU+9TTAOpEoK9Bth24/6F/XrfHJ5yR5n6Q==", + "license": "ISC" }, "node_modules/@socket.io/component-emitter": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/@socket.io/component-emitter/-/component-emitter-3.1.2.tgz", - "integrity": "sha512-9BCxFwvbGg/RsZK9tjXd8s4UcwR0MWeFQ1XEKIQVVvAGJyINdrqKMcTRyLoK8Rse1GjzLV9cwjWV1olXRWEXVA==" + "integrity": "sha512-9BCxFwvbGg/RsZK9tjXd8s4UcwR0MWeFQ1XEKIQVVvAGJyINdrqKMcTRyLoK8Rse1GjzLV9cwjWV1olXRWEXVA==", + "license": "MIT" }, "node_modules/@standard-schema/spec": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.1.0.tgz", - "integrity": "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==" + "integrity": "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==", + "license": "MIT" }, "node_modules/@standard-schema/utils": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/@standard-schema/utils/-/utils-0.3.0.tgz", - "integrity": "sha512-e7Mew686owMaPJVNNLs55PUvgz371nKgwsc4vxE49zsODpJEnxgxRo2y/OKrqueavXgZNMDVj3DdHFlaSAeU8g==" + "integrity": "sha512-e7Mew686owMaPJVNNLs55PUvgz371nKgwsc4vxE49zsODpJEnxgxRo2y/OKrqueavXgZNMDVj3DdHFlaSAeU8g==", + "license": "MIT" }, "node_modules/@storybook/addons": { "version": "6.5.16", "resolved": "https://registry.npmjs.org/@storybook/addons/-/addons-6.5.16.tgz", "integrity": "sha512-p3DqQi+8QRL5k7jXhXmJZLsE/GqHqyY6PcoA1oNTJr0try48uhTGUOYkgzmqtDaa/qPFO5LP+xCPzZXckGtquQ==", + "license": "MIT", "peer": true, "dependencies": { "@storybook/api": "6.5.16", @@ -2020,10 +2292,23 @@ "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0" } }, + "node_modules/@storybook/addons/node_modules/core-js": { + "version": "3.48.0", + "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.48.0.tgz", + "integrity": "sha512-zpEHTy1fjTMZCKLHUZoVeylt9XrzaIN2rbPXEt0k+q7JE5CkCZdo6bNq55bn24a69CH7ErAVLKijxJja4fw+UQ==", + "hasInstallScript": true, + "license": "MIT", + "peer": true, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/core-js" + } + }, "node_modules/@storybook/api": { "version": "6.5.16", "resolved": "https://registry.npmjs.org/@storybook/api/-/api-6.5.16.tgz", "integrity": "sha512-HOsuT8iomqeTMQJrRx5U8nsC7lJTwRr1DhdD0SzlqL4c80S/7uuCy4IZvOt4sYQjOzW5fOo/kamcoBXyLproTA==", + "license": "MIT", "peer": true, "dependencies": { "@storybook/channels": "6.5.16", @@ -2053,10 +2338,23 @@ "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0" } }, + "node_modules/@storybook/api/node_modules/core-js": { + "version": "3.48.0", + "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.48.0.tgz", + "integrity": "sha512-zpEHTy1fjTMZCKLHUZoVeylt9XrzaIN2rbPXEt0k+q7JE5CkCZdo6bNq55bn24a69CH7ErAVLKijxJja4fw+UQ==", + "hasInstallScript": true, + "license": "MIT", + "peer": true, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/core-js" + } + }, "node_modules/@storybook/channels": { "version": "6.5.16", "resolved": "https://registry.npmjs.org/@storybook/channels/-/channels-6.5.16.tgz", "integrity": "sha512-VylzaWQZaMozEwZPJdyJoz+0jpDa8GRyaqu9TGG6QGv+KU5POoZaGLDkRE7TzWkyyP0KQLo80K99MssZCpgSeg==", + "license": "MIT", "peer": true, "dependencies": { "core-js": "^3.8.2", @@ -2068,10 +2366,23 @@ "url": "https://opencollective.com/storybook" } }, + "node_modules/@storybook/channels/node_modules/core-js": { + "version": "3.48.0", + "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.48.0.tgz", + "integrity": "sha512-zpEHTy1fjTMZCKLHUZoVeylt9XrzaIN2rbPXEt0k+q7JE5CkCZdo6bNq55bn24a69CH7ErAVLKijxJja4fw+UQ==", + "hasInstallScript": true, + "license": "MIT", + "peer": true, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/core-js" + } + }, "node_modules/@storybook/client-logger": { "version": "6.5.16", "resolved": "https://registry.npmjs.org/@storybook/client-logger/-/client-logger-6.5.16.tgz", "integrity": "sha512-pxcNaCj3ItDdicPTXTtmYJE3YC1SjxFrBmHcyrN+nffeNyiMuViJdOOZzzzucTUG0wcOOX8jaSyak+nnHg5H1Q==", + "license": "MIT", "peer": true, "dependencies": { "core-js": "^3.8.2", @@ -2082,10 +2393,23 @@ "url": "https://opencollective.com/storybook" } }, + "node_modules/@storybook/client-logger/node_modules/core-js": { + "version": "3.48.0", + "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.48.0.tgz", + "integrity": "sha512-zpEHTy1fjTMZCKLHUZoVeylt9XrzaIN2rbPXEt0k+q7JE5CkCZdo6bNq55bn24a69CH7ErAVLKijxJja4fw+UQ==", + "hasInstallScript": true, + "license": "MIT", + "peer": true, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/core-js" + } + }, "node_modules/@storybook/components": { "version": "6.5.16", "resolved": "https://registry.npmjs.org/@storybook/components/-/components-6.5.16.tgz", "integrity": "sha512-LzBOFJKITLtDcbW9jXl0/PaG+4xAz25PK8JxPZpIALbmOpYWOAPcO6V9C2heX6e6NgWFMUxjplkULEk9RCQMNA==", + "license": "MIT", "peer": true, "dependencies": { "@storybook/client-logger": "6.5.16", @@ -2106,10 +2430,23 @@ "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0" } }, + "node_modules/@storybook/components/node_modules/core-js": { + "version": "3.48.0", + "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.48.0.tgz", + "integrity": "sha512-zpEHTy1fjTMZCKLHUZoVeylt9XrzaIN2rbPXEt0k+q7JE5CkCZdo6bNq55bn24a69CH7ErAVLKijxJja4fw+UQ==", + "hasInstallScript": true, + "license": "MIT", + "peer": true, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/core-js" + } + }, "node_modules/@storybook/core-events": { "version": "6.5.16", "resolved": "https://registry.npmjs.org/@storybook/core-events/-/core-events-6.5.16.tgz", "integrity": "sha512-qMZQwmvzpH5F2uwNUllTPg6eZXr2OaYZQRRN8VZJiuorZzDNdAFmiVWMWdkThwmyLEJuQKXxqCL8lMj/7PPM+g==", + "license": "MIT", "peer": true, "dependencies": { "core-js": "^3.8.2" @@ -2119,10 +2456,23 @@ "url": "https://opencollective.com/storybook" } }, + "node_modules/@storybook/core-events/node_modules/core-js": { + "version": "3.48.0", + "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.48.0.tgz", + "integrity": "sha512-zpEHTy1fjTMZCKLHUZoVeylt9XrzaIN2rbPXEt0k+q7JE5CkCZdo6bNq55bn24a69CH7ErAVLKijxJja4fw+UQ==", + "hasInstallScript": true, + "license": "MIT", + "peer": true, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/core-js" + } + }, "node_modules/@storybook/csf": { "version": "0.0.2--canary.4566f4d.1", "resolved": "https://registry.npmjs.org/@storybook/csf/-/csf-0.0.2--canary.4566f4d.1.tgz", "integrity": "sha512-9OVvMVh3t9znYZwb0Svf/YQoxX2gVOeQTGe2bses2yj+a3+OJnCrUF3/hGv6Em7KujtOdL2LL+JnG49oMVGFgQ==", + "license": "MIT", "peer": true, "dependencies": { "lodash": "^4.17.15" @@ -2132,6 +2482,7 @@ "version": "6.5.16", "resolved": "https://registry.npmjs.org/@storybook/router/-/router-6.5.16.tgz", "integrity": "sha512-ZgeP8a5YV/iuKbv31V8DjPxlV4AzorRiR8OuSt/KqaiYXNXlOoQDz/qMmiNcrshrfLpmkzoq7fSo4T8lWo2UwQ==", + "license": "MIT", "peer": true, "dependencies": { "@storybook/client-logger": "6.5.16", @@ -2149,10 +2500,23 @@ "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0" } }, + "node_modules/@storybook/router/node_modules/core-js": { + "version": "3.48.0", + "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.48.0.tgz", + "integrity": "sha512-zpEHTy1fjTMZCKLHUZoVeylt9XrzaIN2rbPXEt0k+q7JE5CkCZdo6bNq55bn24a69CH7ErAVLKijxJja4fw+UQ==", + "hasInstallScript": true, + "license": "MIT", + "peer": true, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/core-js" + } + }, "node_modules/@storybook/semver": { "version": "7.3.2", "resolved": "https://registry.npmjs.org/@storybook/semver/-/semver-7.3.2.tgz", "integrity": "sha512-SWeszlsiPsMI0Ps0jVNtH64cI5c0UF3f7KgjVKJoNP30crQ6wUSddY2hsdeczZXEKVJGEn50Q60flcGsQGIcrg==", + "license": "ISC", "peer": true, "dependencies": { "core-js": "^3.6.5", @@ -2165,10 +2529,79 @@ "node": ">=10" } }, + "node_modules/@storybook/semver/node_modules/core-js": { + "version": "3.48.0", + "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.48.0.tgz", + "integrity": "sha512-zpEHTy1fjTMZCKLHUZoVeylt9XrzaIN2rbPXEt0k+q7JE5CkCZdo6bNq55bn24a69CH7ErAVLKijxJja4fw+UQ==", + "hasInstallScript": true, + "license": "MIT", + "peer": true, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/core-js" + } + }, + "node_modules/@storybook/semver/node_modules/find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "license": "MIT", + "peer": true, + "dependencies": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@storybook/semver/node_modules/locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "license": "MIT", + "peer": true, + "dependencies": { + "p-locate": "^4.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@storybook/semver/node_modules/p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "license": "MIT", + "peer": true, + "dependencies": { + "p-try": "^2.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@storybook/semver/node_modules/p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "license": "MIT", + "peer": true, + "dependencies": { + "p-limit": "^2.2.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/@storybook/theming": { "version": "6.5.16", "resolved": "https://registry.npmjs.org/@storybook/theming/-/theming-6.5.16.tgz", "integrity": "sha512-hNLctkjaYLRdk1+xYTkC1mg4dYz2wSv6SqbLpcKMbkPHTE0ElhddGPHQqB362md/w9emYXNkt1LSMD8Xk9JzVQ==", + "license": "MIT", "peer": true, "dependencies": { "@storybook/client-logger": "6.5.16", @@ -2185,11 +2618,24 @@ "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0" } }, + "node_modules/@storybook/theming/node_modules/core-js": { + "version": "3.48.0", + "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.48.0.tgz", + "integrity": "sha512-zpEHTy1fjTMZCKLHUZoVeylt9XrzaIN2rbPXEt0k+q7JE5CkCZdo6bNq55bn24a69CH7ErAVLKijxJja4fw+UQ==", + "hasInstallScript": true, + "license": "MIT", + "peer": true, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/core-js" + } + }, "node_modules/@swc/core": { "version": "1.15.11", "resolved": "https://registry.npmjs.org/@swc/core/-/core-1.15.11.tgz", "integrity": "sha512-iLmLTodbYxU39HhMPaMUooPwO/zqJWvsqkrXv1ZI38rMb048p6N7qtAtTp37sw9NzSrvH6oli8EdDygo09IZ/w==", "hasInstallScript": true, + "license": "Apache-2.0", "dependencies": { "@swc/counter": "^0.1.3", "@swc/types": "^0.1.25" @@ -2229,6 +2675,7 @@ "cpu": [ "arm64" ], + "license": "Apache-2.0 AND MIT", "optional": true, "os": [ "darwin" @@ -2244,6 +2691,7 @@ "cpu": [ "x64" ], + "license": "Apache-2.0 AND MIT", "optional": true, "os": [ "darwin" @@ -2259,6 +2707,7 @@ "cpu": [ "arm" ], + "license": "Apache-2.0", "optional": true, "os": [ "linux" @@ -2274,6 +2723,7 @@ "cpu": [ "arm64" ], + "license": "Apache-2.0 AND MIT", "optional": true, "os": [ "linux" @@ -2289,6 +2739,7 @@ "cpu": [ "arm64" ], + "license": "Apache-2.0 AND MIT", "optional": true, "os": [ "linux" @@ -2304,6 +2755,7 @@ "cpu": [ "x64" ], + "license": "Apache-2.0 AND MIT", "optional": true, "os": [ "linux" @@ -2319,6 +2771,7 @@ "cpu": [ "x64" ], + "license": "Apache-2.0 AND MIT", "optional": true, "os": [ "linux" @@ -2334,6 +2787,7 @@ "cpu": [ "arm64" ], + "license": "Apache-2.0 AND MIT", "optional": true, "os": [ "win32" @@ -2349,6 +2803,7 @@ "cpu": [ "ia32" ], + "license": "Apache-2.0 AND MIT", "optional": true, "os": [ "win32" @@ -2364,31 +2819,75 @@ "cpu": [ "x64" ], + "license": "Apache-2.0 AND MIT", "optional": true, "os": [ "win32" ], "engines": { - "node": ">=10" + "node": ">=10" + } + }, + "node_modules/@swc/counter": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/@swc/counter/-/counter-0.1.3.tgz", + "integrity": "sha512-e2BR4lsJkkRlKZ/qCHPw9ZaSxc0MVUd7gtbtaB7aMvHeJVYe8sOB8DBZkP2DtISHGSku9sCK6T6cnY0CtXrOCQ==", + "license": "Apache-2.0" + }, + "node_modules/@swc/types": { + "version": "0.1.25", + "resolved": "https://registry.npmjs.org/@swc/types/-/types-0.1.25.tgz", + "integrity": "sha512-iAoY/qRhNH8a/hBvm3zKj9qQ4oc2+3w1unPJa2XvTK3XjeLXtzcCingVPw/9e5mn1+0yPqxcBGp9Jf0pkfMb1g==", + "license": "Apache-2.0", + "dependencies": { + "@swc/counter": "^0.1.3" + } + }, + "node_modules/@testing-library/dom": { + "version": "10.4.1", + "resolved": "https://registry.npmjs.org/@testing-library/dom/-/dom-10.4.1.tgz", + "integrity": "sha512-o4PXJQidqJl82ckFaXUeoAW+XysPLauYI43Abki5hABd853iMhitooc6znOnczgbTYmEP6U6/y1ZyKAIsvMKGg==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@babel/code-frame": "^7.10.4", + "@babel/runtime": "^7.12.5", + "@types/aria-query": "^5.0.1", + "aria-query": "5.3.0", + "dom-accessibility-api": "^0.5.9", + "lz-string": "^1.5.0", + "picocolors": "1.1.1", + "pretty-format": "^27.0.2" + }, + "engines": { + "node": ">=18" } }, - "node_modules/@swc/counter": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/@swc/counter/-/counter-0.1.3.tgz", - "integrity": "sha512-e2BR4lsJkkRlKZ/qCHPw9ZaSxc0MVUd7gtbtaB7aMvHeJVYe8sOB8DBZkP2DtISHGSku9sCK6T6cnY0CtXrOCQ==" - }, - "node_modules/@swc/types": { - "version": "0.1.25", - "resolved": "https://registry.npmjs.org/@swc/types/-/types-0.1.25.tgz", - "integrity": "sha512-iAoY/qRhNH8a/hBvm3zKj9qQ4oc2+3w1unPJa2XvTK3XjeLXtzcCingVPw/9e5mn1+0yPqxcBGp9Jf0pkfMb1g==", + "node_modules/@testing-library/dom/node_modules/aria-query": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.3.0.tgz", + "integrity": "sha512-b0P0sZPKtyu8HkeRAfCq0IfURZK+SuwMjY1UXGBU27wpAiTwQAIlq56IbIO+ytk/JjS1fMR14ee5WBBfKi5J6A==", + "dev": true, + "license": "Apache-2.0", + "peer": true, "dependencies": { - "@swc/counter": "^0.1.3" + "dequal": "^2.0.3" } }, + "node_modules/@testing-library/dom/node_modules/dom-accessibility-api": { + "version": "0.5.16", + "resolved": "https://registry.npmjs.org/dom-accessibility-api/-/dom-accessibility-api-0.5.16.tgz", + "integrity": "sha512-X7BJ2yElsnOJ30pZF4uIIDfBEVgF4XEBxL9Bxhy6dnrm5hkzqmsWHGTiHqRiITNhMyFLyAiWndIJP7Z1NTteDg==", + "dev": true, + "license": "MIT", + "peer": true + }, "node_modules/@testing-library/jest-dom": { "version": "6.9.1", "resolved": "https://registry.npmjs.org/@testing-library/jest-dom/-/jest-dom-6.9.1.tgz", "integrity": "sha512-zIcONa+hVtVSSep9UT3jZ5rizo2BsxgyDYU7WFD5eICBE7no3881HGeb/QkGfsJs6JTkY1aQhT7rIPC7e+0nnA==", + "dev": true, "license": "MIT", "dependencies": { "@adobe/css-tools": "^4.4.0", @@ -2404,10 +2903,83 @@ "yarn": ">=1" } }, + "node_modules/@testing-library/react": { + "version": "16.3.2", + "resolved": "https://registry.npmjs.org/@testing-library/react/-/react-16.3.2.tgz", + "integrity": "sha512-XU5/SytQM+ykqMnAnvB2umaJNIOsLF3PVv//1Ew4CTcpz0/BRyy/af40qqrt7SjKpDdT1saBMc42CUok5gaw+g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.12.5" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@testing-library/dom": "^10.0.0", + "@types/react": "^18.0.0 || ^19.0.0", + "@types/react-dom": "^18.0.0 || ^19.0.0", + "react": "^18.0.0 || ^19.0.0", + "react-dom": "^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@testing-library/react-hooks": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/@testing-library/react-hooks/-/react-hooks-8.0.1.tgz", + "integrity": "sha512-Aqhl2IVmLt8IovEVarNDFuJDVWVvhnr9/GCU6UUnrYXwgDFF9h2L2o2P9KBni1AST5sT6riAyoukFLyjQUgD/g==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.12.5", + "react-error-boundary": "^3.1.0" + }, + "engines": { + "node": ">=12" + }, + "peerDependencies": { + "@types/react": "^16.9.0 || ^17.0.0", + "react": "^16.9.0 || ^17.0.0", + "react-dom": "^16.9.0 || ^17.0.0", + "react-test-renderer": "^16.9.0 || ^17.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "react-dom": { + "optional": true + }, + "react-test-renderer": { + "optional": true + } + } + }, + "node_modules/@testing-library/user-event": { + "version": "14.6.1", + "resolved": "https://registry.npmjs.org/@testing-library/user-event/-/user-event-14.6.1.tgz", + "integrity": "sha512-vq7fv0rnt+QTXgPxr5Hjc210p6YKq2kmdziLgnsZGgLJ9e6VAShx1pACLuRjd/AS/sr7phAR58OIIpf0LlmQNw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12", + "npm": ">=6" + }, + "peerDependencies": { + "@testing-library/dom": ">=7.21.4" + } + }, "node_modules/@tippyjs/react": { "version": "4.2.6", "resolved": "https://registry.npmjs.org/@tippyjs/react/-/react-4.2.6.tgz", "integrity": "sha512-91RicDR+H7oDSyPycI13q3b7o4O60wa2oRbjlz2fyRLmHImc4vyDwuUP8NtZaN0VARJY5hybvDYrFzhY9+Lbyw==", + "license": "MIT", "dependencies": { "tippy.js": "^6.3.1" }, @@ -2416,25 +2988,48 @@ "react-dom": ">=16.8" } }, + "node_modules/@types/aria-query": { + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/@types/aria-query/-/aria-query-5.0.4.tgz", + "integrity": "sha512-rfT93uj5s0PRL7EzccGMs3brplhcrghnDoV26NqKhCAS1hVo+WdNsPvE/yb6ilfr5hi2MEk6d5EWJTKdxg8jVw==", + "dev": true, + "license": "MIT", + "peer": true + }, + "node_modules/@types/chai": { + "version": "5.2.3", + "resolved": "https://registry.npmjs.org/@types/chai/-/chai-5.2.3.tgz", + "integrity": "sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/deep-eql": "*", + "assertion-error": "^2.0.1" + } + }, "node_modules/@types/d3-array": { "version": "3.2.2", "resolved": "https://registry.npmjs.org/@types/d3-array/-/d3-array-3.2.2.tgz", - "integrity": "sha512-hOLWVbm7uRza0BYXpIIW5pxfrKe0W+D5lrFiAEYR+pb6w3N2SwSMaJbXdUfSEv+dT4MfHBLtn5js0LAWaO6otw==" + "integrity": "sha512-hOLWVbm7uRza0BYXpIIW5pxfrKe0W+D5lrFiAEYR+pb6w3N2SwSMaJbXdUfSEv+dT4MfHBLtn5js0LAWaO6otw==", + "license": "MIT" }, "node_modules/@types/d3-color": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/@types/d3-color/-/d3-color-3.1.3.tgz", - "integrity": "sha512-iO90scth9WAbmgv7ogoq57O9YpKmFBbmoEoCHDB2xMBY0+/KVrqAaCDyCE16dUspeOvIxFFRI+0sEtqDqy2b4A==" + "integrity": "sha512-iO90scth9WAbmgv7ogoq57O9YpKmFBbmoEoCHDB2xMBY0+/KVrqAaCDyCE16dUspeOvIxFFRI+0sEtqDqy2b4A==", + "license": "MIT" }, "node_modules/@types/d3-ease": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/@types/d3-ease/-/d3-ease-3.0.2.tgz", - "integrity": "sha512-NcV1JjO5oDzoK26oMzbILE6HW7uVXOHLQvHshBUW4UMdZGfiY6v5BeQwh9a9tCzv+CeefZQHJt5SRgK154RtiA==" + "integrity": "sha512-NcV1JjO5oDzoK26oMzbILE6HW7uVXOHLQvHshBUW4UMdZGfiY6v5BeQwh9a9tCzv+CeefZQHJt5SRgK154RtiA==", + "license": "MIT" }, "node_modules/@types/d3-interpolate": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/@types/d3-interpolate/-/d3-interpolate-3.0.4.tgz", "integrity": "sha512-mgLPETlrpVV1YRJIglr4Ez47g7Yxjl1lj7YKsiMCb27VJH9W8NVM6Bb9d8kkpG/uAQS5AmbA48q2IAolKKo1MA==", + "license": "MIT", "dependencies": { "@types/d3-color": "*" } @@ -2442,20 +3037,23 @@ "node_modules/@types/d3-path": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/@types/d3-path/-/d3-path-3.1.1.tgz", - "integrity": "sha512-VMZBYyQvbGmWyWVea0EHs/BwLgxc+MKi1zLDCONksozI4YJMcTt8ZEuIR4Sb1MMTE8MMW49v0IwI5+b7RmfWlg==" + "integrity": "sha512-VMZBYyQvbGmWyWVea0EHs/BwLgxc+MKi1zLDCONksozI4YJMcTt8ZEuIR4Sb1MMTE8MMW49v0IwI5+b7RmfWlg==", + "license": "MIT" }, "node_modules/@types/d3-scale": { "version": "4.0.9", "resolved": "https://registry.npmjs.org/@types/d3-scale/-/d3-scale-4.0.9.tgz", "integrity": "sha512-dLmtwB8zkAeO/juAMfnV+sItKjlsw2lKdZVVy6LRr0cBmegxSABiLEpGVmSJJ8O08i4+sGR6qQtb6WtuwJdvVw==", + "license": "MIT", "dependencies": { "@types/d3-time": "*" } }, "node_modules/@types/d3-shape": { - "version": "3.1.7", - "resolved": "https://registry.npmjs.org/@types/d3-shape/-/d3-shape-3.1.7.tgz", - "integrity": "sha512-VLvUQ33C+3J+8p+Daf+nYSOsjB4GXp19/S/aGo60m9h1v6XaxjiT82lKVWJCfzhtuZ3yD7i/TPeC/fuKLLOSmg==", + "version": "3.1.8", + "resolved": "https://registry.npmjs.org/@types/d3-shape/-/d3-shape-3.1.8.tgz", + "integrity": "sha512-lae0iWfcDeR7qt7rA88BNiqdvPS5pFVPpo5OfjElwNaT2yyekbM0C9vK+yqBqEmHr6lDkRnYNoTBYlAgJa7a4w==", + "license": "MIT", "dependencies": { "@types/d3-path": "*" } @@ -2463,22 +3061,33 @@ "node_modules/@types/d3-time": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/@types/d3-time/-/d3-time-3.0.4.tgz", - "integrity": "sha512-yuzZug1nkAAaBlBBikKZTgzCeA+k1uy4ZFwWANOfKw5z5LRhV0gNA7gNkKm7HoK+HRN0wX3EkxGk0fpbWhmB7g==" + "integrity": "sha512-yuzZug1nkAAaBlBBikKZTgzCeA+k1uy4ZFwWANOfKw5z5LRhV0gNA7gNkKm7HoK+HRN0wX3EkxGk0fpbWhmB7g==", + "license": "MIT" }, "node_modules/@types/d3-timer": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/@types/d3-timer/-/d3-timer-3.0.2.tgz", - "integrity": "sha512-Ps3T8E8dZDam6fUyNiMkekK3XUsaUEik+idO9/YjPtfj2qruF8tFBXS7XhtE4iIXBLxhmLjP3SXpLhVf21I9Lw==" + "integrity": "sha512-Ps3T8E8dZDam6fUyNiMkekK3XUsaUEik+idO9/YjPtfj2qruF8tFBXS7XhtE4iIXBLxhmLjP3SXpLhVf21I9Lw==", + "license": "MIT" + }, + "node_modules/@types/deep-eql": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@types/deep-eql/-/deep-eql-4.0.2.tgz", + "integrity": "sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==", + "dev": true, + "license": "MIT" }, "node_modules/@types/estree": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", - "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==" + "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", + "license": "MIT" }, "node_modules/@types/hoist-non-react-statics": { "version": "3.3.7", "resolved": "https://registry.npmjs.org/@types/hoist-non-react-statics/-/hoist-non-react-statics-3.3.7.tgz", "integrity": "sha512-PQTyIulDkIDro8P+IHbKCsw7U2xxBYflVzW/FgWdCAePD9xGSidgA76/GeJ6lBKoblyhf9pBY763gbrN+1dI8g==", + "license": "MIT", "dependencies": { "hoist-non-react-statics": "^3.3.0" }, @@ -2490,37 +3099,44 @@ "version": "1.0.3", "resolved": "https://registry.npmjs.org/@types/is-function/-/is-function-1.0.3.tgz", "integrity": "sha512-/CLhCW79JUeLKznI6mbVieGbl4QU5Hfn+6udw1YHZoofASjbQ5zaP5LzAUZYDpRYEjS4/P+DhEgyJ/PQmGGTWw==", + "license": "MIT", "peer": true }, "node_modules/@types/is-hotkey": { "version": "0.1.10", "resolved": "https://registry.npmjs.org/@types/is-hotkey/-/is-hotkey-0.1.10.tgz", - "integrity": "sha512-RvC8KMw5BCac1NvRRyaHgMMEtBaZ6wh0pyPTBu7izn4Sj/AX9Y4aXU5c7rX8PnM/knsuUpC1IeoBkANtxBypsQ==" + "integrity": "sha512-RvC8KMw5BCac1NvRRyaHgMMEtBaZ6wh0pyPTBu7izn4Sj/AX9Y4aXU5c7rX8PnM/knsuUpC1IeoBkANtxBypsQ==", + "license": "MIT" }, "node_modules/@types/lodash": { "version": "4.17.23", "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.17.23.tgz", - "integrity": "sha512-RDvF6wTulMPjrNdCoYRC8gNR880JNGT8uB+REUpC2Ns4pRqQJhGz90wh7rgdXDPpCczF3VGktDuFGVnz8zP7HA==" + "integrity": "sha512-RDvF6wTulMPjrNdCoYRC8gNR880JNGT8uB+REUpC2Ns4pRqQJhGz90wh7rgdXDPpCczF3VGktDuFGVnz8zP7HA==", + "license": "MIT" }, "node_modules/@types/mdx": { "version": "2.0.13", "resolved": "https://registry.npmjs.org/@types/mdx/-/mdx-2.0.13.tgz", - "integrity": "sha512-+OWZQfAYyio6YkJb3HLxDrvnx6SWWDbC0zVPfBRzUk0/nqoDyf6dNxQi3eArPe8rJ473nobTMQ/8Zk+LxJ+Yuw==" + "integrity": "sha512-+OWZQfAYyio6YkJb3HLxDrvnx6SWWDbC0zVPfBRzUk0/nqoDyf6dNxQi3eArPe8rJ473nobTMQ/8Zk+LxJ+Yuw==", + "license": "MIT" }, "node_modules/@types/parse-json": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/@types/parse-json/-/parse-json-4.0.2.tgz", - "integrity": "sha512-dISoDXWWQwUquiKsyZ4Ng+HX2KsPL7LyHKHQwgGFEA3IaKac4Obd+h2a/a6waisAoepJlBcx9paWqjA8/HVjCw==" + "integrity": "sha512-dISoDXWWQwUquiKsyZ4Ng+HX2KsPL7LyHKHQwgGFEA3IaKac4Obd+h2a/a6waisAoepJlBcx9paWqjA8/HVjCw==", + "license": "MIT" }, "node_modules/@types/prop-types": { "version": "15.7.15", "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.15.tgz", - "integrity": "sha512-F6bEyamV9jKGAFBEmlQnesRPGOQqS2+Uwi0Em15xenOxHaf2hv6L8YCVn3rPdPJOiJfPiCnLIRyvwVaqMY3MIw==" + "integrity": "sha512-F6bEyamV9jKGAFBEmlQnesRPGOQqS2+Uwi0Em15xenOxHaf2hv6L8YCVn3rPdPJOiJfPiCnLIRyvwVaqMY3MIw==", + "license": "MIT" }, "node_modules/@types/react": { - "version": "18.3.27", - "resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.27.tgz", - "integrity": "sha512-cisd7gxkzjBKU2GgdYrTdtQx1SORymWyaAFhaxQPK9bYO9ot3Y5OikQRvY0VYQtvwjeQnizCINJAenh/V7MK2w==", + "version": "18.3.28", + "resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.28.tgz", + "integrity": "sha512-z9VXpC7MWrhfWipitjNdgCauoMLRdIILQsAEV+ZesIzBq/oUlxk0m3ApZuMFCXdnS4U7KrI+l3WRUEGQ8K1QKw==", + "license": "MIT", "dependencies": { "@types/prop-types": "*", "csstype": "^3.2.2" @@ -2530,6 +3146,7 @@ "version": "18.3.7", "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.3.7.tgz", "integrity": "sha512-MEe3UeoENYVFXzoXEWsvcpg6ZvlrFNlOQ7EOsvhI3CfAXwzPfO8Qwuxd40nepsYKqyyVQnTdEfv68q91yLcKrQ==", + "license": "MIT", "peerDependencies": { "@types/react": "^18.0.0" } @@ -2538,6 +3155,7 @@ "version": "7.1.34", "resolved": "https://registry.npmjs.org/@types/react-redux/-/react-redux-7.1.34.tgz", "integrity": "sha512-GdFaVjEbYv4Fthm2ZLvj1VSCedV7TqE5y1kNwnjSdBOTXuRSgowux6J8TAct15T3CKBr63UMk+2CO7ilRhyrAQ==", + "license": "MIT", "dependencies": { "@types/hoist-non-react-statics": "^3.3.0", "@types/react": "*", @@ -2549,44 +3167,43 @@ "version": "4.2.1", "resolved": "https://registry.npmjs.org/redux/-/redux-4.2.1.tgz", "integrity": "sha512-LAUYz4lc+Do8/g7aeRa8JkyDErK6ekstQaqWQrNRW//MY1TvCEpMtpTWvlQ+FPbWCx+Xixu/6SHt5N0HR+SB4w==", + "license": "MIT", "dependencies": { "@babel/runtime": "^7.9.2" } }, - "node_modules/@types/scheduler": { - "version": "0.16.8", - "resolved": "https://registry.npmjs.org/@types/scheduler/-/scheduler-0.16.8.tgz", - "integrity": "sha512-WZLiwShhwLRmeV6zH+GkbOFT6Z6VklCItrDioxUnv+u4Ll+8vKeFySoFyK/0ctcRpOmwAicELfmys1sDc/Rw+A==", - "optional": true, - "peer": true - }, "node_modules/@types/trusted-types": { "version": "2.0.7", "resolved": "https://registry.npmjs.org/@types/trusted-types/-/trusted-types-2.0.7.tgz", "integrity": "sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==", + "license": "MIT", "optional": true }, "node_modules/@types/use-sync-external-store": { "version": "0.0.6", "resolved": "https://registry.npmjs.org/@types/use-sync-external-store/-/use-sync-external-store-0.0.6.tgz", - "integrity": "sha512-zFDAD+tlpf2r4asuHEj0XH6pY6i0g5NeAHPn+15wk3BV6JA69eERFXC1gyGThDkVa1zCyKr5jox1+2LbV/AMLg==" + "integrity": "sha512-zFDAD+tlpf2r4asuHEj0XH6pY6i0g5NeAHPn+15wk3BV6JA69eERFXC1gyGThDkVa1zCyKr5jox1+2LbV/AMLg==", + "license": "MIT" }, "node_modules/@types/webpack-env": { "version": "1.18.8", "resolved": "https://registry.npmjs.org/@types/webpack-env/-/webpack-env-1.18.8.tgz", "integrity": "sha512-G9eAoJRMLjcvN4I08wB5I7YofOb/kaJNd5uoCMX+LbKXTPCF+ZIHuqTnFaK9Jz1rgs035f9JUPUhNFtqgucy/A==", + "license": "MIT", "peer": true }, "node_modules/@ungap/structured-clone": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.3.0.tgz", "integrity": "sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==", - "dev": true + "dev": true, + "license": "ISC" }, "node_modules/@vitejs/plugin-react-swc": { "version": "4.2.3", "resolved": "https://registry.npmjs.org/@vitejs/plugin-react-swc/-/plugin-react-swc-4.2.3.tgz", "integrity": "sha512-QIluDil2prhY1gdA3GGwxZzTAmLdi8cQ2CcuMW4PB/Wu4e/1pzqrwhYWVd09LInCRlDUidQjd0B70QWbjWtLxA==", + "license": "MIT", "dependencies": { "@rolldown/pluginutils": "1.0.0-rc.2", "@swc/core": "^1.15.11" @@ -2598,24 +3215,191 @@ "vite": "^4 || ^5 || ^6 || ^7" } }, + "node_modules/@vitest/coverage-v8": { + "version": "4.0.18", + "resolved": "https://registry.npmjs.org/@vitest/coverage-v8/-/coverage-v8-4.0.18.tgz", + "integrity": "sha512-7i+N2i0+ME+2JFZhfuz7Tg/FqKtilHjGyGvoHYQ6iLV0zahbsJ9sljC9OcFcPDbhYKCet+sG8SsVqlyGvPflZg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@bcoe/v8-coverage": "^1.0.2", + "@vitest/utils": "4.0.18", + "ast-v8-to-istanbul": "^0.3.10", + "istanbul-lib-coverage": "^3.2.2", + "istanbul-lib-report": "^3.0.1", + "istanbul-reports": "^3.2.0", + "magicast": "^0.5.1", + "obug": "^2.1.1", + "std-env": "^3.10.0", + "tinyrainbow": "^3.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "@vitest/browser": "4.0.18", + "vitest": "4.0.18" + }, + "peerDependenciesMeta": { + "@vitest/browser": { + "optional": true + } + } + }, + "node_modules/@vitest/expect": { + "version": "4.0.18", + "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-4.0.18.tgz", + "integrity": "sha512-8sCWUyckXXYvx4opfzVY03EOiYVxyNrHS5QxX3DAIi5dpJAAkyJezHCP77VMX4HKA2LDT/Jpfo8i2r5BE3GnQQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@standard-schema/spec": "^1.0.0", + "@types/chai": "^5.2.2", + "@vitest/spy": "4.0.18", + "@vitest/utils": "4.0.18", + "chai": "^6.2.1", + "tinyrainbow": "^3.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/mocker": { + "version": "4.0.18", + "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-4.0.18.tgz", + "integrity": "sha512-HhVd0MDnzzsgevnOWCBj5Otnzobjy5wLBe4EdeeFGv8luMsGcYqDuFRMcttKWZA5vVO8RFjexVovXvAM4JoJDQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/spy": "4.0.18", + "estree-walker": "^3.0.3", + "magic-string": "^0.30.21" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "msw": "^2.4.9", + "vite": "^6.0.0 || ^7.0.0-0" + }, + "peerDependenciesMeta": { + "msw": { + "optional": true + }, + "vite": { + "optional": true + } + } + }, + "node_modules/@vitest/pretty-format": { + "version": "4.0.18", + "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-4.0.18.tgz", + "integrity": "sha512-P24GK3GulZWC5tz87ux0m8OADrQIUVDPIjjj65vBXYG17ZeU3qD7r+MNZ1RNv4l8CGU2vtTRqixrOi9fYk/yKw==", + "dev": true, + "license": "MIT", + "dependencies": { + "tinyrainbow": "^3.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/runner": { + "version": "4.0.18", + "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-4.0.18.tgz", + "integrity": "sha512-rpk9y12PGa22Jg6g5M3UVVnTS7+zycIGk9ZNGN+m6tZHKQb7jrP7/77WfZy13Y/EUDd52NDsLRQhYKtv7XfPQw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/utils": "4.0.18", + "pathe": "^2.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/snapshot": { + "version": "4.0.18", + "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-4.0.18.tgz", + "integrity": "sha512-PCiV0rcl7jKQjbgYqjtakly6T1uwv/5BQ9SwBLekVg/EaYeQFPiXcgrC2Y7vDMA8dM1SUEAEV82kgSQIlXNMvA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/pretty-format": "4.0.18", + "magic-string": "^0.30.21", + "pathe": "^2.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/spy": { + "version": "4.0.18", + "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-4.0.18.tgz", + "integrity": "sha512-cbQt3PTSD7P2OARdVW3qWER5EGq7PHlvE+QfzSC0lbwO+xnt7+XH06ZzFjFRgzUX//JmpxrCu92VdwvEPlWSNw==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/ui": { + "version": "4.0.18", + "resolved": "https://registry.npmjs.org/@vitest/ui/-/ui-4.0.18.tgz", + "integrity": "sha512-CGJ25bc8fRi8Lod/3GHSvXRKi7nBo3kxh0ApW4yCjmrWmRmlT53B5E08XRSZRliygG0aVNxLrBEqPYdz/KcCtQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/utils": "4.0.18", + "fflate": "^0.8.2", + "flatted": "^3.3.3", + "pathe": "^2.0.3", + "sirv": "^3.0.2", + "tinyglobby": "^0.2.15", + "tinyrainbow": "^3.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "vitest": "4.0.18" + } + }, + "node_modules/@vitest/utils": { + "version": "4.0.18", + "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-4.0.18.tgz", + "integrity": "sha512-msMRKLMVLWygpK3u2Hybgi4MNjcYJvwTb0Ru09+fOyCXIgT5raYP041DRRdiJiI3k/2U6SEbAETB3YtBrUkCFA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/pretty-format": "4.0.18", + "tinyrainbow": "^3.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, "node_modules/abbrev": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-2.0.0.tgz", "integrity": "sha512-6/mh1E2u2YgEsCHdY0Yx5oW+61gZU+1vXaoiHHrpKeuRNNgFvS+/jrwHiQhB5apAf5oB7UB7E19ol2R2LKH8hQ==", + "license": "ISC", "engines": { "node": "^14.17.0 || ^16.13.0 || >=18.0.0" } }, "node_modules/ace-builds": { - "version": "1.43.5", - "resolved": "https://registry.npmjs.org/ace-builds/-/ace-builds-1.43.5.tgz", - "integrity": "sha512-iH5FLBKdB7SVn9GR37UgA/tpQS8OTWIxWAuq3Ofaw+Qbc69FfPXsXd9jeW7KRG2xKpKMqBDnu0tHBrCWY5QI7A==" + "version": "1.43.6", + "resolved": "https://registry.npmjs.org/ace-builds/-/ace-builds-1.43.6.tgz", + "integrity": "sha512-L1ddibQ7F3vyXR2k2fg+I8TQTPWVA6CKeDQr/h2+8CeyTp3W6EQL8xNFZRTztuP8xNOAqL3IYPqdzs31GCjDvg==", + "license": "BSD-3-Clause" }, "node_modules/acorn": { - "version": "8.15.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", - "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", + "version": "8.16.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.16.0.tgz", + "integrity": "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==", "dev": true, + "license": "MIT", "bin": { "acorn": "bin/acorn" }, @@ -2628,15 +3412,27 @@ "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", "dev": true, + "license": "MIT", "peerDependencies": { "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" } }, + "node_modules/agent-base": { + "version": "7.1.4", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.4.tgz", + "integrity": "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 14" + } + }, "node_modules/ajv": { "version": "6.12.6", "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", "dev": true, + "license": "MIT", "dependencies": { "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", @@ -2652,6 +3448,7 @@ "version": "5.0.1", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "license": "MIT", "engines": { "node": ">=8" } @@ -2660,6 +3457,7 @@ "version": "4.3.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "license": "MIT", "dependencies": { "color-convert": "^2.0.1" }, @@ -2674,12 +3472,14 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", - "dev": true + "dev": true, + "license": "Python-2.0" }, "node_modules/aria-query": { "version": "5.3.2", "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.3.2.tgz", "integrity": "sha512-COROpnaoap1E2F000S62r6A60uHZnmlvomhfyT2DlTcrY1OrBKn2UhH7qn5wTC9zMvD0AY7csdPSNwKP+7WiQw==", + "dev": true, "license": "Apache-2.0", "engines": { "node": ">= 0.4" @@ -2690,6 +3490,7 @@ "resolved": "https://registry.npmjs.org/array-buffer-byte-length/-/array-buffer-byte-length-1.0.2.tgz", "integrity": "sha512-LHE+8BuR7RYGDKvnrmcuSq3tDcKv9OFEXQt/HpbZhY7V6h0zlUXutnAD82GiFx9rdieCMjkvtcsPqBwgUl1Iiw==", "dev": true, + "license": "MIT", "dependencies": { "call-bound": "^1.0.3", "is-array-buffer": "^3.0.5" @@ -2705,6 +3506,7 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/array-flat-polyfill/-/array-flat-polyfill-1.0.1.tgz", "integrity": "sha512-hfJmKupmQN0lwi0xG6FQ5U8Rd97RnIERplymOv/qpq8AoNKPPAnxJadjFA23FNWm88wykh9HmpLJUUwUtNU/iw==", + "license": "CC0-1.0", "engines": { "node": ">=6.0.0" } @@ -2714,6 +3516,7 @@ "resolved": "https://registry.npmjs.org/array-includes/-/array-includes-3.1.9.tgz", "integrity": "sha512-FmeCCAenzH0KH381SPT5FZmiA/TmpndpcaShhfgEN9eCVjnFBqq3l1xrI42y8+PPLI6hypzou4GXw00WHmPBLQ==", "dev": true, + "license": "MIT", "dependencies": { "call-bind": "^1.0.8", "call-bound": "^1.0.4", @@ -2735,6 +3538,7 @@ "version": "3.0.1", "resolved": "https://registry.npmjs.org/array-move/-/array-move-3.0.1.tgz", "integrity": "sha512-H3Of6NIn2nNU1gsVDqDnYKY/LCdWvCMMOWifNGhKcVQgiZ6nOek39aESOvro6zmueP07exSl93YLvkN4fZOkSg==", + "license": "MIT", "engines": { "node": ">=10" }, @@ -2747,6 +3551,7 @@ "resolved": "https://registry.npmjs.org/array.prototype.findlast/-/array.prototype.findlast-1.2.5.tgz", "integrity": "sha512-CVvd6FHg1Z3POpBLxO6E6zr+rSKEQ9L6rZHAaY7lLfhKsWYUBBOuMs0e9o24oopj6H+geRCX0YJ+TJLBK2eHyQ==", "dev": true, + "license": "MIT", "dependencies": { "call-bind": "^1.0.7", "define-properties": "^1.2.1", @@ -2767,6 +3572,7 @@ "resolved": "https://registry.npmjs.org/array.prototype.flat/-/array.prototype.flat-1.3.3.tgz", "integrity": "sha512-rwG/ja1neyLqCuGZ5YYrznA62D4mZXg0i1cIskIUKSiqF3Cje9/wXAls9B9s1Wa2fomMsIv8czB8jZcPmxCXFg==", "dev": true, + "license": "MIT", "dependencies": { "call-bind": "^1.0.8", "define-properties": "^1.2.1", @@ -2785,6 +3591,7 @@ "resolved": "https://registry.npmjs.org/array.prototype.flatmap/-/array.prototype.flatmap-1.3.3.tgz", "integrity": "sha512-Y7Wt51eKJSyi80hFrJCePGGNo5ktJCslFuboqJsbf57CCPcm5zztluPlc4/aD8sWsKvlwatezpV4U1efk8kpjg==", "dev": true, + "license": "MIT", "dependencies": { "call-bind": "^1.0.8", "define-properties": "^1.2.1", @@ -2803,6 +3610,7 @@ "resolved": "https://registry.npmjs.org/array.prototype.tosorted/-/array.prototype.tosorted-1.1.4.tgz", "integrity": "sha512-p6Fx8B7b7ZhL/gmUsAy0D15WhvDccw3mnGNbZpi3pmeJdxtWsj2jEaI4Y6oo3XiHfzuSgPwKc04MYt6KgvC/wA==", "dev": true, + "license": "MIT", "dependencies": { "call-bind": "^1.0.7", "define-properties": "^1.2.1", @@ -2819,6 +3627,7 @@ "resolved": "https://registry.npmjs.org/arraybuffer.prototype.slice/-/arraybuffer.prototype.slice-1.0.4.tgz", "integrity": "sha512-BNoCY6SXXPQ7gF2opIP4GBE+Xw7U+pHMYKuzjgCN3GwiaIR09UUeKfheyIry77QtrCBlC0KK0q5/TER/tYh3PQ==", "dev": true, + "license": "MIT", "dependencies": { "array-buffer-byte-length": "^1.0.1", "call-bind": "^1.0.8", @@ -2835,16 +3644,47 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/assertion-error": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-2.0.1.tgz", + "integrity": "sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + } + }, + "node_modules/ast-v8-to-istanbul": { + "version": "0.3.11", + "resolved": "https://registry.npmjs.org/ast-v8-to-istanbul/-/ast-v8-to-istanbul-0.3.11.tgz", + "integrity": "sha512-Qya9fkoofMjCBNVdWINMjB5KZvkYfaO9/anwkWnjxibpWUxo5iHl2sOdP7/uAqaRuUYuoo8rDwnbaaKVFxoUvw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/trace-mapping": "^0.3.31", + "estree-walker": "^3.0.3", + "js-tokens": "^10.0.0" + } + }, + "node_modules/ast-v8-to-istanbul/node_modules/js-tokens": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-10.0.0.tgz", + "integrity": "sha512-lM/UBzQmfJRo9ABXbPWemivdCW8V2G8FHaHdypQaIy523snUjog0W71ayWXTjiR+ixeMyVHN2XcpnTd/liPg/Q==", + "dev": true, + "license": "MIT" + }, "node_modules/async": { "version": "0.9.2", "resolved": "https://registry.npmjs.org/async/-/async-0.9.2.tgz", - "integrity": "sha512-l6ToIJIotphWahxxHyzK9bnLR6kM4jJIIgLShZeqLY7iboHoGkdgFl7W2/Ivi4SkMJYGKqW8vSuk0uKUj6qsSw==" + "integrity": "sha512-l6ToIJIotphWahxxHyzK9bnLR6kM4jJIIgLShZeqLY7iboHoGkdgFl7W2/Ivi4SkMJYGKqW8vSuk0uKUj6qsSw==", + "license": "MIT" }, "node_modules/async-function": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/async-function/-/async-function-1.0.0.tgz", "integrity": "sha512-hsU18Ae8CDTR6Kgu9DYf0EbCr/a5iGL0rytQDobUcdpYOKokk8LEjVphnXkDkgpi0wYVsqrXuP0bZxJaTqdgoA==", "dev": true, + "license": "MIT", "engines": { "node": ">= 0.4" } @@ -2852,12 +3692,14 @@ "node_modules/asynckit": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", - "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==" + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", + "license": "MIT" }, "node_modules/attr-accept": { "version": "2.2.5", "resolved": "https://registry.npmjs.org/attr-accept/-/attr-accept-2.2.5.tgz", "integrity": "sha512-0bDNnY/u6pPwHDMoF0FieU354oBi0a8rD9FcsLwzcGWbc8KS8KPIi7y+s13OlVY+gMWc/9xEMUgNE6Qm8ZllYQ==", + "license": "MIT", "engines": { "node": ">=4" } @@ -2867,6 +3709,7 @@ "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz", "integrity": "sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==", "dev": true, + "license": "MIT", "dependencies": { "possible-typed-array-names": "^1.0.0" }, @@ -2881,6 +3724,7 @@ "version": "1.13.5", "resolved": "https://registry.npmjs.org/axios/-/axios-1.13.5.tgz", "integrity": "sha512-cz4ur7Vb0xS4/KUN0tPWe44eqxrIu31me+fbang3ijiNscE129POzipJJA6zniq2C/Z6sJCjMimjS8Lc/GAs8Q==", + "license": "MIT", "dependencies": { "follow-redirects": "^1.15.11", "form-data": "^4.0.5", @@ -2891,6 +3735,7 @@ "version": "10.2.2", "resolved": "https://registry.npmjs.org/babel-plugin-emotion/-/babel-plugin-emotion-10.2.2.tgz", "integrity": "sha512-SMSkGoqTbTyUTDeuVuPIWifPdUGkTk1Kf9BWRiXIOIcuyMfsdp2EjeiiFvOzX8NOBvEh/ypKYvUh2rkgAJMCLA==", + "license": "MIT", "dependencies": { "@babel/helper-module-imports": "^7.0.0", "@emotion/hash": "0.8.0", @@ -2907,17 +3752,20 @@ "node_modules/babel-plugin-emotion/node_modules/@emotion/hash": { "version": "0.8.0", "resolved": "https://registry.npmjs.org/@emotion/hash/-/hash-0.8.0.tgz", - "integrity": "sha512-kBJtf7PH6aWwZ6fka3zQ0p6SBYzx4fl1LoZXE2RrnYST9Xljm7WfKJrU4g/Xr3Beg72MLrp1AWNUmuYJTL7Cow==" + "integrity": "sha512-kBJtf7PH6aWwZ6fka3zQ0p6SBYzx4fl1LoZXE2RrnYST9Xljm7WfKJrU4g/Xr3Beg72MLrp1AWNUmuYJTL7Cow==", + "license": "MIT" }, "node_modules/babel-plugin-emotion/node_modules/@emotion/memoize": { "version": "0.7.4", "resolved": "https://registry.npmjs.org/@emotion/memoize/-/memoize-0.7.4.tgz", - "integrity": "sha512-Ja/Vfqe3HpuzRsG1oBtWTHk2PGZ7GR+2Vz5iYGelAw8dx32K0y7PjVuxK6z1nMpZOqAFsRUPCkK1YjJ56qJlgw==" + "integrity": "sha512-Ja/Vfqe3HpuzRsG1oBtWTHk2PGZ7GR+2Vz5iYGelAw8dx32K0y7PjVuxK6z1nMpZOqAFsRUPCkK1YjJ56qJlgw==", + "license": "MIT" }, "node_modules/babel-plugin-emotion/node_modules/@emotion/serialize": { "version": "0.11.16", "resolved": "https://registry.npmjs.org/@emotion/serialize/-/serialize-0.11.16.tgz", "integrity": "sha512-G3J4o8by0VRrO+PFeSc3js2myYNOXVJ3Ya+RGVxnshRYgsvErfAOglKAiy1Eo1vhzxqtUvjCyS5gtewzkmvSSg==", + "license": "MIT", "dependencies": { "@emotion/hash": "0.8.0", "@emotion/memoize": "0.7.4", @@ -2929,32 +3777,31 @@ "node_modules/babel-plugin-emotion/node_modules/@emotion/unitless": { "version": "0.7.5", "resolved": "https://registry.npmjs.org/@emotion/unitless/-/unitless-0.7.5.tgz", - "integrity": "sha512-OWORNpfjMsSSUBVrRBVGECkhWcULOAJz9ZW8uK9qgxD+87M7jHRcvh/A96XXNhXTLmKcoYSQtBEX7lHMO7YRwg==" + "integrity": "sha512-OWORNpfjMsSSUBVrRBVGECkhWcULOAJz9ZW8uK9qgxD+87M7jHRcvh/A96XXNhXTLmKcoYSQtBEX7lHMO7YRwg==", + "license": "MIT" }, "node_modules/babel-plugin-emotion/node_modules/@emotion/utils": { "version": "0.11.3", "resolved": "https://registry.npmjs.org/@emotion/utils/-/utils-0.11.3.tgz", - "integrity": "sha512-0o4l6pZC+hI88+bzuaX/6BgOvQVhbt2PfmxauVaYOGgbsAw14wdKyvMCZXnsnsHys94iadcF+RG/wZyx6+ZZBw==" + "integrity": "sha512-0o4l6pZC+hI88+bzuaX/6BgOvQVhbt2PfmxauVaYOGgbsAw14wdKyvMCZXnsnsHys94iadcF+RG/wZyx6+ZZBw==", + "license": "MIT" }, "node_modules/babel-plugin-emotion/node_modules/babel-plugin-macros": { "version": "2.8.0", "resolved": "https://registry.npmjs.org/babel-plugin-macros/-/babel-plugin-macros-2.8.0.tgz", "integrity": "sha512-SEP5kJpfGYqYKpBrj5XU3ahw5p5GOHJ0U5ssOSQ/WBVdwkD2Dzlce95exQTs3jOVWPPKLBN2rlEWkCK7dSmLvg==", + "license": "MIT", "dependencies": { "@babel/runtime": "^7.7.2", "cosmiconfig": "^6.0.0", "resolve": "^1.12.0" } }, - "node_modules/babel-plugin-emotion/node_modules/convert-source-map": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.9.0.tgz", - "integrity": "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==" - }, "node_modules/babel-plugin-emotion/node_modules/cosmiconfig": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-6.0.0.tgz", "integrity": "sha512-xb3ZL6+L8b9JLLCx3ZdoZy4+2ECphCMo2PwqgP1tlfVq6M6YReyzBJtvWWtbDSpNr9hn96pkCiZqUcFEc+54Qg==", + "license": "MIT", "dependencies": { "@types/parse-json": "^4.0.0", "import-fresh": "^3.1.0", @@ -2969,12 +3816,14 @@ "node_modules/babel-plugin-emotion/node_modules/csstype": { "version": "2.6.21", "resolved": "https://registry.npmjs.org/csstype/-/csstype-2.6.21.tgz", - "integrity": "sha512-Z1PhmomIfypOpoMjRQB70jfvy/wxT50qW08YXO5lMIJkrdq4yOTR+AW7FqutScmB9NkLwxo+jU+kZLbofZZq/w==" + "integrity": "sha512-Z1PhmomIfypOpoMjRQB70jfvy/wxT50qW08YXO5lMIJkrdq4yOTR+AW7FqutScmB9NkLwxo+jU+kZLbofZZq/w==", + "license": "MIT" }, "node_modules/babel-plugin-emotion/node_modules/escape-string-regexp": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", + "license": "MIT", "engines": { "node": ">=0.8.0" } @@ -2983,6 +3832,7 @@ "version": "1.10.2", "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz", "integrity": "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==", + "license": "ISC", "engines": { "node": ">= 6" } @@ -2991,6 +3841,7 @@ "version": "3.1.0", "resolved": "https://registry.npmjs.org/babel-plugin-macros/-/babel-plugin-macros-3.1.0.tgz", "integrity": "sha512-Cg7TFGpIr01vOQNODXOOaGz2NpCU5gl8x1qJFbb6hbZxR7XrcE2vtbAsTAbJ7/xwJtUuJEw8K8Zr/AE0LHlesg==", + "license": "MIT", "dependencies": { "@babel/runtime": "^7.12.5", "cosmiconfig": "^7.0.0", @@ -3004,23 +3855,37 @@ "node_modules/babel-plugin-syntax-jsx": { "version": "6.18.0", "resolved": "https://registry.npmjs.org/babel-plugin-syntax-jsx/-/babel-plugin-syntax-jsx-6.18.0.tgz", - "integrity": "sha512-qrPaCSo9c8RHNRHIotaufGbuOBN8rtdC4QrrFFc43vyWCCz7Kl7GL1PGaXtMGQZUXrkCjNEgxDfmAuAabr/rlw==" + "integrity": "sha512-qrPaCSo9c8RHNRHIotaufGbuOBN8rtdC4QrrFFc43vyWCCz7Kl7GL1PGaXtMGQZUXrkCjNEgxDfmAuAabr/rlw==", + "license": "MIT" }, "node_modules/balanced-match": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", - "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==" + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "license": "MIT" + }, + "node_modules/bidi-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/bidi-js/-/bidi-js-1.0.3.tgz", + "integrity": "sha512-RKshQI1R3YQ+n9YJz2QQ147P66ELpa1FQEg20Dk8oW9t2KgLbpDLLp9aGZ7y8WHSshDknG0bknqGw5/tyCs5tw==", + "dev": true, + "license": "MIT", + "dependencies": { + "require-from-string": "^2.0.2" + } }, "node_modules/block-elements": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/block-elements/-/block-elements-1.2.0.tgz", - "integrity": "sha512-4E+pnt4v8HSEEH3Dwe2Bcu8TIbdReez7b5Qjs11dJIdbGFaNSobDgphWxy9NtjYB9ZsZd7DzByDbeXy4DvYz5Q==" + "integrity": "sha512-4E+pnt4v8HSEEH3Dwe2Bcu8TIbdReez7b5Qjs11dJIdbGFaNSobDgphWxy9NtjYB9ZsZd7DzByDbeXy4DvYz5Q==", + "license": "MIT" }, "node_modules/brace-expansion": { "version": "1.1.12", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", "dev": true, + "license": "MIT", "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" @@ -3030,21 +3895,16 @@ "version": "0.2.28", "resolved": "https://registry.npmjs.org/browser-detect/-/browser-detect-0.2.28.tgz", "integrity": "sha512-KeWGHqYQmHDkCFG2dIiX/2wFUgqevbw/rd6wNi9N6rZbaSJFtG5kel0HtprRwCGp8sqpQP79LzDJXf/WCx4WAw==", + "license": "MIT", "dependencies": { "core-js": "^2.5.7" } }, - "node_modules/browser-detect/node_modules/core-js": { - "version": "2.6.12", - "resolved": "https://registry.npmjs.org/core-js/-/core-js-2.6.12.tgz", - "integrity": "sha512-Kb2wC0fvsWfQrgk8HU5lW6U/Lcs8+9aaYcy4ZFc6DDlo4nZ7n70dEgE5rtR0oG6ufKDUnrwfWL1mXR5ljDatrQ==", - "deprecated": "core-js@<3.23.3 is no longer maintained and not recommended for usage due to the number of issues. Because of the V8 engine whims, feature detection in old core-js versions could cause a slowdown up to 100x even if nothing is polyfilled. Some versions have web compatibility issues. Please, upgrade your dependencies to the actual version of core-js.", - "hasInstallScript": true - }, "node_modules/cache": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/cache/-/cache-3.0.0.tgz", "integrity": "sha512-sNoM5jithfalxIceo/uFFm5bOlGjux2y8jEvjNb0F/cACWQaMmWuEPTLl6GzLHdFcNsbWBBdqkBd9NyefZ5UQQ==", + "license": "ISC", "dependencies": { "ds": "^1.4.2" } @@ -3053,6 +3913,7 @@ "version": "1.0.8", "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.8.tgz", "integrity": "sha512-oKlSFMcMwpUg2ednkhQ454wfWiU/ul3CkJe/PEHcTKuiX6RpbehUiFMXu13HalGZxfUwCQzZG747YXBn1im9ww==", + "license": "MIT", "dependencies": { "call-bind-apply-helpers": "^1.0.0", "es-define-property": "^1.0.0", @@ -3070,6 +3931,7 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "license": "MIT", "dependencies": { "es-errors": "^1.3.0", "function-bind": "^1.1.2" @@ -3082,6 +3944,7 @@ "version": "1.0.4", "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", + "license": "MIT", "dependencies": { "call-bind-apply-helpers": "^1.0.2", "get-intrinsic": "^1.3.0" @@ -3097,15 +3960,27 @@ "version": "3.1.0", "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "license": "MIT", "engines": { "node": ">=6" } }, + "node_modules/chai": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/chai/-/chai-6.2.2.tgz", + "integrity": "sha512-NUPRluOfOiTKBKvWPtSD4PhFvWCqOi0BGStNWs57X9js7XGTprSmFoz5F0tWhR4WPjNeR9jXqdC7/UpSJTnlRg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, "node_modules/chalk": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", "dev": true, + "license": "MIT", "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" @@ -3117,15 +3992,32 @@ "url": "https://github.com/chalk/chalk?sponsor=1" } }, + "node_modules/chokidar": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz", + "integrity": "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==", + "license": "MIT", + "dependencies": { + "readdirp": "^4.0.1" + }, + "engines": { + "node": ">= 14.16.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, "node_modules/classnames": { "version": "2.5.1", "resolved": "https://registry.npmjs.org/classnames/-/classnames-2.5.1.tgz", - "integrity": "sha512-saHYOzhIQs6wy2sVxTM6bUDsQO4F50V9RQ22qBpEdCW+I+/Wmke2HOl6lS6dTpdxVhb88/I6+Hs+438c3lfUow==" + "integrity": "sha512-saHYOzhIQs6wy2sVxTM6bUDsQO4F50V9RQ22qBpEdCW+I+/Wmke2HOl6lS6dTpdxVhb88/I6+Hs+438c3lfUow==", + "license": "MIT" }, "node_modules/clean": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/clean/-/clean-4.0.2.tgz", "integrity": "sha512-2LGVh4dNtI16L4UzqDHO6Hbl74YjG1vWvEUU78dgLO4kuyqJZFMNMPBx+EGtYKTFb14e24p+gWXgkabqxc1EUw==", + "license": "MIT", "dependencies": { "async": "^0.9.0", "minimist": "^1.1.0", @@ -3137,6 +4029,7 @@ "version": "1.2.1", "resolved": "https://registry.npmjs.org/clsx/-/clsx-1.2.1.tgz", "integrity": "sha512-EcR6r5a8bj6pu3ycsa/E/cKVGuTgZJZdsyUYHOksG/UHIiKfjxzRxYJpyVBwYaQeOvghal9fcc4PidlgzugAQg==", + "license": "MIT", "engines": { "node": ">=6" } @@ -3145,6 +4038,7 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "license": "MIT", "dependencies": { "color-name": "~1.1.4" }, @@ -3155,12 +4049,14 @@ "node_modules/color-name": { "version": "1.1.4", "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "license": "MIT" }, "node_modules/combined-stream": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "license": "MIT", "dependencies": { "delayed-stream": "~1.0.0" }, @@ -3172,6 +4068,7 @@ "version": "10.0.1", "resolved": "https://registry.npmjs.org/commander/-/commander-10.0.1.tgz", "integrity": "sha512-y4Mg2tXshplEbSGzx7amzPwKKOCGuoSRP/CjEdwwk0FOGlUbq6lKuoyDZTNZkmxHdJtp54hdfY/JUrdL7Xfdug==", + "license": "MIT", "engines": { "node": ">=14" } @@ -3179,18 +4076,21 @@ "node_modules/compute-scroll-into-view": { "version": "1.0.20", "resolved": "https://registry.npmjs.org/compute-scroll-into-view/-/compute-scroll-into-view-1.0.20.tgz", - "integrity": "sha512-UCB0ioiyj8CRjtrvaceBLqqhZCVP+1B8+NWQhmdsm0VXOJtobBCf1dBQmebCCo34qZmUwZfIH2MZLqNHazrfjg==" + "integrity": "sha512-UCB0ioiyj8CRjtrvaceBLqqhZCVP+1B8+NWQhmdsm0VXOJtobBCf1dBQmebCCo34qZmUwZfIH2MZLqNHazrfjg==", + "license": "MIT" }, "node_modules/concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/condense-newlines": { "version": "0.2.1", "resolved": "https://registry.npmjs.org/condense-newlines/-/condense-newlines-0.2.1.tgz", "integrity": "sha512-P7X+QL9Hb9B/c8HI5BFFKmjgBu2XpQuF98WZ9XkO+dBGgk5XgwiQz7o1SmpglNWId3581UcS0SFAWfoIhMHPfg==", + "license": "MIT", "dependencies": { "extend-shallow": "^2.0.1", "is-whitespace": "^0.3.0", @@ -3204,15 +4104,23 @@ "version": "1.1.13", "resolved": "https://registry.npmjs.org/config-chain/-/config-chain-1.1.13.tgz", "integrity": "sha512-qj+f8APARXHrM0hraqXYb2/bOVSV4PvJQlNZ/DVj0QrmNM2q2euizkeuVckQ57J+W0mRH6Hvi+k50M4Jul2VRQ==", + "license": "MIT", "dependencies": { "ini": "^1.3.4", "proto-list": "~1.2.1" } }, + "node_modules/convert-source-map": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.9.0.tgz", + "integrity": "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==", + "license": "MIT" + }, "node_modules/cookie": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/cookie/-/cookie-1.1.1.tgz", "integrity": "sha512-ei8Aos7ja0weRpFzJnEA9UHJ/7XQmqglbRwnf2ATjcB9Wq874VKH9kfjjirM6UhU2/E5fFYadylyhFldcqSidQ==", + "license": "MIT", "engines": { "node": ">=18" }, @@ -3222,20 +4130,18 @@ } }, "node_modules/core-js": { - "version": "3.47.0", - "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.47.0.tgz", - "integrity": "sha512-c3Q2VVkGAUyupsjRnaNX6u8Dq2vAdzm9iuPj5FW0fRxzlxgq9Q39MDq10IvmQSpLgHQNyQzQmOo6bgGHmH3NNg==", + "version": "2.6.12", + "resolved": "https://registry.npmjs.org/core-js/-/core-js-2.6.12.tgz", + "integrity": "sha512-Kb2wC0fvsWfQrgk8HU5lW6U/Lcs8+9aaYcy4ZFc6DDlo4nZ7n70dEgE5rtR0oG6ufKDUnrwfWL1mXR5ljDatrQ==", + "deprecated": "core-js@<3.23.3 is no longer maintained and not recommended for usage due to the number of issues. Because of the V8 engine whims, feature detection in old core-js versions could cause a slowdown up to 100x even if nothing is polyfilled. Some versions have web compatibility issues. Please, upgrade your dependencies to the actual version of core-js.", "hasInstallScript": true, - "peer": true, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/core-js" - } + "license": "MIT" }, "node_modules/cosmiconfig": { "version": "7.1.0", "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-7.1.0.tgz", "integrity": "sha512-AdmX6xUzdNASswsFtmwSt7Vj8po9IuqXm0UXz7QKPuEUmPB4XyjGfaAr2PSuELMwkRMVH1EpIkX5bTZGRB3eCA==", + "license": "MIT", "dependencies": { "@types/parse-json": "^4.0.0", "import-fresh": "^3.2.1", @@ -3251,6 +4157,7 @@ "version": "1.10.2", "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz", "integrity": "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==", + "license": "ISC", "engines": { "node": ">= 6" } @@ -3259,6 +4166,7 @@ "version": "3.2.0", "resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-3.2.0.tgz", "integrity": "sha512-Q+xVJLoGOeIMXZmbUK4HYk+69cQH6LudR0Vu/pRm2YlU/hDV9CiS0gKUMaWY5f2NeUH9C1nV3bsTlCo0FsTV1Q==", + "license": "MIT", "dependencies": { "node-fetch": "^2.7.0" } @@ -3267,6 +4175,7 @@ "version": "7.0.6", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "license": "MIT", "dependencies": { "path-key": "^3.1.0", "shebang-command": "^2.0.0", @@ -3280,6 +4189,7 @@ "version": "1.2.1", "resolved": "https://registry.npmjs.org/css-box-model/-/css-box-model-1.2.1.tgz", "integrity": "sha512-a7Vr4Q/kd/aw96bnJG332W9V9LkJO69JRcaCYDUqjp6/z0w6VcZjgAcTbgFxEPfBgdnAwlh3iwu+hLopa+flJw==", + "license": "MIT", "dependencies": { "tiny-invariant": "^1.0.6" } @@ -3290,21 +4200,54 @@ "integrity": "sha512-COtn4EROW5dBGlE/4PiKnh6rZpAPxDeFLaEEwt4i10jpDMFt2EhQGS79QmmrO+iKCHv0PU/HrOWEhijFd1x99Q==", "license": "BSD" }, + "node_modules/css-tree": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-3.1.0.tgz", + "integrity": "sha512-0eW44TGN5SQXU1mWSkKwFstI/22X2bG1nYzZTYMAWjylYURhse752YgbE4Cx46AC+bAvI+/dYTPRk1LqSUnu6w==", + "dev": true, + "license": "MIT", + "dependencies": { + "mdn-data": "2.12.2", + "source-map-js": "^1.0.1" + }, + "engines": { + "node": "^10 || ^12.20.0 || ^14.13.0 || >=15.0.0" + } + }, "node_modules/css.escape": { "version": "1.5.1", "resolved": "https://registry.npmjs.org/css.escape/-/css.escape-1.5.1.tgz", "integrity": "sha512-YUifsXXuknHlUsmlgyY0PKzgPOr7/FjCePfHNt0jxm83wHZi44VDMQ7/fGNkjY3/jV1MC+1CmZbaHzugyeRtpg==", + "dev": true, "license": "MIT" }, + "node_modules/cssstyle": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/cssstyle/-/cssstyle-6.0.1.tgz", + "integrity": "sha512-IoJs7La+oFp/AB033wBStxNOJt4+9hHMxsXUPANcoXL2b3W4DZKghlJ2cI/eyeRZIQ9ysvYEorVhjrcYctWbog==", + "dev": true, + "license": "MIT", + "dependencies": { + "@asamuzakjp/css-color": "^4.1.2", + "@csstools/css-syntax-patches-for-csstree": "^1.0.26", + "css-tree": "^3.1.0", + "lru-cache": "^11.2.5" + }, + "engines": { + "node": ">=20" + } + }, "node_modules/csstype": { "version": "3.2.3", "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", - "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==" + "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==", + "license": "MIT" }, "node_modules/d3-array": { "version": "3.2.4", "resolved": "https://registry.npmjs.org/d3-array/-/d3-array-3.2.4.tgz", "integrity": "sha512-tdQAmyA18i4J7wprpYq8ClcxZy3SC31QMeByyCFyRt7BVHdREQZ5lpzoe5mFEYZUWe+oq8HBvk9JjpibyEV4Jg==", + "license": "ISC", "dependencies": { "internmap": "1 - 2" }, @@ -3316,6 +4259,7 @@ "version": "3.1.0", "resolved": "https://registry.npmjs.org/d3-color/-/d3-color-3.1.0.tgz", "integrity": "sha512-zg/chbXyeBtMQ1LbD/WSoW2DpC3I0mpmPdW+ynRTj/x2DAWYrIY7qeZIHidozwV24m4iavr15lNwIwLxRmOxhA==", + "license": "ISC", "engines": { "node": ">=12" } @@ -3324,14 +4268,16 @@ "version": "3.0.1", "resolved": "https://registry.npmjs.org/d3-ease/-/d3-ease-3.0.1.tgz", "integrity": "sha512-wR/XK3D3XcLIZwpbvQwQ5fK+8Ykds1ip7A2Txe0yxncXSdq1L9skcG7blcedkOX+ZcgxGAmLX1FrRGbADwzi0w==", + "license": "BSD-3-Clause", "engines": { "node": ">=12" } }, "node_modules/d3-format": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/d3-format/-/d3-format-3.1.0.tgz", - "integrity": "sha512-YyUI6AEuY/Wpt8KWLgZHsIU86atmikuoOmCfommt0LYHiQSPjvX2AcFc38PX0CBpr2RCyZhjex+NS/LPOv6YqA==", + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/d3-format/-/d3-format-3.1.2.tgz", + "integrity": "sha512-AJDdYOdnyRDV5b6ArilzCPPwc1ejkHcoyFarqlPqT7zRYjhavcT3uSrqcMvsgh2CgoPbK3RCwyHaVyxYcP2Arg==", + "license": "ISC", "engines": { "node": ">=12" } @@ -3340,6 +4286,7 @@ "version": "3.0.1", "resolved": "https://registry.npmjs.org/d3-interpolate/-/d3-interpolate-3.0.1.tgz", "integrity": "sha512-3bYs1rOD33uo8aqJfKP3JWPAibgw8Zm2+L9vBKEHJ2Rg+viTR7o5Mmv5mZcieN+FRYaAOWX5SJATX6k1PWz72g==", + "license": "ISC", "dependencies": { "d3-color": "1 - 3" }, @@ -3351,6 +4298,7 @@ "version": "3.1.0", "resolved": "https://registry.npmjs.org/d3-path/-/d3-path-3.1.0.tgz", "integrity": "sha512-p3KP5HCf/bvjBSSKuXid6Zqijx7wIfNW+J/maPs+iwR35at5JCbLUT0LzF1cnjbCHWhqzQTIN2Jpe8pRebIEFQ==", + "license": "ISC", "engines": { "node": ">=12" } @@ -3359,6 +4307,7 @@ "version": "4.0.2", "resolved": "https://registry.npmjs.org/d3-scale/-/d3-scale-4.0.2.tgz", "integrity": "sha512-GZW464g1SH7ag3Y7hXjf8RoUuAFIqklOAq3MRl4OaWabTFJY9PN/E1YklhXLh+OQ3fM9yS2nOkCoS+WLZ6kvxQ==", + "license": "ISC", "dependencies": { "d3-array": "2.10.0 - 3", "d3-format": "1 - 3", @@ -3374,6 +4323,7 @@ "version": "3.2.0", "resolved": "https://registry.npmjs.org/d3-shape/-/d3-shape-3.2.0.tgz", "integrity": "sha512-SaLBuwGm3MOViRq2ABk3eLoxwZELpH6zhl3FbAoJ7Vm1gofKx6El1Ib5z23NUEhF9AsGl7y+dzLe5Cw2AArGTA==", + "license": "ISC", "dependencies": { "d3-path": "^3.1.0" }, @@ -3385,6 +4335,7 @@ "version": "3.1.0", "resolved": "https://registry.npmjs.org/d3-time/-/d3-time-3.1.0.tgz", "integrity": "sha512-VqKjzBLejbSMT4IgbmVgDjpkYrNWUYJnbCGo874u7MMKIWsILRX+OpX/gTk8MqjpT1A/c6HY2dCA77ZN0lkQ2Q==", + "license": "ISC", "dependencies": { "d3-array": "2 - 3" }, @@ -3396,6 +4347,7 @@ "version": "4.1.0", "resolved": "https://registry.npmjs.org/d3-time-format/-/d3-time-format-4.1.0.tgz", "integrity": "sha512-dJxPBlzC7NugB2PDLwo9Q8JiTR3M3e4/XANkreKSUxF8vvXKqm1Yfq4Q5dl8budlunRVlUUaDUgFt7eA8D6NLg==", + "license": "ISC", "dependencies": { "d3-time": "1 - 3" }, @@ -3407,15 +4359,31 @@ "version": "3.0.1", "resolved": "https://registry.npmjs.org/d3-timer/-/d3-timer-3.0.1.tgz", "integrity": "sha512-ndfJ/JxxMd3nw31uyKoY2naivF+r29V+Lc0svZxe1JvvIRmi8hUsrMvdOwgS1o6uBHmiz91geQ0ylPP0aj1VUA==", + "license": "ISC", "engines": { "node": ">=12" } }, + "node_modules/data-urls": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/data-urls/-/data-urls-7.0.0.tgz", + "integrity": "sha512-23XHcCF+coGYevirZceTVD7NdJOqVn+49IHyxgszm+JIiHLoB2TkmPtsYkNWT1pvRSGkc35L6NHs0yHkN2SumA==", + "dev": true, + "license": "MIT", + "dependencies": { + "whatwg-mimetype": "^5.0.0", + "whatwg-url": "^16.0.0" + }, + "engines": { + "node": "^20.19.0 || ^22.12.0 || >=24.0.0" + } + }, "node_modules/data-view-buffer": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/data-view-buffer/-/data-view-buffer-1.0.2.tgz", "integrity": "sha512-EmKO5V3OLXh1rtK2wgXRansaK1/mtVdTUEiEI0W8RkvgT05kfxaH29PliLnpLP73yYO6142Q72QNa8Wx/A5CqQ==", "dev": true, + "license": "MIT", "dependencies": { "call-bound": "^1.0.3", "es-errors": "^1.3.0", @@ -3433,6 +4401,7 @@ "resolved": "https://registry.npmjs.org/data-view-byte-length/-/data-view-byte-length-1.0.2.tgz", "integrity": "sha512-tuhGbE6CfTM9+5ANGf+oQb72Ky/0+s3xKUpHvShfiz2RxMFgFPjsXuRLBVMtvMs15awe45SRb83D6wH4ew6wlQ==", "dev": true, + "license": "MIT", "dependencies": { "call-bound": "^1.0.3", "es-errors": "^1.3.0", @@ -3450,6 +4419,7 @@ "resolved": "https://registry.npmjs.org/data-view-byte-offset/-/data-view-byte-offset-1.0.1.tgz", "integrity": "sha512-BS8PfmtDGnrgYdOonGZQdLZslWIeCGFP9tpan0hi1Co2Zr2NKADsvGYA8XxuG/4UWgJ6Cjtv+YJnB6MM69QGlQ==", "dev": true, + "license": "MIT", "dependencies": { "call-bound": "^1.0.2", "es-errors": "^1.3.0", @@ -3466,6 +4436,7 @@ "version": "2.30.0", "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-2.30.0.tgz", "integrity": "sha512-fnULvOpxnC5/Vg3NCiWelDsLiUc9bRwAPs/+LfTLNvetFCtCTN+yQz15C/fs4AwX1R9K5GLtLfn8QW+dWisaAw==", + "license": "MIT", "dependencies": { "@babel/runtime": "^7.21.0" }, @@ -3481,6 +4452,7 @@ "version": "4.4.3", "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "license": "MIT", "dependencies": { "ms": "^2.1.3" }, @@ -3493,15 +4465,24 @@ } } }, + "node_modules/decimal.js": { + "version": "10.6.0", + "resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-10.6.0.tgz", + "integrity": "sha512-YpgQiITW3JXGntzdUmyUR1V812Hn8T1YVXhCu+wO3OpS4eU9l4YdD3qjyiKdV6mvV29zapkMeD390UVEf2lkUg==", + "dev": true, + "license": "MIT" + }, "node_modules/decimal.js-light": { "version": "2.5.1", "resolved": "https://registry.npmjs.org/decimal.js-light/-/decimal.js-light-2.5.1.tgz", - "integrity": "sha512-qIMFpTMZmny+MMIitAB6D7iVPEorVw6YQRWkvarTkT4tBeSLLiHzcwj6q0MmYSFCiVpiqPJTJEYIrpcPzVEIvg==" + "integrity": "sha512-qIMFpTMZmny+MMIitAB6D7iVPEorVw6YQRWkvarTkT4tBeSLLiHzcwj6q0MmYSFCiVpiqPJTJEYIrpcPzVEIvg==", + "license": "MIT" }, "node_modules/deep-equal": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/deep-equal/-/deep-equal-1.1.2.tgz", "integrity": "sha512-5tdhKF6DbU7iIzrIOa1AOUt39ZRm13cmL1cGEh//aqR8x9+tNfbywRf0n5FD/18OKMdo7DNEtrX2t22ZAkI+eg==", + "license": "MIT", "dependencies": { "is-arguments": "^1.1.1", "is-date-object": "^1.0.5", @@ -3521,12 +4502,14 @@ "version": "0.1.4", "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/deepmerge": { "version": "2.2.1", "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-2.2.1.tgz", "integrity": "sha512-R9hc1Xa/NOBi9WRVUWg19rl1UB7Tt4kuPd+thNJgFZoxXsTz7ncaPaeIm+40oSGuP33DfMb4sZt1QIGiJzC4EA==", + "license": "MIT", "engines": { "node": ">=0.10.0" } @@ -3535,6 +4518,7 @@ "version": "1.1.4", "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==", + "license": "MIT", "dependencies": { "es-define-property": "^1.0.0", "es-errors": "^1.3.0", @@ -3551,6 +4535,7 @@ "version": "1.2.1", "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.2.1.tgz", "integrity": "sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==", + "license": "MIT", "dependencies": { "define-data-property": "^1.0.1", "has-property-descriptors": "^1.0.0", @@ -3567,19 +4552,33 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "license": "MIT", "engines": { "node": ">=0.4.0" } }, + "node_modules/dequal": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz", + "integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">=6" + } + }, "node_modules/detect-browser": { "version": "5.3.0", "resolved": "https://registry.npmjs.org/detect-browser/-/detect-browser-5.3.0.tgz", - "integrity": "sha512-53rsFbGdwMwlF7qvCt0ypLM5V5/Mbl0szB7GPN8y9NCcbknYOeVVXdrXEq+90IwAfrrzt6Hd+u2E2ntakICU8w==" + "integrity": "sha512-53rsFbGdwMwlF7qvCt0ypLM5V5/Mbl0szB7GPN8y9NCcbknYOeVVXdrXEq+90IwAfrrzt6Hd+u2E2ntakICU8w==", + "license": "MIT" }, "node_modules/detect-libc": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", + "license": "Apache-2.0", "optional": true, "engines": { "node": ">=8" @@ -3588,12 +4587,14 @@ "node_modules/diff-match-patch": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/diff-match-patch/-/diff-match-patch-1.0.5.tgz", - "integrity": "sha512-IayShXAgj/QMXgB0IWmKx+rOPuGMhqm5w6jvFxmVenXKIzRqTAAsbBPT3kWQeGANj3jGgvcvv4yK6SxqYmikgw==" + "integrity": "sha512-IayShXAgj/QMXgB0IWmKx+rOPuGMhqm5w6jvFxmVenXKIzRqTAAsbBPT3kWQeGANj3jGgvcvv4yK6SxqYmikgw==", + "license": "Apache-2.0" }, "node_modules/direction": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/direction/-/direction-1.0.4.tgz", "integrity": "sha512-GYqKi1aH7PJXxdhTeZBFrg8vUBeKXi+cNprXsC1kpJcbcVnV9wBsrOu1cQEdG0WeQwlfHiy3XvnKfIrJ2R0NzQ==", + "license": "MIT", "bin": { "direction": "cli.js" }, @@ -3606,6 +4607,7 @@ "version": "11.1.3", "resolved": "https://registry.npmjs.org/dnd-core/-/dnd-core-11.1.3.tgz", "integrity": "sha512-QugF55dNW+h+vzxVJ/LSJeTeUw9MCJ2cllhmVThVPEtF16ooBkxj0WBE5RB+AceFxMFo1rO6bJKXtqKl+JNnyA==", + "license": "MIT", "dependencies": { "@react-dnd/asap": "^4.0.0", "@react-dnd/invariant": "^2.0.0", @@ -3616,6 +4618,7 @@ "version": "4.2.1", "resolved": "https://registry.npmjs.org/redux/-/redux-4.2.1.tgz", "integrity": "sha512-LAUYz4lc+Do8/g7aeRa8JkyDErK6ekstQaqWQrNRW//MY1TvCEpMtpTWvlQ+FPbWCx+Xixu/6SHt5N0HR+SB4w==", + "license": "MIT", "dependencies": { "@babel/runtime": "^7.9.2" } @@ -3625,6 +4628,7 @@ "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", "dev": true, + "license": "Apache-2.0", "dependencies": { "esutils": "^2.0.2" }, @@ -3636,12 +4640,14 @@ "version": "0.6.3", "resolved": "https://registry.npmjs.org/dom-accessibility-api/-/dom-accessibility-api-0.6.3.tgz", "integrity": "sha512-7ZgogeTnjuHbo+ct10G9Ffp0mif17idi0IyWNVA/wcwcm7NPOD/WEHVP3n7n3MhXqxoIYm8d6MuZohYWIZ4T3w==", + "dev": true, "license": "MIT" }, "node_modules/dom-helpers": { "version": "5.2.1", "resolved": "https://registry.npmjs.org/dom-helpers/-/dom-helpers-5.2.1.tgz", "integrity": "sha512-nRCa7CK3VTrM2NmGkIy4cbK7IZlgBE/PYMn55rrXefr5xXDP0LdtfPnblFDoVdcAfslJ7or6iqAUnx0CCGIWQA==", + "license": "MIT", "dependencies": { "@babel/runtime": "^7.8.7", "csstype": "^3.0.2" @@ -3651,6 +4657,7 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-2.0.0.tgz", "integrity": "sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==", + "license": "MIT", "dependencies": { "domelementtype": "^2.3.0", "domhandler": "^5.0.2", @@ -3675,12 +4682,14 @@ "type": "github", "url": "https://github.com/sponsors/fb55" } - ] + ], + "license": "BSD-2-Clause" }, "node_modules/domhandler": { "version": "5.0.3", "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-5.0.3.tgz", "integrity": "sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==", + "license": "BSD-2-Clause", "dependencies": { "domelementtype": "^2.3.0" }, @@ -3695,6 +4704,7 @@ "version": "3.3.1", "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.3.1.tgz", "integrity": "sha512-qkdCKzLNtrgPFP1Vo+98FRzJnBRGe4ffyCea9IwHB1fyxPOeNTHpLKYGd4Uk9xvNoH0ZoOjwZxNptyMwqrId1Q==", + "license": "(MPL-2.0 OR Apache-2.0)", "optionalDependencies": { "@types/trusted-types": "^2.0.7" } @@ -3703,6 +4713,7 @@ "version": "3.2.2", "resolved": "https://registry.npmjs.org/domutils/-/domutils-3.2.2.tgz", "integrity": "sha512-6kZKyUajlDuqlHKVX1w7gyslj9MPIXzIFiz/rGu35uC1wMi+kMhQwGhl4lt9unC9Vb9INnY9Z3/ZA3+FhASLaw==", + "license": "BSD-2-Clause", "dependencies": { "dom-serializer": "^2.0.0", "domelementtype": "^2.3.0", @@ -3715,12 +4726,14 @@ "node_modules/ds": { "version": "1.4.2", "resolved": "https://registry.npmjs.org/ds/-/ds-1.4.2.tgz", - "integrity": "sha512-d5nMCjfod+srvE/1Bnt/u+L++6N8KJx3ZAi95AGp0g6RtfuGDNlGciWL/iiwKHsFVBVnA3/HEFUq5SW1NgTQ3Q==" + "integrity": "sha512-d5nMCjfod+srvE/1Bnt/u+L++6N8KJx3ZAi95AGp0g6RtfuGDNlGciWL/iiwKHsFVBVnA3/HEFUq5SW1NgTQ3Q==", + "license": "MIT" }, "node_modules/dunder-proto": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "license": "MIT", "dependencies": { "call-bind-apply-helpers": "^1.0.1", "es-errors": "^1.3.0", @@ -3733,12 +4746,14 @@ "node_modules/eastasianwidth": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", - "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==" + "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", + "license": "MIT" }, "node_modules/editorconfig": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/editorconfig/-/editorconfig-1.0.4.tgz", "integrity": "sha512-L9Qe08KWTlqYMVvMcTIvMAdl1cDUubzRNYL+WfA4bLDMHe4nemKkpmYzkznE1FwLKu0EEmy6obgQKzMJrg4x9Q==", + "license": "MIT", "dependencies": { "@one-ini/wasm": "0.1.1", "commander": "^10.0.0", @@ -3756,6 +4771,7 @@ "version": "2.0.2", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", + "license": "MIT", "dependencies": { "balanced-match": "^1.0.0" } @@ -3764,6 +4780,7 @@ "version": "9.0.1", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.1.tgz", "integrity": "sha512-0jWhJpD/MdhPXwPuiRkCbfYfSKp2qnn2eOc279qI7f+osl/l+prKSrvhg157zSYvx/1nmgn2NqdT6k2Z7zSH9w==", + "license": "ISC", "dependencies": { "brace-expansion": "^2.0.1" }, @@ -3775,9 +4792,10 @@ } }, "node_modules/editorconfig/node_modules/semver": { - "version": "7.7.3", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz", - "integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==", + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", + "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", + "license": "ISC", "bin": { "semver": "bin/semver.js" }, @@ -3788,12 +4806,14 @@ "node_modules/emoji-regex": { "version": "9.2.2", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", - "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==" + "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", + "license": "MIT" }, "node_modules/engine.io-client": { "version": "6.6.4", "resolved": "https://registry.npmjs.org/engine.io-client/-/engine.io-client-6.6.4.tgz", "integrity": "sha512-+kjUJnZGwzewFDw951CDWcwj35vMNf2fcj7xQWOctq1F2i1jkDdVvdFG9kM/BEChymCH36KgjnW0NsL58JYRxw==", + "license": "MIT", "dependencies": { "@socket.io/component-emitter": "~3.1.0", "debug": "~4.4.1", @@ -3802,30 +4822,11 @@ "xmlhttprequest-ssl": "~2.1.1" } }, - "node_modules/engine.io-client/node_modules/ws": { - "version": "8.18.3", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.3.tgz", - "integrity": "sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg==", - "engines": { - "node": ">=10.0.0" - }, - "peerDependencies": { - "bufferutil": "^4.0.1", - "utf-8-validate": ">=5.0.2" - }, - "peerDependenciesMeta": { - "bufferutil": { - "optional": true - }, - "utf-8-validate": { - "optional": true - } - } - }, "node_modules/engine.io-parser": { "version": "5.2.3", "resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-5.2.3.tgz", "integrity": "sha512-HqD3yTBfnBxIrbnM1DoD6Pcq8NECnh8d4As1Qgh0z5Gg3jRRIqijury0CL3ghu/edArpUYiYqQiDUQBIs4np3Q==", + "license": "MIT", "engines": { "node": ">=10.0.0" } @@ -3834,6 +4835,7 @@ "version": "4.5.0", "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", + "license": "BSD-2-Clause", "engines": { "node": ">=0.12" }, @@ -3845,6 +4847,7 @@ "version": "1.3.4", "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.4.tgz", "integrity": "sha512-sqQamAnR14VgCr1A618A3sGrygcpK+HEbenA/HiEAkkUwcZIIB/tgWqHFxWgOyDh4nB4JCRimh79dR5Ywc9MDQ==", + "license": "MIT", "dependencies": { "is-arrayish": "^0.2.1" } @@ -3854,6 +4857,7 @@ "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.24.1.tgz", "integrity": "sha512-zHXBLhP+QehSSbsS9Pt23Gg964240DPd6QCf8WpkqEXxQ7fhdZzYsocOr5u7apWonsS5EjZDmTF+/slGMyasvw==", "dev": true, + "license": "MIT", "dependencies": { "array-buffer-byte-length": "^1.0.2", "arraybuffer.prototype.slice": "^1.0.4", @@ -3921,6 +4925,7 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "license": "MIT", "engines": { "node": ">= 0.4" } @@ -3929,6 +4934,7 @@ "version": "1.3.0", "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "license": "MIT", "engines": { "node": ">= 0.4" } @@ -3938,6 +4944,7 @@ "resolved": "https://registry.npmjs.org/es-iterator-helpers/-/es-iterator-helpers-1.2.2.tgz", "integrity": "sha512-BrUQ0cPTB/IwXj23HtwHjS9n7O4h9FX94b4xc5zlTHxeLgTAdzYUDyy6KdExAl9lbN5rtfe44xpjpmj9grxs5w==", "dev": true, + "license": "MIT", "dependencies": { "call-bind": "^1.0.8", "call-bound": "^1.0.4", @@ -3960,10 +4967,18 @@ "node": ">= 0.4" } }, + "node_modules/es-module-lexer": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.7.0.tgz", + "integrity": "sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==", + "dev": true, + "license": "MIT" + }, "node_modules/es-object-atoms": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", + "license": "MIT", "dependencies": { "es-errors": "^1.3.0" }, @@ -3975,6 +4990,7 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", + "license": "MIT", "dependencies": { "es-errors": "^1.3.0", "get-intrinsic": "^1.2.6", @@ -3990,6 +5006,7 @@ "resolved": "https://registry.npmjs.org/es-shim-unscopables/-/es-shim-unscopables-1.1.0.tgz", "integrity": "sha512-d9T8ucsEhh8Bi1woXCf+TIKDIROLG5WCkxg8geBCbvk22kzwC5G2OnXVMO6FUsvQlgUUXQ2itephWDLqDzbeCw==", "dev": true, + "license": "MIT", "dependencies": { "hasown": "^2.0.2" }, @@ -4002,6 +5019,7 @@ "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.3.0.tgz", "integrity": "sha512-w+5mJ3GuFL+NjVtJlvydShqE1eN3h3PbI7/5LAsYJP/2qtuMXjfL2LpHSRqo4b4eSF5K/DH1JXKUAHSB2UW50g==", "dev": true, + "license": "MIT", "dependencies": { "is-callable": "^1.2.7", "is-date-object": "^1.0.5", @@ -4019,6 +5037,7 @@ "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.27.3.tgz", "integrity": "sha512-8VwMnyGCONIs6cWue2IdpHxHnAjzxnw2Zr7MkVxB2vjmQ2ivqGFb4LEG3SMnv0Gb2F/G/2yA8zUaiL1gywDCCg==", "hasInstallScript": true, + "license": "MIT", "bin": { "esbuild": "bin/esbuild" }, @@ -4058,6 +5077,7 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "license": "MIT", "engines": { "node": ">=10" }, @@ -4071,6 +5091,7 @@ "integrity": "sha512-ypowyDxpVSYpkXr9WPv2PAZCtNip1Mv5KTW0SCurXv/9iOpcrH9PaqUElksqEB6pChqHGDRCFTyrZlGhnLNGiA==", "deprecated": "This version is no longer supported. Please see https://eslint.org/version-support for other options.", "dev": true, + "license": "MIT", "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/regexpp": "^4.6.1", @@ -4126,6 +5147,7 @@ "resolved": "https://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-7.37.5.tgz", "integrity": "sha512-Qteup0SqU15kdocexFNAJMvCJEfa2xUKNV4CC1xsVMrIIqEy3SQ/rqyxCWNzfrd3/ldy6HMlD2e0JDVpDg2qIA==", "dev": true, + "license": "MIT", "dependencies": { "array-includes": "^3.1.8", "array.prototype.findlast": "^1.2.5", @@ -4158,6 +5180,7 @@ "resolved": "https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-4.6.2.tgz", "integrity": "sha512-QzliNJq4GinDBcD8gPB5v0wh6g8q3SUi6EFF0x8N/BL9PoVs0atuGc47ozMRyOWAKdwaZ5OnbOEa3WR+dSGKuQ==", "dev": true, + "license": "MIT", "engines": { "node": ">=10" }, @@ -4170,6 +5193,7 @@ "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz", "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==", "dev": true, + "license": "Apache-2.0", "dependencies": { "esutils": "^2.0.2" }, @@ -4178,18 +5202,25 @@ } }, "node_modules/eslint-plugin-react/node_modules/resolve": { - "version": "2.0.0-next.5", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-2.0.0-next.5.tgz", - "integrity": "sha512-U7WjGVG9sH8tvjW5SmGbQuui75FiyjAX72HX15DwBBwF9dNiQZRQAg9nnPhYy+TUnE0+VcrttuvNI8oSxZcocA==", + "version": "2.0.0-next.6", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-2.0.0-next.6.tgz", + "integrity": "sha512-3JmVl5hMGtJ3kMmB3zi3DL25KfkCEyy3Tw7Gmw7z5w8M9WlwoPFnIvwChzu1+cF3iaK3sp18hhPz8ANeimdJfA==", "dev": true, + "license": "MIT", "dependencies": { - "is-core-module": "^2.13.0", + "es-errors": "^1.3.0", + "is-core-module": "^2.16.1", + "node-exports-info": "^1.6.0", + "object-keys": "^1.1.1", "path-parse": "^1.0.7", "supports-preserve-symlinks-flag": "^1.0.0" }, "bin": { "resolve": "bin/resolve" }, + "engines": { + "node": ">= 0.4" + }, "funding": { "url": "https://github.com/sponsors/ljharb" } @@ -4199,6 +5230,7 @@ "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.2.2.tgz", "integrity": "sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==", "dev": true, + "license": "BSD-2-Clause", "dependencies": { "esrecurse": "^4.3.0", "estraverse": "^5.2.0" @@ -4209,90 +5241,18 @@ "funding": { "url": "https://opencollective.com/eslint" } - }, - "node_modules/eslint-visitor-keys": { - "version": "3.4.3", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", - "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", - "dev": true, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/eslint/node_modules/find-up": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", - "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", - "dev": true, - "dependencies": { - "locate-path": "^6.0.0", - "path-exists": "^4.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/eslint/node_modules/glob-parent": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", - "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", - "dev": true, - "dependencies": { - "is-glob": "^4.0.3" - }, - "engines": { - "node": ">=10.13.0" - } - }, - "node_modules/eslint/node_modules/locate-path": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", - "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", - "dev": true, - "dependencies": { - "p-locate": "^5.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/eslint/node_modules/p-limit": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", - "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", - "dev": true, - "dependencies": { - "yocto-queue": "^0.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/eslint/node_modules/p-locate": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", - "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + }, + "node_modules/eslint-visitor-keys": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", "dev": true, - "dependencies": { - "p-limit": "^3.0.2" - }, + "license": "Apache-2.0", "engines": { - "node": ">=10" + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "url": "https://opencollective.com/eslint" } }, "node_modules/espree": { @@ -4300,6 +5260,7 @@ "resolved": "https://registry.npmjs.org/espree/-/espree-9.6.1.tgz", "integrity": "sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==", "dev": true, + "license": "BSD-2-Clause", "dependencies": { "acorn": "^8.9.0", "acorn-jsx": "^5.3.2", @@ -4317,6 +5278,7 @@ "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.7.0.tgz", "integrity": "sha512-Ap6G0WQwcU/LHsvLwON1fAQX9Zp0A2Y6Y/cJBl9r/JbW90Zyg4/zbG6zzKa2OTALELarYHmKu0GhpM5EO+7T0g==", "dev": true, + "license": "BSD-3-Clause", "dependencies": { "estraverse": "^5.1.0" }, @@ -4329,6 +5291,7 @@ "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", "dev": true, + "license": "BSD-2-Clause", "dependencies": { "estraverse": "^5.2.0" }, @@ -4341,15 +5304,27 @@ "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", "dev": true, + "license": "BSD-2-Clause", "engines": { "node": ">=4.0" } }, + "node_modules/estree-walker": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz", + "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0" + } + }, "node_modules/esutils": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", "dev": true, + "license": "BSD-2-Clause", "engines": { "node": ">=0.10.0" } @@ -4357,17 +5332,30 @@ "node_modules/eventemitter3": { "version": "4.0.7", "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz", - "integrity": "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==" + "integrity": "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==", + "license": "MIT" }, "node_modules/exenv": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/exenv/-/exenv-1.2.2.tgz", - "integrity": "sha512-Z+ktTxTwv9ILfgKCk32OX3n/doe+OcLTRtqK9pcL+JsP3J1/VW8Uvl4ZjLlKqeW4rzK4oesDOGMEMRIZqtP4Iw==" + "integrity": "sha512-Z+ktTxTwv9ILfgKCk32OX3n/doe+OcLTRtqK9pcL+JsP3J1/VW8Uvl4ZjLlKqeW4rzK4oesDOGMEMRIZqtP4Iw==", + "license": "BSD-3-Clause" + }, + "node_modules/expect-type": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/expect-type/-/expect-type-1.3.0.tgz", + "integrity": "sha512-knvyeauYhqjOYvQ66MznSMs83wmHrCycNEN6Ao+2AeYEfxUIkuiVxdEa1qlGEPK+We3n0THiDciYSsCcgW/DoA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=12.0.0" + } }, "node_modules/extend-shallow": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", "integrity": "sha512-zCnTtlxNoAiDc3gqY2aYAWFx7XWWiasuF2K8Me5WbN8otHKTUKBwjPtNpRs/rbUZm7KxWAaNj7P1a/p52GbVug==", + "license": "MIT", "dependencies": { "is-extendable": "^0.1.0" }, @@ -4378,12 +5366,14 @@ "node_modules/fast-deep-equal": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", - "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==" + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "license": "MIT" }, "node_modules/fast-equals": { "version": "5.4.0", "resolved": "https://registry.npmjs.org/fast-equals/-/fast-equals-5.4.0.tgz", "integrity": "sha512-jt2DW/aNFNwke7AUd+Z+e6pz39KO5rzdbbFCg2sGafS4mk13MI7Z8O5z9cADNn5lhGODIgLwug6TZO2ctf7kcw==", + "license": "MIT", "engines": { "node": ">=6.0.0" } @@ -4392,19 +5382,22 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/fast-levenshtein": { "version": "2.0.6", "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/fastq": { "version": "1.20.1", "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.20.1.tgz", "integrity": "sha512-GGToxJ/w1x32s/D2EKND7kTil4n8OVk/9mycTc4VDza13lOvpUZTGX3mFSCtV9ksdGBVzvsyAVLM6mHFThxXxw==", "dev": true, + "license": "ISC", "dependencies": { "reusify": "^1.0.4" } @@ -4413,6 +5406,7 @@ "version": "6.5.0", "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "license": "MIT", "engines": { "node": ">=12.0.0" }, @@ -4428,13 +5422,22 @@ "node_modules/fetch-retry": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/fetch-retry/-/fetch-retry-4.1.1.tgz", - "integrity": "sha512-e6eB7zN6UBSwGVwrbWVH+gdLnkW9WwHhmq2YDK1Sh30pzx1onRVGBvogTlUeWxwTa+L86NYdo4hFkh7O8ZjSnA==" + "integrity": "sha512-e6eB7zN6UBSwGVwrbWVH+gdLnkW9WwHhmq2YDK1Sh30pzx1onRVGBvogTlUeWxwTa+L86NYdo4hFkh7O8ZjSnA==", + "license": "MIT" + }, + "node_modules/fflate": { + "version": "0.8.2", + "resolved": "https://registry.npmjs.org/fflate/-/fflate-0.8.2.tgz", + "integrity": "sha512-cPJU47OaAoCbg0pBvzsgpTPhmhqI5eJjh/JIu8tPj5q+T7iLvW/JAYUqmE7KOB4R1ZyEhzBaIQpQpardBF5z8A==", + "dev": true, + "license": "MIT" }, "node_modules/file-entry-cache": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==", "dev": true, + "license": "MIT", "dependencies": { "flat-cache": "^3.0.4" }, @@ -4446,6 +5449,7 @@ "version": "0.4.0", "resolved": "https://registry.npmjs.org/file-selector/-/file-selector-0.4.0.tgz", "integrity": "sha512-iACCiXeMYOvZqlF1kTiYINzgepRBymz1wwjiuup9u9nayhb6g4fSwiyJ/6adli+EPwrWtpgQAh2PoS7HukEGEg==", + "license": "MIT", "dependencies": { "tslib": "^2.0.3" }, @@ -4457,6 +5461,7 @@ "version": "4.20.10", "resolved": "https://registry.npmjs.org/final-form/-/final-form-4.20.10.tgz", "integrity": "sha512-TL48Pi1oNHeMOHrKv1bCJUrWZDcD3DIG6AGYVNOnyZPr7Bd/pStN0pL+lfzF5BNoj/FclaoiaLenk4XUIFVYng==", + "license": "MIT", "dependencies": { "@babel/runtime": "^7.10.0" }, @@ -4471,19 +5476,24 @@ "node_modules/find-root": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/find-root/-/find-root-1.1.0.tgz", - "integrity": "sha512-NKfW6bec6GfKc0SGx1e07QZY9PE99u0Bft/0rzSD5k3sO/vwkVUpDUKVm5Gpp5Ue3YfShPFTX2070tDs5kB9Ng==" + "integrity": "sha512-NKfW6bec6GfKc0SGx1e07QZY9PE99u0Bft/0rzSD5k3sO/vwkVUpDUKVm5Gpp5Ue3YfShPFTX2070tDs5kB9Ng==", + "license": "MIT" }, "node_modules/find-up": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", - "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", - "peer": true, + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dev": true, + "license": "MIT", "dependencies": { - "locate-path": "^5.0.0", + "locate-path": "^6.0.0", "path-exists": "^4.0.0" }, "engines": { - "node": ">=8" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/flat-cache": { @@ -4491,6 +5501,7 @@ "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.2.0.tgz", "integrity": "sha512-CYcENa+FtcUKLmhhqyctpclsq7QF38pKjZHsGNiSQF5r4FtoKDWabFDl3hzaEQMvT1LHEysw5twgLvpYYb4vbw==", "dev": true, + "license": "MIT", "dependencies": { "flatted": "^3.2.9", "keyv": "^4.5.3", @@ -4504,7 +5515,8 @@ "version": "3.3.3", "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.3.tgz", "integrity": "sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==", - "dev": true + "dev": true, + "license": "ISC" }, "node_modules/follow-redirects": { "version": "1.15.11", @@ -4516,6 +5528,7 @@ "url": "https://github.com/sponsors/RubenVerborgh" } ], + "license": "MIT", "engines": { "node": ">=4.0" }, @@ -4530,6 +5543,7 @@ "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.5.tgz", "integrity": "sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg==", "dev": true, + "license": "MIT", "dependencies": { "is-callable": "^1.2.7" }, @@ -4544,6 +5558,7 @@ "version": "3.3.1", "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.1.tgz", "integrity": "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==", + "license": "ISC", "dependencies": { "cross-spawn": "^7.0.6", "signal-exit": "^4.0.1" @@ -4559,6 +5574,7 @@ "version": "4.0.5", "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.5.tgz", "integrity": "sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==", + "license": "MIT", "dependencies": { "asynckit": "^0.4.0", "combined-stream": "^1.0.8", @@ -4580,6 +5596,7 @@ "url": "https://opencollective.com/formik" } ], + "license": "Apache-2.0", "dependencies": { "@types/hoist-non-react-statics": "^3.3.1", "deepmerge": "^2.1.1", @@ -4598,13 +5615,15 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", - "dev": true + "dev": true, + "license": "ISC" }, "node_modules/fsevents": { "version": "2.3.3", "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", "hasInstallScript": true, + "license": "MIT", "optional": true, "os": [ "darwin" @@ -4617,6 +5636,7 @@ "version": "1.1.2", "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "license": "MIT", "funding": { "url": "https://github.com/sponsors/ljharb" } @@ -4626,6 +5646,7 @@ "resolved": "https://registry.npmjs.org/function.prototype.name/-/function.prototype.name-1.1.8.tgz", "integrity": "sha512-e5iwyodOHhbMr/yNrc7fDYG4qlbIvI5gajyzPnb5TCwyhjApznQh1BMFou9b30SevY43gCJKXycoCBjMbsuW0Q==", "dev": true, + "license": "MIT", "dependencies": { "call-bind": "^1.0.8", "call-bound": "^1.0.3", @@ -4645,6 +5666,7 @@ "version": "1.2.3", "resolved": "https://registry.npmjs.org/functions-have-names/-/functions-have-names-1.2.3.tgz", "integrity": "sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==", + "license": "MIT", "funding": { "url": "https://github.com/sponsors/ljharb" } @@ -4654,6 +5676,7 @@ "resolved": "https://registry.npmjs.org/generator-function/-/generator-function-2.0.1.tgz", "integrity": "sha512-SFdFmIJi+ybC0vjlHN0ZGVGHc3lgE0DxPAT0djjVg+kjOnSqclqmj0KQ7ykTOLP6YxoqOvuAODGdcHJn+43q3g==", "dev": true, + "license": "MIT", "engines": { "node": ">= 0.4" } @@ -4662,6 +5685,7 @@ "version": "1.3.0", "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", + "license": "MIT", "dependencies": { "call-bind-apply-helpers": "^1.0.2", "es-define-property": "^1.0.1", @@ -4685,6 +5709,7 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "license": "MIT", "dependencies": { "dunder-proto": "^1.0.1", "es-object-atoms": "^1.0.0" @@ -4698,6 +5723,7 @@ "resolved": "https://registry.npmjs.org/get-symbol-description/-/get-symbol-description-1.1.0.tgz", "integrity": "sha512-w9UMqWwJxHNOvoNzSJ2oPF5wvYcvP7jUvYzhp67yEhTi17ZDBBC1z9pTdGuzjD+EFIqLSYRweZjqfiPzQ06Ebg==", "dev": true, + "license": "MIT", "dependencies": { "call-bound": "^1.0.3", "es-errors": "^1.3.0", @@ -4714,6 +5740,8 @@ "version": "10.5.0", "resolved": "https://registry.npmjs.org/glob/-/glob-10.5.0.tgz", "integrity": "sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg==", + "deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me", + "license": "ISC", "dependencies": { "foreground-child": "^3.1.0", "jackspeak": "^3.1.2", @@ -4729,10 +5757,24 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, "node_modules/glob/node_modules/brace-expansion": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", + "license": "MIT", "dependencies": { "balanced-match": "^1.0.0" } @@ -4741,6 +5783,7 @@ "version": "9.0.5", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "license": "ISC", "dependencies": { "brace-expansion": "^2.0.1" }, @@ -4755,6 +5798,7 @@ "version": "4.4.0", "resolved": "https://registry.npmjs.org/global/-/global-4.4.0.tgz", "integrity": "sha512-wv/LAoHdRE3BeTGz53FAamhGlPLhlssK45usmGFThIi4XqnBmjKQ16u+RNbP7WvigRZDxUsM0J3gcQ5yicaL0w==", + "license": "MIT", "peer": true, "dependencies": { "min-document": "^2.19.0", @@ -4766,6 +5810,7 @@ "resolved": "https://registry.npmjs.org/globals/-/globals-13.24.0.tgz", "integrity": "sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==", "dev": true, + "license": "MIT", "dependencies": { "type-fest": "^0.20.2" }, @@ -4781,6 +5826,7 @@ "resolved": "https://registry.npmjs.org/globalthis/-/globalthis-1.0.4.tgz", "integrity": "sha512-DpLKbNU4WylpxJykQujfCcwYWiV/Jhm50Goo0wrVILAv5jOr9d+H+UR3PhSCD2rCCEIg0uc+G+muBTwD54JhDQ==", "dev": true, + "license": "MIT", "dependencies": { "define-properties": "^1.2.1", "gopd": "^1.0.1" @@ -4795,12 +5841,14 @@ "node_modules/globrex": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/globrex/-/globrex-0.1.2.tgz", - "integrity": "sha512-uHJgbwAMwNFf5mLst7IWLNg14x1CkeqglJb/K3doi4dw6q2IvAAmM/Y81kevy83wP+Sst+nutFTYOGg3d1lsxg==" + "integrity": "sha512-uHJgbwAMwNFf5mLst7IWLNg14x1CkeqglJb/K3doi4dw6q2IvAAmM/Y81kevy83wP+Sst+nutFTYOGg3d1lsxg==", + "license": "MIT" }, "node_modules/gopd": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "license": "MIT", "engines": { "node": ">= 0.4" }, @@ -4812,13 +5860,15 @@ "version": "1.4.0", "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz", "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/has-bigints": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.1.0.tgz", "integrity": "sha512-R3pbpkcIqv2Pm3dUwgjclDRVmWpTJW2DcMzcIhEXEx1oh/CEMObMm3KLmRJOdvhM7o4uQBnwr8pzRK2sJWIqfg==", "dev": true, + "license": "MIT", "engines": { "node": ">= 0.4" }, @@ -4831,6 +5881,7 @@ "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", "dev": true, + "license": "MIT", "engines": { "node": ">=8" } @@ -4839,6 +5890,7 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==", + "license": "MIT", "dependencies": { "es-define-property": "^1.0.0" }, @@ -4851,6 +5903,7 @@ "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.2.0.tgz", "integrity": "sha512-KIL7eQPfHQRC8+XluaIw7BHUwwqL19bQn4hzNgdr+1wXoU0KKj6rufu47lhY7KbJR2C6T6+PfyN0Ea7wkSS+qQ==", "dev": true, + "license": "MIT", "dependencies": { "dunder-proto": "^1.0.0" }, @@ -4865,6 +5918,7 @@ "version": "1.1.0", "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "license": "MIT", "engines": { "node": ">= 0.4" }, @@ -4876,6 +5930,7 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", + "license": "MIT", "dependencies": { "has-symbols": "^1.0.3" }, @@ -4890,6 +5945,7 @@ "version": "2.0.2", "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "license": "MIT", "dependencies": { "function-bind": "^1.1.2" }, @@ -4901,6 +5957,7 @@ "version": "3.3.2", "resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz", "integrity": "sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==", + "license": "BSD-3-Clause", "dependencies": { "react-is": "^16.7.0" } @@ -4909,15 +5966,37 @@ "version": "5.0.3", "resolved": "https://registry.npmjs.org/html-dom-parser/-/html-dom-parser-5.0.3.tgz", "integrity": "sha512-slsc6ipw88OUZjAayRs5NTmfOQCwcUa3hNyk6AdsbQxY09H5Lr1Y3CZ4ZlconMKql3Ga6sWg3HMoUzo7KSItaQ==", + "license": "MIT", "dependencies": { "domhandler": "5.0.3", "htmlparser2": "9.0.0" } }, + "node_modules/html-encoding-sniffer": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-6.0.0.tgz", + "integrity": "sha512-CV9TW3Y3f8/wT0BRFc1/KAVQ3TUHiXmaAb6VW9vtiMFf7SLoMd1PdAc4W3KFOFETBJUb90KatHqlsZMWV+R9Gg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@exodus/bytes": "^1.6.0" + }, + "engines": { + "node": "^20.19.0 || ^22.12.0 || >=24.0.0" + } + }, + "node_modules/html-escaper": { + "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" + }, "node_modules/html-react-parser": { "version": "4.2.10", "resolved": "https://registry.npmjs.org/html-react-parser/-/html-react-parser-4.2.10.tgz", "integrity": "sha512-JyKZVQ+kQ8PdycISwkuLbEEvV/k4hWhU6cb6TT7yGaYwdqA7cPt4VRYXkCZcix2vlQtgDBSMJUmPI2jpNjPGvg==", + "license": "MIT", "dependencies": { "domhandler": "5.0.3", "html-dom-parser": "5.0.3", @@ -4939,6 +6018,7 @@ "url": "https://github.com/sponsors/fb55" } ], + "license": "MIT", "dependencies": { "domelementtype": "^2.3.0", "domhandler": "^5.0.3", @@ -4946,6 +6026,34 @@ "entities": "^4.5.0" } }, + "node_modules/http-proxy-agent": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz", + "integrity": "sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==", + "dev": true, + "license": "MIT", + "dependencies": { + "agent-base": "^7.1.0", + "debug": "^4.3.4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/https-proxy-agent": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz", + "integrity": "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==", + "dev": true, + "license": "MIT", + "dependencies": { + "agent-base": "^7.1.2", + "debug": "4" + }, + "engines": { + "node": ">= 14" + } + }, "node_modules/hyphenate-style-name": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/hyphenate-style-name/-/hyphenate-style-name-1.1.0.tgz", @@ -4957,14 +6065,16 @@ "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", "dev": true, + "license": "MIT", "engines": { "node": ">= 4" } }, "node_modules/immer": { - "version": "11.1.3", - "resolved": "https://registry.npmjs.org/immer/-/immer-11.1.3.tgz", - "integrity": "sha512-6jQTc5z0KJFtr1UgFpIL3N9XSC3saRaI9PwWtzM2pSqkNGtiNkYY2OSwkOGDK2XcTRcLb1pi/aNkKZz0nxVH4Q==", + "version": "11.1.4", + "resolved": "https://registry.npmjs.org/immer/-/immer-11.1.4.tgz", + "integrity": "sha512-XREFCPo6ksxVzP4E0ekD5aMdf8WMwmdNaz6vuvxgI40UaEiu6q3p8X52aU6GdyvLY3XXX/8R7JOTXStz/nBbRw==", + "license": "MIT", "funding": { "type": "opencollective", "url": "https://opencollective.com/immer" @@ -4973,12 +6083,14 @@ "node_modules/immutable": { "version": "5.1.4", "resolved": "https://registry.npmjs.org/immutable/-/immutable-5.1.4.tgz", - "integrity": "sha512-p6u1bG3YSnINT5RQmx/yRZBpenIl30kVxkTLDyHLIMk0gict704Q9n+thfDI7lTRm9vXdDYutVzXhzcThxTnXA==" + "integrity": "sha512-p6u1bG3YSnINT5RQmx/yRZBpenIl30kVxkTLDyHLIMk0gict704Q9n+thfDI7lTRm9vXdDYutVzXhzcThxTnXA==", + "license": "MIT" }, "node_modules/import-fresh": { "version": "3.3.1", "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz", "integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==", + "license": "MIT", "dependencies": { "parent-module": "^1.0.0", "resolve-from": "^4.0.0" @@ -4995,6 +6107,7 @@ "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", "dev": true, + "license": "MIT", "engines": { "node": ">=0.8.19" } @@ -5003,6 +6116,7 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz", "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==", + "dev": true, "license": "MIT", "engines": { "node": ">=8" @@ -5014,6 +6128,7 @@ "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", "deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.", "dev": true, + "license": "ISC", "dependencies": { "once": "^1.3.0", "wrappy": "1" @@ -5023,23 +6138,27 @@ "version": "2.0.4", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", - "dev": true + "dev": true, + "license": "ISC" }, "node_modules/ini": { "version": "1.3.8", "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", - "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==" + "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==", + "license": "ISC" }, "node_modules/inline-style-parser": { "version": "0.2.2", "resolved": "https://registry.npmjs.org/inline-style-parser/-/inline-style-parser-0.2.2.tgz", - "integrity": "sha512-EcKzdTHVe8wFVOGEYXiW9WmJXPjqi1T+234YpJr98RiFYKHV3cdy1+3mkTE+KHTHxFFLH51SfaGOoUdW+v7ViQ==" + "integrity": "sha512-EcKzdTHVe8wFVOGEYXiW9WmJXPjqi1T+234YpJr98RiFYKHV3cdy1+3mkTE+KHTHxFFLH51SfaGOoUdW+v7ViQ==", + "license": "MIT" }, "node_modules/internal-slot": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.1.0.tgz", "integrity": "sha512-4gd7VpWNQNB4UKKCFFVcp1AVv+FMOgs9NKzjHKusc8jTMhd5eL1NqQqOpE0KzMds804/yHlglp3uxgluOqAPLw==", "dev": true, + "license": "MIT", "dependencies": { "es-errors": "^1.3.0", "hasown": "^2.0.2", @@ -5053,6 +6172,7 @@ "version": "2.0.3", "resolved": "https://registry.npmjs.org/internmap/-/internmap-2.0.3.tgz", "integrity": "sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg==", + "license": "ISC", "engines": { "node": ">=12" } @@ -5061,6 +6181,7 @@ "version": "2.2.4", "resolved": "https://registry.npmjs.org/invariant/-/invariant-2.2.4.tgz", "integrity": "sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA==", + "license": "MIT", "dependencies": { "loose-envify": "^1.0.0" } @@ -5069,6 +6190,7 @@ "version": "1.2.0", "resolved": "https://registry.npmjs.org/is-arguments/-/is-arguments-1.2.0.tgz", "integrity": "sha512-7bVbi0huj/wrIAOzb8U1aszg9kdi3KN/CyU19CTI7tAoZYEZoL9yCDXpbXN+uPsuWnP02cyug1gleqq+TU+YCA==", + "license": "MIT", "dependencies": { "call-bound": "^1.0.2", "has-tostringtag": "^1.0.2" @@ -5085,6 +6207,7 @@ "resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.5.tgz", "integrity": "sha512-DDfANUiiG2wC1qawP66qlTugJeL5HyzMpfr8lLK+jMQirGzNod0B12cFB/9q838Ru27sBwfw78/rdoU7RERz6A==", "dev": true, + "license": "MIT", "dependencies": { "call-bind": "^1.0.8", "call-bound": "^1.0.3", @@ -5100,13 +6223,15 @@ "node_modules/is-arrayish": { "version": "0.2.1", "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", - "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==" + "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==", + "license": "MIT" }, "node_modules/is-async-function": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/is-async-function/-/is-async-function-2.1.1.tgz", "integrity": "sha512-9dgM/cZBnNvjzaMYHVoxxfPj2QXt22Ev7SuuPrs+xav0ukGB0S6d4ydZdEiM48kLx5kDV+QBPrpVnFyefL8kkQ==", "dev": true, + "license": "MIT", "dependencies": { "async-function": "^1.0.0", "call-bound": "^1.0.3", @@ -5126,6 +6251,7 @@ "resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.1.0.tgz", "integrity": "sha512-n4ZT37wG78iz03xPRKJrHTdZbe3IicyucEtdRsV5yglwc3GyUfbAfpSeD0FJ41NbUNSt5wbhqfp1fS+BgnvDFQ==", "dev": true, + "license": "MIT", "dependencies": { "has-bigints": "^1.0.2" }, @@ -5141,6 +6267,7 @@ "resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.2.2.tgz", "integrity": "sha512-wa56o2/ElJMYqjCjGkXri7it5FbebW5usLw/nPmCMs5DeZ7eziSYZhSmPRn0txqeW4LnAmQQU7FgqLpsEFKM4A==", "dev": true, + "license": "MIT", "dependencies": { "call-bound": "^1.0.3", "has-tostringtag": "^1.0.2" @@ -5155,13 +6282,15 @@ "node_modules/is-buffer": { "version": "1.1.6", "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", - "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==" + "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==", + "license": "MIT" }, "node_modules/is-callable": { "version": "1.2.7", "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz", "integrity": "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==", "dev": true, + "license": "MIT", "engines": { "node": ">= 0.4" }, @@ -5173,6 +6302,7 @@ "version": "2.16.1", "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.1.tgz", "integrity": "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==", + "license": "MIT", "dependencies": { "hasown": "^2.0.2" }, @@ -5188,6 +6318,7 @@ "resolved": "https://registry.npmjs.org/is-data-view/-/is-data-view-1.0.2.tgz", "integrity": "sha512-RKtWF8pGmS87i2D6gqQu/l7EYRlVdfzemCJN/P3UOs//x1QE7mfhvzHIApBTRf7axvT6DMGwSwBXYCT0nfB9xw==", "dev": true, + "license": "MIT", "dependencies": { "call-bound": "^1.0.2", "get-intrinsic": "^1.2.6", @@ -5204,6 +6335,7 @@ "version": "1.1.0", "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.1.0.tgz", "integrity": "sha512-PwwhEakHVKTdRNVOw+/Gyh0+MzlCl4R6qKvkhuvLtPMggI1WAHt9sOwZxQLSGpUaDnrdyDsomoRgNnCfKNSXXg==", + "license": "MIT", "dependencies": { "call-bound": "^1.0.2", "has-tostringtag": "^1.0.2" @@ -5219,6 +6351,7 @@ "version": "0.1.1", "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", "integrity": "sha512-5BMULNob1vgFX6EjQw5izWDxrecWK9AM72rugNr0TFldMOi0fj6Jk+zeKIt0xGj4cEfQIJth4w3OKWOJ4f+AFw==", + "license": "MIT", "engines": { "node": ">=0.10.0" } @@ -5228,6 +6361,7 @@ "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", "devOptional": true, + "license": "MIT", "engines": { "node": ">=0.10.0" } @@ -5237,6 +6371,7 @@ "resolved": "https://registry.npmjs.org/is-finalizationregistry/-/is-finalizationregistry-1.1.1.tgz", "integrity": "sha512-1pC6N8qWJbWoPtEjgcL2xyhQOP491EQjeUo3qTKcmV8YSDDJrOepfG8pcC7h/QgnQHYSv0mJ3Z/ZWxmatVrysg==", "dev": true, + "license": "MIT", "dependencies": { "call-bound": "^1.0.3" }, @@ -5251,6 +6386,7 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "license": "MIT", "engines": { "node": ">=8" } @@ -5259,6 +6395,7 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/is-function/-/is-function-1.0.2.tgz", "integrity": "sha512-lw7DUp0aWXYg+CBCN+JKkcE0Q2RayZnSvnZBlwgxHBQhqt5pZNVy4Ri7H9GmmXkdu7LUthszM+Tor1u/2iBcpQ==", + "license": "MIT", "peer": true }, "node_modules/is-generator-function": { @@ -5266,6 +6403,7 @@ "resolved": "https://registry.npmjs.org/is-generator-function/-/is-generator-function-1.1.2.tgz", "integrity": "sha512-upqt1SkGkODW9tsGNG5mtXTXtECizwtS2kA161M+gJPc1xdb/Ax629af6YrTwcOeQHbewrPNlE5Dx7kzvXTizA==", "dev": true, + "license": "MIT", "dependencies": { "call-bound": "^1.0.4", "generator-function": "^2.0.0", @@ -5285,6 +6423,7 @@ "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", "devOptional": true, + "license": "MIT", "dependencies": { "is-extglob": "^2.1.1" }, @@ -5295,13 +6434,15 @@ "node_modules/is-hotkey": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/is-hotkey/-/is-hotkey-0.2.0.tgz", - "integrity": "sha512-UknnZK4RakDmTgz4PI1wIph5yxSs/mvChWs9ifnlXsKuXgWmOkY/hAE0H/k2MIqH0RlRye0i1oC07MCRSD28Mw==" + "integrity": "sha512-UknnZK4RakDmTgz4PI1wIph5yxSs/mvChWs9ifnlXsKuXgWmOkY/hAE0H/k2MIqH0RlRye0i1oC07MCRSD28Mw==", + "license": "MIT" }, "node_modules/is-map": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/is-map/-/is-map-2.0.3.tgz", "integrity": "sha512-1Qed0/Hr2m+YqxnM09CjA2d/i6YZNfF6R2oRAOj36eUdS6qIV/huPJNSEpKbupewFs+ZsJlxsjjPbc0/afW6Lw==", "dev": true, + "license": "MIT", "engines": { "node": ">= 0.4" }, @@ -5314,6 +6455,7 @@ "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.3.tgz", "integrity": "sha512-5KoIu2Ngpyek75jXodFvnafB6DJgr3u8uuK0LEZJjrU19DrMD3EVERaR8sjz8CCGgpZvxPl9SuE1GMVPFHx1mw==", "dev": true, + "license": "MIT", "engines": { "node": ">= 0.4" }, @@ -5326,6 +6468,7 @@ "resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.1.1.tgz", "integrity": "sha512-lZhclumE1G6VYD8VHe35wFaIif+CTy5SJIi5+3y4psDgWu4wPDoBhF8NxUOinEc7pHgiTsT6MaBb92rKhhD+Xw==", "dev": true, + "license": "MIT", "dependencies": { "call-bound": "^1.0.3", "has-tostringtag": "^1.0.2" @@ -5342,6 +6485,7 @@ "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz", "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==", "dev": true, + "license": "MIT", "engines": { "node": ">=8" } @@ -5350,14 +6494,23 @@ "version": "5.0.0", "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-5.0.0.tgz", "integrity": "sha512-VRSzKkbMm5jMDoKLbltAkFQ5Qr7VDiTFGXxYFXXowVj387GeGNOCsOH6Msy00SGZ3Fp84b1Naa1psqgcCIEP5Q==", + "license": "MIT", "engines": { "node": ">=0.10.0" } }, + "node_modules/is-potential-custom-element-name": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.1.tgz", + "integrity": "sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==", + "dev": true, + "license": "MIT" + }, "node_modules/is-regex": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.2.1.tgz", "integrity": "sha512-MjYsKHO5O7mCsmRGxWcLWheFqN9DJ/2TmngvjKXihe6efViPqc274+Fx/4fYj/r03+ESvBdTXK0V6tA3rgez1g==", + "license": "MIT", "dependencies": { "call-bound": "^1.0.2", "gopd": "^1.2.0", @@ -5376,6 +6529,7 @@ "resolved": "https://registry.npmjs.org/is-set/-/is-set-2.0.3.tgz", "integrity": "sha512-iPAjerrse27/ygGLxw+EBR9agv9Y6uLeYVJMu+QNCoouJ1/1ri0mGrcWpfCqFZuzzx3WjtwxG098X+n4OuRkPg==", "dev": true, + "license": "MIT", "engines": { "node": ">= 0.4" }, @@ -5388,6 +6542,7 @@ "resolved": "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.4.tgz", "integrity": "sha512-ISWac8drv4ZGfwKl5slpHG9OwPNty4jOWPRIhBpxOoD+hqITiwuipOQ2bNthAzwA3B4fIjO4Nln74N0S9byq8A==", "dev": true, + "license": "MIT", "dependencies": { "call-bound": "^1.0.3" }, @@ -5403,6 +6558,7 @@ "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.1.1.tgz", "integrity": "sha512-BtEeSsoaQjlSPBemMQIrY1MY0uM6vnS1g5fmufYOtnxLGUZM2178PKbhsk7Ffv58IX+ZtcvoGwccYsh0PglkAA==", "dev": true, + "license": "MIT", "dependencies": { "call-bound": "^1.0.3", "has-tostringtag": "^1.0.2" @@ -5418,6 +6574,7 @@ "version": "1.1.1", "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.1.1.tgz", "integrity": "sha512-9gGx6GTtCQM73BgmHQXfDmLtfjjTUDSyoxTCbp5WtoixAhfgsDirWIcVQ/IHpvI5Vgd5i/J5F7B9cN/WlVbC/w==", + "license": "MIT", "dependencies": { "call-bound": "^1.0.2", "has-symbols": "^1.1.0", @@ -5435,6 +6592,7 @@ "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.15.tgz", "integrity": "sha512-p3EcsicXjit7SaskXHs1hA91QxgTw46Fv6EFKKGS5DRFLD8yKnohjF3hxoju94b/OcMZoQukzpPpBE9uLVKzgQ==", "dev": true, + "license": "MIT", "dependencies": { "which-typed-array": "^1.1.16" }, @@ -5448,13 +6606,15 @@ "node_modules/is-url": { "version": "1.2.4", "resolved": "https://registry.npmjs.org/is-url/-/is-url-1.2.4.tgz", - "integrity": "sha512-ITvGim8FhRiYe4IQ5uHSkj7pVaPDrCTkNd3yq3cV7iZAcJdHTUMPMEHcqSOy9xZ9qFenQCvi+2wjH9a1nXqHww==" + "integrity": "sha512-ITvGim8FhRiYe4IQ5uHSkj7pVaPDrCTkNd3yq3cV7iZAcJdHTUMPMEHcqSOy9xZ9qFenQCvi+2wjH9a1nXqHww==", + "license": "MIT" }, "node_modules/is-weakmap": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/is-weakmap/-/is-weakmap-2.0.2.tgz", "integrity": "sha512-K5pXYOm9wqY1RgjpL3YTkF39tni1XajUIkawTLUo9EZEVUFga5gSQJF8nNS7ZwJQ02y+1YCNYcMh+HIf1ZqE+w==", "dev": true, + "license": "MIT", "engines": { "node": ">= 0.4" }, @@ -5467,6 +6627,7 @@ "resolved": "https://registry.npmjs.org/is-weakref/-/is-weakref-1.1.1.tgz", "integrity": "sha512-6i9mGWSlqzNMEqpCp93KwRS1uUOodk2OJ6b+sq7ZPDSy2WuI5NFIxp/254TytR8ftefexkWn5xNiHUNpPOfSew==", "dev": true, + "license": "MIT", "dependencies": { "call-bound": "^1.0.3" }, @@ -5482,6 +6643,7 @@ "resolved": "https://registry.npmjs.org/is-weakset/-/is-weakset-2.0.4.tgz", "integrity": "sha512-mfcwb6IzQyOKTs84CQMrOwW4gQcaTOAWJ0zzJCl2WSPDrWk/OzDaImWFH3djXhb24g4eudZfLRozAvPGw4d9hQ==", "dev": true, + "license": "MIT", "dependencies": { "call-bound": "^1.0.3", "get-intrinsic": "^1.2.6" @@ -5497,6 +6659,7 @@ "version": "0.3.0", "resolved": "https://registry.npmjs.org/is-whitespace/-/is-whitespace-0.3.0.tgz", "integrity": "sha512-RydPhl4S6JwAyj0JJjshWJEFG6hNye3pZFBRZaTUfZFwGHxzppNaNOVgQuS/E/SlhrApuMXrpnK1EEIXfdo3Dg==", + "license": "MIT", "engines": { "node": ">=0.10.0" } @@ -5505,27 +6668,70 @@ "version": "2.0.5", "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/isexe": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==" + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "license": "ISC" }, "node_modules/isobject": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/isobject/-/isobject-4.0.0.tgz", "integrity": "sha512-S/2fF5wH8SJA/kmwr6HYhK/RI/OkhD84k8ntalo0iJjZikgq1XFvR5M8NPT1x5F7fBwCG3qHfnzeP/Vh/ZxCUA==", + "license": "MIT", "peer": true, "engines": { "node": ">=0.10.0" } }, + "node_modules/istanbul-lib-coverage": { + "version": "3.2.2", + "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", + "engines": { + "node": ">=8" + } + }, + "node_modules/istanbul-lib-report": { + "version": "3.0.1", + "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", + "dependencies": { + "istanbul-lib-coverage": "^3.0.0", + "make-dir": "^4.0.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/istanbul-reports": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.2.0.tgz", + "integrity": "sha512-HGYWWS/ehqTV3xN10i23tkPkpH46MLCIMFNCaaKNavAXTF1RkqxawEPtnjnGZ6XKSInBKkiOA5BKS+aZiY3AvA==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "html-escaper": "^2.0.0", + "istanbul-lib-report": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/iterator.prototype": { "version": "1.1.5", "resolved": "https://registry.npmjs.org/iterator.prototype/-/iterator.prototype-1.1.5.tgz", "integrity": "sha512-H0dkQoCa3b2VEeKQBOxFph+JAbcrQdE7KC0UkqwpLmv2EC4P41QXP+rqo9wYodACiG5/WM5s9oDApTU8utwj9g==", "dev": true, + "license": "MIT", "dependencies": { "define-data-property": "^1.1.4", "es-object-atoms": "^1.0.0", @@ -5542,6 +6748,7 @@ "version": "3.4.3", "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz", "integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==", + "license": "BlueOak-1.0.0", "dependencies": { "@isaacs/cliui": "^8.0.2" }, @@ -5556,6 +6763,7 @@ "version": "3.0.3", "resolved": "https://registry.npmjs.org/jest-fetch-mock/-/jest-fetch-mock-3.0.3.tgz", "integrity": "sha512-Ux1nWprtLrdrH4XwE7O7InRY6psIi3GOsqNESJgMJ+M5cv4A8Lh7SN9d2V2kKRZ8ebAfcd1LNyZguAOb6JiDqw==", + "license": "MIT", "dependencies": { "cross-fetch": "^3.0.4", "promise-polyfill": "^8.1.3" @@ -5565,6 +6773,7 @@ "version": "1.15.1", "resolved": "https://registry.npmjs.org/js-beautify/-/js-beautify-1.15.1.tgz", "integrity": "sha512-ESjNzSlt/sWE8sciZH8kBF8BPlwXPwhR6pWKAw8bw4Bwj+iZcnKW6ONWUutJ7eObuBZQpiIb8S7OYspWrKt7rA==", + "license": "MIT", "dependencies": { "config-chain": "^1.1.13", "editorconfig": "^1.0.4", @@ -5585,6 +6794,7 @@ "version": "3.0.5", "resolved": "https://registry.npmjs.org/js-cookie/-/js-cookie-3.0.5.tgz", "integrity": "sha512-cEiJEAEoIbWfCZYKWhVwFuvPX1gETRYPw6LlaTKoxD3s2AkXzkCjnp6h0V77ozyqj0jakteJ4YqDJT830+lVGw==", + "license": "MIT", "engines": { "node": ">=14" } @@ -5592,13 +6802,15 @@ "node_modules/js-tokens": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", - "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==" + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "license": "MIT" }, "node_modules/js-yaml": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz", "integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==", "dev": true, + "license": "MIT", "dependencies": { "argparse": "^2.0.1" }, @@ -5606,10 +6818,52 @@ "js-yaml": "bin/js-yaml.js" } }, + "node_modules/jsdom": { + "version": "28.1.0", + "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-28.1.0.tgz", + "integrity": "sha512-0+MoQNYyr2rBHqO1xilltfDjV9G7ymYGlAUazgcDLQaUf8JDHbuGwsxN6U9qWaElZ4w1B2r7yEGIL3GdeW3Rug==", + "dev": true, + "license": "MIT", + "dependencies": { + "@acemir/cssom": "^0.9.31", + "@asamuzakjp/dom-selector": "^6.8.1", + "@bramus/specificity": "^2.4.2", + "@exodus/bytes": "^1.11.0", + "cssstyle": "^6.0.1", + "data-urls": "^7.0.0", + "decimal.js": "^10.6.0", + "html-encoding-sniffer": "^6.0.0", + "http-proxy-agent": "^7.0.2", + "https-proxy-agent": "^7.0.6", + "is-potential-custom-element-name": "^1.0.1", + "parse5": "^8.0.0", + "saxes": "^6.0.0", + "symbol-tree": "^3.2.4", + "tough-cookie": "^6.0.0", + "undici": "^7.21.0", + "w3c-xmlserializer": "^5.0.0", + "webidl-conversions": "^8.0.1", + "whatwg-mimetype": "^5.0.0", + "whatwg-url": "^16.0.0", + "xml-name-validator": "^5.0.0" + }, + "engines": { + "node": "^20.19.0 || ^22.12.0 || >=24.0.0" + }, + "peerDependencies": { + "canvas": "^3.0.0" + }, + "peerDependenciesMeta": { + "canvas": { + "optional": true + } + } + }, "node_modules/jsesc": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==", + "license": "MIT", "bin": { "jsesc": "bin/jsesc" }, @@ -5621,24 +6875,28 @@ "version": "3.0.1", "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/json-parse-even-better-errors": { "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==" + "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", + "license": "MIT" }, "node_modules/json-schema-traverse": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/json-stable-stringify-without-jsonify": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/jsonp": { "version": "0.2.1", @@ -5652,6 +6910,7 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/jsonp-p/-/jsonp-p-2.0.0.tgz", "integrity": "sha512-r4Cx+919EUlt9qBYiEMNySduoEPsIlmv+6N3jgWqF6PPFa3WmzSGodhpUiTT9XG/ofhpnrzl3Tg3f73KjzmBWw==", + "license": "MIT", "dependencies": { "jsonp": "^0.2.0" } @@ -5660,6 +6919,7 @@ "version": "2.6.9", "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "license": "MIT", "dependencies": { "ms": "2.0.0" } @@ -5667,13 +6927,15 @@ "node_modules/jsonp/node_modules/ms": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "license": "MIT" }, "node_modules/jsx-ast-utils": { "version": "3.3.5", "resolved": "https://registry.npmjs.org/jsx-ast-utils/-/jsx-ast-utils-3.3.5.tgz", "integrity": "sha512-ZZow9HBI5O6EPgSJLUb8n2NKgmVWTwCvHGwFuJlMjvLFqlGG6pjirPhtdsseaLZjSibD8eegzmYpUZwoIlj2cQ==", "dev": true, + "license": "MIT", "dependencies": { "array-includes": "^3.1.6", "array.prototype.flat": "^1.3.1", @@ -5688,6 +6950,7 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/jwt-decode/-/jwt-decode-4.0.0.tgz", "integrity": "sha512-+KJGIyHgkGuIq3IEBNftfhW/LfWhXUIY6OmyVWjliu5KH1y0fw7VQ8YndE2O4qZdMSd9SqbnC8GOcZEy0Om7sA==", + "license": "MIT", "engines": { "node": ">=18" } @@ -5697,6 +6960,7 @@ "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", "dev": true, + "license": "MIT", "dependencies": { "json-buffer": "3.0.1" } @@ -5705,6 +6969,7 @@ "version": "3.2.2", "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", "integrity": "sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ==", + "license": "MIT", "dependencies": { "is-buffer": "^1.1.5" }, @@ -5717,6 +6982,7 @@ "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", "dev": true, + "license": "MIT", "dependencies": { "prelude-ls": "^1.2.1", "type-check": "~0.4.0" @@ -5728,18 +6994,23 @@ "node_modules/lines-and-columns": { "version": "1.2.4", "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", - "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==" + "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", + "license": "MIT" }, "node_modules/locate-path": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", - "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", - "peer": true, + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "dev": true, + "license": "MIT", "dependencies": { - "p-locate": "^4.1.0" + "p-locate": "^5.0.0" }, "engines": { - "node": ">=8" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/lodash": { @@ -5749,67 +7020,79 @@ "license": "MIT" }, "node_modules/lodash-es": { - "version": "4.17.22", - "resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.17.22.tgz", - "integrity": "sha512-XEawp1t0gxSi9x01glktRZ5HDy0HXqrM0x5pXQM98EaI0NxO6jVM7omDOxsuEo5UIASAnm2bRp1Jt/e0a2XU8Q==" + "version": "4.17.23", + "resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.17.23.tgz", + "integrity": "sha512-kVI48u3PZr38HdYz98UmfPnXl2DXrpdctLrFLCd3kOx1xUkOmpFPx7gCWWM5MPkL/fD8zb+Ph0QzjGFs4+hHWg==", + "license": "MIT" }, "node_modules/lodash.clonedeep": { "version": "4.5.0", "resolved": "https://registry.npmjs.org/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz", - "integrity": "sha512-H5ZhCF25riFd9uB5UCkVKo61m3S/xZk1x4wA6yp/L3RFP6Z/eHH1ymQcGLo7J3GMPfm0V/7m1tryHuGVxpqEBQ==" + "integrity": "sha512-H5ZhCF25riFd9uB5UCkVKo61m3S/xZk1x4wA6yp/L3RFP6Z/eHH1ymQcGLo7J3GMPfm0V/7m1tryHuGVxpqEBQ==", + "license": "MIT" }, "node_modules/lodash.flatten": { "version": "4.4.0", "resolved": "https://registry.npmjs.org/lodash.flatten/-/lodash.flatten-4.4.0.tgz", - "integrity": "sha512-C5N2Z3DgnnKr0LOpv/hKCgKdb7ZZwafIrsesve6lmzvZIRZRGaZ/l6Q8+2W7NaT+ZwO3fFlSCzCzrDCFdJfZ4g==" + "integrity": "sha512-C5N2Z3DgnnKr0LOpv/hKCgKdb7ZZwafIrsesve6lmzvZIRZRGaZ/l6Q8+2W7NaT+ZwO3fFlSCzCzrDCFdJfZ4g==", + "license": "MIT" }, "node_modules/lodash.get": { "version": "4.4.2", "resolved": "https://registry.npmjs.org/lodash.get/-/lodash.get-4.4.2.tgz", "integrity": "sha512-z+Uw/vLuy6gQe8cfaFWD7p0wVv8fJl3mbzXh33RS+0oW2wvUqiRXiQ69gLWSLpgB5/6sU+r6BlQR0MBILadqTQ==", - "deprecated": "This package is deprecated. Use the optional chaining (?.) operator instead." + "deprecated": "This package is deprecated. Use the optional chaining (?.) operator instead.", + "license": "MIT" }, "node_modules/lodash.isempty": { "version": "4.4.0", "resolved": "https://registry.npmjs.org/lodash.isempty/-/lodash.isempty-4.4.0.tgz", - "integrity": "sha512-oKMuF3xEeqDltrGMfDxAPGIVMSSRv8tbRSODbrs4KGsRRLEhrW8N8Rd4DRgB2+621hY8A8XwwrTVhXWpxFvMzg==" + "integrity": "sha512-oKMuF3xEeqDltrGMfDxAPGIVMSSRv8tbRSODbrs4KGsRRLEhrW8N8Rd4DRgB2+621hY8A8XwwrTVhXWpxFvMzg==", + "license": "MIT" }, "node_modules/lodash.isequal": { "version": "4.5.0", "resolved": "https://registry.npmjs.org/lodash.isequal/-/lodash.isequal-4.5.0.tgz", "integrity": "sha512-pDo3lu8Jhfjqls6GkMgpahsF9kCyayhgykjyLMNFTKWrpVdAQtYyB4muAMWozBB4ig/dtWAmsMxLEI8wuz+DYQ==", - "deprecated": "This package is deprecated. Use require('node:util').isDeepStrictEqual instead." + "deprecated": "This package is deprecated. Use require('node:util').isDeepStrictEqual instead.", + "license": "MIT" }, "node_modules/lodash.isobject": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/lodash.isobject/-/lodash.isobject-3.0.2.tgz", - "integrity": "sha512-3/Qptq2vr7WeJbB4KHUSKlq8Pl7ASXi3UG6CMbBm8WRtXi8+GHm7mKaU3urfpSEzWe2wCIChs6/sdocUsTKJiA==" + "integrity": "sha512-3/Qptq2vr7WeJbB4KHUSKlq8Pl7ASXi3UG6CMbBm8WRtXi8+GHm7mKaU3urfpSEzWe2wCIChs6/sdocUsTKJiA==", + "license": "MIT" }, "node_modules/lodash.isplainobject": { "version": "4.0.6", "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz", - "integrity": "sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==" + "integrity": "sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==", + "license": "MIT" }, "node_modules/lodash.isundefined": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/lodash.isundefined/-/lodash.isundefined-3.0.1.tgz", - "integrity": "sha512-MXB1is3s899/cD8jheYYE2V9qTHwKvt+npCwpD+1Sxm3Q3cECXCiYHjeHWXNwr6Q0SOBPrYUDxendrO6goVTEA==" + "integrity": "sha512-MXB1is3s899/cD8jheYYE2V9qTHwKvt+npCwpD+1Sxm3Q3cECXCiYHjeHWXNwr6Q0SOBPrYUDxendrO6goVTEA==", + "license": "MIT" }, "node_modules/lodash.kebabcase": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/lodash.kebabcase/-/lodash.kebabcase-4.1.1.tgz", - "integrity": "sha512-N8XRTIMMqqDgSy4VLKPnJ/+hpGZN+PHQiJnSenYqPaVV/NCqEogTnAdZLQiGKhxX+JCs8waWq2t1XHWKOmlY8g==" + "integrity": "sha512-N8XRTIMMqqDgSy4VLKPnJ/+hpGZN+PHQiJnSenYqPaVV/NCqEogTnAdZLQiGKhxX+JCs8waWq2t1XHWKOmlY8g==", + "license": "MIT" }, "node_modules/lodash.merge": { "version": "4.6.2", "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/loose-envify": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", + "license": "MIT", "dependencies": { "js-tokens": "^3.0.0 || ^4.0.0" }, @@ -5817,18 +7100,92 @@ "loose-envify": "cli.js" } }, + "node_modules/lru-cache": { + "version": "11.2.6", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.2.6.tgz", + "integrity": "sha512-ESL2CrkS/2wTPfuend7Zhkzo2u0daGJ/A2VucJOgQ/C48S/zB8MMeMHSGKYpXhIjbPxfuezITkaBH1wqv00DDQ==", + "dev": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": "20 || >=22" + } + }, + "node_modules/lz-string": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/lz-string/-/lz-string-1.5.0.tgz", + "integrity": "sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ==", + "dev": true, + "license": "MIT", + "peer": true, + "bin": { + "lz-string": "bin/bin.js" + } + }, + "node_modules/magic-string": { + "version": "0.30.21", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz", + "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.5" + } + }, + "node_modules/magicast": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/magicast/-/magicast-0.5.2.tgz", + "integrity": "sha512-E3ZJh4J3S9KfwdjZhe2afj6R9lGIN5Pher1pF39UGrXRqq/VDaGVIGN13BjHd2u8B61hArAGOnso7nBOouW3TQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.29.0", + "@babel/types": "^7.29.0", + "source-map-js": "^1.2.1" + } + }, "node_modules/make-array": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/make-array/-/make-array-0.1.2.tgz", "integrity": "sha512-bcFmxgZ+OTaMYJp/w6eifElKTcfum7Gi5H7vQ8KzAf9X6swdxkVuilCaG3ZjXr/qJsQT4JJ2Rq9SDYScWEdu9Q==", + "license": "MIT", "engines": { "node": ">=0.10.0" } }, + "node_modules/make-dir": { + "version": "4.0.0", + "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", + "dependencies": { + "semver": "^7.5.3" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/make-dir/node_modules/semver": { + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", + "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/map-or-similar": { "version": "1.5.0", "resolved": "https://registry.npmjs.org/map-or-similar/-/map-or-similar-1.5.0.tgz", "integrity": "sha512-0aF7ZmVon1igznGI4VS30yugpduQW3y3GkcgGJOp7d8x8QrizhigUxjI/m2UojsXXto+jLAH3KSz+xOJTiORjg==", + "license": "MIT", "peer": true }, "node_modules/matchmediaquery": { @@ -5843,25 +7200,36 @@ "node_modules/material-colors": { "version": "1.2.6", "resolved": "https://registry.npmjs.org/material-colors/-/material-colors-1.2.6.tgz", - "integrity": "sha512-6qE4B9deFBIa9YSpOc9O0Sgc43zTeVYbgDT5veRKSlB2+ZuHNoVVxA1L/ckMUayV9Ay9y7Z/SZCLcGteW9i7bg==" + "integrity": "sha512-6qE4B9deFBIa9YSpOc9O0Sgc43zTeVYbgDT5veRKSlB2+ZuHNoVVxA1L/ckMUayV9Ay9y7Z/SZCLcGteW9i7bg==", + "license": "ISC" }, "node_modules/math-intrinsics": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "license": "MIT", "engines": { "node": ">= 0.4" } }, + "node_modules/mdn-data": { + "version": "2.12.2", + "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.12.2.tgz", + "integrity": "sha512-IEn+pegP1aManZuckezWCO+XZQDplx1366JoVhTpMpBB1sPey/SbveZQUosKiKiGYjg1wH4pMlNgXbCiYgihQA==", + "dev": true, + "license": "CC0-1.0" + }, "node_modules/memoize-one": { "version": "5.2.1", "resolved": "https://registry.npmjs.org/memoize-one/-/memoize-one-5.2.1.tgz", - "integrity": "sha512-zYiwtZUcYyXKo/np96AGZAckk+FWWsUdJ3cHGGmld7+AhvcWmQyGCYUh1hc4Q/pkOhb65dQR/pqCyK0cOaHz4Q==" + "integrity": "sha512-zYiwtZUcYyXKo/np96AGZAckk+FWWsUdJ3cHGGmld7+AhvcWmQyGCYUh1hc4Q/pkOhb65dQR/pqCyK0cOaHz4Q==", + "license": "MIT" }, "node_modules/memoizerific": { "version": "1.11.3", "resolved": "https://registry.npmjs.org/memoizerific/-/memoizerific-1.11.3.tgz", "integrity": "sha512-/EuHYwAPdLtXwAwSZkh/Gutery6pD2KYd44oQLhAvQp/50mpyduZh8Q7PYHXTCJ+wuXxt7oij2LXyIJOOYFPog==", + "license": "MIT", "peer": true, "dependencies": { "map-or-similar": "^1.5.0" @@ -5871,6 +7239,7 @@ "version": "1.52.0", "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "license": "MIT", "engines": { "node": ">= 0.6" } @@ -5879,6 +7248,7 @@ "version": "2.1.35", "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "license": "MIT", "dependencies": { "mime-db": "1.52.0" }, @@ -5890,6 +7260,7 @@ "version": "2.19.2", "resolved": "https://registry.npmjs.org/min-document/-/min-document-2.19.2.tgz", "integrity": "sha512-8S5I8db/uZN8r9HSLFVWPdJCvYOejMcEC82VIzNUc6Zkklf/d1gg2psfE79/vyhWOj4+J8MtwmoOz3TmvaGu5A==", + "license": "MIT", "peer": true, "dependencies": { "dom-walk": "^0.1.0" @@ -5899,6 +7270,7 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/min-indent/-/min-indent-1.0.1.tgz", "integrity": "sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==", + "dev": true, "license": "MIT", "engines": { "node": ">=4" @@ -5909,6 +7281,7 @@ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", "dev": true, + "license": "ISC", "dependencies": { "brace-expansion": "^1.1.7" }, @@ -5920,14 +7293,16 @@ "version": "1.2.8", "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", + "license": "MIT", "funding": { "url": "https://github.com/sponsors/ljharb" } }, "node_modules/minipass": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", - "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.3.tgz", + "integrity": "sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A==", + "license": "BlueOak-1.0.0", "engines": { "node": ">=16 || 14 >=14.17" } @@ -5936,6 +7311,7 @@ "version": "1.0.5", "resolved": "https://registry.npmjs.org/mix2/-/mix2-1.0.5.tgz", "integrity": "sha512-ybWz7nY+WHBBIyliND5eYaJKzkoa+qXRYNTmVqAxSLlFtL/umT2iv+pmyTu1oU7WNkrirwheqR8d9EaKVz0e5g==", + "license": "MIT", "engines": { "node": ">=0.10.0" } @@ -5944,14 +7320,26 @@ "version": "2.30.1", "resolved": "https://registry.npmjs.org/moment/-/moment-2.30.1.tgz", "integrity": "sha512-uEmtNhbDOrWPFS+hdjFCBfy9f2YoyzRpwcl+DqpC6taX21FzsTLQVbMV/W7PzNSX6x/bhC1zA3c2UQ5NzH6how==", + "license": "MIT", "engines": { "node": "*" } }, + "node_modules/mrmime": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/mrmime/-/mrmime-2.0.1.tgz", + "integrity": "sha512-Y3wQdFg2Va6etvQ5I82yUhGdsKrcYox6p7FfL1LbK2J4V01F9TGlepTIhnK24t7koZibmg82KGglhA1XK5IsLQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + } + }, "node_modules/ms": { "version": "2.1.3", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" }, "node_modules/nanoid": { "version": "3.3.11", @@ -5963,6 +7351,7 @@ "url": "https://github.com/sponsors/ai" } ], + "license": "MIT", "bin": { "nanoid": "bin/nanoid.cjs" }, @@ -5974,18 +7363,40 @@ "version": "1.4.0", "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/node-addon-api": { "version": "7.1.1", "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-7.1.1.tgz", "integrity": "sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ==", + "license": "MIT", "optional": true }, + "node_modules/node-exports-info": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/node-exports-info/-/node-exports-info-1.6.0.tgz", + "integrity": "sha512-pyFS63ptit/P5WqUkt+UUfe+4oevH+bFeIiPPdfb0pFeYEu/1ELnJu5l+5EcTKYL5M7zaAa7S8ddywgXypqKCw==", + "dev": true, + "license": "MIT", + "dependencies": { + "array.prototype.flatmap": "^1.3.3", + "es-errors": "^1.3.0", + "object.entries": "^1.1.9", + "semver": "^6.3.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/node-fetch": { "version": "2.7.0", "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", + "license": "MIT", "dependencies": { "whatwg-url": "^5.0.0" }, @@ -6001,10 +7412,33 @@ } } }, + "node_modules/node-fetch/node_modules/tr46": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", + "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==", + "license": "MIT" + }, + "node_modules/node-fetch/node_modules/webidl-conversions": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", + "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==", + "license": "BSD-2-Clause" + }, + "node_modules/node-fetch/node_modules/whatwg-url": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", + "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", + "license": "MIT", + "dependencies": { + "tr46": "~0.0.3", + "webidl-conversions": "^3.0.0" + } + }, "node_modules/nopt": { "version": "7.2.1", "resolved": "https://registry.npmjs.org/nopt/-/nopt-7.2.1.tgz", "integrity": "sha512-taM24ViiimT/XntxbPyJQzCG+p4EKOpgD3mxFwW38mGjVUrfERQOeY4EDHjdnptttfHuHQXFx+lTP08Q+mLa/w==", + "license": "ISC", "dependencies": { "abbrev": "^2.0.0" }, @@ -6019,6 +7453,7 @@ "version": "4.1.1", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "license": "MIT", "engines": { "node": ">=0.10.0" } @@ -6027,6 +7462,7 @@ "version": "1.13.4", "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==", + "license": "MIT", "engines": { "node": ">= 0.4" }, @@ -6038,6 +7474,7 @@ "version": "1.1.6", "resolved": "https://registry.npmjs.org/object-is/-/object-is-1.1.6.tgz", "integrity": "sha512-F8cZ+KfGlSGi09lJT7/Nd6KJZ9ygtvYC0/UYYLI9nmQKLMnydpB9yvbv9K1uSkEu7FU9vYPmVwLg328tX+ot3Q==", + "license": "MIT", "dependencies": { "call-bind": "^1.0.7", "define-properties": "^1.2.1" @@ -6053,6 +7490,7 @@ "version": "1.1.1", "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", + "license": "MIT", "engines": { "node": ">= 0.4" } @@ -6062,6 +7500,7 @@ "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.7.tgz", "integrity": "sha512-nK28WOo+QIjBkDduTINE4JkF/UJJKyf2EJxvJKfblDpyg0Q+pkOHNTL0Qwy6NP6FhE/EnzV73BxxqcJaXY9anw==", "dev": true, + "license": "MIT", "dependencies": { "call-bind": "^1.0.8", "call-bound": "^1.0.3", @@ -6082,6 +7521,7 @@ "resolved": "https://registry.npmjs.org/object.entries/-/object.entries-1.1.9.tgz", "integrity": "sha512-8u/hfXFRBD1O0hPUjioLhoWFHRmt6tKA4/vZPyckBr18l1KE9uHrFaFaUi8MDRTpi4uak2goyPTSNJLXX2k2Hw==", "dev": true, + "license": "MIT", "dependencies": { "call-bind": "^1.0.8", "call-bound": "^1.0.4", @@ -6097,6 +7537,7 @@ "resolved": "https://registry.npmjs.org/object.fromentries/-/object.fromentries-2.0.8.tgz", "integrity": "sha512-k6E21FzySsSK5a21KRADBd/NGneRegFO5pLHfdQLpRDETUNJueLXs3WCzyQ3tFRDYgbq3KHGXfTbi2bs8WQ6rQ==", "dev": true, + "license": "MIT", "dependencies": { "call-bind": "^1.0.7", "define-properties": "^1.2.1", @@ -6115,6 +7556,7 @@ "resolved": "https://registry.npmjs.org/object.values/-/object.values-1.2.1.tgz", "integrity": "sha512-gXah6aZrcUxjWg2zR2MwouP2eHlCBzdV4pygudehaKXSGW4v2AsRQUK+lwwXhii6KFZcunEnmSUoYp5CXibxtA==", "dev": true, + "license": "MIT", "dependencies": { "call-bind": "^1.0.8", "call-bound": "^1.0.3", @@ -6128,11 +7570,23 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/obug": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/obug/-/obug-2.1.1.tgz", + "integrity": "sha512-uTqF9MuPraAQ+IsnPf366RG4cP9RtUi7MLO1N3KEc+wb0a6yKpeL0lmk2IB1jY5KHPAlTc6T/JRdC/YqxHNwkQ==", + "dev": true, + "funding": [ + "https://github.com/sponsors/sxzz", + "https://opencollective.com/debug" + ], + "license": "MIT" + }, "node_modules/once": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", "dev": true, + "license": "ISC", "dependencies": { "wrappy": "1" } @@ -6142,6 +7596,7 @@ "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==", "dev": true, + "license": "MIT", "dependencies": { "deep-is": "^0.1.3", "fast-levenshtein": "^2.0.6", @@ -6159,6 +7614,7 @@ "resolved": "https://registry.npmjs.org/own-keys/-/own-keys-1.0.1.tgz", "integrity": "sha512-qFOyK5PjiWZd+QQIh+1jhdb9LpxTF0qs7Pm8o5QHYZ0M3vKqSqzsZaEB6oWlxZ+q2sJBMI/Ktgd2N5ZwQoRHfg==", "dev": true, + "license": "MIT", "dependencies": { "get-intrinsic": "^1.2.6", "object-keys": "^1.1.1", @@ -6172,36 +7628,42 @@ } }, "node_modules/p-limit": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", - "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", - "peer": true, + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "license": "MIT", "dependencies": { - "p-try": "^2.0.0" + "yocto-queue": "^0.1.0" }, "engines": { - "node": ">=6" + "node": ">=10" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/p-locate": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", - "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", - "peer": true, + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dev": true, + "license": "MIT", "dependencies": { - "p-limit": "^2.2.0" + "p-limit": "^3.0.2" }, "engines": { - "node": ">=8" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/p-try": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", + "license": "MIT", "peer": true, "engines": { "node": ">=6" @@ -6210,12 +7672,14 @@ "node_modules/package-json-from-dist": { "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==" + "integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==", + "license": "BlueOak-1.0.0" }, "node_modules/parent-module": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "license": "MIT", "dependencies": { "callsites": "^3.0.0" }, @@ -6227,6 +7691,7 @@ "version": "5.2.0", "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", + "license": "MIT", "dependencies": { "@babel/code-frame": "^7.0.0", "error-ex": "^1.3.1", @@ -6240,15 +7705,43 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/parse5": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-8.0.0.tgz", + "integrity": "sha512-9m4m5GSgXjL4AjumKzq1Fgfp3Z8rsvjRNbnkVwfu2ImRqE5D0LnY2QfDen18FSY9C573YU5XxSapdHZTZ2WolA==", + "dev": true, + "license": "MIT", + "dependencies": { + "entities": "^6.0.0" + }, + "funding": { + "url": "https://github.com/inikulin/parse5?sponsor=1" + } + }, + "node_modules/parse5/node_modules/entities": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/entities/-/entities-6.0.1.tgz", + "integrity": "sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, "node_modules/path-browserify": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/path-browserify/-/path-browserify-1.0.1.tgz", - "integrity": "sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g==" + "integrity": "sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g==", + "license": "MIT" }, "node_modules/path-exists": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "license": "MIT", "engines": { "node": ">=8" } @@ -6258,6 +7751,7 @@ "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", "dev": true, + "license": "MIT", "engines": { "node": ">=0.10.0" } @@ -6266,6 +7760,7 @@ "version": "3.1.1", "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "license": "MIT", "engines": { "node": ">=8" } @@ -6273,12 +7768,14 @@ "node_modules/path-parse": { "version": "1.0.7", "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", - "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==" + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", + "license": "MIT" }, "node_modules/path-scurry": { "version": "1.11.1", "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz", "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==", + "license": "BlueOak-1.0.0", "dependencies": { "lru-cache": "^10.2.0", "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" @@ -6293,25 +7790,36 @@ "node_modules/path-scurry/node_modules/lru-cache": { "version": "10.4.3", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", - "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==" + "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", + "license": "ISC" }, "node_modules/path-type": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", + "license": "MIT", "engines": { "node": ">=8" } }, + "node_modules/pathe": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz", + "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==", + "dev": true, + "license": "MIT" + }, "node_modules/picocolors": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", - "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==" + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "license": "ISC" }, "node_modules/picomatch": { "version": "4.0.3", "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", + "license": "MIT", "engines": { "node": ">=12" }, @@ -6324,6 +7832,7 @@ "resolved": "https://registry.npmjs.org/possible-typed-array-names/-/possible-typed-array-names-1.1.0.tgz", "integrity": "sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg==", "dev": true, + "license": "MIT", "engines": { "node": ">= 0.4" } @@ -6346,6 +7855,7 @@ "url": "https://github.com/sponsors/ai" } ], + "license": "MIT", "dependencies": { "nanoid": "^3.3.11", "picocolors": "^1.1.1", @@ -6360,15 +7870,17 @@ "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", "dev": true, + "license": "MIT", "engines": { "node": ">= 0.8.0" } }, "node_modules/prettier": { - "version": "3.7.4", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.7.4.tgz", - "integrity": "sha512-v6UNi1+3hSlVvv8fSaoUbggEM5VErKmmpGA7Pl3HF8V6uKY7rvClBOJlH6yNwQtfTueNkGVpOv/mtWL9L4bgRA==", + "version": "3.8.1", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.8.1.tgz", + "integrity": "sha512-UOnG6LftzbdaHZcKoPFtOcCKztrQ57WkHDeRD9t/PTQtmT0NHSeWWepj6pS0z/N7+08BHFDQVUrfmfMRcZwbMg==", "dev": true, + "license": "MIT", "bin": { "prettier": "bin/prettier.cjs" }, @@ -6383,6 +7895,7 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/pretty/-/pretty-2.0.0.tgz", "integrity": "sha512-G9xUchgTEiNpormdYBl+Pha50gOUovT18IvAe7EYMZ1/f9W/WWMPRn+xI68yXNMUk3QXHDwo/1wV/4NejVNe1w==", + "license": "MIT", "dependencies": { "condense-newlines": "^0.2.1", "extend-shallow": "^2.0.1", @@ -6392,10 +7905,49 @@ "node": ">=0.10.0" } }, + "node_modules/pretty-format": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-27.5.1.tgz", + "integrity": "sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "ansi-regex": "^5.0.1", + "ansi-styles": "^5.0.0", + "react-is": "^17.0.1" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/pretty-format/node_modules/ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/pretty-format/node_modules/react-is": { + "version": "17.0.2", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", + "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==", + "dev": true, + "license": "MIT", + "peer": true + }, "node_modules/prism-react-renderer": { "version": "1.3.5", "resolved": "https://registry.npmjs.org/prism-react-renderer/-/prism-react-renderer-1.3.5.tgz", "integrity": "sha512-IJ+MSwBWKG+SM3b2SUfdrhC+gu01QkV2KmRQgREThBfSQRoufqRfxfHUxpG1WcaFjP+kojcFyO9Qqtpgt3qLCg==", + "license": "MIT", "peerDependencies": { "react": ">=0.14.9" } @@ -6404,6 +7956,7 @@ "version": "1.30.0", "resolved": "https://registry.npmjs.org/prismjs/-/prismjs-1.30.0.tgz", "integrity": "sha512-DEvV2ZF2r2/63V+tK8hQvrR2ZGn10srHbXviTlcv7Kpzw8jWiNTqbVgjO3IY8RxrrOUF8VPMQQFysYYYv0YZxw==", + "license": "MIT", "engines": { "node": ">=6" } @@ -6412,6 +7965,7 @@ "version": "0.11.10", "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz", "integrity": "sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==", + "license": "MIT", "peer": true, "engines": { "node": ">= 0.6.0" @@ -6420,12 +7974,14 @@ "node_modules/promise-polyfill": { "version": "8.3.0", "resolved": "https://registry.npmjs.org/promise-polyfill/-/promise-polyfill-8.3.0.tgz", - "integrity": "sha512-H5oELycFml5yto/atYqmjyigJoAo3+OXwolYiH7OfQuYlAqhxNvTfiNMbV9hsC6Yp83yE5r2KTVmtrG6R9i6Pg==" + "integrity": "sha512-H5oELycFml5yto/atYqmjyigJoAo3+OXwolYiH7OfQuYlAqhxNvTfiNMbV9hsC6Yp83yE5r2KTVmtrG6R9i6Pg==", + "license": "MIT" }, "node_modules/prop-types": { "version": "15.8.1", "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz", "integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==", + "license": "MIT", "dependencies": { "loose-envify": "^1.4.0", "object-assign": "^4.1.1", @@ -6435,31 +7991,36 @@ "node_modules/property-expr": { "version": "2.0.6", "resolved": "https://registry.npmjs.org/property-expr/-/property-expr-2.0.6.tgz", - "integrity": "sha512-SVtmxhRE/CGkn3eZY1T6pC8Nln6Fr/lu1mKSgRud0eC73whjGfoAogbn78LkD8aFL0zz3bAFerKSnOl7NlErBA==" + "integrity": "sha512-SVtmxhRE/CGkn3eZY1T6pC8Nln6Fr/lu1mKSgRud0eC73whjGfoAogbn78LkD8aFL0zz3bAFerKSnOl7NlErBA==", + "license": "MIT" }, "node_modules/proto-list": { "version": "1.2.4", "resolved": "https://registry.npmjs.org/proto-list/-/proto-list-1.2.4.tgz", - "integrity": "sha512-vtK/94akxsTMhe0/cbfpR+syPuszcuwhqVjJq26CuNDgFGj682oRBXOP5MJpv2r7JtE8MsiepGIqvvOTBwn2vA==" + "integrity": "sha512-vtK/94akxsTMhe0/cbfpR+syPuszcuwhqVjJq26CuNDgFGj682oRBXOP5MJpv2r7JtE8MsiepGIqvvOTBwn2vA==", + "license": "ISC" }, "node_modules/proxy-from-env": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", - "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==" + "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==", + "license": "MIT" }, "node_modules/punycode": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", "dev": true, + "license": "MIT", "engines": { "node": ">=6" } }, "node_modules/qs": { - "version": "6.14.1", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.14.1.tgz", - "integrity": "sha512-4EK3+xJl8Ts67nLYNwqw/dsFVnCf+qR7RgXSK9jEEm9unao3njwMDdmsdvoKBKHzxd7tCYz5e5M+SnMjdtXGQQ==", + "version": "6.15.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.15.0.tgz", + "integrity": "sha512-mAZTtNCeetKMH+pSjrb76NAM8V9a05I9aBZOHztWy/UqcJdQYNsf59vrRKWnojAT9Y+GbIvoTBC++CPHqpDBhQ==", + "license": "BSD-3-Clause", "peer": true, "dependencies": { "side-channel": "^1.1.0" @@ -6489,17 +8050,20 @@ "type": "consulting", "url": "https://feross.org/support" } - ] + ], + "license": "MIT" }, "node_modules/raf-schd": { "version": "4.0.3", "resolved": "https://registry.npmjs.org/raf-schd/-/raf-schd-4.0.3.tgz", - "integrity": "sha512-tQkJl2GRWh83ui2DiPTJz9wEiMN20syf+5oKfB03yYP7ioZcJwsIK8FjrtLwH1m7C7e+Tt2yYBlrOpdT+dyeIQ==" + "integrity": "sha512-tQkJl2GRWh83ui2DiPTJz9wEiMN20syf+5oKfB03yYP7ioZcJwsIK8FjrtLwH1m7C7e+Tt2yYBlrOpdT+dyeIQ==", + "license": "MIT" }, "node_modules/re-resizable": { "version": "6.11.2", "resolved": "https://registry.npmjs.org/re-resizable/-/re-resizable-6.11.2.tgz", "integrity": "sha512-2xI2P3OHs5qw7K0Ud1aLILK6MQxW50TcO+DetD9eIV58j84TqYeHoZcL9H4GXFXXIh7afhH8mv5iUCXII7OW7A==", + "license": "MIT", "peerDependencies": { "react": "^16.13.1 || ^17.0.0 || ^18.0.0 || ^19.0.0", "react-dom": "^16.13.1 || ^17.0.0 || ^18.0.0 || ^19.0.0" @@ -6509,6 +8073,7 @@ "version": "18.3.1", "resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz", "integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==", + "license": "MIT", "dependencies": { "loose-envify": "^1.1.0" }, @@ -6516,11 +8081,29 @@ "node": ">=0.10.0" } }, + "node_modules/react-ace": { + "version": "9.5.0", + "resolved": "https://registry.npmjs.org/react-ace/-/react-ace-9.5.0.tgz", + "integrity": "sha512-4l5FgwGh6K7A0yWVMQlPIXDItM4Q9zzXRqOae8KkCl6MkOob7sC1CzHxZdOGvV+QioKWbX2p5HcdOVUv6cAdSg==", + "license": "MIT", + "dependencies": { + "ace-builds": "^1.4.13", + "diff-match-patch": "^1.0.5", + "lodash.get": "^4.4.2", + "lodash.isequal": "^4.5.0", + "prop-types": "^15.7.2" + }, + "peerDependencies": { + "react": "^0.13.0 || ^0.14.0 || ^15.0.1 || ^16.0.0 || ^17.0.0", + "react-dom": "^0.13.0 || ^0.14.0 || ^15.0.1 || ^16.0.0 || ^17.0.0" + } + }, "node_modules/react-beautiful-dnd": { "version": "13.1.1", "resolved": "https://registry.npmjs.org/react-beautiful-dnd/-/react-beautiful-dnd-13.1.1.tgz", "integrity": "sha512-0Lvs4tq2VcrEjEgDXHjT98r+63drkKEgqyxdA7qD3mvKwga6a5SscbdLPO2IExotU1jW8L0Ksdl0Cj2AF67nPQ==", "deprecated": "react-beautiful-dnd is now deprecated. Context and options: https://github.com/atlassian/react-beautiful-dnd/issues/2672", + "license": "Apache-2.0", "dependencies": { "@babel/runtime": "^7.9.2", "css-box-model": "^1.2.0", @@ -6538,12 +8121,14 @@ "node_modules/react-beautiful-dnd/node_modules/react-is": { "version": "17.0.2", "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", - "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==" + "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==", + "license": "MIT" }, "node_modules/react-beautiful-dnd/node_modules/react-redux": { "version": "7.2.9", "resolved": "https://registry.npmjs.org/react-redux/-/react-redux-7.2.9.tgz", "integrity": "sha512-Gx4L3uM182jEEayZfRbI/G11ZpYdNAnBs70lFVMNdHJI76XYtR+7m0MN+eAs7UHBPhWXcnFPaS+9owSCJQHNpQ==", + "license": "MIT", "dependencies": { "@babel/runtime": "^7.15.4", "@types/react-redux": "^7.1.20", @@ -6568,6 +8153,7 @@ "version": "4.2.1", "resolved": "https://registry.npmjs.org/redux/-/redux-4.2.1.tgz", "integrity": "sha512-LAUYz4lc+Do8/g7aeRa8JkyDErK6ekstQaqWQrNRW//MY1TvCEpMtpTWvlQ+FPbWCx+Xixu/6SHt5N0HR+SB4w==", + "license": "MIT", "dependencies": { "@babel/runtime": "^7.9.2" } @@ -6576,6 +8162,7 @@ "version": "2.19.3", "resolved": "https://registry.npmjs.org/react-color/-/react-color-2.19.3.tgz", "integrity": "sha512-LEeGE/ZzNLIsFWa1TMe8y5VYqr7bibneWmvJwm1pCn/eNmrabWDh659JSPn9BuaMpEfU83WTOJfnCcjDZwNQTA==", + "license": "MIT", "dependencies": { "@icons/material": "^0.2.4", "lodash": "^4.17.15", @@ -6594,6 +8181,7 @@ "version": "4.25.7", "resolved": "https://registry.npmjs.org/@contentstack/react-datepicker/-/react-datepicker-4.25.7.tgz", "integrity": "sha512-wNcKiFBgmOzp/sLRB/7YR/jeDTCRfY3leTACR14LaJmiX944HbjqC2i0wEBQLHKacPT6H2GRNX2z9uPAhgw/dg==", + "license": "MIT", "dependencies": { "@popperjs/core": "^2.11.8", "classnames": "^2.2.6", @@ -6611,6 +8199,7 @@ "version": "11.1.3", "resolved": "https://registry.npmjs.org/react-dnd/-/react-dnd-11.1.3.tgz", "integrity": "sha512-8rtzzT8iwHgdSC89VktwhqdKKtfXaAyC4wiqp0SywpHG12TTLvfOoL6xNEIUWXwIEWu+CFfDn4GZJyynCEuHIQ==", + "license": "MIT", "dependencies": { "@react-dnd/shallowequal": "^2.0.0", "@types/hoist-non-react-statics": "^3.3.1", @@ -6626,6 +8215,7 @@ "version": "11.1.3", "resolved": "https://registry.npmjs.org/react-dnd-html5-backend/-/react-dnd-html5-backend-11.1.3.tgz", "integrity": "sha512-/1FjNlJbW/ivkUxlxQd7o3trA5DE33QiRZgxent3zKme8DwF4Nbw3OFVhTRFGaYhHFNL1rZt6Rdj1D78BjnNLw==", + "license": "MIT", "dependencies": { "dnd-core": "^11.1.3" } @@ -6634,6 +8224,7 @@ "version": "18.3.1", "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz", "integrity": "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==", + "license": "MIT", "dependencies": { "loose-envify": "^1.1.0", "scheduler": "^0.23.2" @@ -6646,6 +8237,7 @@ "version": "11.7.1", "resolved": "https://registry.npmjs.org/react-dropzone/-/react-dropzone-11.7.1.tgz", "integrity": "sha512-zxCMwhfPy1olUEbw3FLNPLhAm/HnaYH5aELIEglRbqabizKAdHs0h+WuyOpmA+v1JXn0++fpQDdNfUagWt5hJQ==", + "license": "MIT", "dependencies": { "attr-accept": "^2.2.2", "file-selector": "^0.4.0", @@ -6662,6 +8254,7 @@ "version": "3.1.4", "resolved": "https://registry.npmjs.org/react-error-boundary/-/react-error-boundary-3.1.4.tgz", "integrity": "sha512-uM9uPzZJTF6wRQORmSrvOIgt4lJ9MC1sNgEOj2XGsDTRE4kmpWxg7ENK9EWNKJRMAOY9z0MuF4yIfl6gp4sotA==", + "license": "MIT", "dependencies": { "@babel/runtime": "^7.12.5" }, @@ -6676,12 +8269,14 @@ "node_modules/react-fast-compare": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/react-fast-compare/-/react-fast-compare-2.0.4.tgz", - "integrity": "sha512-suNP+J1VU1MWFKcyt7RtjiSWUjvidmQSlqu+eHslq+342xCbGTYmC0mEhPCOHxlW0CywylOC1u2DFAT+bv4dBw==" + "integrity": "sha512-suNP+J1VU1MWFKcyt7RtjiSWUjvidmQSlqu+eHslq+342xCbGTYmC0mEhPCOHxlW0CywylOC1u2DFAT+bv4dBw==", + "license": "MIT" }, "node_modules/react-final-form": { "version": "6.5.9", "resolved": "https://registry.npmjs.org/react-final-form/-/react-final-form-6.5.9.tgz", "integrity": "sha512-x3XYvozolECp3nIjly+4QqxdjSSWfcnpGEL5K8OBT6xmGrq5kBqbA6+/tOqoom9NwqIPPbxPNsOViFlbKgowbA==", + "license": "MIT", "dependencies": { "@babel/runtime": "^7.15.4" }, @@ -6698,6 +8293,7 @@ "version": "6.1.0", "resolved": "https://registry.npmjs.org/react-helmet/-/react-helmet-6.1.0.tgz", "integrity": "sha512-4uMzEY9nlDlgxr61NL3XbKRy1hEkXmKNXhjbAIOVw5vcFrsdYbH2FEwcNyWvWinl103nXgzYNlns9ca+8kFiWw==", + "license": "MIT", "dependencies": { "object-assign": "^4.1.1", "prop-types": "^15.7.2", @@ -6711,12 +8307,14 @@ "node_modules/react-helmet/node_modules/react-fast-compare": { "version": "3.2.2", "resolved": "https://registry.npmjs.org/react-fast-compare/-/react-fast-compare-3.2.2.tgz", - "integrity": "sha512-nsO+KSNgo1SbJqJEYRE9ERzo7YtYbou/OqjSQKxV7jcKox7+usiUVZOAC+XnDOABXggQTno0Y1CpVnuWEc1boQ==" + "integrity": "sha512-nsO+KSNgo1SbJqJEYRE9ERzo7YtYbou/OqjSQKxV7jcKox7+usiUVZOAC+XnDOABXggQTno0Y1CpVnuWEc1boQ==", + "license": "MIT" }, "node_modules/react-infinite-scroll-component": { "version": "6.1.1", "resolved": "https://registry.npmjs.org/react-infinite-scroll-component/-/react-infinite-scroll-component-6.1.1.tgz", "integrity": "sha512-R8YoOyiNDynSWmfVme5LHslsKrP+/xcRUWR2ies8UgUab9dtyw5ECnMCVPPmnmjjF4MWQmfVdRwRWcWaDgeyMA==", + "license": "MIT", "dependencies": { "throttle-debounce": "^2.1.0" }, @@ -6724,10 +8322,23 @@ "react": ">=16.0.0" } }, + "node_modules/react-input-autosize": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/react-input-autosize/-/react-input-autosize-3.0.0.tgz", + "integrity": "sha512-nL9uS7jEs/zu8sqwFE5MAPx6pPkNAriACQ2rGLlqmKr2sPGtN7TXTyDdQt4lbNXVx7Uzadb40x8qotIuru6Rhg==", + "license": "MIT", + "dependencies": { + "prop-types": "^15.5.8" + }, + "peerDependencies": { + "react": "^16.3.0 || ^17.0.0" + } + }, "node_modules/react-input-mask": { "version": "3.0.0-alpha.2", "resolved": "https://registry.npmjs.org/react-input-mask/-/react-input-mask-3.0.0-alpha.2.tgz", "integrity": "sha512-9U7qL+mvDMOJcbOFPdt6Vj+zzmCMNnBjhhjGDrL8BGQmymgvMVKhu/oOVfAkl+5VWOsLr+G3EhZOmae5fBcAkA==", + "license": "MIT", "dependencies": { "invariant": "^2.2.4", "prop-types": "^15.7.2", @@ -6741,17 +8352,30 @@ "node_modules/react-is": { "version": "16.13.1", "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", - "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==" + "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==", + "license": "MIT" + }, + "node_modules/react-is-mounted-hook": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/react-is-mounted-hook/-/react-is-mounted-hook-1.1.2.tgz", + "integrity": "sha512-yjq3Tj34CiFcdVOS/h6JerWLOLdJqEGKMNpTHc4kWebzz2YtIpgqMRrqxdmQhewM1KJREojdAV2tsNvBsUWyhA==", + "license": "MIT", + "peerDependencies": { + "react": "^16.8.6 || ^17", + "react-dom": "^16.8.6 || ^17" + } }, "node_modules/react-lifecycles-compat": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/react-lifecycles-compat/-/react-lifecycles-compat-3.0.4.tgz", - "integrity": "sha512-fBASbA6LnOU9dOU2eW7aQ8xmYBSXUIWr+UmF9b1efZBazGNO+rcXT/icdKnYm2pTwcRylVUYwW7H1PHfLekVzA==" + "integrity": "sha512-fBASbA6LnOU9dOU2eW7aQ8xmYBSXUIWr+UmF9b1efZBazGNO+rcXT/icdKnYm2pTwcRylVUYwW7H1PHfLekVzA==", + "license": "MIT" }, "node_modules/react-mentions": { "version": "4.4.10", "resolved": "https://registry.npmjs.org/react-mentions/-/react-mentions-4.4.10.tgz", "integrity": "sha512-JHiQlgF1oSZR7VYPjq32wy97z1w1oE4x10EuhKjPr4WUKhVzG1uFQhQjKqjQkbVqJrmahf+ldgBTv36NrkpKpA==", + "license": "BSD-3-Clause", "dependencies": { "@babel/runtime": "7.4.5", "invariant": "^2.2.4", @@ -6776,6 +8400,7 @@ "version": "3.16.3", "resolved": "https://registry.npmjs.org/react-modal/-/react-modal-3.16.3.tgz", "integrity": "sha512-yCYRJB5YkeQDQlTt17WGAgFJ7jr2QYcWa1SHqZ3PluDmnKJ/7+tVU+E6uKyZ0nODaeEj+xCpK4LcSnKXLMC0Nw==", + "license": "MIT", "dependencies": { "exenv": "^1.2.0", "prop-types": "^15.7.2", @@ -6791,6 +8416,7 @@ "version": "2.3.0", "resolved": "https://registry.npmjs.org/react-popper/-/react-popper-2.3.0.tgz", "integrity": "sha512-e1hj8lL3uM+sgSR4Lxzn5h1GxBlpa4CQz0XLF8kx4MDrDRWY0Ena4c97PUeSX9i5W3UAfDP0z0FXCTQkoXUl3Q==", + "license": "MIT", "dependencies": { "react-fast-compare": "^3.0.1", "warning": "^4.0.2" @@ -6804,17 +8430,20 @@ "node_modules/react-popper/node_modules/react-fast-compare": { "version": "3.2.2", "resolved": "https://registry.npmjs.org/react-fast-compare/-/react-fast-compare-3.2.2.tgz", - "integrity": "sha512-nsO+KSNgo1SbJqJEYRE9ERzo7YtYbou/OqjSQKxV7jcKox7+usiUVZOAC+XnDOABXggQTno0Y1CpVnuWEc1boQ==" + "integrity": "sha512-nsO+KSNgo1SbJqJEYRE9ERzo7YtYbou/OqjSQKxV7jcKox7+usiUVZOAC+XnDOABXggQTno0Y1CpVnuWEc1boQ==", + "license": "MIT" }, "node_modules/react-property": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/react-property/-/react-property-2.0.2.tgz", - "integrity": "sha512-+PbtI3VuDV0l6CleQMsx2gtK0JZbZKbpdu5ynr+lbsuvtmgbNcS3VM0tuY2QjFNOcWxvXeHjDpy42RO+4U2rug==" + "integrity": "sha512-+PbtI3VuDV0l6CleQMsx2gtK0JZbZKbpdu5ynr+lbsuvtmgbNcS3VM0tuY2QjFNOcWxvXeHjDpy42RO+4U2rug==", + "license": "MIT" }, "node_modules/react-redux": { "version": "9.2.0", "resolved": "https://registry.npmjs.org/react-redux/-/react-redux-9.2.0.tgz", "integrity": "sha512-ROY9fvHhwOD9ySfrF0wmvu//bKCQ6AeZZq1nJNtbDC+kk5DuSuNX/n6YWYF/SYy7bSba4D4FSz8DJeKY/S/r+g==", + "license": "MIT", "dependencies": { "@types/use-sync-external-store": "^0.0.6", "use-sync-external-store": "^1.4.0" @@ -6852,9 +8481,10 @@ } }, "node_modules/react-router": { - "version": "7.12.0", - "resolved": "https://registry.npmjs.org/react-router/-/react-router-7.12.0.tgz", - "integrity": "sha512-kTPDYPFzDVGIIGNLS5VJykK0HfHLY5MF3b+xj0/tTyNYL1gF1qs7u67Z9jEhQk2sQ98SUaHxlG31g1JtF7IfVw==", + "version": "7.13.0", + "resolved": "https://registry.npmjs.org/react-router/-/react-router-7.13.0.tgz", + "integrity": "sha512-PZgus8ETambRT17BUm/LL8lX3Of+oiLaPuVTRH3l1eLvSPpKO3AvhAEb5N7ihAFZQrYDqkvvWfFh9p0z9VsjLw==", + "license": "MIT", "dependencies": { "cookie": "^1.0.1", "set-cookie-parser": "^2.6.0" @@ -6873,11 +8503,12 @@ } }, "node_modules/react-router-dom": { - "version": "7.12.0", - "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-7.12.0.tgz", - "integrity": "sha512-pfO9fiBcpEfX4Tx+iTYKDtPbrSLLCbwJ5EqP+SPYQu1VYCXdy79GSj0wttR0U4cikVdlImZuEZ/9ZNCgoaxwBA==", + "version": "7.13.0", + "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-7.13.0.tgz", + "integrity": "sha512-5CO/l5Yahi2SKC6rGZ+HDEjpjkGaG/ncEP7eWFTvFxbHP8yeeI0PxTDjimtpXYlR3b3i9/WIL4VJttPrESIf2g==", + "license": "MIT", "dependencies": { - "react-router": "7.12.0" + "react-router": "7.13.0" }, "engines": { "node": ">=20.0.0" @@ -6887,10 +8518,161 @@ "react-dom": ">=18" } }, + "node_modules/react-select": { + "name": "@contentstack/react-select", + "version": "3.2.8", + "resolved": "https://registry.npmjs.org/@contentstack/react-select/-/react-select-3.2.8.tgz", + "integrity": "sha512-ffc5PKxXgOS0oc1UDUIhZNFZ+XNymw9sk9USRaemzFlCEI5ywl68uJRICiRySO1YMxZIN96aG0cUXTXKfXE4qg==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "7.26.10", + "@emotion/cache": "10.0.9", + "@emotion/core": "10.0.9", + "@emotion/css": "10.0.9", + "memoize-one": "5.0.0", + "react-input-autosize": "3.0.0", + "react-transition-group": "4.3.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0", + "react-dom": "^16.8.0 || ^17.0.0" + } + }, + "node_modules/react-select-async-paginate": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/react-select-async-paginate/-/react-select-async-paginate-0.4.1.tgz", + "integrity": "sha512-zWeaN9C9PVQej4bz1+OvU6/ylHE6rHscDYcP+KiWdBedVQ5j2vXBjf/5RWLEvobvtUUHBOTbUF8+m2HDoeIcvQ==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.11.2", + "@seznam/compose-react-refs": "^1.0.4", + "react-is-mounted-hook": "^1.0.3", + "sleep-promise": "^8.0.1" + }, + "peerDependencies": { + "react": "^16.8.0", + "react-select": "^2.0.0 || ^3.0.0" + } + }, + "node_modules/react-select/node_modules/@babel/runtime": { + "version": "7.26.10", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.26.10.tgz", + "integrity": "sha512-2WJMeRQPHKSPemqk/awGrAiuFfzBmOIPXKizAsVhWH9YJqLZ0H+HS4c8loHGgW6utJ3E/ejXQUsiGaQy2NZ9Fw==", + "license": "MIT", + "dependencies": { + "regenerator-runtime": "^0.14.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/react-select/node_modules/@emotion/cache": { + "version": "10.0.9", + "resolved": "https://registry.npmjs.org/@emotion/cache/-/cache-10.0.9.tgz", + "integrity": "sha512-f7MblpE2xoimC4fCMZ9pivmsIn7hyWRIvY75owMDi8pdOSeh+w5tH3r4hBJv/LLrwiMM7cTQURqTPcYoL5pWnw==", + "license": "MIT", + "dependencies": { + "@emotion/sheet": "0.9.2", + "@emotion/stylis": "0.8.3", + "@emotion/utils": "0.11.1", + "@emotion/weak-memoize": "0.2.2" + } + }, + "node_modules/react-select/node_modules/@emotion/css": { + "version": "10.0.9", + "resolved": "https://registry.npmjs.org/@emotion/css/-/css-10.0.9.tgz", + "integrity": "sha512-jtHhUSWw+L7yxYgNtC+KJ3Ory90/jiAtpG1qT+gTQQ/RR5AMiigs9/lDHu/vnwljaq2S48FoKb/FZZMlJcC4bw==", + "license": "MIT", + "dependencies": { + "@emotion/serialize": "^0.11.6", + "@emotion/utils": "0.11.1", + "babel-plugin-emotion": "^10.0.9" + } + }, + "node_modules/react-select/node_modules/@emotion/hash": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/@emotion/hash/-/hash-0.8.0.tgz", + "integrity": "sha512-kBJtf7PH6aWwZ6fka3zQ0p6SBYzx4fl1LoZXE2RrnYST9Xljm7WfKJrU4g/Xr3Beg72MLrp1AWNUmuYJTL7Cow==", + "license": "MIT" + }, + "node_modules/react-select/node_modules/@emotion/memoize": { + "version": "0.7.4", + "resolved": "https://registry.npmjs.org/@emotion/memoize/-/memoize-0.7.4.tgz", + "integrity": "sha512-Ja/Vfqe3HpuzRsG1oBtWTHk2PGZ7GR+2Vz5iYGelAw8dx32K0y7PjVuxK6z1nMpZOqAFsRUPCkK1YjJ56qJlgw==", + "license": "MIT" + }, + "node_modules/react-select/node_modules/@emotion/serialize": { + "version": "0.11.16", + "resolved": "https://registry.npmjs.org/@emotion/serialize/-/serialize-0.11.16.tgz", + "integrity": "sha512-G3J4o8by0VRrO+PFeSc3js2myYNOXVJ3Ya+RGVxnshRYgsvErfAOglKAiy1Eo1vhzxqtUvjCyS5gtewzkmvSSg==", + "license": "MIT", + "dependencies": { + "@emotion/hash": "0.8.0", + "@emotion/memoize": "0.7.4", + "@emotion/unitless": "0.7.5", + "@emotion/utils": "0.11.3", + "csstype": "^2.5.7" + } + }, + "node_modules/react-select/node_modules/@emotion/serialize/node_modules/@emotion/utils": { + "version": "0.11.3", + "resolved": "https://registry.npmjs.org/@emotion/utils/-/utils-0.11.3.tgz", + "integrity": "sha512-0o4l6pZC+hI88+bzuaX/6BgOvQVhbt2PfmxauVaYOGgbsAw14wdKyvMCZXnsnsHys94iadcF+RG/wZyx6+ZZBw==", + "license": "MIT" + }, + "node_modules/react-select/node_modules/@emotion/sheet": { + "version": "0.9.2", + "resolved": "https://registry.npmjs.org/@emotion/sheet/-/sheet-0.9.2.tgz", + "integrity": "sha512-pVBLzIbC/QCHDKJF2E82V2H/W/B004mDFQZiyo/MSR+VC4pV5JLG0TF/zgQDFvP3fZL/5RTPGEmXlYJBMUuJ+A==", + "license": "MIT" + }, + "node_modules/react-select/node_modules/@emotion/stylis": { + "version": "0.8.3", + "resolved": "https://registry.npmjs.org/@emotion/stylis/-/stylis-0.8.3.tgz", + "integrity": "sha512-M3nMfJ6ndJMYloSIbYEBq6G3eqoYD41BpDOxreE8j0cb4fzz/5qvmqU9Mb2hzsXcCnIlGlWhS03PCzVGvTAe0Q==", + "license": "MIT" + }, + "node_modules/react-select/node_modules/@emotion/unitless": { + "version": "0.7.5", + "resolved": "https://registry.npmjs.org/@emotion/unitless/-/unitless-0.7.5.tgz", + "integrity": "sha512-OWORNpfjMsSSUBVrRBVGECkhWcULOAJz9ZW8uK9qgxD+87M7jHRcvh/A96XXNhXTLmKcoYSQtBEX7lHMO7YRwg==", + "license": "MIT" + }, + "node_modules/react-select/node_modules/@emotion/utils": { + "version": "0.11.1", + "resolved": "https://registry.npmjs.org/@emotion/utils/-/utils-0.11.1.tgz", + "integrity": "sha512-8M3VN0hetwhsJ8dH8VkVy7xo5/1VoBsDOk/T4SJOeXwTO1c4uIqVNx2qyecLFnnUWD5vvUqHQ1gASSeUN6zcTg==", + "license": "MIT" + }, + "node_modules/react-select/node_modules/@emotion/weak-memoize": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/@emotion/weak-memoize/-/weak-memoize-0.2.2.tgz", + "integrity": "sha512-n/VQ4mbfr81aqkx/XmVicOLjviMuy02eenSdJY33SVA7S2J42EU0P1H0mOogfYedb3wXA0d/LVtBrgTSm04WEA==", + "license": "MIT" + }, + "node_modules/react-select/node_modules/csstype": { + "version": "2.6.21", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-2.6.21.tgz", + "integrity": "sha512-Z1PhmomIfypOpoMjRQB70jfvy/wxT50qW08YXO5lMIJkrdq4yOTR+AW7FqutScmB9NkLwxo+jU+kZLbofZZq/w==", + "license": "MIT" + }, + "node_modules/react-select/node_modules/memoize-one": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/memoize-one/-/memoize-one-5.0.0.tgz", + "integrity": "sha512-7g0+ejkOaI9w5x6LvQwmj68kUj6rxROywPSCqmclG/HBacmFnZqhVscQ8kovkn9FBCNJmOz6SY42+jnvZzDWdw==", + "license": "MIT" + }, + "node_modules/react-select/node_modules/regenerator-runtime": { + "version": "0.14.1", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz", + "integrity": "sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==", + "license": "MIT" + }, "node_modules/react-shallow-renderer": { "version": "16.15.0", "resolved": "https://registry.npmjs.org/react-shallow-renderer/-/react-shallow-renderer-16.15.0.tgz", "integrity": "sha512-oScf2FqQ9LFVQgA73vr86xl2NaOIX73rh+YFqcOp68CWj56tSfgtGKrEbyhCj0rSijyG9M1CYprTh39fBi5hzA==", + "license": "MIT", "dependencies": { "object-assign": "^4.1.1", "react-is": "^16.12.0 || ^17.0.0 || ^18.0.0" @@ -6903,6 +8685,7 @@ "version": "2.1.2", "resolved": "https://registry.npmjs.org/react-side-effect/-/react-side-effect-2.1.2.tgz", "integrity": "sha512-PVjOcvVOyIILrYoyGEpDN3vmYNLdy1CajSFNt4TDsVQC5KpTijDvWVoR+/7Rz2xT978D8/ZtFceXxzsPwZEDvw==", + "license": "MIT", "peerDependencies": { "react": "^16.3.0 || ^17.0.0 || ^18.0.0" } @@ -6911,6 +8694,7 @@ "version": "0.11.3", "resolved": "https://registry.npmjs.org/react-simple-code-editor/-/react-simple-code-editor-0.11.3.tgz", "integrity": "sha512-7bVI4Yd1aNCeuldErXUt8ksaAG5Fi+GZ6vp3mtFBnckKdzsQtrgkDvdwMFXIhwTGG+mUYmk5ZpMo0axSW9JBzA==", + "license": "MIT", "peerDependencies": { "react": "*", "react-dom": "*" @@ -6920,6 +8704,7 @@ "version": "4.0.4", "resolved": "https://registry.npmjs.org/react-smooth/-/react-smooth-4.0.4.tgz", "integrity": "sha512-gnGKTpYwqL0Iii09gHobNolvX4Kiq4PKx6eWBCYYix+8cdw+cGo3do906l1NBPKkSWx1DghC1dlWG9L2uGd61Q==", + "license": "MIT", "dependencies": { "fast-equals": "^5.0.1", "prop-types": "^15.8.1", @@ -6930,10 +8715,43 @@ "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, + "node_modules/react-smooth/node_modules/react-transition-group": { + "version": "4.4.5", + "resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-4.4.5.tgz", + "integrity": "sha512-pZcd1MCJoiKiBR2NRxeCRg13uCXbydPnmB4EOeRrY7480qNWO8IIgQG6zlDkm6uRMsURXPuKq0GWtiM59a5Q6g==", + "license": "BSD-3-Clause", + "dependencies": { + "@babel/runtime": "^7.5.5", + "dom-helpers": "^5.0.1", + "loose-envify": "^1.4.0", + "prop-types": "^15.6.2" + }, + "peerDependencies": { + "react": ">=16.6.0", + "react-dom": ">=16.6.0" + } + }, + "node_modules/react-sortable-hoc": { + "version": "1.11.0", + "resolved": "https://registry.npmjs.org/react-sortable-hoc/-/react-sortable-hoc-1.11.0.tgz", + "integrity": "sha512-v1CDCvdfoR3zLGNp6qsBa4J1BWMEVH25+UKxF/RvQRh+mrB+emqtVHMgZ+WreUiKJoEaiwYoScaueIKhMVBHUg==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.2.0", + "invariant": "^2.2.4", + "prop-types": "^15.5.7" + }, + "peerDependencies": { + "prop-types": "^15.5.7", + "react": "^0.14.0 || ^15.0.0 || ^16.0.0", + "react-dom": "^0.14.0 || ^15.0.0 || ^16.0.0" + } + }, "node_modules/react-style-tag": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/react-style-tag/-/react-style-tag-3.0.1.tgz", "integrity": "sha512-XeiiTQtG04uT+Lrtr0YvA5I2qcbUzHMCtS40iH3/bzP98xhPUm1qzRUOoOXgMfyJRPwHh36MH8ebj1C7nuaolg==", + "license": "MIT", "dependencies": { "stylis": "^4.1.1" }, @@ -6946,6 +8764,7 @@ "version": "7.8.0", "resolved": "https://registry.npmjs.org/react-table/-/react-table-7.8.0.tgz", "integrity": "sha512-hNaz4ygkZO4bESeFfnfOft73iBUj8K5oKi1EcSHPAibEydfsX2MyU6Z8KCr3mv3C9Kqqh71U+DhZkFvibbnPbA==", + "license": "MIT", "funding": { "type": "github", "url": "https://github.com/sponsors/tannerlinsley" @@ -6958,6 +8777,7 @@ "version": "18.3.1", "resolved": "https://registry.npmjs.org/react-test-renderer/-/react-test-renderer-18.3.1.tgz", "integrity": "sha512-KkAgygexHUkQqtvvx/otwxtuFu5cVjfzTCtjXLH9boS19/Nbtg84zS7wIQn39G8IlrhThBpQsMKkq5ZHZIYFXA==", + "license": "MIT", "dependencies": { "react-is": "^18.3.1", "react-shallow-renderer": "^16.15.0", @@ -6970,12 +8790,14 @@ "node_modules/react-test-renderer/node_modules/react-is": { "version": "18.3.1", "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", - "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==" + "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==", + "license": "MIT" }, "node_modules/react-tiktok": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/react-tiktok/-/react-tiktok-1.0.0.tgz", "integrity": "sha512-t7CDkzEI6CWTxuqW7NJGF4u4N+21qvA1NGn06+kOzWDphueoAFg+BdxEKz0jtyzJYB+2nvuoPHuPK4cYdLIXvg==", + "license": "MIT", "dependencies": { "fetch-retry": "^4.0.1", "react-helmet": "^6.1.0" @@ -6990,6 +8812,7 @@ "version": "6.1.5", "resolved": "https://registry.npmjs.org/@contentstack/react-toastify/-/react-toastify-6.1.5.tgz", "integrity": "sha512-ibY34/1kdHytblf7PRI7QBSPaUV31bVP/WPVJMqTdpjDX1AEz+ZczbFBb9l3modcBMPBos+bDgbMsuOkiK00dw==", + "license": "MIT", "dependencies": { "clsx": "^1.1.1", "prop-types": "^15.7.2", @@ -6999,10 +8822,27 @@ "react": ">=16" } }, - "node_modules/react-transition-group": { + "node_modules/react-toastify/node_modules/react-transition-group": { "version": "4.4.5", "resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-4.4.5.tgz", "integrity": "sha512-pZcd1MCJoiKiBR2NRxeCRg13uCXbydPnmB4EOeRrY7480qNWO8IIgQG6zlDkm6uRMsURXPuKq0GWtiM59a5Q6g==", + "license": "BSD-3-Clause", + "dependencies": { + "@babel/runtime": "^7.5.5", + "dom-helpers": "^5.0.1", + "loose-envify": "^1.4.0", + "prop-types": "^15.6.2" + }, + "peerDependencies": { + "react": ">=16.6.0", + "react-dom": ">=16.6.0" + } + }, + "node_modules/react-transition-group": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-4.3.0.tgz", + "integrity": "sha512-1qRV1ZuVSdxPlPf4O8t7inxUGpdyO5zG9IoNfJxSO0ImU2A1YWkEQvFPuIPZmMLkg5hYs7vv5mMOyfgSkvAwvw==", + "license": "BSD-3-Clause", "dependencies": { "@babel/runtime": "^7.5.5", "dom-helpers": "^5.0.1", @@ -7018,6 +8858,7 @@ "version": "3.2.4", "resolved": "https://registry.npmjs.org/react-treebeard/-/react-treebeard-3.2.4.tgz", "integrity": "sha512-TsvdUq2kbLavRXa8k4mmqfPse8HmSA9G9s1SZUtIpiYSccSwa0Tm6miMgx7DZ5gpKofQ+j/3Ua0rjsahM3/FQg==", + "license": "MIT", "dependencies": { "@emotion/core": "^10.0.10", "@emotion/styled": "^10.0.10", @@ -7033,48 +8874,106 @@ "react-dom": ">=16.7.0" } }, - "node_modules/react-treebeard/node_modules/dom-helpers": { - "version": "3.4.0", - "resolved": "https://registry.npmjs.org/dom-helpers/-/dom-helpers-3.4.0.tgz", - "integrity": "sha512-LnuPJ+dwqKDIyotW1VzmOZ5TONUN7CwkCR5hrgawTUbkBGYdeoNLZo6nNfGkCrjtE1nXXaj7iMMpDa8/d9WoIA==", + "node_modules/react-treebeard/node_modules/@emotion/cache": { + "version": "10.0.29", + "resolved": "https://registry.npmjs.org/@emotion/cache/-/cache-10.0.29.tgz", + "integrity": "sha512-fU2VtSVlHiF27empSbxi1O2JFdNWZO+2NFHfwO0pxgTep6Xa3uGb+3pVKfLww2l/IBGLNEZl5Xf/++A4wAYDYQ==", + "license": "MIT", "dependencies": { - "@babel/runtime": "^7.1.2" + "@emotion/sheet": "0.9.4", + "@emotion/stylis": "0.8.5", + "@emotion/utils": "0.11.3", + "@emotion/weak-memoize": "0.2.5" } }, - "node_modules/react-treebeard/node_modules/react-transition-group": { - "version": "2.9.0", - "resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-2.9.0.tgz", - "integrity": "sha512-+HzNTCHpeQyl4MJ/bdE0u6XRMe9+XG/+aL4mCxVN4DnPBQ0/5bfHWPDuOZUzYdMj94daZaZdCCc1Dzt9R/xSSg==", + "node_modules/react-treebeard/node_modules/@emotion/core": { + "version": "10.3.1", + "resolved": "https://registry.npmjs.org/@emotion/core/-/core-10.3.1.tgz", + "integrity": "sha512-447aUEjPIm0MnE6QYIaFz9VQOHSXf4Iu6EWOIqq11EAPqinkSZmfymPTmlOE3QjLv846lH4JVZBUOtwGbuQoww==", + "license": "MIT", "dependencies": { - "dom-helpers": "^3.4.0", - "loose-envify": "^1.4.0", - "prop-types": "^15.6.2", - "react-lifecycles-compat": "^3.0.4" + "@babel/runtime": "^7.5.5", + "@emotion/cache": "^10.0.27", + "@emotion/css": "^10.0.27", + "@emotion/serialize": "^0.11.15", + "@emotion/sheet": "0.9.4", + "@emotion/utils": "0.11.3" }, "peerDependencies": { - "react": ">=15.0.0", - "react-dom": ">=15.0.0" + "react": ">=16.3.0" + } + }, + "node_modules/react-treebeard/node_modules/@emotion/css": { + "version": "10.0.27", + "resolved": "https://registry.npmjs.org/@emotion/css/-/css-10.0.27.tgz", + "integrity": "sha512-6wZjsvYeBhyZQYNrGoR5yPMYbMBNEnanDrqmsqS1mzDm1cOTu12shvl2j4QHNS36UaTE0USIJawCH9C8oW34Zw==", + "license": "MIT", + "dependencies": { + "@emotion/serialize": "^0.11.15", + "@emotion/utils": "0.11.3", + "babel-plugin-emotion": "^10.0.27" } }, - "node_modules/react-treebeard/node_modules/velocity-react": { - "version": "1.4.3", - "resolved": "https://registry.npmjs.org/velocity-react/-/velocity-react-1.4.3.tgz", - "integrity": "sha512-zvefGm85A88S3KdF9/dz5vqyFLAiwKYlXGYkHH2EbXl+CZUD1OT0a0aS1tkX/WXWTa/FUYqjBaAzAEFYuSobBQ==", + "node_modules/react-treebeard/node_modules/@emotion/hash": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/@emotion/hash/-/hash-0.8.0.tgz", + "integrity": "sha512-kBJtf7PH6aWwZ6fka3zQ0p6SBYzx4fl1LoZXE2RrnYST9Xljm7WfKJrU4g/Xr3Beg72MLrp1AWNUmuYJTL7Cow==", + "license": "MIT" + }, + "node_modules/react-treebeard/node_modules/@emotion/memoize": { + "version": "0.7.4", + "resolved": "https://registry.npmjs.org/@emotion/memoize/-/memoize-0.7.4.tgz", + "integrity": "sha512-Ja/Vfqe3HpuzRsG1oBtWTHk2PGZ7GR+2Vz5iYGelAw8dx32K0y7PjVuxK6z1nMpZOqAFsRUPCkK1YjJ56qJlgw==", + "license": "MIT" + }, + "node_modules/react-treebeard/node_modules/@emotion/serialize": { + "version": "0.11.16", + "resolved": "https://registry.npmjs.org/@emotion/serialize/-/serialize-0.11.16.tgz", + "integrity": "sha512-G3J4o8by0VRrO+PFeSc3js2myYNOXVJ3Ya+RGVxnshRYgsvErfAOglKAiy1Eo1vhzxqtUvjCyS5gtewzkmvSSg==", + "license": "MIT", "dependencies": { - "lodash": "^4.17.5", - "prop-types": "^15.5.8", - "react-transition-group": "^2.0.0", - "velocity-animate": "^1.4.0" - }, - "peerDependencies": { - "react": "^15.3.0 || ^16.0.0", - "react-dom": "^15.3.0 || ^16.0.0" + "@emotion/hash": "0.8.0", + "@emotion/memoize": "0.7.4", + "@emotion/unitless": "0.7.5", + "@emotion/utils": "0.11.3", + "csstype": "^2.5.7" } }, + "node_modules/react-treebeard/node_modules/@emotion/sheet": { + "version": "0.9.4", + "resolved": "https://registry.npmjs.org/@emotion/sheet/-/sheet-0.9.4.tgz", + "integrity": "sha512-zM9PFmgVSqBw4zL101Q0HrBVTGmpAxFZH/pYx/cjJT5advXguvcgjHFTCaIO3enL/xr89vK2bh0Mfyj9aa0ANA==", + "license": "MIT" + }, + "node_modules/react-treebeard/node_modules/@emotion/unitless": { + "version": "0.7.5", + "resolved": "https://registry.npmjs.org/@emotion/unitless/-/unitless-0.7.5.tgz", + "integrity": "sha512-OWORNpfjMsSSUBVrRBVGECkhWcULOAJz9ZW8uK9qgxD+87M7jHRcvh/A96XXNhXTLmKcoYSQtBEX7lHMO7YRwg==", + "license": "MIT" + }, + "node_modules/react-treebeard/node_modules/@emotion/utils": { + "version": "0.11.3", + "resolved": "https://registry.npmjs.org/@emotion/utils/-/utils-0.11.3.tgz", + "integrity": "sha512-0o4l6pZC+hI88+bzuaX/6BgOvQVhbt2PfmxauVaYOGgbsAw14wdKyvMCZXnsnsHys94iadcF+RG/wZyx6+ZZBw==", + "license": "MIT" + }, + "node_modules/react-treebeard/node_modules/@emotion/weak-memoize": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/@emotion/weak-memoize/-/weak-memoize-0.2.5.tgz", + "integrity": "sha512-6U71C2Wp7r5XtFtQzYrW5iKFT67OixrSxjI4MptCHzdSVlgabczzqLe0ZSgnub/5Kp4hSbpDB1tMytZY9pwxxA==", + "license": "MIT" + }, + "node_modules/react-treebeard/node_modules/csstype": { + "version": "2.6.21", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-2.6.21.tgz", + "integrity": "sha512-Z1PhmomIfypOpoMjRQB70jfvy/wxT50qW08YXO5lMIJkrdq4yOTR+AW7FqutScmB9NkLwxo+jU+kZLbofZZq/w==", + "license": "MIT" + }, "node_modules/react-virtualized": { "version": "9.22.6", "resolved": "https://registry.npmjs.org/react-virtualized/-/react-virtualized-9.22.6.tgz", "integrity": "sha512-U5j7KuUQt3AaMatlMJ0UJddqSiX+Km0YJxSqbAzIiGw5EmNz0khMyqP2hzgu4+QUtm+QPIrxzUX4raJxmVJnHg==", + "license": "MIT", "dependencies": { "@babel/runtime": "^7.7.2", "clsx": "^1.0.4", @@ -7088,10 +8987,24 @@ "react-dom": "^16.3.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, + "node_modules/react-virtualized-auto-sizer": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/react-virtualized-auto-sizer/-/react-virtualized-auto-sizer-1.0.5.tgz", + "integrity": "sha512-kivjYVWX15TX2IUrm8F1jaCEX8EXrpy3DD+u41WGqJ1ZqbljWpiwscV+VxOM1l7sSIM1jwi2LADjhhAJkJ9dxA==", + "license": "MIT", + "engines": { + "node": ">8.0.0" + }, + "peerDependencies": { + "react": "^15.3.0 || ^16.0.0-alpha || ^17.0.0", + "react-dom": "^15.3.0 || ^16.0.0-alpha || ^17.0.0" + } + }, "node_modules/react-window": { "version": "1.8.11", "resolved": "https://registry.npmjs.org/react-window/-/react-window-1.8.11.tgz", "integrity": "sha512-+SRbUVT2scadgFSWx+R1P754xHPEqvcfSfVX10QYg6POOz+WNgkN48pS+BtZNIMGiL1HYrSEiCkwsMS15QogEQ==", + "license": "MIT", "dependencies": { "@babel/runtime": "^7.0.0", "memoize-one": ">=3.1.1 <6" @@ -7108,6 +9021,7 @@ "version": "1.0.10", "resolved": "https://registry.npmjs.org/react-window-infinite-loader/-/react-window-infinite-loader-1.0.10.tgz", "integrity": "sha512-NO/csdHlxjWqA2RJZfzQgagAjGHspbO2ik9GtWZb0BY1Nnapq0auG8ErI+OhGCzpjYJsCYerqUlK6hkq9dfAAA==", + "license": "MIT", "engines": { "node": ">8.0.0" }, @@ -7120,14 +9034,47 @@ "version": "1.2.3", "resolved": "https://registry.npmjs.org/reactcss/-/reactcss-1.2.3.tgz", "integrity": "sha512-KiwVUcFu1RErkI97ywr8nvx8dNOpT03rbnma0SSalTYjkrPYaEajR4a/MRt6DZ46K6arDRbWMNHF+xH7G7n/8A==", + "license": "MIT", "dependencies": { "lodash": "^4.0.1" } }, + "node_modules/reactjs-social-embed": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/reactjs-social-embed/-/reactjs-social-embed-1.2.0.tgz", + "integrity": "sha512-Avi6yyQJ5rbLmAzLkJJHBL+86+3if40QGgOUEu0/GxpARWyMd6w4R7C4AAufQqGC2x7V+HFpMkUovdk4/HjHSg==", + "license": "MIT", + "dependencies": { + "jsonp-p": "^2.0.0" + }, + "engines": { + "node": ">=8", + "npm": ">=5" + }, + "peerDependencies": { + "prop-types": "^15.5.4", + "react": "^15.0.0 || ^16.0.0", + "react-dom": "^15.0.0 || ^16.0.0" + } + }, + "node_modules/readdirp": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.1.2.tgz", + "integrity": "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==", + "license": "MIT", + "engines": { + "node": ">= 14.18.0" + }, + "funding": { + "type": "individual", + "url": "https://paulmillr.com/funding/" + } + }, "node_modules/recharts": { "version": "2.15.4", "resolved": "https://registry.npmjs.org/recharts/-/recharts-2.15.4.tgz", "integrity": "sha512-UT/q6fwS3c1dHbXv2uFgYJ9BMFHu3fwnd7AYZaEQhXuYQ4hgsxLvsUXzGdKeZrW5xopzDCvuA2N41WJ88I7zIw==", + "license": "MIT", "dependencies": { "clsx": "^2.0.0", "eventemitter3": "^4.0.1", @@ -7150,6 +9097,7 @@ "version": "0.4.5", "resolved": "https://registry.npmjs.org/recharts-scale/-/recharts-scale-0.4.5.tgz", "integrity": "sha512-kivNFO+0OcUNu7jQquLXAxz1FIwZj8nrj+YkOKc5694NbjCvcT6aSZiIzNzd2Kul4o4rTto8QVR9lMNtxD4G1w==", + "license": "MIT", "dependencies": { "decimal.js-light": "^2.4.1" } @@ -7158,6 +9106,7 @@ "version": "2.1.1", "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz", "integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==", + "license": "MIT", "engines": { "node": ">=6" } @@ -7165,12 +9114,14 @@ "node_modules/recharts/node_modules/react-is": { "version": "18.3.1", "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", - "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==" + "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==", + "license": "MIT" }, "node_modules/redent": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/redent/-/redent-3.0.0.tgz", "integrity": "sha512-6tDA8g98We0zd0GvVeMT9arEOnTw9qM03L9cJXaCjrip1OO764RDBLBfrB4cwzNGDj5OA5ioymC9GkizgWJDUg==", + "dev": true, "license": "MIT", "dependencies": { "indent-string": "^4.0.0", @@ -7183,12 +9134,14 @@ "node_modules/redux": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/redux/-/redux-5.0.1.tgz", - "integrity": "sha512-M9/ELqF6fy8FwmkpnF0S3YKOqMyoWJ4+CS5Efg2ct3oY9daQvd/Pc71FpGZsVsbl3Cpb+IIcjBDUnnyBdQbq4w==" + "integrity": "sha512-M9/ELqF6fy8FwmkpnF0S3YKOqMyoWJ4+CS5Efg2ct3oY9daQvd/Pc71FpGZsVsbl3Cpb+IIcjBDUnnyBdQbq4w==", + "license": "MIT" }, "node_modules/redux-persist": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/redux-persist/-/redux-persist-6.0.0.tgz", "integrity": "sha512-71LLMbUq2r02ng2We9S215LtPu3fY0KgaGE0k8WRgl6RkqxtGfl7HUozz1Dftwsb0D/5mZ8dwAaPbtnzfvbEwQ==", + "license": "MIT", "peerDependencies": { "redux": ">4.0.0" } @@ -7197,6 +9150,7 @@ "version": "3.1.0", "resolved": "https://registry.npmjs.org/redux-thunk/-/redux-thunk-3.1.0.tgz", "integrity": "sha512-NW2r5T6ksUKXCabzhL9z+h206HQw/NJkcLm1GPImRQ8IzfXwRGqjVhKJGauHirT0DAuyy6hjdnMZaRoAcy0Klw==", + "license": "MIT", "peerDependencies": { "redux": "^5.0.0" } @@ -7206,6 +9160,7 @@ "resolved": "https://registry.npmjs.org/reflect.getprototypeof/-/reflect.getprototypeof-1.0.10.tgz", "integrity": "sha512-00o4I+DVrefhv+nX0ulyi3biSHCPDe+yLv5o/p6d/UVlirijB8E16FtfwSAi4g3tcqrQ4lRAqQSoFEZJehYEcw==", "dev": true, + "license": "MIT", "dependencies": { "call-bind": "^1.0.8", "define-properties": "^1.2.1", @@ -7226,12 +9181,14 @@ "node_modules/regenerator-runtime": { "version": "0.13.11", "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz", - "integrity": "sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==" + "integrity": "sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==", + "license": "MIT" }, "node_modules/regexp.prototype.flags": { "version": "1.5.4", "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.4.tgz", "integrity": "sha512-dYqgNSZbDwkaJ2ceRd9ojCGjBq+mOm9LmtXnAnEGyHhN/5R7iDW2TRw3h+o/jCFxus3P2LfWIIiwowAjANm7IA==", + "license": "MIT", "dependencies": { "call-bind": "^1.0.8", "define-properties": "^1.2.1", @@ -7247,20 +9204,33 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/require-from-string": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", + "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/reselect": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/reselect/-/reselect-5.1.1.tgz", - "integrity": "sha512-K/BG6eIky/SBpzfHZv/dd+9JBFiS4SWV7FIujVyJRux6e45+73RaUHXLmIR1f7WOMaQ0U1km6qwklRQxpJJY0w==" + "integrity": "sha512-K/BG6eIky/SBpzfHZv/dd+9JBFiS4SWV7FIujVyJRux6e45+73RaUHXLmIR1f7WOMaQ0U1km6qwklRQxpJJY0w==", + "license": "MIT" }, "node_modules/resize-observer-polyfill": { "version": "1.5.1", "resolved": "https://registry.npmjs.org/resize-observer-polyfill/-/resize-observer-polyfill-1.5.1.tgz", - "integrity": "sha512-LwZrotdHOo12nQuZlHEmtuXdqGoOD0OhaxopaNFxWzInpEgaLWoVuAMbTzixuosCx2nEG58ngzW3vxdWoxIgdg==" + "integrity": "sha512-LwZrotdHOo12nQuZlHEmtuXdqGoOD0OhaxopaNFxWzInpEgaLWoVuAMbTzixuosCx2nEG58ngzW3vxdWoxIgdg==", + "license": "MIT" }, "node_modules/resolve": { "version": "1.22.11", "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.11.tgz", "integrity": "sha512-RfqAvLnMl313r7c9oclB1HhUEAezcpLjz95wFH4LVuhk9JF/r22qmVP9AMmOU4vMX7Q8pN8jwNg/CSpdFnMjTQ==", + "license": "MIT", "dependencies": { "is-core-module": "^2.16.1", "path-parse": "^1.0.7", @@ -7280,6 +9250,7 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "license": "MIT", "engines": { "node": ">=4" } @@ -7289,6 +9260,7 @@ "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz", "integrity": "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==", "dev": true, + "license": "MIT", "engines": { "iojs": ">=1.0.0", "node": ">=0.10.0" @@ -7300,6 +9272,7 @@ "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", "deprecated": "Rimraf versions prior to v4 are no longer supported", "dev": true, + "license": "ISC", "dependencies": { "glob": "^7.1.3" }, @@ -7314,8 +9287,9 @@ "version": "7.2.3", "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", - "deprecated": "Glob versions prior to v9 are no longer supported", + "deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me", "dev": true, + "license": "ISC", "dependencies": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", @@ -7335,6 +9309,7 @@ "version": "4.57.1", "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.57.1.tgz", "integrity": "sha512-oQL6lgK3e2QZeQ7gcgIkS2YZPg5slw37hYufJ3edKlfQSGGm8ICoxswK15ntSzF/a8+h7ekRy7k7oWc3BQ7y8A==", + "license": "MIT", "dependencies": { "@types/estree": "1.0.8" }, @@ -7377,7 +9352,8 @@ "node_modules/rtl-detect": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/rtl-detect/-/rtl-detect-1.1.2.tgz", - "integrity": "sha512-PGMBq03+TTG/p/cRB7HCLKJ1MgDIi07+QU1faSjiYRfmY5UsAttV9Hs08jDAHVwcOwmVLcSJkpwyfXszVjWfIQ==" + "integrity": "sha512-PGMBq03+TTG/p/cRB7HCLKJ1MgDIi07+QU1faSjiYRfmY5UsAttV9Hs08jDAHVwcOwmVLcSJkpwyfXszVjWfIQ==", + "license": "BSD-3-Clause" }, "node_modules/run-parallel": { "version": "1.2.0", @@ -7398,6 +9374,7 @@ "url": "https://feross.org/support" } ], + "license": "MIT", "dependencies": { "queue-microtask": "^1.2.2" } @@ -7407,6 +9384,7 @@ "resolved": "https://registry.npmjs.org/safe-array-concat/-/safe-array-concat-1.1.3.tgz", "integrity": "sha512-AURm5f0jYEOydBj7VQlVvDrjeFgthDdEF5H1dP+6mNpoXOMo1quQqJ4wvJDyRZ9+pO3kGWoOdmV08cSv2aJV6Q==", "dev": true, + "license": "MIT", "dependencies": { "call-bind": "^1.0.8", "call-bound": "^1.0.2", @@ -7426,6 +9404,7 @@ "resolved": "https://registry.npmjs.org/safe-push-apply/-/safe-push-apply-1.0.0.tgz", "integrity": "sha512-iKE9w/Z7xCzUMIZqdBsp6pEQvwuEebH4vdpjcDWnyzaI6yl6O9FHvVpmGelvEHNsoY6wGblkxR6Zty/h00WiSA==", "dev": true, + "license": "MIT", "dependencies": { "es-errors": "^1.3.0", "isarray": "^2.0.5" @@ -7441,6 +9420,7 @@ "version": "1.1.0", "resolved": "https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.1.0.tgz", "integrity": "sha512-x/+Cz4YrimQxQccJf5mKEbIa1NzeCRNI5Ecl/ekmlYaampdNLPalVyIcCZNNH3MvmqBugV5TMYZXv0ljslUlaw==", + "license": "MIT", "dependencies": { "call-bound": "^1.0.2", "es-errors": "^1.3.0", @@ -7454,9 +9434,10 @@ } }, "node_modules/sass": { - "version": "1.97.2", - "resolved": "https://registry.npmjs.org/sass/-/sass-1.97.2.tgz", - "integrity": "sha512-y5LWb0IlbO4e97Zr7c3mlpabcbBtS+ieiZ9iwDooShpFKWXf62zz5pEPdwrLYm+Bxn1fnbwFGzHuCLSA9tBmrw==", + "version": "1.97.3", + "resolved": "https://registry.npmjs.org/sass/-/sass-1.97.3.tgz", + "integrity": "sha512-fDz1zJpd5GycprAbu4Q2PV/RprsRtKC/0z82z0JLgdytmcq0+ujJbJ/09bPGDxCLkKY3Np5cRAOcWiVkLXJURg==", + "license": "MIT", "dependencies": { "chokidar": "^4.0.0", "immutable": "^5.0.2", @@ -7472,36 +9453,24 @@ "@parcel/watcher": "^2.4.1" } }, - "node_modules/sass/node_modules/chokidar": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz", - "integrity": "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==", + "node_modules/saxes": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/saxes/-/saxes-6.0.0.tgz", + "integrity": "sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA==", + "dev": true, + "license": "ISC", "dependencies": { - "readdirp": "^4.0.1" - }, - "engines": { - "node": ">= 14.16.0" + "xmlchars": "^2.2.0" }, - "funding": { - "url": "https://paulmillr.com/funding/" - } - }, - "node_modules/sass/node_modules/readdirp": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.1.2.tgz", - "integrity": "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==", "engines": { - "node": ">= 14.18.0" - }, - "funding": { - "type": "individual", - "url": "https://paulmillr.com/funding/" + "node": ">=v12.22.7" } }, "node_modules/scheduler": { "version": "0.23.2", "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.2.tgz", "integrity": "sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==", + "license": "MIT", "dependencies": { "loose-envify": "^1.1.0" } @@ -7510,6 +9479,7 @@ "version": "2.2.31", "resolved": "https://registry.npmjs.org/scroll-into-view-if-needed/-/scroll-into-view-if-needed-2.2.31.tgz", "integrity": "sha512-dGCXy99wZQivjmjIqihaBQNjryrz5rueJY7eHfTdyWEiR4ttYpsajb14rn9s5d4DY4EcY6+4+U/maARBXJedkA==", + "license": "MIT", "dependencies": { "compute-scroll-into-view": "^1.0.20" } @@ -7519,6 +9489,7 @@ "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", "dev": true, + "license": "ISC", "bin": { "semver": "bin/semver.js" } @@ -7526,12 +9497,14 @@ "node_modules/set-cookie-parser": { "version": "2.7.2", "resolved": "https://registry.npmjs.org/set-cookie-parser/-/set-cookie-parser-2.7.2.tgz", - "integrity": "sha512-oeM1lpU/UvhTxw+g3cIfxXHyJRc/uidd3yK1P242gzHds0udQBYzs3y8j4gCCW+ZJ7ad0yctld8RYO+bdurlvw==" + "integrity": "sha512-oeM1lpU/UvhTxw+g3cIfxXHyJRc/uidd3yK1P242gzHds0udQBYzs3y8j4gCCW+ZJ7ad0yctld8RYO+bdurlvw==", + "license": "MIT" }, "node_modules/set-function-length": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz", "integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==", + "license": "MIT", "dependencies": { "define-data-property": "^1.1.4", "es-errors": "^1.3.0", @@ -7548,6 +9521,7 @@ "version": "2.0.2", "resolved": "https://registry.npmjs.org/set-function-name/-/set-function-name-2.0.2.tgz", "integrity": "sha512-7PGFlmtwsEADb0WYyvCMa1t+yke6daIG4Wirafur5kcf+MhUnPms1UeR0CKQdTZD81yESwMHbtn+TR+dMviakQ==", + "license": "MIT", "dependencies": { "define-data-property": "^1.1.4", "es-errors": "^1.3.0", @@ -7563,6 +9537,7 @@ "resolved": "https://registry.npmjs.org/set-proto/-/set-proto-1.0.0.tgz", "integrity": "sha512-RJRdvCo6IAnPdsvP/7m6bsQqNnn1FCBX5ZNtFL98MmFF/4xAIJTIg1YbHW5DC2W5SKZanrC6i4HsJqlajw/dZw==", "dev": true, + "license": "MIT", "dependencies": { "dunder-proto": "^1.0.1", "es-errors": "^1.3.0", @@ -7581,12 +9556,14 @@ "node_modules/shallowequal": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/shallowequal/-/shallowequal-1.1.0.tgz", - "integrity": "sha512-y0m1JoUZSlPAjXVtPPW70aZWfIL/dSP7AFkRnniLCrK/8MDKog3TySTBmckD+RObVxH0v4Tox67+F14PdED2oQ==" + "integrity": "sha512-y0m1JoUZSlPAjXVtPPW70aZWfIL/dSP7AFkRnniLCrK/8MDKog3TySTBmckD+RObVxH0v4Tox67+F14PdED2oQ==", + "license": "MIT" }, "node_modules/shebang-command": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "license": "MIT", "dependencies": { "shebang-regex": "^3.0.0" }, @@ -7598,6 +9575,7 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "license": "MIT", "engines": { "node": ">=8" } @@ -7606,6 +9584,7 @@ "version": "1.1.0", "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz", "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==", + "license": "MIT", "dependencies": { "es-errors": "^1.3.0", "object-inspect": "^1.13.3", @@ -7624,6 +9603,7 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz", "integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==", + "license": "MIT", "dependencies": { "es-errors": "^1.3.0", "object-inspect": "^1.13.3" @@ -7639,6 +9619,7 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz", "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", + "license": "MIT", "dependencies": { "call-bound": "^1.0.2", "es-errors": "^1.3.0", @@ -7656,6 +9637,7 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", + "license": "MIT", "dependencies": { "call-bound": "^1.0.2", "es-errors": "^1.3.0", @@ -7670,10 +9652,18 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/siginfo": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/siginfo/-/siginfo-2.0.0.tgz", + "integrity": "sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==", + "dev": true, + "license": "ISC" + }, "node_modules/signal-exit": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "license": "ISC", "engines": { "node": ">=14" }, @@ -7681,10 +9671,26 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/sirv": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/sirv/-/sirv-3.0.2.tgz", + "integrity": "sha512-2wcC/oGxHis/BoHkkPwldgiPSYcpZK3JU28WoMVv55yHJgcZ8rlXvuG9iZggz+sU1d4bRgIGASwyWqjxu3FM0g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@polka/url": "^1.0.0-next.24", + "mrmime": "^2.0.0", + "totalist": "^3.0.0" + }, + "engines": { + "node": ">=18" + } + }, "node_modules/skema": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/skema/-/skema-1.0.2.tgz", "integrity": "sha512-5LWfF2RSW2B3xfOaY6j49X8aNwsnj9cRVrM5QMF7it+cZvpv5ufiOUT13ps2U52sIbAzs11bdRP6mi5qyg75VQ==", + "license": "MIT", "dependencies": { "async": "^0.9.0", "make-array": "^0.1.2", @@ -7695,6 +9701,7 @@ "version": "0.103.0", "resolved": "https://registry.npmjs.org/slate/-/slate-0.103.0.tgz", "integrity": "sha512-eCUOVqUpADYMZ59O37QQvUdnFG+8rin0OGQAXNHvHbQeVJ67Bu0spQbcy621vtf8GQUXTEQBlk6OP9atwwob4w==", + "license": "MIT", "dependencies": { "immer": "^10.0.3", "is-plain-object": "^5.0.0", @@ -7705,6 +9712,7 @@ "version": "0.66.0", "resolved": "https://registry.npmjs.org/slate-history/-/slate-history-0.66.0.tgz", "integrity": "sha512-6MWpxGQZiMvSINlCbMW43E2YBSVMCMCIwQfBzGssjWw4kb0qfvj0pIdblWNRQZD0hR6WHP+dHHgGSeVdMWzfng==", + "license": "MIT", "dependencies": { "is-plain-object": "^5.0.0" }, @@ -7716,6 +9724,7 @@ "version": "0.77.0", "resolved": "https://registry.npmjs.org/slate-hyperscript/-/slate-hyperscript-0.77.0.tgz", "integrity": "sha512-M6uRpttwKnosniQORNPYQABHQ9XWC7qaSr/127LWWPjTOR5MSSwrHGrghN81BhZVqpICHrI7jkPA2813cWdHNA==", + "license": "MIT", "dependencies": { "is-plain-object": "^5.0.0" }, @@ -7727,6 +9736,7 @@ "version": "0.77.4", "resolved": "https://registry.npmjs.org/slate-react/-/slate-react-0.77.4.tgz", "integrity": "sha512-e3gYuEhjbEX4IhydC4kWZgcvH5fgJkJMuu9paarO6gdW5vRRSWnys/EbOs5ALp4e9NWr5u1XzQkJnbpyW5AqBA==", + "license": "MIT", "dependencies": { "@types/is-hotkey": "^0.1.1", "@types/lodash": "^4.14.149", @@ -7746,17 +9756,20 @@ "node_modules/slate-react/node_modules/is-hotkey": { "version": "0.1.8", "resolved": "https://registry.npmjs.org/is-hotkey/-/is-hotkey-0.1.8.tgz", - "integrity": "sha512-qs3NZ1INIS+H+yeo7cD9pDfwYV/jqRh1JG9S9zYrNudkoUQg7OL7ziXqRKu+InFjUIDoP2o6HIkLYMh1pcWgyQ==" + "integrity": "sha512-qs3NZ1INIS+H+yeo7cD9pDfwYV/jqRh1JG9S9zYrNudkoUQg7OL7ziXqRKu+InFjUIDoP2o6HIkLYMh1pcWgyQ==", + "license": "MIT" }, "node_modules/slate-react/node_modules/tiny-invariant": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/tiny-invariant/-/tiny-invariant-1.0.6.tgz", - "integrity": "sha512-FOyLWWVjG+aC0UqG76V53yAWdXfH8bO6FNmyZOuUrzDzK8DI3/JRY25UD7+g49JWM1LXwymsKERB+DzI0dTEQA==" + "integrity": "sha512-FOyLWWVjG+aC0UqG76V53yAWdXfH8bO6FNmyZOuUrzDzK8DI3/JRY25UD7+g49JWM1LXwymsKERB+DzI0dTEQA==", + "license": "MIT" }, "node_modules/slate/node_modules/immer": { "version": "10.2.0", "resolved": "https://registry.npmjs.org/immer/-/immer-10.2.0.tgz", "integrity": "sha512-d/+XTN3zfODyjr89gM3mPq1WNX2B8pYsu7eORitdwyA2sBubnTl3laYlBk4sXY5FUa5qTZGBDPJICVbvqzjlbw==", + "license": "MIT", "funding": { "type": "opencollective", "url": "https://opencollective.com/immer" @@ -7765,12 +9778,14 @@ "node_modules/sleep-promise": { "version": "8.0.1", "resolved": "https://registry.npmjs.org/sleep-promise/-/sleep-promise-8.0.1.tgz", - "integrity": "sha512-nfwyX+G1dsx2R1DMMKWLpNxuHMOCL7JIRBUw0fl7Z4nZ1YZK0apZuGY8MDexn0HDZzgbERgj/CrNtsYpo/B7eA==" + "integrity": "sha512-nfwyX+G1dsx2R1DMMKWLpNxuHMOCL7JIRBUw0fl7Z4nZ1YZK0apZuGY8MDexn0HDZzgbERgj/CrNtsYpo/B7eA==", + "license": "MIT" }, "node_modules/socket.io-client": { "version": "4.8.3", "resolved": "https://registry.npmjs.org/socket.io-client/-/socket.io-client-4.8.3.tgz", "integrity": "sha512-uP0bpjWrjQmUt5DTHq9RuoCBdFJF10cdX9X+a368j/Ft0wmaVgxlrjvK3kjvgCODOMMOz9lcaRzxmso0bTWZ/g==", + "license": "MIT", "dependencies": { "@socket.io/component-emitter": "~3.1.0", "debug": "~4.4.1", @@ -7785,6 +9800,7 @@ "version": "4.2.5", "resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-4.2.5.tgz", "integrity": "sha512-bPMmpy/5WWKHea5Y/jYAP6k74A+hvmRCQaJuJB6I/ML5JZq/KfNieUVo/3Mh7SAqn7TyFdIo6wqYHInG1MU1bQ==", + "license": "MIT", "dependencies": { "@socket.io/component-emitter": "~3.1.0", "debug": "~4.4.1" @@ -7797,6 +9813,7 @@ "version": "0.5.7", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", "integrity": "sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ==", + "license": "BSD-3-Clause", "engines": { "node": ">=0.10.0" } @@ -7805,15 +9822,31 @@ "version": "1.2.1", "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "license": "BSD-3-Clause", "engines": { "node": ">=0.10.0" } }, + "node_modules/stackback": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/stackback/-/stackback-0.0.2.tgz", + "integrity": "sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==", + "dev": true, + "license": "MIT" + }, + "node_modules/std-env": { + "version": "3.10.0", + "resolved": "https://registry.npmjs.org/std-env/-/std-env-3.10.0.tgz", + "integrity": "sha512-5GS12FdOZNliM5mAOxFRg7Ir0pWz8MdpYm6AY6VPkGpbA7ZzmbzNcBJQ0GPvvyWgcY7QAhCgf9Uy89I03faLkg==", + "dev": true, + "license": "MIT" + }, "node_modules/stop-iteration-iterator": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/stop-iteration-iterator/-/stop-iteration-iterator-1.1.0.tgz", "integrity": "sha512-eLoXW/DHyl62zxY4SCaIgnRhuMr6ri4juEYARS8E6sCEqzKpOiE521Ucofdx+KnDZl5xmvGYaaKCk5FEOxJCoQ==", "dev": true, + "license": "MIT", "dependencies": { "es-errors": "^1.3.0", "internal-slot": "^1.1.0" @@ -7826,12 +9859,37 @@ "version": "2.14.4", "resolved": "https://registry.npmjs.org/store2/-/store2-2.14.4.tgz", "integrity": "sha512-srTItn1GOvyvOycgxjAnPA63FZNwy0PTyUBFMHRM+hVFltAeoh0LmNBz9SZqUS9mMqGk8rfyWyXn3GH5ReJ8Zw==", + "license": "MIT", "peer": true }, + "node_modules/storybook-addon-whats-new": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/storybook-addon-whats-new/-/storybook-addon-whats-new-1.0.3.tgz", + "integrity": "sha512-5je0qJh2ahPbbS2pwOkgKKmUDJAT0aAn3cSmap0Hp3hzFlu4uaDPiY00T4mPF3BsavBrkPNmy7kKIHTXnmRu5A==", + "license": "MIT", + "peerDependencies": { + "@storybook/addons": "^6.4.0", + "@storybook/api": "^6.4.0", + "@storybook/components": "^6.4.0", + "@storybook/core-events": "^6.4.0", + "@storybook/theming": "^6.4.0", + "react": "^16.8.0 || ^17.0.0", + "react-dom": "^16.8.0 || ^17.0.0" + }, + "peerDependenciesMeta": { + "react": { + "optional": true + }, + "react-dom": { + "optional": true + } + } + }, "node_modules/string-width": { "version": "5.1.2", "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", + "license": "MIT", "dependencies": { "eastasianwidth": "^0.2.0", "emoji-regex": "^9.2.2", @@ -7849,6 +9907,7 @@ "version": "4.2.3", "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "license": "MIT", "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", @@ -7861,12 +9920,14 @@ "node_modules/string-width-cjs/node_modules/emoji-regex": { "version": "8.0.0", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==" + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "license": "MIT" }, "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==", + "license": "MIT", "engines": { "node": ">=12" }, @@ -7878,6 +9939,7 @@ "version": "7.1.2", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.2.tgz", "integrity": "sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA==", + "license": "MIT", "dependencies": { "ansi-regex": "^6.0.1" }, @@ -7893,6 +9955,7 @@ "resolved": "https://registry.npmjs.org/string.prototype.matchall/-/string.prototype.matchall-4.0.12.tgz", "integrity": "sha512-6CC9uyBL+/48dYizRf7H7VAYCMCNTBeM78x/VTUe9bFEaxBepPJDa1Ow99LqI/1yF7kuy7Q3cQsYMrcjGUcskA==", "dev": true, + "license": "MIT", "dependencies": { "call-bind": "^1.0.8", "call-bound": "^1.0.3", @@ -7920,6 +9983,7 @@ "resolved": "https://registry.npmjs.org/string.prototype.repeat/-/string.prototype.repeat-1.0.0.tgz", "integrity": "sha512-0u/TldDbKD8bFCQ/4f5+mNRrXwZ8hg2w7ZR8wa16e8z9XpePWl3eGEcUD0OXpEH/VJH/2G3gjUtR3ZOiBe2S/w==", "dev": true, + "license": "MIT", "dependencies": { "define-properties": "^1.1.3", "es-abstract": "^1.17.5" @@ -7930,6 +9994,7 @@ "resolved": "https://registry.npmjs.org/string.prototype.trim/-/string.prototype.trim-1.2.10.tgz", "integrity": "sha512-Rs66F0P/1kedk5lyYyH9uBzuiI/kNRmwJAR9quK6VOtIpZ2G+hMZd+HQbbv25MgCA6gEffoMZYxlTod4WcdrKA==", "dev": true, + "license": "MIT", "dependencies": { "call-bind": "^1.0.8", "call-bound": "^1.0.2", @@ -7951,6 +10016,7 @@ "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.9.tgz", "integrity": "sha512-G7Ok5C6E/j4SGfyLCloXTrngQIQU3PWtXGst3yM7Bea9FRURf1S42ZHlZZtsNque2FN2PoUhfZXYLNWwEr4dLQ==", "dev": true, + "license": "MIT", "dependencies": { "call-bind": "^1.0.8", "call-bound": "^1.0.2", @@ -7969,6 +10035,7 @@ "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.8.tgz", "integrity": "sha512-UXSH262CSZY1tfu3G3Secr6uGLCFVPMhIqHjlgCUtCCcgihYc/xKs9djMTMUOb2j1mVSeU8EU6NWc/iQKU6Gfg==", "dev": true, + "license": "MIT", "dependencies": { "call-bind": "^1.0.7", "define-properties": "^1.2.1", @@ -7985,6 +10052,7 @@ "version": "6.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "license": "MIT", "dependencies": { "ansi-regex": "^5.0.1" }, @@ -7997,6 +10065,7 @@ "version": "6.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "license": "MIT", "dependencies": { "ansi-regex": "^5.0.1" }, @@ -8008,6 +10077,7 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-3.0.0.tgz", "integrity": "sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ==", + "dev": true, "license": "MIT", "dependencies": { "min-indent": "^1.0.0" @@ -8021,6 +10091,7 @@ "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", "dev": true, + "license": "MIT", "engines": { "node": ">=8" }, @@ -8032,6 +10103,7 @@ "version": "1.1.8", "resolved": "https://registry.npmjs.org/style-to-js/-/style-to-js-1.1.8.tgz", "integrity": "sha512-bPSspCXkkhETLXnEgDbaoWRWyv3lF2bj32YIc8IElok2IIMHUlZtQUrxYmAkKUNxpluhH0qnKWrmuoXUyTY12g==", + "license": "MIT", "dependencies": { "style-to-object": "1.0.3" } @@ -8040,6 +10112,7 @@ "version": "1.0.3", "resolved": "https://registry.npmjs.org/style-to-object/-/style-to-object-1.0.3.tgz", "integrity": "sha512-xOpx7S53E0V3DpVsvt7ySvoiumRpfXiC99PUXLqGB3wiAnN9ybEIpuzlZ8LAZg+h1sl9JkEUwtSQXxcCgFqbbg==", + "license": "MIT", "dependencies": { "inline-style-parser": "0.2.2" } @@ -8047,12 +10120,14 @@ "node_modules/stylis": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/stylis/-/stylis-4.2.0.tgz", - "integrity": "sha512-Orov6g6BB1sDfYgzWfTHDOxamtX1bE/zo104Dh9e6fqJ3PooipYyfJ0pUmrZO2wAvO8YbEyeFrkV91XTsGMSrw==" + "integrity": "sha512-Orov6g6BB1sDfYgzWfTHDOxamtX1bE/zo104Dh9e6fqJ3PooipYyfJ0pUmrZO2wAvO8YbEyeFrkV91XTsGMSrw==", + "license": "MIT" }, "node_modules/substyle": { "version": "9.4.1", "resolved": "https://registry.npmjs.org/substyle/-/substyle-9.4.1.tgz", "integrity": "sha512-VOngeq/W1/UkxiGzeqVvDbGDPM8XgUyJVWjrqeh+GgKqspEPiLYndK+XRcsKUHM5Muz/++1ctJ1QCF/OqRiKWA==", + "license": "MIT", "dependencies": { "@babel/runtime": "^7.3.4", "invariant": "^2.2.4" @@ -8066,6 +10141,7 @@ "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", "dev": true, + "license": "MIT", "dependencies": { "has-flag": "^4.0.0" }, @@ -8077,6 +10153,7 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", + "license": "MIT", "engines": { "node": ">= 0.4" }, @@ -8084,15 +10161,24 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/symbol-tree": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.4.tgz", + "integrity": "sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==", + "dev": true, + "license": "MIT" + }, "node_modules/systemjs": { "version": "6.15.1", "resolved": "https://registry.npmjs.org/systemjs/-/systemjs-6.15.1.tgz", - "integrity": "sha512-Nk8c4lXvMB98MtbmjX7JwJRgJOL8fluecYCfCeYBznwmpOs8Bf15hLM6z4z71EDAhQVrQrI+wt1aLWSXZq+hXA==" + "integrity": "sha512-Nk8c4lXvMB98MtbmjX7JwJRgJOL8fluecYCfCeYBznwmpOs8Bf15hLM6z4z71EDAhQVrQrI+wt1aLWSXZq+hXA==", + "license": "MIT" }, "node_modules/telejson": { "version": "6.0.8", "resolved": "https://registry.npmjs.org/telejson/-/telejson-6.0.8.tgz", "integrity": "sha512-nerNXi+j8NK1QEfBHtZUN/aLdDcyupA//9kAboYLrtzZlPLpUfqbVGWb9zz91f/mIjRbAYhbgtnJHY8I1b5MBg==", + "license": "MIT", "peer": true, "dependencies": { "@types/is-function": "^1.0.0", @@ -8109,12 +10195,14 @@ "version": "0.2.0", "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/throttle-debounce": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/throttle-debounce/-/throttle-debounce-2.3.0.tgz", "integrity": "sha512-H7oLPV0P7+jgvrk+6mwwwBDmxTaxnu9HMXmloNLXwnNO0ZxZ31Orah2n8lU1eMPvsaowP2CX+USCgyovXfdOFQ==", + "license": "MIT", "engines": { "node": ">=8" } @@ -8122,27 +10210,49 @@ "node_modules/tiny-case": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/tiny-case/-/tiny-case-1.0.3.tgz", - "integrity": "sha512-Eet/eeMhkO6TX8mnUteS9zgPbUMQa4I6Kkp5ORiBD5476/m+PIRiumP5tmh5ioJpH7k51Kehawy2UDfsnxxY8Q==" + "integrity": "sha512-Eet/eeMhkO6TX8mnUteS9zgPbUMQa4I6Kkp5ORiBD5476/m+PIRiumP5tmh5ioJpH7k51Kehawy2UDfsnxxY8Q==", + "license": "MIT" }, "node_modules/tiny-invariant": { "version": "1.3.3", "resolved": "https://registry.npmjs.org/tiny-invariant/-/tiny-invariant-1.3.3.tgz", - "integrity": "sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg==" + "integrity": "sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg==", + "license": "MIT" }, "node_modules/tiny-warning": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/tiny-warning/-/tiny-warning-1.0.3.tgz", - "integrity": "sha512-lBN9zLN/oAf68o3zNXYrdCt1kP8WsiGW8Oo2ka41b2IM5JL/S1CTyX1rW0mb/zSuJun0ZUrDxx4sqvYS2FWzPA==" + "integrity": "sha512-lBN9zLN/oAf68o3zNXYrdCt1kP8WsiGW8Oo2ka41b2IM5JL/S1CTyX1rW0mb/zSuJun0ZUrDxx4sqvYS2FWzPA==", + "license": "MIT" + }, + "node_modules/tinybench": { + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/tinybench/-/tinybench-2.9.0.tgz", + "integrity": "sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==", + "dev": true, + "license": "MIT" }, "node_modules/tinycolor2": { "version": "1.6.0", "resolved": "https://registry.npmjs.org/tinycolor2/-/tinycolor2-1.6.0.tgz", - "integrity": "sha512-XPaBkWQJdsf3pLKJV9p4qN/S+fm2Oj8AIPo1BTUhg5oxkvm9+SVEGFdhyOz7tTdUTfvxMiAs4sp6/eZO2Ew+pw==" + "integrity": "sha512-XPaBkWQJdsf3pLKJV9p4qN/S+fm2Oj8AIPo1BTUhg5oxkvm9+SVEGFdhyOz7tTdUTfvxMiAs4sp6/eZO2Ew+pw==", + "license": "MIT" + }, + "node_modules/tinyexec": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-1.0.2.tgz", + "integrity": "sha512-W/KYk+NFhkmsYpuHq5JykngiOCnxeVL8v8dFnqxSD8qEEdRfXk1SDM6JzNqcERbcGYj9tMrDQBYV9cjgnunFIg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } }, "node_modules/tinyglobby": { "version": "0.2.15", "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz", "integrity": "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==", + "license": "MIT", "dependencies": { "fdir": "^6.5.0", "picomatch": "^4.0.3" @@ -8154,43 +10264,129 @@ "url": "https://github.com/sponsors/SuperchupuDev" } }, + "node_modules/tinyrainbow": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/tinyrainbow/-/tinyrainbow-3.0.3.tgz", + "integrity": "sha512-PSkbLUoxOFRzJYjjxHJt9xro7D+iilgMX/C9lawzVuYiIdcihh9DXmVibBe8lmcFrRi/VzlPjBxbN7rH24q8/Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, "node_modules/tippy.js": { "version": "6.3.7", "resolved": "https://registry.npmjs.org/tippy.js/-/tippy.js-6.3.7.tgz", "integrity": "sha512-E1d3oP2emgJ9dRQZdf3Kkn0qJgI6ZLpyS5z6ZkY1DF3kaQaBsGZsndEpHwx+eC+tYM41HaSNvNtLx8tU57FzTQ==", + "license": "MIT", "dependencies": { "@popperjs/core": "^2.9.0" } }, + "node_modules/tldts": { + "version": "7.0.23", + "resolved": "https://registry.npmjs.org/tldts/-/tldts-7.0.23.tgz", + "integrity": "sha512-ASdhgQIBSay0R/eXggAkQ53G4nTJqTXqC2kbaBbdDwM7SkjyZyO0OaaN1/FH7U/yCeqOHDwFO5j8+Os/IS1dXw==", + "dev": true, + "license": "MIT", + "dependencies": { + "tldts-core": "^7.0.23" + }, + "bin": { + "tldts": "bin/cli.js" + } + }, + "node_modules/tldts-core": { + "version": "7.0.23", + "resolved": "https://registry.npmjs.org/tldts-core/-/tldts-core-7.0.23.tgz", + "integrity": "sha512-0g9vrtDQLrNIiCj22HSe9d4mLVG3g5ph5DZ8zCKBr4OtrspmNB6ss7hVyzArAeE88ceZocIEGkyW1Ime7fxPtQ==", + "dev": true, + "license": "MIT" + }, "node_modules/toposort": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/toposort/-/toposort-2.0.2.tgz", - "integrity": "sha512-0a5EOkAUp8D4moMi2W8ZF8jcga7BgZd91O/yabJCFY8az+XSzeGyTKs0Aoo897iV1Nj6guFq8orWDS96z91oGg==" + "integrity": "sha512-0a5EOkAUp8D4moMi2W8ZF8jcga7BgZd91O/yabJCFY8az+XSzeGyTKs0Aoo897iV1Nj6guFq8orWDS96z91oGg==", + "license": "MIT" + }, + "node_modules/totalist": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/totalist/-/totalist-3.0.1.tgz", + "integrity": "sha512-sf4i37nQ2LBx4m3wB74y+ubopq6W/dIzXg0FDGjsYnZHVa1Da8FH853wlL2gtUhg+xJXjfk3kUZS3BRoQeoQBQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/tough-cookie": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-6.0.0.tgz", + "integrity": "sha512-kXuRi1mtaKMrsLUxz3sQYvVl37B0Ns6MzfrtV5DvJceE9bPyspOqk9xxv7XbZWcfLWbFmm997vl83qUWVJA64w==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "tldts": "^7.0.5" + }, + "engines": { + "node": ">=16" + } }, "node_modules/tr46": { - "version": "0.0.3", - "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", - "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==" + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-6.0.0.tgz", + "integrity": "sha512-bLVMLPtstlZ4iMQHpFHTR7GAGj2jxi8Dg0s2h2MafAE4uSWF98FC/3MomU51iQAMf8/qDUbKWf5GxuvvVcXEhw==", + "dev": true, + "license": "MIT", + "dependencies": { + "punycode": "^2.3.1" + }, + "engines": { + "node": ">=20" + } }, "node_modules/ts-dedent": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/ts-dedent/-/ts-dedent-2.2.0.tgz", "integrity": "sha512-q5W7tVM71e2xjHZTlgfTDoPF/SmqKG5hddq9SzR49CH2hayqRKJtQ4mtRlSxKaJlR/+9rEM+mnBHf7I2/BQcpQ==", + "license": "MIT", "peer": true, "engines": { "node": ">=6.10" } }, + "node_modules/tsconfck": { + "version": "3.1.6", + "resolved": "https://registry.npmjs.org/tsconfck/-/tsconfck-3.1.6.tgz", + "integrity": "sha512-ks6Vjr/jEw0P1gmOVwutM3B7fWxoWBL2KRDb1JfqGVawBmO5UsvmWOQFGHBPl5yxYz4eERr19E6L7NMv+Fej4w==", + "license": "MIT", + "bin": { + "tsconfck": "bin/tsconfck.js" + }, + "engines": { + "node": "^18 || >=20" + }, + "peerDependencies": { + "typescript": "^5.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, "node_modules/tslib": { "version": "2.8.1", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", - "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==" + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "license": "0BSD" }, "node_modules/type-check": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", "dev": true, + "license": "MIT", "dependencies": { "prelude-ls": "^1.2.1" }, @@ -8203,6 +10399,7 @@ "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", "dev": true, + "license": "(MIT OR CC0-1.0)", "engines": { "node": ">=10" }, @@ -8215,6 +10412,7 @@ "resolved": "https://registry.npmjs.org/typed-array-buffer/-/typed-array-buffer-1.0.3.tgz", "integrity": "sha512-nAYYwfY3qnzX30IkA6AQZjVbtK6duGontcQm1WSG1MD94YLqK0515GNApXkoxKOWMusVssAHWLh9SeaoefYFGw==", "dev": true, + "license": "MIT", "dependencies": { "call-bound": "^1.0.3", "es-errors": "^1.3.0", @@ -8229,6 +10427,7 @@ "resolved": "https://registry.npmjs.org/typed-array-byte-length/-/typed-array-byte-length-1.0.3.tgz", "integrity": "sha512-BaXgOuIxz8n8pIq3e7Atg/7s+DpiYrxn4vdot3w9KbnBhcRQq6o3xemQdIfynqSeXeDrF32x+WvfzmOjPiY9lg==", "dev": true, + "license": "MIT", "dependencies": { "call-bind": "^1.0.8", "for-each": "^0.3.3", @@ -8248,6 +10447,7 @@ "resolved": "https://registry.npmjs.org/typed-array-byte-offset/-/typed-array-byte-offset-1.0.4.tgz", "integrity": "sha512-bTlAFB/FBYMcuX81gbL4OcpH5PmlFHqlCCpAl8AlEzMz5k53oNDvN8p1PNOWLEmI2x4orp3raOFB51tv9X+MFQ==", "dev": true, + "license": "MIT", "dependencies": { "available-typed-arrays": "^1.0.7", "call-bind": "^1.0.8", @@ -8269,6 +10469,7 @@ "resolved": "https://registry.npmjs.org/typed-array-length/-/typed-array-length-1.0.7.tgz", "integrity": "sha512-3KS2b+kL7fsuk/eJZ7EQdnEmQoaho/r6KUef7hxvltNA5DR8NAUM+8wJMbJyZ4G9/7i3v5zPBIMN5aybAh2/Jg==", "dev": true, + "license": "MIT", "dependencies": { "call-bind": "^1.0.7", "for-each": "^0.3.3", @@ -8288,6 +10489,7 @@ "version": "4.9.5", "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.5.tgz", "integrity": "sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==", + "license": "Apache-2.0", "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -8301,6 +10503,7 @@ "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.1.0.tgz", "integrity": "sha512-nWJ91DjeOkej/TA8pXQ3myruKpKEYgqvpw9lz4OPHj/NWFNluYrjbz9j01CJ8yKQd2g4jFoOkINCTW2I5LEEyw==", "dev": true, + "license": "MIT", "dependencies": { "call-bound": "^1.0.3", "has-bigints": "^1.0.2", @@ -8314,11 +10517,22 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/undici": { + "version": "7.22.0", + "resolved": "https://registry.npmjs.org/undici/-/undici-7.22.0.tgz", + "integrity": "sha512-RqslV2Us5BrllB+JeiZnK4peryVTndy9Dnqq62S3yYRRTj0tFQCwEniUy2167skdGOy3vqRzEvl1Dm4sV2ReDg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=20.18.1" + } + }, "node_modules/uri-js": { "version": "4.4.1", "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", "dev": true, + "license": "BSD-2-Clause", "dependencies": { "punycode": "^2.1.0" } @@ -8327,6 +10541,7 @@ "version": "1.1.3", "resolved": "https://registry.npmjs.org/use-memo-one/-/use-memo-one-1.1.3.tgz", "integrity": "sha512-g66/K7ZQGYrI6dy8GLpVcMsBp4s17xNkYJVSMvTEevGy3nDxHOfE6z8BVE22+5G5x7t3+bhzrlTDB7ObrEE0cQ==", + "license": "MIT", "peerDependencies": { "react": "^16.8.0 || ^17.0.0 || ^18.0.0" } @@ -8335,6 +10550,7 @@ "version": "1.6.0", "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.6.0.tgz", "integrity": "sha512-Pp6GSwGP/NrPIrxVFAIkOQeyw8lFenOHijQWkUTrDvrF4ALqylP2C/KCkeS9dpUM3KvYRQhna5vt7IL95+ZQ9w==", + "license": "MIT", "peerDependencies": { "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" } @@ -8349,12 +10565,14 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", + "license": "MIT", "peer": true }, "node_modules/uuid": { "version": "8.3.2", "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", + "license": "MIT", "bin": { "uuid": "dist/bin/uuid" } @@ -8362,12 +10580,55 @@ "node_modules/velocity-animate": { "version": "1.5.2", "resolved": "https://registry.npmjs.org/velocity-animate/-/velocity-animate-1.5.2.tgz", - "integrity": "sha512-m6EXlCAMetKztO1ppBhGU1/1MR3IiEevO6ESq6rcrSQ3Q77xYSW13jkfXW88o4xMrkXJhy/U7j4wFR/twMB0Eg==" + "integrity": "sha512-m6EXlCAMetKztO1ppBhGU1/1MR3IiEevO6ESq6rcrSQ3Q77xYSW13jkfXW88o4xMrkXJhy/U7j4wFR/twMB0Eg==", + "license": "MIT" + }, + "node_modules/velocity-react": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/velocity-react/-/velocity-react-1.4.3.tgz", + "integrity": "sha512-zvefGm85A88S3KdF9/dz5vqyFLAiwKYlXGYkHH2EbXl+CZUD1OT0a0aS1tkX/WXWTa/FUYqjBaAzAEFYuSobBQ==", + "license": "MIT", + "dependencies": { + "lodash": "^4.17.5", + "prop-types": "^15.5.8", + "react-transition-group": "^2.0.0", + "velocity-animate": "^1.4.0" + }, + "peerDependencies": { + "react": "^15.3.0 || ^16.0.0", + "react-dom": "^15.3.0 || ^16.0.0" + } + }, + "node_modules/velocity-react/node_modules/dom-helpers": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/dom-helpers/-/dom-helpers-3.4.0.tgz", + "integrity": "sha512-LnuPJ+dwqKDIyotW1VzmOZ5TONUN7CwkCR5hrgawTUbkBGYdeoNLZo6nNfGkCrjtE1nXXaj7iMMpDa8/d9WoIA==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.1.2" + } + }, + "node_modules/velocity-react/node_modules/react-transition-group": { + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-2.9.0.tgz", + "integrity": "sha512-+HzNTCHpeQyl4MJ/bdE0u6XRMe9+XG/+aL4mCxVN4DnPBQ0/5bfHWPDuOZUzYdMj94daZaZdCCc1Dzt9R/xSSg==", + "license": "BSD-3-Clause", + "dependencies": { + "dom-helpers": "^3.4.0", + "loose-envify": "^1.4.0", + "prop-types": "^15.6.2", + "react-lifecycles-compat": "^3.0.4" + }, + "peerDependencies": { + "react": ">=15.0.0", + "react-dom": ">=15.0.0" + } }, "node_modules/victory-vendor": { "version": "36.9.2", "resolved": "https://registry.npmjs.org/victory-vendor/-/victory-vendor-36.9.2.tgz", "integrity": "sha512-PnpQQMuxlwYdocC8fIJqVXvkeViHYzotI+NJrCuav0ZYFoq912ZHBk3mCeuj+5/VpodOjPe1z0Fk2ihgzlXqjQ==", + "license": "MIT AND ISC", "dependencies": { "@types/d3-array": "^3.0.3", "@types/d3-ease": "^3.0.0", @@ -8389,6 +10650,7 @@ "version": "7.3.1", "resolved": "https://registry.npmjs.org/vite/-/vite-7.3.1.tgz", "integrity": "sha512-w+N7Hifpc3gRjZ63vYBXA56dvvRlNWRczTdmCBBa+CotUzAPf5b7YMdMR/8CQoeYE5LX3W4wj6RYTgonm1b9DA==", + "license": "MIT", "dependencies": { "esbuild": "^0.27.0", "fdir": "^6.5.0", @@ -8462,6 +10724,7 @@ "version": "6.1.1", "resolved": "https://registry.npmjs.org/vite-tsconfig-paths/-/vite-tsconfig-paths-6.1.1.tgz", "integrity": "sha512-2cihq7zliibCCZ8P9cKJrQBkfgdvcFkOOc3Y02o3GWUDLgqjWsZudaoiuOwO/gzTzy17cS5F7ZPo4bsnS4DGkg==", + "license": "MIT", "dependencies": { "debug": "^4.1.1", "globrex": "^0.1.2", @@ -8471,73 +10734,155 @@ "vite": "*" } }, - "node_modules/vite-tsconfig-paths/node_modules/tsconfck": { - "version": "3.1.6", - "resolved": "https://registry.npmjs.org/tsconfck/-/tsconfck-3.1.6.tgz", - "integrity": "sha512-ks6Vjr/jEw0P1gmOVwutM3B7fWxoWBL2KRDb1JfqGVawBmO5UsvmWOQFGHBPl5yxYz4eERr19E6L7NMv+Fej4w==", + "node_modules/vitest": { + "version": "4.0.18", + "resolved": "https://registry.npmjs.org/vitest/-/vitest-4.0.18.tgz", + "integrity": "sha512-hOQuK7h0FGKgBAas7v0mSAsnvrIgAvWmRFjmzpJ7SwFHH3g1k2u37JtYwOwmEKhK6ZO3v9ggDBBm0La1LCK4uQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/expect": "4.0.18", + "@vitest/mocker": "4.0.18", + "@vitest/pretty-format": "4.0.18", + "@vitest/runner": "4.0.18", + "@vitest/snapshot": "4.0.18", + "@vitest/spy": "4.0.18", + "@vitest/utils": "4.0.18", + "es-module-lexer": "^1.7.0", + "expect-type": "^1.2.2", + "magic-string": "^0.30.21", + "obug": "^2.1.1", + "pathe": "^2.0.3", + "picomatch": "^4.0.3", + "std-env": "^3.10.0", + "tinybench": "^2.9.0", + "tinyexec": "^1.0.2", + "tinyglobby": "^0.2.15", + "tinyrainbow": "^3.0.3", + "vite": "^6.0.0 || ^7.0.0", + "why-is-node-running": "^2.3.0" + }, "bin": { - "tsconfck": "bin/tsconfck.js" + "vitest": "vitest.mjs" }, "engines": { - "node": "^18 || >=20" + "node": "^20.0.0 || ^22.0.0 || >=24.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" }, "peerDependencies": { - "typescript": "^5.0.0" + "@edge-runtime/vm": "*", + "@opentelemetry/api": "^1.9.0", + "@types/node": "^20.0.0 || ^22.0.0 || >=24.0.0", + "@vitest/browser-playwright": "4.0.18", + "@vitest/browser-preview": "4.0.18", + "@vitest/browser-webdriverio": "4.0.18", + "@vitest/ui": "4.0.18", + "happy-dom": "*", + "jsdom": "*" }, "peerDependenciesMeta": { - "typescript": { + "@edge-runtime/vm": { + "optional": true + }, + "@opentelemetry/api": { + "optional": true + }, + "@types/node": { + "optional": true + }, + "@vitest/browser-playwright": { + "optional": true + }, + "@vitest/browser-preview": { + "optional": true + }, + "@vitest/browser-webdriverio": { + "optional": true + }, + "@vitest/ui": { + "optional": true + }, + "happy-dom": { + "optional": true + }, + "jsdom": { "optional": true } } }, - "node_modules/vite-tsconfig-paths/node_modules/typescript": { - "version": "5.9.3", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", - "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", - "optional": true, - "peer": true, - "bin": { - "tsc": "bin/tsc", - "tsserver": "bin/tsserver" - }, - "engines": { - "node": ">=14.17" - } - }, "node_modules/void-elements": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/void-elements/-/void-elements-3.1.0.tgz", "integrity": "sha512-Dhxzh5HZuiHQhbvTW9AMetFfBHDMYpo23Uo9btPXgdYP+3T5S+p+jgNy7spra+veYhBP2dCSgxR/i2Y02h5/6w==", + "license": "MIT", "engines": { "node": ">=0.10.0" } }, + "node_modules/w3c-xmlserializer": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-5.0.0.tgz", + "integrity": "sha512-o8qghlI8NZHU1lLPrpi2+Uq7abh4GGPpYANlalzWxyWteJOCsr/P+oPBA49TOLu5FTZO4d3F9MnWJfiMo4BkmA==", + "dev": true, + "license": "MIT", + "dependencies": { + "xml-name-validator": "^5.0.0" + }, + "engines": { + "node": ">=18" + } + }, "node_modules/warning": { "version": "4.0.3", "resolved": "https://registry.npmjs.org/warning/-/warning-4.0.3.tgz", "integrity": "sha512-rpJyN222KWIvHJ/F53XSZv0Zl/accqHR8et1kpaMTD/fLCRxtV8iX8czMzY7sVZupTI3zcUTg8eycS2kNF9l6w==", + "license": "MIT", "dependencies": { "loose-envify": "^1.0.0" } }, - "node_modules/whatwg-url": { + "node_modules/webidl-conversions": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-8.0.1.tgz", + "integrity": "sha512-BMhLD/Sw+GbJC21C/UgyaZX41nPt8bUTg+jWyDeg7e7YN4xOM05YPSIXceACnXVtqyEw/LMClUQMtMZ+PGGpqQ==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=20" + } + }, + "node_modules/whatwg-mimetype": { "version": "5.0.0", - "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", - "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", - "dependencies": { - "tr46": "~0.0.3", - "webidl-conversions": "^3.0.0" + "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-5.0.0.tgz", + "integrity": "sha512-sXcNcHOC51uPGF0P/D4NVtrkjSU2fNsm9iog4ZvZJsL3rjoDAzXZhkm2MWt1y+PUdggKAYVoMAIYcs78wJ51Cw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=20" } }, - "node_modules/whatwg-url/node_modules/webidl-conversions": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", - "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==" + "node_modules/whatwg-url": { + "version": "16.0.1", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-16.0.1.tgz", + "integrity": "sha512-1to4zXBxmXHV3IiSSEInrreIlu02vUOvrhxJJH5vcxYTBDAx51cqZiKdyTxlecdKNSjj8EcxGBxNf6Vg+945gw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@exodus/bytes": "^1.11.0", + "tr46": "^6.0.0", + "webidl-conversions": "^8.0.1" + }, + "engines": { + "node": "^20.19.0 || ^22.12.0 || >=24.0.0" + } }, "node_modules/which": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "license": "ISC", "dependencies": { "isexe": "^2.0.0" }, @@ -8553,6 +10898,7 @@ "resolved": "https://registry.npmjs.org/which-boxed-primitive/-/which-boxed-primitive-1.1.1.tgz", "integrity": "sha512-TbX3mj8n0odCBFVlY8AxkqcHASw3L60jIuF8jFP78az3C2YhmGvqbHBpAjTRH2/xqYunrJ9g1jSyjCjpoWzIAA==", "dev": true, + "license": "MIT", "dependencies": { "is-bigint": "^1.1.0", "is-boolean-object": "^1.2.1", @@ -8572,6 +10918,7 @@ "resolved": "https://registry.npmjs.org/which-builtin-type/-/which-builtin-type-1.2.1.tgz", "integrity": "sha512-6iBczoX+kDQ7a3+YJBnh3T+KZRxM/iYNPXicqk66/Qfm1b93iu+yOImkg0zHbj5LNOcNv1TEADiZ0xa34B4q6Q==", "dev": true, + "license": "MIT", "dependencies": { "call-bound": "^1.0.2", "function.prototype.name": "^1.1.6", @@ -8599,6 +10946,7 @@ "resolved": "https://registry.npmjs.org/which-collection/-/which-collection-1.0.2.tgz", "integrity": "sha512-K4jVyjnBdgvc86Y6BkaLZEN933SwYOuBFkdmBu9ZfkcAbdVbpITnDmjvZ/aQjRXQrv5EPkTnD1s39GiiqbngCw==", "dev": true, + "license": "MIT", "dependencies": { "is-map": "^2.0.3", "is-set": "^2.0.3", @@ -8613,10 +10961,11 @@ } }, "node_modules/which-typed-array": { - "version": "1.1.19", - "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.19.tgz", - "integrity": "sha512-rEvr90Bck4WZt9HHFC4DJMsjvu7x+r6bImz0/BrbWb7A2djJ8hnZMrWnHo9F8ssv0OMErasDhftrfROTyqSDrw==", + "version": "1.1.20", + "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.20.tgz", + "integrity": "sha512-LYfpUkmqwl0h9A2HL09Mms427Q1RZWuOHsukfVcKRq9q95iQxdw0ix1JQrqbcDR9PH1QDwf5Qo8OZb5lksZ8Xg==", "dev": true, + "license": "MIT", "dependencies": { "available-typed-arrays": "^1.0.7", "call-bind": "^1.0.8", @@ -8633,11 +10982,29 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/why-is-node-running": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/why-is-node-running/-/why-is-node-running-2.3.0.tgz", + "integrity": "sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==", + "dev": true, + "license": "MIT", + "dependencies": { + "siginfo": "^2.0.0", + "stackback": "0.0.2" + }, + "bin": { + "why-is-node-running": "cli.js" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/word-wrap": { "version": "1.2.5", "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==", "dev": true, + "license": "MIT", "engines": { "node": ">=0.10.0" } @@ -8646,6 +11013,7 @@ "version": "8.1.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", + "license": "MIT", "dependencies": { "ansi-styles": "^6.1.0", "string-width": "^5.0.1", @@ -8663,6 +11031,7 @@ "version": "7.0.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "license": "MIT", "dependencies": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", @@ -8678,12 +11047,14 @@ "node_modules/wrap-ansi-cjs/node_modules/emoji-regex": { "version": "8.0.0", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==" + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "license": "MIT" }, "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==", + "license": "MIT", "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", @@ -8697,6 +11068,7 @@ "version": "6.2.2", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz", "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==", + "license": "MIT", "engines": { "node": ">=12" }, @@ -8708,6 +11080,7 @@ "version": "6.2.3", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz", "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==", + "license": "MIT", "engines": { "node": ">=12" }, @@ -8719,6 +11092,7 @@ "version": "7.1.2", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.2.tgz", "integrity": "sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA==", + "license": "MIT", "dependencies": { "ansi-regex": "^6.0.1" }, @@ -8733,7 +11107,46 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", - "dev": true + "dev": true, + "license": "ISC" + }, + "node_modules/ws": { + "version": "8.18.3", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.3.tgz", + "integrity": "sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg==", + "license": "MIT", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, + "node_modules/xml-name-validator": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-5.0.0.tgz", + "integrity": "sha512-EvGK8EJ3DhaHfbRlETOWAS5pO9MZITeauHKJyb8wyajUfQUenkIg2MvLDTZ4T/TgIcm3HU0TFBgWWboAZ30UHg==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18" + } + }, + "node_modules/xmlchars": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/xmlchars/-/xmlchars-2.2.0.tgz", + "integrity": "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==", + "dev": true, + "license": "MIT" }, "node_modules/xmlhttprequest-ssl": { "version": "2.1.2", @@ -8747,6 +11160,7 @@ "version": "2.8.2", "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.8.2.tgz", "integrity": "sha512-mplynKqc1C2hTVYxd0PU2xQAc22TI1vShAYGksCCfxbn/dFwnHTNi1bvYsBTkhdUNtGIf5xNOg938rrSSYvS9A==", + "license": "ISC", "optional": true, "peer": true, "bin": { @@ -8764,6 +11178,7 @@ "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", "dev": true, + "license": "MIT", "engines": { "node": ">=10" }, @@ -8775,6 +11190,7 @@ "version": "1.7.1", "resolved": "https://registry.npmjs.org/yup/-/yup-1.7.1.tgz", "integrity": "sha512-GKHFX2nXul2/4Dtfxhozv701jLQHdf6J34YDh2cEkpqoo8le5Mg6/LrdseVLrFarmFygZTlfIhHx/QKfb/QWXw==", + "license": "MIT", "dependencies": { "property-expr": "^2.0.5", "tiny-case": "^1.0.3", @@ -8786,6 +11202,7 @@ "version": "2.19.0", "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-2.19.0.tgz", "integrity": "sha512-RAH822pAdBgcNMAfWnCBU3CFZcfZ/i1eZjwFU/dsLKumyuuP3niueg2UAukXYF0E2AAoc82ZSSf9J0WQBinzHA==", + "license": "(MIT OR CC0-1.0)", "engines": { "node": ">=12.20" }, diff --git a/ui/package.json b/ui/package.json index 96186721d..7723c3972 100644 --- a/ui/package.json +++ b/ui/package.json @@ -6,7 +6,6 @@ "@contentstack/json-rte-serializer": "^3.0.5", "@contentstack/venus-components": "^3.0.3", "@reduxjs/toolkit": "^2.8.2", - "@testing-library/jest-dom": "^6.0.0", "@types/react": "^18.3.21", "@types/react-dom": "^18.2.13", "@types/react-redux": "^7.1.33", @@ -36,13 +35,25 @@ "lint": "eslint .", "lint:fix": "eslint --fix --ext .js,.jsx,.ts,.tsx . --ignore-pattern './node_modules/' --ignore-pattern './build/' --ignore-pattern '.eslintrc.js'", "precommit": "npm run prettify && npm run lint:fix", - "format": "prettier --write './**/*.{js,jsx,ts,tsx,css,md,json}' --config ./.prettierrc" + "format": "prettier --write './**/*.{js,jsx,ts,tsx,css,md,json}' --config ./.prettierrc", + "test": "vitest run", + "test:watch": "vitest", + "test:coverage": "vitest run --coverage", + "test:coverage:open": "vitest run --coverage && open coverage/index.html", + "coverage:ui": "npx serve coverage -l 3939" }, "devDependencies": { + "@testing-library/jest-dom": "^6.9.1", + "@testing-library/react": "^16.3.2", + "@testing-library/user-event": "^14.6.1", + "@vitest/coverage-v8": "^4.0.18", + "@vitest/ui": "^4.0.18", "eslint": "^8.51.0", "eslint-plugin-react": "^7.33.2", "eslint-plugin-react-hooks": "^4.6.0", - "prettier": "^3.3.3" + "jsdom": "^28.1.0", + "prettier": "^3.3.3", + "vitest": "^4.0.18" }, "eslintConfig": { "extends": [ diff --git a/ui/tests/fixtures/user.fixture.ts b/ui/tests/fixtures/user.fixture.ts new file mode 100644 index 000000000..b7cd8889f --- /dev/null +++ b/ui/tests/fixtures/user.fixture.ts @@ -0,0 +1,72 @@ +export const createMockUser = (overrides = {}) => ({ + email: 'test@example.com', + username: 'testuser', + first_name: 'Test', + last_name: 'User', + mobile_number: '1234567890', + country_code: '+1', + organizations: [], + region: 'NA', + ...overrides +}); + +export const createMockOrganisation = (overrides = {}) => ({ + uid: 'org-123', + value: 'org-123', + label: 'Test Org', + master_locale: 'en-us', + locales: [], + created_at: '2024-01-01', + ...overrides +}); + +export const createMockProject = (overrides = {}) => ({ + _id: 'proj-123', + name: 'Test Project', + description: 'A test project', + status: 0, + org_id: 'org-123', + org_name: 'Test Org', + region: 'NA', + owner: 'user-123', + created_by: 'user-123', + created_at: '2024-01-01T00:00:00.000Z', + updated_at: '2024-01-01T00:00:00.000Z', + legacy_cms: { + cms_id: 'wordpress', + allowed_file_formats: ['json'], + affix: 'cs', + file_format: 'json', + file_path: '/path/to/file', + is_localPath: true, + is_fileValid: true, + awsDetails: { + awsRegion: 'us-east-1', + bucketName: 'test-bucket', + bucketKey: 'test-key' + } + }, + destination_stack_id: 'stack-123', + destination_stack_name: 'Test Stack', + destination_stack_master_locale: 'en-us', + destination_stack_created_at: '2024-01-01', + content_mapping: {}, + stackDetails: { label: 'Test Stack', value: 'stack-123' }, + mapperKeys: {}, + ...overrides +}); + +export const createMockLoginUser = (overrides = {}) => ({ + email: 'test@example.com', + password: 'password123', + ...overrides +}); + +export const createMockAxiosResponse = (overrides = {}) => ({ + status: 200, + statusText: 'OK', + data: {}, + headers: {}, + config: {}, + ...overrides +}); diff --git a/ui/tests/setup.ts b/ui/tests/setup.ts new file mode 100644 index 000000000..090074d50 --- /dev/null +++ b/ui/tests/setup.ts @@ -0,0 +1,20 @@ +import { vi, beforeAll, afterAll, afterEach } from 'vitest'; +import '@testing-library/jest-dom/vitest'; + +beforeAll(() => { + vi.stubEnv('VITE_BASE_API_URL', 'http://localhost:5001/'); + vi.stubEnv('VITE_WEBSITE_BASE_URL', 'https://test.contentstack.com'); + vi.stubEnv('VITE_API_VERSION', 'v2'); + vi.stubEnv('VITE_UPLOAD_SERVER', 'http://localhost:5002/'); + vi.stubEnv('VITE_OFFLINE_CMS', 'true'); +}); + +afterEach(() => { + vi.restoreAllMocks(); + localStorage.clear(); + sessionStorage.clear(); +}); + +afterAll(() => { + vi.unstubAllEnvs(); +}); diff --git a/ui/tests/unit/cmsData/cmsSelector.test.ts b/ui/tests/unit/cmsData/cmsSelector.test.ts new file mode 100644 index 000000000..5acdb9e90 --- /dev/null +++ b/ui/tests/unit/cmsData/cmsSelector.test.ts @@ -0,0 +1,114 @@ +import { describe, it, expect, vi } from 'vitest'; + +vi.mock('../../../src/utilities/constants', () => ({ + CS_ENTRIES: { + HEADER: 'header', + MAIN_HEADER: 'main_header', + HOME_PAGE: 'homepage', + REGIONS: 'region_login', + LOGIN: 'login', + PROJECTS: 'projects', + MIGRATION_FLOW: 'migration_steps', + LEGACY_CMS: 'legacy_cms', + DESTINATION_STACK: 'destination_stack', + CONTENT_MAPPING: 'content_mapping', + TEST_MIGRATION: 'test_migration', + MIGRATION_EXECUTION: 'migration_execution', + SETTING: 'settings', + NOT_FOUND_ERROR: { type: 'error_handler', url: '404' }, + INTERNAL_SERVER_ERROR: { type: 'error_handler', url: '500' }, + ERROR_HANDLER: 'error_handler', + ADD_STACK: 'add_stack', + UNMAPPED_LOCALE_KEY: 'undefined' + }, + BASE_API_URL: 'http://localhost:5001/', + TOKEN_KEY: 'access_token', + TOKEN: null, + HEADERS: {} +})); + +import { getCMSDataFromFile } from '../../../src/cmsData/cmsSelector'; + +describe('cmsData/cmsSelector', () => { + describe('getCMSDataFromFile', () => { + it('should return homepage data for HOME_PAGE content type', async () => { + const result = await getCMSDataFromFile('homepage'); + expect(result).toBeDefined(); + }); + + it('should return login data for LOGIN content type', async () => { + const result = await getCMSDataFromFile('login'); + expect(result).toBeDefined(); + }); + + it('should return region login data for REGIONS content type', async () => { + const result = await getCMSDataFromFile('region_login'); + expect(result).toBeDefined(); + }); + + it('should return main header data for MAIN_HEADER content type', async () => { + const result = await getCMSDataFromFile('main_header'); + expect(result).toBeDefined(); + }); + + it('should return projects data for PROJECTS content type', async () => { + const result = await getCMSDataFromFile('projects'); + expect(result).toBeDefined(); + }); + + it('should return legacy CMS data for LEGACY_CMS content type', async () => { + const result = await getCMSDataFromFile('legacy_cms'); + expect(result).toBeDefined(); + }); + + it('should return destination stack data for DESTINATION_STACK content type', async () => { + const result = await getCMSDataFromFile('destination_stack'); + expect(result).toBeDefined(); + }); + + it('should return add stack data for ADD_STACK content type', async () => { + const result = await getCMSDataFromFile('add_stack'); + expect(result).toBeDefined(); + }); + + it('should return migration steps data for MIGRATION_FLOW content type', async () => { + const result = await getCMSDataFromFile('migration_steps'); + expect(result).toBeDefined(); + }); + + it('should return content mapping data for CONTENT_MAPPING content type', async () => { + const result = await getCMSDataFromFile('content_mapping'); + expect(result).toBeDefined(); + }); + + it('should return test migration data for TEST_MIGRATION content type', async () => { + const result = await getCMSDataFromFile('test_migration'); + expect(result).toBeDefined(); + }); + + it('should return migration execution data for MIGRATION_EXECUTION content type', async () => { + const result = await getCMSDataFromFile('migration_execution'); + expect(result).toBeDefined(); + }); + + it('should return settings data for SETTING content type', async () => { + const result = await getCMSDataFromFile('settings'); + expect(result).toBeDefined(); + }); + + it('should return 500 error data for ERROR_HANDLER with 500 url', async () => { + const result = await getCMSDataFromFile('error_handler', '500'); + expect(result).toBeDefined(); + }); + + it('should return 404 error data for ERROR_HANDLER with 404 url', async () => { + const result = await getCMSDataFromFile('error_handler', '404'); + expect(result).toBeDefined(); + }); + + it('should return undefined for unknown content type', async () => { + const result = await getCMSDataFromFile('unknown_type'); + expect(result).toBeUndefined(); + }); + }); +}); diff --git a/ui/tests/unit/hooks/useAuthCheck.test.ts b/ui/tests/unit/hooks/useAuthCheck.test.ts new file mode 100644 index 000000000..8b50491b8 --- /dev/null +++ b/ui/tests/unit/hooks/useAuthCheck.test.ts @@ -0,0 +1,104 @@ +import { describe, it, expect, vi, beforeEach } from 'vitest'; +import { renderHook } from '@testing-library/react'; +import React from 'react'; + +const mockNavigate = vi.fn(); +const mockDispatch = vi.fn(); +const mockSelector = vi.fn(); + +vi.mock('react-redux', () => ({ + useSelector: (fn: any) => mockSelector(fn), + useDispatch: () => mockDispatch +})); + +vi.mock('react-router-dom', () => ({ + useNavigate: () => mockNavigate +})); + +vi.mock('jwt-decode', () => ({ + jwtDecode: vi.fn() +})); + +vi.mock('../../../src/store/slice/authSlice', () => ({ + reInitiliseState: vi.fn(() => ({ type: 'auth/reInitiliseState' })) +})); + +import useAuthCheck from '../../../src/hooks/authentication'; +import { jwtDecode } from 'jwt-decode'; +import { reInitiliseState } from '../../../src/store/slice/authSlice'; + +describe('hooks/useAuthCheck', () => { + beforeEach(() => { + vi.clearAllMocks(); + mockNavigate.mockClear(); + mockDispatch.mockClear(); + }); + + it('should not dispatch or navigate when authToken is falsy', () => { + let callCount = 0; + mockSelector.mockImplementation(() => { + callCount++; + if (callCount % 2 === 1) return ''; // authToken + return {}; // selectedOrganisation + }); + + renderHook(() => useAuthCheck()); + + expect(mockDispatch).not.toHaveBeenCalled(); + expect(mockNavigate).not.toHaveBeenCalled(); + }); + + it('should not dispatch if token is valid (not expired)', () => { + let callCount = 0; + mockSelector.mockImplementation(() => { + callCount++; + if (callCount % 2 === 1) return 'valid-token'; + return {}; + }); + + const futureExp = (Date.now() / 1000) + 3600; + vi.mocked(jwtDecode).mockReturnValue({ exp: futureExp }); + + renderHook(() => useAuthCheck()); + + expect(mockDispatch).not.toHaveBeenCalled(); + expect(mockNavigate).not.toHaveBeenCalled(); + }); + + it('should dispatch reInitiliseState and navigate to "/" when token is expired', () => { + let callCount = 0; + mockSelector.mockImplementation(() => { + callCount++; + if (callCount % 2 === 1) return 'expired-token'; + return {}; + }); + + const pastExp = (Date.now() / 1000) - 3600; + vi.mocked(jwtDecode).mockReturnValue({ exp: pastExp }); + + renderHook(() => useAuthCheck()); + + expect(mockDispatch).toHaveBeenCalledWith(reInitiliseState()); + expect(mockNavigate).toHaveBeenCalledWith('/'); + }); + + it('should dispatch reInitiliseState and navigate on jwtDecode error', () => { + let callCount = 0; + mockSelector.mockImplementation(() => { + callCount++; + if (callCount % 2 === 1) return 'bad-token'; + return {}; + }); + + vi.mocked(jwtDecode).mockImplementation(() => { + throw new Error('Invalid token'); + }); + + const consoleSpy = vi.spyOn(console, 'error').mockImplementation(() => {}); + renderHook(() => useAuthCheck()); + + expect(mockDispatch).toHaveBeenCalledWith(reInitiliseState()); + expect(mockNavigate).toHaveBeenCalledWith('/'); + consoleSpy.mockRestore(); + }); +}); diff --git a/ui/tests/unit/hooks/useBlockNavigation.test.ts b/ui/tests/unit/hooks/useBlockNavigation.test.ts new file mode 100644 index 000000000..cdd77d317 --- /dev/null +++ b/ui/tests/unit/hooks/useBlockNavigation.test.ts @@ -0,0 +1,85 @@ +import { describe, it, expect, vi, beforeEach } from 'vitest'; +import { renderHook } from '@testing-library/react'; + +const mockLocation = { pathname: '/migration', search: '', hash: '' }; + +vi.mock('react-router-dom', () => ({ + useLocation: () => mockLocation +})); + +vi.mock('../../../src/utilities/constants', () => ({ + WEBSITE_BASE_URL: 'https://test.contentstack.com' +})); + +import useBlockNavigation from '../../../src/hooks/userNavigation'; + +describe('hooks/useBlockNavigation', () => { + let pushStateSpy: ReturnType; + let addEventSpy: ReturnType; + let removeEventSpy: ReturnType; + + beforeEach(() => { + vi.clearAllMocks(); + pushStateSpy = vi.spyOn(window.history, 'pushState').mockImplementation(() => {}); + addEventSpy = vi.spyOn(window, 'addEventListener'); + removeEventSpy = vi.spyOn(window, 'removeEventListener'); + }); + + it('should not push state or add listener when modal is closed', () => { + renderHook(() => useBlockNavigation(false)); + expect(pushStateSpy).not.toHaveBeenCalled(); + expect(addEventSpy).not.toHaveBeenCalledWith('popstate', expect.any(Function)); + }); + + it('should push state and add popstate listener when modal is open', () => { + renderHook(() => useBlockNavigation(true)); + + expect(pushStateSpy).toHaveBeenCalledWith( + { blockNav: true }, + '', + '/migration' + ); + expect(addEventSpy).toHaveBeenCalledWith('popstate', expect.any(Function)); + }); + + it('should remove popstate listener on unmount', () => { + const { unmount } = renderHook(() => useBlockNavigation(true)); + unmount(); + expect(removeEventSpy).toHaveBeenCalledWith('popstate', expect.any(Function)); + }); + + it('should re-push state on popstate when modal is open', () => { + renderHook(() => useBlockNavigation(true)); + + const handler = addEventSpy.mock.calls.find( + (call) => call[0] === 'popstate' + )?.[1] as EventListener; + + pushStateSpy.mockClear(); + handler(new PopStateEvent('popstate')); + + expect(pushStateSpy).toHaveBeenCalledWith( + { blockNav: true }, + '', + '/migration' + ); + }); + + it('should update stored pathname when modal closes', () => { + const { rerender } = renderHook( + ({ isOpen }) => useBlockNavigation(isOpen), + { initialProps: { isOpen: true } } + ); + + rerender({ isOpen: false }); + + pushStateSpy.mockClear(); + rerender({ isOpen: true }); + + expect(pushStateSpy).toHaveBeenCalledWith( + { blockNav: true }, + '', + '/migration' + ); + }); +}); diff --git a/ui/tests/unit/hooks/useDebouncer.test.ts b/ui/tests/unit/hooks/useDebouncer.test.ts new file mode 100644 index 000000000..f21d724d2 --- /dev/null +++ b/ui/tests/unit/hooks/useDebouncer.test.ts @@ -0,0 +1,80 @@ +import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest'; +import { useDebouncer } from '../../../src/hooks/index'; + +describe('hooks/useDebouncer', () => { + beforeEach(() => { + vi.useFakeTimers(); + }); + + afterEach(() => { + vi.useRealTimers(); + }); + + it('should debounce the callback', () => { + const callback = vi.fn(); + const debounced = useDebouncer(callback, 300); + + debounced('arg1'); + debounced('arg2'); + debounced('arg3'); + + expect(callback).not.toHaveBeenCalled(); + + vi.advanceTimersByTime(300); + expect(callback).toHaveBeenCalledTimes(1); + expect(callback).toHaveBeenCalledWith('arg3'); + }); + + it('should use 250ms as default wait time', () => { + const callback = vi.fn(); + const debounced = useDebouncer(callback); + + debounced(); + vi.advanceTimersByTime(249); + expect(callback).not.toHaveBeenCalled(); + + vi.advanceTimersByTime(1); + expect(callback).toHaveBeenCalledTimes(1); + }); + + it('should reset the timer on each call', () => { + const callback = vi.fn(); + const debounced = useDebouncer(callback, 100); + + debounced('first'); + vi.advanceTimersByTime(80); + + debounced('second'); + vi.advanceTimersByTime(80); + + expect(callback).not.toHaveBeenCalled(); + + vi.advanceTimersByTime(20); + expect(callback).toHaveBeenCalledTimes(1); + expect(callback).toHaveBeenCalledWith('second'); + }); + + it('should pass multiple arguments to the callback', () => { + const callback = vi.fn(); + const debounced = useDebouncer(callback, 100); + + debounced('arg1', 'arg2', 42); + vi.advanceTimersByTime(100); + + expect(callback).toHaveBeenCalledWith('arg1', 'arg2', 42); + }); + + it('should allow separate invocations after wait period', () => { + const callback = vi.fn(); + const debounced = useDebouncer(callback, 100); + + debounced('first'); + vi.advanceTimersByTime(100); + expect(callback).toHaveBeenCalledWith('first'); + + debounced('second'); + vi.advanceTimersByTime(100); + expect(callback).toHaveBeenCalledWith('second'); + expect(callback).toHaveBeenCalledTimes(2); + }); +}); diff --git a/ui/tests/unit/hooks/usePreventBackNavigation.test.ts b/ui/tests/unit/hooks/usePreventBackNavigation.test.ts new file mode 100644 index 000000000..4765cf843 --- /dev/null +++ b/ui/tests/unit/hooks/usePreventBackNavigation.test.ts @@ -0,0 +1,68 @@ +import { describe, it, expect, vi, beforeEach } from 'vitest'; +import { renderHook } from '@testing-library/react'; + +const mockNavigate = vi.fn(); +const mockLocation = { pathname: '/projects', search: '?id=1', hash: '#top' }; + +vi.mock('react-router-dom', () => ({ + useNavigate: () => mockNavigate, + useLocation: () => mockLocation +})); + +vi.mock('../../../src/utilities/constants', () => ({ + WEBSITE_BASE_URL: 'https://test.contentstack.com' +})); + +import usePreventBackNavigation from '../../../src/hooks/usePreventBackNavigation'; + +describe('hooks/usePreventBackNavigation', () => { + let pushStateSpy: ReturnType; + let addEventSpy: ReturnType; + let removeEventSpy: ReturnType; + + beforeEach(() => { + vi.clearAllMocks(); + pushStateSpy = vi.spyOn(window.history, 'pushState').mockImplementation(() => {}); + addEventSpy = vi.spyOn(window, 'addEventListener'); + removeEventSpy = vi.spyOn(window, 'removeEventListener'); + }); + + it('should push history state on mount', () => { + renderHook(() => usePreventBackNavigation()); + + expect(pushStateSpy).toHaveBeenCalledWith( + { preventBack: true }, + '', + '/projects?id=1#top' + ); + }); + + it('should add popstate event listener', () => { + renderHook(() => usePreventBackNavigation()); + expect(addEventSpy).toHaveBeenCalledWith('popstate', expect.any(Function)); + }); + + it('should remove popstate event listener on unmount', () => { + const { unmount } = renderHook(() => usePreventBackNavigation()); + unmount(); + expect(removeEventSpy).toHaveBeenCalledWith('popstate', expect.any(Function)); + }); + + it('should push state again on popstate (back navigation)', () => { + renderHook(() => usePreventBackNavigation()); + + const handler = addEventSpy.mock.calls.find( + (call) => call[0] === 'popstate' + )?.[1] as EventListener; + + pushStateSpy.mockClear(); + const event = new PopStateEvent('popstate'); + handler(event); + + expect(pushStateSpy).toHaveBeenCalledWith( + { preventBack: true }, + '', + '/projects?id=1#top' + ); + }); +}); diff --git a/ui/tests/unit/hooks/useWarnOnRefresh.test.ts b/ui/tests/unit/hooks/useWarnOnRefresh.test.ts new file mode 100644 index 000000000..376534e60 --- /dev/null +++ b/ui/tests/unit/hooks/useWarnOnRefresh.test.ts @@ -0,0 +1,52 @@ +import { describe, it, expect, vi, beforeEach } from 'vitest'; +import { renderHook } from '@testing-library/react'; +import { useWarnOnRefresh } from '../../../src/hooks/useWarnOnrefresh'; + +describe('hooks/useWarnOnRefresh', () => { + let addSpy: ReturnType; + let removeSpy: ReturnType; + + beforeEach(() => { + addSpy = vi.spyOn(window, 'addEventListener'); + removeSpy = vi.spyOn(window, 'removeEventListener'); + }); + + it('should add beforeunload event listener on mount', () => { + renderHook(() => useWarnOnRefresh(true)); + expect(addSpy).toHaveBeenCalledWith('beforeunload', expect.any(Function)); + }); + + it('should remove beforeunload event listener on unmount', () => { + const { unmount } = renderHook(() => useWarnOnRefresh(true)); + unmount(); + expect(removeSpy).toHaveBeenCalledWith('beforeunload', expect.any(Function)); + }); + + it('should call preventDefault when isUnsaved is true', () => { + renderHook(() => useWarnOnRefresh(true)); + + const handler = addSpy.mock.calls.find( + (call) => call[0] === 'beforeunload' + )?.[1] as EventListener; + + const event = new Event('beforeunload') as BeforeUnloadEvent; + const preventDefaultSpy = vi.spyOn(event, 'preventDefault'); + handler(event); + + expect(preventDefaultSpy).toHaveBeenCalled(); + }); + + it('should not call preventDefault when isUnsaved is false', () => { + renderHook(() => useWarnOnRefresh(false)); + + const handler = addSpy.mock.calls.find( + (call) => call[0] === 'beforeunload' + )?.[1] as EventListener; + + const event = new Event('beforeunload') as BeforeUnloadEvent; + const preventDefaultSpy = vi.spyOn(event, 'preventDefault'); + handler(event); + + expect(preventDefaultSpy).not.toHaveBeenCalled(); + }); +}); diff --git a/ui/tests/unit/services/login.service.test.ts b/ui/tests/unit/services/login.service.test.ts new file mode 100644 index 000000000..a6634efa0 --- /dev/null +++ b/ui/tests/unit/services/login.service.test.ts @@ -0,0 +1,90 @@ +import { describe, it, expect, vi, beforeEach } from 'vitest'; + +const { mockPostCall } = vi.hoisted(() => ({ + mockPostCall: vi.fn() +})); + +vi.mock('../../../src/services/api/service', () => ({ + postCall: mockPostCall +})); + +vi.mock('../../../src/utilities/constants', () => ({ + AUTH_ROUTES: 'v2/auth', + API_VERSION: 'v2', + BASE_API_URL: 'http://localhost:5001/', + TOKEN_KEY: 'access_token', + TOKEN: null, + HEADERS: {} +})); + +import { userSession, requestSMSToken } from '../../../src/services/api/login.service'; + +describe('services/api/login.service', () => { + beforeEach(() => { + vi.clearAllMocks(); + }); + + describe('userSession', () => { + it('should call postCall with user-session endpoint and data', async () => { + const mockResponse = { status: 200, data: { notice: 'Login successful' } }; + mockPostCall.mockResolvedValue(mockResponse); + + const userData = { email: 'test@example.com', password: 'pass123' }; + const result = await userSession(userData); + + expect(mockPostCall).toHaveBeenCalledWith('v2/auth/user-session', userData); + expect(result).toEqual(mockResponse); + }); + + it('should propagate the response from postCall', async () => { + const mockResponse = { status: 401, data: { error_message: 'Invalid credentials' } }; + mockPostCall.mockResolvedValue(mockResponse); + + const result = await userSession({ email: 'test@example.com', password: 'wrong' }); + expect(result).toEqual(mockResponse); + }); + }); + + describe('requestSMSToken', () => { + it('should call postCall with request-token-sms endpoint', async () => { + const mockResponse = { status: 200, data: { notice: 'SMS sent' } }; + mockPostCall.mockResolvedValue(mockResponse); + + const smsData = { email: 'test@example.com', password: 'pass123', region: 'NA' }; + const result = await requestSMSToken(smsData); + + expect(mockPostCall).toHaveBeenCalledWith('v2/auth/request-token-sms', smsData); + expect(result).toEqual(mockResponse); + }); + + it('should throw with error message when postCall throws an Error', () => { + mockPostCall.mockImplementation(() => { throw new Error('Network failure'); }); + + expect(() => requestSMSToken({ email: 'a@b.com', password: 'p', region: 'NA' })) + .toThrow('Error in requestSMSToken: Network failure'); + }); + + it('should throw generic message when postCall throws a non-Error', () => { + mockPostCall.mockImplementation(() => { throw 'something'; }); + + expect(() => requestSMSToken({ email: 'a@b.com', password: 'p', region: 'NA' })) + .toThrow('Unknown error in requestSMSToken'); + }); + }); + + describe('userSession - error handling', () => { + it('should throw with error message when postCall throws an Error', () => { + mockPostCall.mockImplementation(() => { throw new Error('Connection refused'); }); + + expect(() => userSession({ email: 'a@b.com', password: 'p' })) + .toThrow('Error in userSession: Connection refused'); + }); + + it('should throw generic message when postCall throws a non-Error', () => { + mockPostCall.mockImplementation(() => { throw 42; }); + + expect(() => userSession({ email: 'a@b.com', password: 'p' })) + .toThrow('Unknown error in userSession'); + }); + }); +}); diff --git a/ui/tests/unit/services/migration.service.test.ts b/ui/tests/unit/services/migration.service.test.ts new file mode 100644 index 000000000..f1098b61e --- /dev/null +++ b/ui/tests/unit/services/migration.service.test.ts @@ -0,0 +1,565 @@ +import { describe, it, expect, vi, beforeEach } from 'vitest'; + +const { mockGetCall, mockPostCall, mockPutCall, mockPatchCall } = vi.hoisted(() => ({ + mockGetCall: vi.fn(), + mockPostCall: vi.fn(), + mockPutCall: vi.fn(), + mockPatchCall: vi.fn() +})); + +vi.mock('../../../src/services/api/service', () => ({ + getCall: mockGetCall, + postCall: mockPostCall, + putCall: mockPutCall, + patchCall: mockPatchCall +})); + +vi.mock('../../../src/utilities/constants', () => ({ + API_VERSION: 'v2', + BASE_API_URL: 'http://localhost:5001/', + TOKEN_KEY: 'access_token', + TOKEN: null, + HEADERS: {}, + EXECUTION_LOGS_ERROR_TEXT: { ERROR: 'Error in Getting Migration Logs' } +})); + +vi.mock('../../../src/utilities/functions', () => ({ + getDataFromLocalStorage: vi.fn(() => 'mock-app-token') +})); + +import { + getMigrationData, + updateLegacyCMSData, + updateAffixData, + updateFileFormatData, + updateDestinationStack, + updateCurrentStepData, + affixConfirmation, + fileformatConfirmation, + getContentTypes, + getFieldMapping, + updateContentType, + resetToInitialMapping, + getExistingContentTypes, + getExistingGlobalFields, + removeContentMapper, + updateContentMapper, + updateStackDetails, + getOrgDetails, + createTestStack, + createTestMigration, + startMigration, + updateMigrationKey, + updateLocaleMapper, + getExistingTaxonomies, + getMigrationLogs +} from '../../../src/services/api/migration.service'; + +describe('services/api/migration.service', () => { + const orgId = 'org-123'; + const projectId = 'proj-456'; + + beforeEach(() => { + vi.clearAllMocks(); + }); + + describe('getMigrationData', () => { + it('should call getCall with org and project ID', () => { + const mockResponse = { status: 200, data: {} }; + mockGetCall.mockResolvedValue(mockResponse); + + getMigrationData(orgId, projectId); + expect(mockGetCall).toHaveBeenCalledWith( + `v2/org/${orgId}/project/${projectId}`, + expect.objectContaining({ headers: { app_token: 'mock-app-token' } }) + ); + }); + }); + + describe('updateLegacyCMSData', () => { + it('should call putCall with legacy-cms endpoint', () => { + const data = { cms: 'wordpress' }; + mockPutCall.mockResolvedValue({ status: 200 }); + + updateLegacyCMSData(orgId, projectId, data); + expect(mockPutCall).toHaveBeenCalledWith( + `v2/org/${orgId}/project/${projectId}/legacy-cms`, + data, + expect.objectContaining({ headers: { app_token: 'mock-app-token' } }) + ); + }); + }); + + describe('updateAffixData', () => { + it('should call putCall with affix endpoint', () => { + const data = { affix: 'cs' }; + mockPutCall.mockResolvedValue({ status: 200 }); + + updateAffixData(orgId, projectId, data); + expect(mockPutCall).toHaveBeenCalledWith( + `v2/org/${orgId}/project/${projectId}/affix`, + data, + expect.objectContaining({ headers: { app_token: 'mock-app-token' } }) + ); + }); + }); + + describe('updateFileFormatData', () => { + it('should call putCall with file-format endpoint', () => { + const data = { file_format: 'json' }; + mockPutCall.mockResolvedValue({ status: 200 }); + + updateFileFormatData(orgId, projectId, data); + expect(mockPutCall).toHaveBeenCalledWith( + `v2/org/${orgId}/project/${projectId}/file-format`, + data, + expect.objectContaining({ headers: { app_token: 'mock-app-token' } }) + ); + }); + }); + + describe('updateDestinationStack', () => { + it('should call putCall with destination-stack endpoint', () => { + const data = { stack_id: 'stack-1' }; + mockPutCall.mockResolvedValue({ status: 200 }); + + updateDestinationStack(orgId, projectId, data); + expect(mockPutCall).toHaveBeenCalledWith( + `v2/org/${orgId}/project/${projectId}/destination-stack`, + data, + expect.objectContaining({ headers: { app_token: 'mock-app-token' } }) + ); + }); + }); + + describe('updateCurrentStepData', () => { + it('should call putCall with current-step endpoint', () => { + const data = { step: 2 }; + mockPutCall.mockResolvedValue({ status: 200 }); + + updateCurrentStepData(orgId, projectId, data); + expect(mockPutCall).toHaveBeenCalledWith( + `v2/org/${orgId}/project/${projectId}/current-step`, + data, + expect.objectContaining({ headers: { app_token: 'mock-app-token' } }) + ); + }); + + it('should use empty object as default data', () => { + mockPutCall.mockResolvedValue({ status: 200 }); + + updateCurrentStepData(orgId, projectId); + expect(mockPutCall).toHaveBeenCalledWith( + `v2/org/${orgId}/project/${projectId}/current-step`, + {}, + expect.objectContaining({ headers: { app_token: 'mock-app-token' } }) + ); + }); + }); + + describe('affixConfirmation', () => { + it('should call putCall with affix_confirmation endpoint', () => { + mockPutCall.mockResolvedValue({ status: 200 }); + + affixConfirmation(orgId, projectId, { confirmed: true }); + expect(mockPutCall).toHaveBeenCalledWith( + `v2/org/${orgId}/project/${projectId}/affix_confirmation`, + { confirmed: true }, + expect.objectContaining({ headers: { app_token: 'mock-app-token' } }) + ); + }); + }); + + describe('fileformatConfirmation', () => { + it('should call putCall with fileformat_confirmation endpoint', () => { + mockPutCall.mockResolvedValue({ status: 200 }); + + fileformatConfirmation(orgId, projectId, { confirmed: true }); + expect(mockPutCall).toHaveBeenCalledWith( + `v2/org/${orgId}/project/${projectId}/fileformat_confirmation`, + { confirmed: true }, + expect.objectContaining({ headers: { app_token: 'mock-app-token' } }) + ); + }); + }); + + describe('getContentTypes', () => { + it('should call getCall with encoded search text', () => { + mockGetCall.mockResolvedValue({ status: 200, data: {} }); + + getContentTypes('proj-1', 0, 10, 'search text'); + expect(mockGetCall).toHaveBeenCalledWith( + 'v2/mapper/contentTypes/proj-1/0/10/search%20text?', + expect.objectContaining({ headers: { app_token: 'mock-app-token' } }) + ); + }); + }); + + describe('getFieldMapping', () => { + it('should call getCall with content type and pagination', async () => { + mockGetCall.mockResolvedValue({ status: 200, data: {} }); + + await getFieldMapping('ct-1', 0, 10, 'field', 'proj-1'); + expect(mockGetCall).toHaveBeenCalledWith( + 'v2/mapper/fieldMapping/proj-1/ct-1/0/10/field?', + expect.objectContaining({ headers: { app_token: 'mock-app-token' } }) + ); + }); + }); + + describe('updateContentType', () => { + it('should call putCall with content type update data', async () => { + mockPutCall.mockResolvedValue({ status: 200 }); + + await updateContentType(orgId, projectId, 'ct-1', { mapping: {} }); + expect(mockPutCall).toHaveBeenCalledWith( + `v2/mapper/contentTypes/${orgId}/${projectId}/ct-1`, + { mapping: {} }, + expect.objectContaining({ headers: { app_token: 'mock-app-token' } }) + ); + }); + }); + + describe('resetToInitialMapping', () => { + it('should call putCall with reset endpoint', async () => { + mockPutCall.mockResolvedValue({ status: 200 }); + + await resetToInitialMapping(orgId, projectId, 'ct-1', {}); + expect(mockPutCall).toHaveBeenCalledWith( + `v2/mapper/resetFields/${orgId}/${projectId}/ct-1`, + {}, + expect.objectContaining({ headers: { app_token: 'mock-app-token' } }) + ); + }); + }); + + describe('getExistingContentTypes', () => { + it('should call getCall with content type uid', async () => { + mockGetCall.mockResolvedValue({ status: 200, data: [] }); + + await getExistingContentTypes('proj-1', 'ct-1'); + expect(mockGetCall).toHaveBeenCalledWith( + 'v2/mapper/proj-1/contentTypes/ct-1', + expect.objectContaining({ headers: { app_token: 'mock-app-token' } }) + ); + }); + + it('should call getCall without content type uid when not provided', async () => { + mockGetCall.mockResolvedValue({ status: 200, data: [] }); + + await getExistingContentTypes('proj-1'); + expect(mockGetCall).toHaveBeenCalledWith( + 'v2/mapper/proj-1/contentTypes/', + expect.objectContaining({ headers: { app_token: 'mock-app-token' } }) + ); + }); + }); + + describe('getExistingGlobalFields', () => { + it('should call getCall with global field uid', async () => { + mockGetCall.mockResolvedValue({ status: 200, data: [] }); + + await getExistingGlobalFields('proj-1', 'gf-1'); + expect(mockGetCall).toHaveBeenCalledWith( + 'v2/mapper/proj-1/globalFields/gf-1', + expect.objectContaining({ headers: { app_token: 'mock-app-token' } }) + ); + }); + }); + + describe('removeContentMapper', () => { + it('should call getCall for content-mapper endpoint', async () => { + mockGetCall.mockResolvedValue({ status: 200 }); + + await removeContentMapper(orgId, projectId); + expect(mockGetCall).toHaveBeenCalledWith( + `v2/mapper/${orgId}/${projectId}/content-mapper`, + expect.objectContaining({ headers: { app_token: 'mock-app-token' } }) + ); + }); + }); + + describe('updateContentMapper', () => { + it('should call patchCall with content_mapper wrapped data', async () => { + mockPatchCall.mockResolvedValue({ status: 200 }); + const data = { key: 'value' }; + + await updateContentMapper(orgId, projectId, data); + expect(mockPatchCall).toHaveBeenCalledWith( + `v2/mapper/${orgId}/${projectId}/mapper_keys`, + { content_mapper: data }, + expect.objectContaining({ headers: { app_token: 'mock-app-token' } }) + ); + }); + }); + + describe('updateStackDetails', () => { + it('should call patchCall with stack_details wrapped data', async () => { + mockPatchCall.mockResolvedValue({ status: 200 }); + const data = { locale: 'en-us' }; + + await updateStackDetails(orgId, projectId, data); + expect(mockPatchCall).toHaveBeenCalledWith( + `v2/org/${orgId}/project/${projectId}/stack-details`, + { stack_details: data }, + expect.objectContaining({ headers: { app_token: 'mock-app-token' } }) + ); + }); + }); + + describe('getOrgDetails', () => { + it('should call getCall with org details endpoint', async () => { + mockGetCall.mockResolvedValue({ status: 200, data: {} }); + + await getOrgDetails(orgId); + expect(mockGetCall).toHaveBeenCalledWith( + `v2/org/${orgId}/get_org_details`, + expect.objectContaining({ headers: { app_token: 'mock-app-token' } }) + ); + }); + }); + + describe('createTestStack', () => { + it('should call postCall with test stack data', async () => { + mockPostCall.mockResolvedValue({ status: 201 }); + + await createTestStack(orgId, projectId, { name: 'test-stack' }); + expect(mockPostCall).toHaveBeenCalledWith( + `v2/migration/create-test-stack/${orgId}/${projectId}`, + { name: 'test-stack' }, + expect.objectContaining({ headers: { app_token: 'mock-app-token' } }) + ); + }); + }); + + describe('createTestMigration', () => { + it('should call postCall with empty body', async () => { + mockPostCall.mockResolvedValue({ status: 200 }); + + await createTestMigration(orgId, projectId); + expect(mockPostCall).toHaveBeenCalledWith( + `v2/migration/test-stack/${orgId}/${projectId}`, + {}, + expect.objectContaining({ headers: { app_token: 'mock-app-token' } }) + ); + }); + }); + + describe('startMigration', () => { + it('should call postCall with migration start endpoint', async () => { + mockPostCall.mockResolvedValue({ status: 200 }); + + await startMigration(orgId, projectId); + expect(mockPostCall).toHaveBeenCalledWith( + `v2/migration/start/${orgId}/${projectId}`, + {}, + expect.objectContaining({ headers: { app_token: 'mock-app-token' } }) + ); + }); + }); + + describe('updateMigrationKey', () => { + it('should call putCall with migration-execution endpoint', async () => { + mockPutCall.mockResolvedValue({ status: 200 }); + + await updateMigrationKey(orgId, projectId); + expect(mockPutCall).toHaveBeenCalledWith( + `v2/org/${orgId}/project/${projectId}/migration-excution`, + {}, + expect.objectContaining({ headers: { app_token: 'mock-app-token' } }) + ); + }); + }); + + describe('updateLocaleMapper', () => { + it('should call postCall with locale data', async () => { + mockPostCall.mockResolvedValue({ status: 200 }); + const data = { locales: { 'en-us': 'en-us' } }; + + await updateLocaleMapper(projectId, data); + expect(mockPostCall).toHaveBeenCalledWith( + `v2/migration/updateLocales/${projectId}`, + data, + expect.objectContaining({ headers: { app_token: 'mock-app-token' } }) + ); + }); + }); + + describe('getExistingTaxonomies', () => { + it('should call getCall with taxonomies endpoint', async () => { + mockGetCall.mockResolvedValue({ status: 200, data: [] }); + + await getExistingTaxonomies(projectId); + expect(mockGetCall).toHaveBeenCalledWith( + `v2/mapper/${projectId}/taxonomies`, + expect.objectContaining({ headers: { app_token: 'mock-app-token' } }) + ); + }); + }); + + describe('getMigrationLogs', () => { + it('should call getCall with all parameters in the URL', async () => { + mockGetCall.mockResolvedValue({ status: 200, data: { logs: [] } }); + + await getMigrationLogs(orgId, projectId, 'stack-1', 0, 10, 0, 10, 'search', 'all'); + expect(mockGetCall).toHaveBeenCalledWith( + `v2/migration/get_migration_logs/${orgId}/${projectId}/stack-1/0/10/0/10/search/all`, + expect.objectContaining({ headers: { app_token: 'mock-app-token' } }) + ); + }); + + it('should throw with formatted message on Error', async () => { + mockGetCall.mockImplementation(() => { throw new Error('timeout'); }); + await expect(getMigrationLogs(orgId, projectId, 's', 0, 10, 0, 10, '', 'all')) + .rejects.toThrow('Error in Getting Migration Logs: timeout'); + }); + + it('should throw generic message on non-Error', async () => { + mockGetCall.mockImplementation(() => { throw null; }); + await expect(getMigrationLogs(orgId, projectId, 's', 0, 10, 0, 10, '', 'all')) + .rejects.toThrow('Unknown Error in Getting Migration Logs'); + }); + }); + + describe('error handling for migration service functions', () => { + it('updateLegacyCMSData should throw on Error', () => { + mockPutCall.mockImplementation(() => { throw new Error('err'); }); + expect(() => updateLegacyCMSData(orgId, projectId, {})).toThrow('err'); + }); + + it('updateLegacyCMSData should throw generic on non-Error', () => { + mockPutCall.mockImplementation(() => { throw 0; }); + expect(() => updateLegacyCMSData(orgId, projectId, {})).toThrow('Unknown error'); + }); + + it('updateAffixData should throw on Error', () => { + mockPutCall.mockImplementation(() => { throw new Error('err'); }); + expect(() => updateAffixData(orgId, projectId, {})).toThrow('err'); + }); + + it('updateAffixData should throw generic on non-Error', () => { + mockPutCall.mockImplementation(() => { throw 0; }); + expect(() => updateAffixData(orgId, projectId, {})).toThrow('Unknown error'); + }); + + it('updateFileFormatData should throw on Error', () => { + mockPutCall.mockImplementation(() => { throw new Error('err'); }); + expect(() => updateFileFormatData(orgId, projectId, {})).toThrow('err'); + }); + + it('updateFileFormatData should throw generic on non-Error', () => { + mockPutCall.mockImplementation(() => { throw 0; }); + expect(() => updateFileFormatData(orgId, projectId, {})).toThrow('Unknown error'); + }); + + it('updateDestinationStack should throw on Error', () => { + mockPutCall.mockImplementation(() => { throw new Error('err'); }); + expect(() => updateDestinationStack(orgId, projectId, {})).toThrow('err'); + }); + + it('updateDestinationStack should throw generic on non-Error', () => { + mockPutCall.mockImplementation(() => { throw 0; }); + expect(() => updateDestinationStack(orgId, projectId, {})).toThrow('Unknown error'); + }); + + it('updateCurrentStepData should throw on Error', () => { + mockPutCall.mockImplementation(() => { throw new Error('err'); }); + expect(() => updateCurrentStepData(orgId, projectId, {})).toThrow('err'); + }); + + it('updateCurrentStepData should throw generic on non-Error', () => { + mockPutCall.mockImplementation(() => { throw 0; }); + expect(() => updateCurrentStepData(orgId, projectId, {})).toThrow('Unknown error'); + }); + + it('getMigrationData should throw on Error', () => { + mockGetCall.mockImplementation(() => { throw new Error('err'); }); + expect(() => getMigrationData(orgId, projectId)).toThrow('Error in getting migrationData: err'); + }); + + it('getMigrationData should throw generic on non-Error', () => { + mockGetCall.mockImplementation(() => { throw 0; }); + expect(() => getMigrationData(orgId, projectId)).toThrow('Unknown error in getting migrationData'); + }); + + it('getContentTypes should throw on Error', () => { + mockGetCall.mockImplementation(() => { throw new Error('err'); }); + expect(() => getContentTypes('p', 0, 10, '')).toThrow('err'); + }); + + it('getContentTypes should throw generic on non-Error', () => { + mockGetCall.mockImplementation(() => { throw 0; }); + expect(() => getContentTypes('p', 0, 10, '')).toThrow('Unknown error'); + }); + + it('getFieldMapping should throw on Error', async () => { + mockGetCall.mockImplementation(() => { throw new Error('err'); }); + await expect(getFieldMapping('ct', 0, 10, '', 'p')).rejects.toThrow('err'); + }); + + it('getFieldMapping should throw generic on non-Error', async () => { + mockGetCall.mockImplementation(() => { throw 0; }); + await expect(getFieldMapping('ct', 0, 10, '', 'p')).rejects.toThrow('Unknown error'); + }); + + it('updateContentType should throw on Error', async () => { + mockPutCall.mockImplementation(() => { throw new Error('err'); }); + await expect(updateContentType(orgId, projectId, 'ct', {})).rejects.toThrow('err'); + }); + + it('updateContentType should throw generic on non-Error', async () => { + mockPutCall.mockImplementation(() => { throw 0; }); + await expect(updateContentType(orgId, projectId, 'ct', {})).rejects.toThrow('Unknown error'); + }); + + it('resetToInitialMapping should throw on Error', async () => { + mockPutCall.mockImplementation(() => { throw new Error('err'); }); + await expect(resetToInitialMapping(orgId, projectId, 'ct', {})).rejects.toThrow('err'); + }); + + it('resetToInitialMapping should throw generic on non-Error', async () => { + mockPutCall.mockImplementation(() => { throw 0; }); + await expect(resetToInitialMapping(orgId, projectId, 'ct', {})).rejects.toThrow('Unknown error'); + }); + + it('getExistingContentTypes should throw on Error', async () => { + mockGetCall.mockImplementation(() => { throw new Error('err'); }); + await expect(getExistingContentTypes('p')).rejects.toThrow('err'); + }); + + it('getExistingContentTypes should throw generic on non-Error', async () => { + mockGetCall.mockImplementation(() => { throw 0; }); + await expect(getExistingContentTypes('p')).rejects.toThrow('Unknown error'); + }); + + it('getExistingGlobalFields should throw on Error', async () => { + mockGetCall.mockImplementation(() => { throw new Error('err'); }); + await expect(getExistingGlobalFields('p')).rejects.toThrow('err'); + }); + + it('getExistingGlobalFields should throw generic on non-Error', async () => { + mockGetCall.mockImplementation(() => { throw 0; }); + await expect(getExistingGlobalFields('p')).rejects.toThrow('Unknown error'); + }); + + it('updateContentMapper should throw on Error', async () => { + mockPatchCall.mockImplementation(() => { throw new Error('err'); }); + await expect(updateContentMapper(orgId, projectId, {})).rejects.toThrow('err'); + }); + + it('updateContentMapper should throw generic on non-Error', async () => { + mockPatchCall.mockImplementation(() => { throw 0; }); + await expect(updateContentMapper(orgId, projectId, {})).rejects.toThrow('Unknown error'); + }); + + it('getExistingTaxonomies should throw on Error', async () => { + mockGetCall.mockImplementation(() => { throw new Error('err'); }); + await expect(getExistingTaxonomies(projectId)).rejects.toThrow('err'); + }); + + it('getExistingTaxonomies should throw generic on non-Error', async () => { + mockGetCall.mockImplementation(() => { throw 0; }); + await expect(getExistingTaxonomies(projectId)).rejects.toThrow('Unknown error'); + }); + }); +}); diff --git a/ui/tests/unit/services/project.service.test.ts b/ui/tests/unit/services/project.service.test.ts new file mode 100644 index 000000000..32a182563 --- /dev/null +++ b/ui/tests/unit/services/project.service.test.ts @@ -0,0 +1,222 @@ +import { describe, it, expect, vi, beforeEach } from 'vitest'; + +const { mockGetCall, mockPostCall, mockPutCall, mockDeleteCall } = vi.hoisted(() => ({ + mockGetCall: vi.fn(), + mockPostCall: vi.fn(), + mockPutCall: vi.fn(), + mockDeleteCall: vi.fn() +})); + +vi.mock('../../../src/services/api/service', () => ({ + getCall: mockGetCall, + postCall: mockPostCall, + putCall: mockPutCall, + deleteCall: mockDeleteCall +})); + +vi.mock('../../../src/utilities/constants', () => ({ + API_VERSION: 'v2', + BASE_API_URL: 'http://localhost:5001/', + TOKEN_KEY: 'access_token', + TOKEN: null, + HEADERS: {} +})); + +vi.mock('../../../src/utilities/functions', () => ({ + getDataFromLocalStorage: vi.fn(() => 'mock-app-token') +})); + +import { + getAllProjects, + getProject, + createProject, + updateProject, + deleteProject, + getMigratedStacks, + getAuditData +} from '../../../src/services/api/project.service'; + +describe('services/api/project.service', () => { + const orgId = 'org-123'; + const projectId = 'proj-456'; + + beforeEach(() => { + vi.clearAllMocks(); + }); + + describe('getAllProjects', () => { + it('should call getCall with the correct URL and options', async () => { + const mockResponse = { status: 200, data: { projects: [] } }; + mockGetCall.mockResolvedValue(mockResponse); + + const result = await getAllProjects(orgId); + expect(mockGetCall).toHaveBeenCalledWith( + `v2/org/${orgId}/project`, + expect.objectContaining({ headers: { app_token: 'mock-app-token' } }) + ); + expect(result).toEqual(mockResponse); + }); + }); + + describe('getProject', () => { + it('should call getCall with org and project ID', async () => { + const mockResponse = { status: 200, data: { project: {} } }; + mockGetCall.mockResolvedValue(mockResponse); + + const result = await getProject(orgId, projectId); + expect(mockGetCall).toHaveBeenCalledWith( + `v2/org/${orgId}/project/${projectId}`, + expect.objectContaining({ headers: { app_token: 'mock-app-token' } }) + ); + expect(result).toEqual(mockResponse); + }); + }); + + describe('createProject', () => { + it('should call postCall with project data', async () => { + const data = { name: 'New Project' }; + const mockResponse = { status: 201, data: { project: data } }; + mockPostCall.mockResolvedValue(mockResponse); + + const result = await createProject(orgId, data); + expect(mockPostCall).toHaveBeenCalledWith( + `v2/org/${orgId}/project/`, + data, + expect.objectContaining({ headers: { app_token: 'mock-app-token' } }) + ); + expect(result).toEqual(mockResponse); + }); + }); + + describe('updateProject', () => { + it('should call putCall with updated data', async () => { + const data = { name: 'Updated Project' }; + const mockResponse = { status: 200, data: {} }; + mockPutCall.mockResolvedValue(mockResponse); + + const result = await updateProject(orgId, projectId, data); + expect(mockPutCall).toHaveBeenCalledWith( + `v2/org/${orgId}/project/${projectId}`, + data, + expect.objectContaining({ headers: { app_token: 'mock-app-token' } }) + ); + expect(result).toEqual(mockResponse); + }); + }); + + describe('deleteProject', () => { + it('should call deleteCall with the correct URL', async () => { + const mockResponse = { status: 204, data: {} }; + mockDeleteCall.mockResolvedValue(mockResponse); + + const result = await deleteProject(orgId, projectId); + expect(mockDeleteCall).toHaveBeenCalledWith( + `v2/org/${orgId}/project/${projectId}`, + expect.objectContaining({ headers: { app_token: 'mock-app-token' } }) + ); + expect(result).toEqual(mockResponse); + }); + }); + + describe('getMigratedStacks', () => { + it('should call getCall for migrated stacks', async () => { + const mockResponse = { status: 200, data: { stacks: [] } }; + mockGetCall.mockResolvedValue(mockResponse); + + const result = await getMigratedStacks(orgId, projectId); + expect(mockGetCall).toHaveBeenCalledWith( + `v2/org/${orgId}/project/${projectId}/get-migrated-stacks`, + expect.objectContaining({ headers: { app_token: 'mock-app-token' } }) + ); + expect(result).toEqual(mockResponse); + }); + }); + + describe('getAuditData', () => { + it('should call getCall with all parameters in the URL', async () => { + const mockResponse = { status: 200, data: { logs: [] } }; + mockGetCall.mockResolvedValue(mockResponse); + + const result = await getAuditData(orgId, projectId, 'stack-1', 'content_types', 0, 10, 0, 10, 'search', 'all'); + expect(mockGetCall).toHaveBeenCalledWith( + `v2/migration/get_audit_data/${orgId}/${projectId}/stack-1/content_types/0/10/0/10/search/all`, + expect.objectContaining({ headers: { app_token: 'mock-app-token' } }) + ); + expect(result).toEqual(mockResponse); + }); + + it('should throw with message when getCall throws an Error', async () => { + mockGetCall.mockImplementation(() => { throw new Error('fail'); }); + await expect(getAuditData(orgId, projectId, 's', 'm', 0, 10, 0, 10, '', 'all')) + .rejects.toThrow('Error in fetching audit data: fail'); + }); + + it('should throw generic message when getCall throws a non-Error', async () => { + mockGetCall.mockImplementation(() => { throw null; }); + await expect(getAuditData(orgId, projectId, 's', 'm', 0, 10, 0, 10, '', 'all')) + .rejects.toThrow('Unknown error in fetching audit data'); + }); + }); + + describe('error handling for all project service functions', () => { + it('getAllProjects should throw on Error', async () => { + mockGetCall.mockImplementation(() => { throw new Error('err'); }); + await expect(getAllProjects(orgId)).rejects.toThrow('Error in userSession: err'); + }); + + it('getAllProjects should throw generic on non-Error', async () => { + mockGetCall.mockImplementation(() => { throw 0; }); + await expect(getAllProjects(orgId)).rejects.toThrow('Unknown error in userSession'); + }); + + it('getProject should throw on Error', async () => { + mockGetCall.mockImplementation(() => { throw new Error('err'); }); + await expect(getProject(orgId, projectId)).rejects.toThrow('Error in userSession: err'); + }); + + it('getProject should throw generic on non-Error', async () => { + mockGetCall.mockImplementation(() => { throw 0; }); + await expect(getProject(orgId, projectId)).rejects.toThrow('Unknown error in userSession'); + }); + + it('createProject should throw on Error', async () => { + mockPostCall.mockImplementation(() => { throw new Error('err'); }); + await expect(createProject(orgId, {})).rejects.toThrow('Error in userSession: err'); + }); + + it('createProject should throw generic on non-Error', async () => { + mockPostCall.mockImplementation(() => { throw 0; }); + await expect(createProject(orgId, {})).rejects.toThrow('Unknown error in userSession'); + }); + + it('updateProject should throw on Error', async () => { + mockPutCall.mockImplementation(() => { throw new Error('err'); }); + await expect(updateProject(orgId, projectId, {})).rejects.toThrow('Error in userSession: err'); + }); + + it('updateProject should throw generic on non-Error', async () => { + mockPutCall.mockImplementation(() => { throw 0; }); + await expect(updateProject(orgId, projectId, {})).rejects.toThrow('Unknown error in userSession'); + }); + + it('deleteProject should throw on Error', async () => { + mockDeleteCall.mockImplementation(() => { throw new Error('err'); }); + await expect(deleteProject(orgId, projectId)).rejects.toThrow('Error in userSession: err'); + }); + + it('deleteProject should throw generic on non-Error', async () => { + mockDeleteCall.mockImplementation(() => { throw 0; }); + await expect(deleteProject(orgId, projectId)).rejects.toThrow('Unknown error in userSession'); + }); + + it('getMigratedStacks should throw on Error', async () => { + mockGetCall.mockImplementation(() => { throw new Error('err'); }); + await expect(getMigratedStacks(orgId, projectId)).rejects.toThrow('Error in userSession: err'); + }); + + it('getMigratedStacks should throw generic on non-Error', async () => { + mockGetCall.mockImplementation(() => { throw 0; }); + await expect(getMigratedStacks(orgId, projectId)).rejects.toThrow('Unknown error in userSession'); + }); + }); +}); diff --git a/ui/tests/unit/services/service.test.ts b/ui/tests/unit/services/service.test.ts new file mode 100644 index 000000000..dd147d8f3 --- /dev/null +++ b/ui/tests/unit/services/service.test.ts @@ -0,0 +1,141 @@ +import { describe, it, expect, vi, beforeEach } from 'vitest'; +import axios from 'axios'; +import { getCall, postCall, putCall, deleteCall, patchCall } from '../../../src/services/api/service'; + +vi.mock('axios'); +vi.mock('../../../src/utilities/constants', () => ({ + BASE_API_URL: 'http://localhost:5001/' +})); + +const mockedAxios = vi.mocked(axios); + +describe('services/api/service', () => { + beforeEach(() => { + vi.clearAllMocks(); + }); + + describe('getCall', () => { + it('should make a GET request with the correct URL', async () => { + const mockResponse = { data: { message: 'ok' }, status: 200 }; + mockedAxios.get.mockResolvedValue(mockResponse); + + const result = await getCall('v2/test'); + expect(mockedAxios.get).toHaveBeenCalledWith('http://localhost:5001/v2/test', {}); + expect(result).toEqual(mockResponse); + }); + + it('should pass options to axios', async () => { + const mockResponse = { data: {}, status: 200 }; + mockedAxios.get.mockResolvedValue(mockResponse); + const options = { headers: { Authorization: 'Bearer token' } }; + + await getCall('v2/test', options); + expect(mockedAxios.get).toHaveBeenCalledWith('http://localhost:5001/v2/test', options); + }); + + it('should return error response on failure', async () => { + const errorResponse = { status: 500, data: { message: 'Server Error' } }; + mockedAxios.get.mockRejectedValue({ response: errorResponse }); + + const result = await getCall('v2/test'); + expect(result).toEqual(errorResponse); + }); + }); + + describe('postCall', () => { + it('should make a POST request with data', async () => { + const mockResponse = { data: { id: 1 }, status: 201 }; + mockedAxios.post.mockResolvedValue(mockResponse); + const data = { name: 'test' }; + + const result = await postCall('v2/test', data); + expect(mockedAxios.post).toHaveBeenCalledWith('http://localhost:5001/v2/test', data, undefined); + expect(result).toEqual(mockResponse); + }); + + it('should pass options to axios', async () => { + mockedAxios.post.mockResolvedValue({ data: {}, status: 200 }); + const options = { headers: { 'Content-Type': 'application/json' } }; + + await postCall('v2/test', { key: 'val' }, options); + expect(mockedAxios.post).toHaveBeenCalledWith( + 'http://localhost:5001/v2/test', + { key: 'val' }, + options + ); + }); + + it('should return error response on failure', async () => { + const errorResponse = { status: 400, data: { message: 'Bad Request' } }; + mockedAxios.post.mockRejectedValue({ response: errorResponse }); + + const result = await postCall('v2/test', {}); + expect(result).toEqual(errorResponse); + }); + }); + + describe('putCall', () => { + it('should make a PUT request with data', async () => { + const mockResponse = { data: {}, status: 200 }; + mockedAxios.put.mockResolvedValue(mockResponse); + + const result = await putCall('v2/test', { updated: true }); + expect(mockedAxios.put).toHaveBeenCalledWith( + 'http://localhost:5001/v2/test', + { updated: true }, + undefined + ); + expect(result).toEqual(mockResponse); + }); + + it('should return error response on failure', async () => { + const errorResponse = { status: 500, data: {} }; + mockedAxios.put.mockRejectedValue({ response: errorResponse }); + + const result = await putCall('v2/test', {}); + expect(result).toEqual(errorResponse); + }); + }); + + describe('deleteCall', () => { + it('should make a DELETE request', async () => { + const mockResponse = { data: {}, status: 204 }; + mockedAxios.delete.mockResolvedValue(mockResponse); + + const result = await deleteCall('v2/test'); + expect(mockedAxios.delete).toHaveBeenCalledWith('http://localhost:5001/v2/test', undefined); + expect(result).toEqual(mockResponse); + }); + + it('should return error response on failure', async () => { + const errorResponse = { status: 404, data: {} }; + mockedAxios.delete.mockRejectedValue({ response: errorResponse }); + + const result = await deleteCall('v2/test'); + expect(result).toEqual(errorResponse); + }); + }); + + describe('patchCall', () => { + it('should make a PATCH request with data', async () => { + const mockResponse = { data: {}, status: 200 }; + mockedAxios.patch.mockResolvedValue(mockResponse); + + const result = await patchCall('v2/test', { patched: true }); + expect(mockedAxios.patch).toHaveBeenCalledWith( + 'http://localhost:5001/v2/test', + { patched: true }, + undefined + ); + expect(result).toEqual(mockResponse); + }); + + it('should return error response on failure', async () => { + const errorResponse = { status: 422, data: {} }; + mockedAxios.patch.mockRejectedValue({ response: errorResponse }); + + const result = await patchCall('v2/test', {}); + expect(result).toEqual(errorResponse); + }); + }); +}); diff --git a/ui/tests/unit/services/stacks.service.test.ts b/ui/tests/unit/services/stacks.service.test.ts new file mode 100644 index 000000000..1504552c1 --- /dev/null +++ b/ui/tests/unit/services/stacks.service.test.ts @@ -0,0 +1,143 @@ +import { describe, it, expect, vi, beforeEach } from 'vitest'; + +const { mockGetCall, mockPostCall } = vi.hoisted(() => ({ + mockGetCall: vi.fn(), + mockPostCall: vi.fn() +})); + +vi.mock('../../../src/services/api/service', () => ({ + getCall: mockGetCall, + postCall: mockPostCall +})); + +vi.mock('../../../src/utilities/constants', () => ({ + API_VERSION: 'v2', + BASE_API_URL: 'http://localhost:5001/', + TOKEN_KEY: 'access_token', + TOKEN: null, + HEADERS: {} +})); + +vi.mock('../../../src/utilities/functions', () => ({ + getDataFromLocalStorage: vi.fn(() => 'mock-app-token') +})); + +import { + getAllStacksInOrg, + createStacksInOrg, + getStackStatus, + getStackLocales +} from '../../../src/services/api/stacks.service'; + +describe('services/api/stacks.service', () => { + const orgId = 'org-123'; + + beforeEach(() => { + vi.clearAllMocks(); + }); + + describe('getAllStacksInOrg', () => { + it('should call getCall with org and search text', async () => { + const mockResponse = { status: 200, data: { stacks: [] } }; + mockGetCall.mockResolvedValue(mockResponse); + + const result = await getAllStacksInOrg(orgId, 'test'); + expect(mockGetCall).toHaveBeenCalledWith( + 'v2/org/org-123/stacks/test?', + expect.objectContaining({ headers: { app_token: 'mock-app-token' } }) + ); + expect(result).toEqual(mockResponse); + }); + + it('should return error on failure', async () => { + const error = new Error('Network error'); + mockGetCall.mockRejectedValue(error); + + const result = await getAllStacksInOrg(orgId, ''); + expect(result).toEqual(error); + }); + }); + + describe('createStacksInOrg', () => { + it('should call postCall with stack data', async () => { + const stackData = { name: 'New Stack', master_locale: 'en-us' }; + const mockResponse = { status: 201, data: { stack: stackData } }; + mockPostCall.mockResolvedValue(mockResponse); + + const result = await createStacksInOrg(orgId, stackData as any); + expect(mockPostCall).toHaveBeenCalledWith( + 'v2/org/org-123/stacks', + stackData, + expect.objectContaining({ headers: { app_token: 'mock-app-token' } }) + ); + expect(result).toEqual(mockResponse); + }); + }); + + describe('getStackStatus', () => { + it('should call postCall with stack_api_key', async () => { + const mockResponse = { status: 200, data: { status: 'active' } }; + mockPostCall.mockResolvedValue(mockResponse); + + const result = await getStackStatus(orgId, 'stack-api-key-123'); + expect(mockPostCall).toHaveBeenCalledWith( + 'v2/org/org-123/stack_status', + { stack_api_key: 'stack-api-key-123' }, + expect.objectContaining({ headers: { app_token: 'mock-app-token' } }) + ); + expect(result).toEqual(mockResponse); + }); + }); + + describe('getStackLocales', () => { + it('should call getCall with locales endpoint', async () => { + const mockResponse = { status: 200, data: { locales: ['en-us'] } }; + mockGetCall.mockResolvedValue(mockResponse); + + const result = await getStackLocales(orgId); + expect(mockGetCall).toHaveBeenCalledWith( + 'v2/org/org-123/locales', + expect.objectContaining({ headers: { app_token: 'mock-app-token' } }) + ); + expect(result).toEqual(mockResponse); + }); + + it('should throw with message on Error', async () => { + mockGetCall.mockImplementation(() => { throw new Error('Locale fetch failed'); }); + await expect(getStackLocales(orgId)).rejects.toThrow('Locale fetch failed'); + }); + + it('should throw generic message on non-Error', async () => { + mockGetCall.mockImplementation(() => { throw null; }); + await expect(getStackLocales(orgId)).rejects.toThrow('Unknown error'); + }); + }); + + describe('createStacksInOrg - error handling', () => { + it('should throw with message on Error', async () => { + mockPostCall.mockImplementation(() => { throw new Error('Create failed'); }); + await expect(createStacksInOrg(orgId, { name: 'x' } as any)) + .rejects.toThrow('Error in userSession: Create failed'); + }); + + it('should throw generic message on non-Error', async () => { + mockPostCall.mockImplementation(() => { throw 0; }); + await expect(createStacksInOrg(orgId, { name: 'x' } as any)) + .rejects.toThrow('Unknown error in userSession'); + }); + }); + + describe('getStackStatus - error handling', () => { + it('should throw with message on Error', async () => { + mockPostCall.mockImplementation(() => { throw new Error('Status failed'); }); + await expect(getStackStatus(orgId, 'key')) + .rejects.toThrow('Error in userSession: Status failed'); + }); + + it('should throw generic message on non-Error', async () => { + mockPostCall.mockImplementation(() => { throw 0; }); + await expect(getStackStatus(orgId, 'key')) + .rejects.toThrow('Unknown error in userSession'); + }); + }); +}); diff --git a/ui/tests/unit/services/upload.service.test.ts b/ui/tests/unit/services/upload.service.test.ts new file mode 100644 index 000000000..723d34a01 --- /dev/null +++ b/ui/tests/unit/services/upload.service.test.ts @@ -0,0 +1,221 @@ +import { describe, it, expect, vi, beforeEach } from 'vitest'; +import axios from 'axios'; + +vi.mock('axios'); + +vi.mock('../../../src/utilities/constants', () => ({ + UPLOAD_FILE_RELATIVE_URL: 'http://localhost:5002/', + BASE_API_URL: 'http://localhost:5001/', + TOKEN_KEY: 'access_token', + TOKEN: null, + HEADERS: {} +})); + +vi.mock('../../../src/utilities/functions', () => ({ + getDataFromLocalStorage: vi.fn(() => 'mock-app-token') +})); + +import { + getCall, + postCall, + putCall, + uploadFilePath, + fileValidation, + getConfig, + getRestrictedKeywords, + getLocales +} from '../../../src/services/api/upload.service'; + +const mockedAxios = vi.mocked(axios); + +describe('services/api/upload.service', () => { + beforeEach(() => { + vi.clearAllMocks(); + }); + + describe('getCall', () => { + it('should make a GET request and return response', async () => { + const mockResponse = { data: { result: true }, status: 200 }; + mockedAxios.get.mockResolvedValue(mockResponse); + + const result = await getCall('http://localhost:5002/config'); + expect(mockedAxios.get).toHaveBeenCalledWith('http://localhost:5002/config', {}); + expect(result).toEqual(mockResponse); + }); + + it('should return error response on failure', async () => { + const errorResponse = { status: 500, data: {} }; + mockedAxios.get.mockRejectedValue({ response: errorResponse }); + + const result = await getCall('http://localhost:5002/config'); + expect(result).toEqual(errorResponse); + }); + }); + + describe('postCall', () => { + it('should make a POST request and return response', async () => { + const mockResponse = { data: {}, status: 200 }; + mockedAxios.post.mockResolvedValue(mockResponse); + + const result = await postCall('http://localhost:5002/upload', { file: 'data' } as any); + expect(mockedAxios.post).toHaveBeenCalledWith( + 'http://localhost:5002/upload', + { file: 'data' }, + undefined + ); + expect(result).toEqual(mockResponse); + }); + + it('should throw error on failure', async () => { + mockedAxios.post.mockRejectedValue(new Error('Upload failed')); + + await expect(postCall('http://localhost:5002/upload', {} as any)).rejects.toThrow( + 'Upload failed' + ); + }); + }); + + describe('putCall', () => { + it('should make a PUT request and return response', async () => { + const mockResponse = { data: {}, status: 200 }; + mockedAxios.put.mockResolvedValue(mockResponse); + + const result = await putCall('http://localhost:5002/update', { data: 'test' } as any); + expect(mockedAxios.put).toHaveBeenCalledWith( + 'http://localhost:5002/update', + { data: 'test' }, + undefined + ); + expect(result).toEqual(mockResponse); + }); + + it('should return error response on failure', async () => { + const errorResponse = { status: 400, data: {} }; + mockedAxios.put.mockRejectedValue({ response: errorResponse }); + + const result = await putCall('http://localhost:5002/update', {} as any); + expect(result).toEqual(errorResponse); + }); + }); + + describe('uploadFilePath', () => { + it('should return the upload URL', () => { + expect(uploadFilePath()).toBe('http://localhost:5002/upload'); + }); + }); + + describe('fileValidation', () => { + it('should call getCall with validator endpoint and correct headers', async () => { + const mockResponse = { data: { valid: true }, status: 200 }; + mockedAxios.get.mockResolvedValue(mockResponse); + + const result = await fileValidation('proj-1', 'cs'); + expect(mockedAxios.get).toHaveBeenCalledWith( + 'http://localhost:5002/validator', + expect.objectContaining({ + headers: { + app_token: 'mock-app-token', + projectId: 'proj-1', + affix: 'cs' + } + }) + ); + expect(result).toEqual(mockResponse); + }); + + it('should use "cs" as default affix', async () => { + mockedAxios.get.mockResolvedValue({ data: {}, status: 200 }); + + await fileValidation('proj-1'); + expect(mockedAxios.get).toHaveBeenCalledWith( + 'http://localhost:5002/validator', + expect.objectContaining({ + headers: expect.objectContaining({ affix: 'cs' }) + }) + ); + }); + }); + + describe('getConfig', () => { + it('should call getCall with config endpoint', async () => { + const mockResponse = { data: { maxFileSize: 100 }, status: 200 }; + mockedAxios.get.mockResolvedValue(mockResponse); + + const result = await getConfig(); + expect(mockedAxios.get).toHaveBeenCalledWith('http://localhost:5002/config', {}); + expect(result).toEqual(mockResponse); + }); + }); + + describe('getRestrictedKeywords', () => { + it('should call getCall with contentstack API URL', async () => { + const mockResponse = { data: { keywords: [] }, status: 200 }; + mockedAxios.get.mockResolvedValue(mockResponse); + + const result = await getRestrictedKeywords(); + expect(mockedAxios.get).toHaveBeenCalledWith( + 'https://api.contentstack.io/v3/restricted_uids', + {} + ); + expect(result).toEqual(mockResponse); + }); + }); + + describe('getLocales', () => { + it('should call getCall with locales endpoint and auth headers', async () => { + const mockResponse = { data: { locales: [] }, status: 200 }; + mockedAxios.get.mockResolvedValue(mockResponse); + + const result = await getLocales('api-key-123'); + expect(mockedAxios.get).toHaveBeenCalledWith( + 'https://api.contentstack.io/v3/locales', + expect.objectContaining({ + headers: { + authtoken: 'mock-app-token', + api_key: 'api-key-123' + } + }) + ); + expect(result).toEqual(mockResponse); + }); + + it('should handle axios failure and return error response', async () => { + const errorResponse = { status: 500, data: { message: 'fail' } }; + mockedAxios.get.mockRejectedValue({ response: errorResponse }); + + const result = await getLocales('api-key-123'); + expect(result).toEqual(errorResponse); + }); + }); + + describe('error handling for upload service functions', () => { + it('postCall should throw generic on non-Error', async () => { + mockedAxios.post.mockRejectedValue('string-error'); + await expect(postCall('http://test', {} as any)).rejects.toThrow('Unknown error in userSession'); + }); + + it('fileValidation should return response on axios failure', async () => { + const errorResponse = { status: 400, data: { valid: false } }; + mockedAxios.get.mockRejectedValue({ response: errorResponse }); + + const result = await fileValidation('p'); + expect(result).toEqual(errorResponse); + }); + + it('getConfig should return error response on axios failure', async () => { + const errorResponse = { status: 500, data: {} }; + mockedAxios.get.mockRejectedValue({ response: errorResponse }); + + const result = await getConfig(); + expect(result).toEqual(errorResponse); + }); + + it('getRestrictedKeywords should return error response on axios failure', async () => { + const errorResponse = { status: 403, data: {} }; + mockedAxios.get.mockRejectedValue({ response: errorResponse }); + + const result = await getRestrictedKeywords(); + expect(result).toEqual(errorResponse); + }); + }); +}); diff --git a/ui/tests/unit/services/user.service.test.ts b/ui/tests/unit/services/user.service.test.ts new file mode 100644 index 000000000..e8d6919eb --- /dev/null +++ b/ui/tests/unit/services/user.service.test.ts @@ -0,0 +1,90 @@ +import { describe, it, expect, vi, beforeEach } from 'vitest'; + +const { mockGetCall } = vi.hoisted(() => ({ + mockGetCall: vi.fn() +})); + +vi.mock('../../../src/services/api/service', () => ({ + getCall: mockGetCall +})); + +vi.mock('../../../src/utilities/constants', () => ({ + API_VERSION: 'v2', + BASE_API_URL: 'http://localhost:5001/', + TOKEN_KEY: 'access_token', + TOKEN: null, + HEADERS: {} +})); + +vi.mock('../../../src/utilities/functions', () => ({ + getDataFromLocalStorage: vi.fn(() => 'mock-app-token') +})); + +import { getUser, getAllLocales } from '../../../src/services/api/user.service'; + +describe('services/api/user.service', () => { + beforeEach(() => { + vi.clearAllMocks(); + }); + + describe('getUser', () => { + it('should call getCall with user profile endpoint', async () => { + const mockResponse = { + status: 200, + data: { user: { email: 'test@example.com', first_name: 'Test' } } + }; + mockGetCall.mockResolvedValue(mockResponse); + + const result = await getUser(); + expect(mockGetCall).toHaveBeenCalledWith( + 'v2/user/profile', + expect.objectContaining({ headers: { app_token: 'mock-app-token' } }) + ); + expect(result).toEqual(mockResponse); + }); + + it('should return error response on failure', async () => { + const errorResponse = { status: 401, data: { message: 'Unauthorized' } }; + mockGetCall.mockResolvedValue(errorResponse); + + const result = await getUser(); + expect(result).toEqual(errorResponse); + }); + }); + + describe('getAllLocales', () => { + it('should call getCall with locales endpoint for the given org', async () => { + const mockResponse = { status: 200, data: { locales: ['en-us', 'fr-fr'] } }; + mockGetCall.mockResolvedValue(mockResponse); + + const result = await getAllLocales('org-123'); + expect(mockGetCall).toHaveBeenCalledWith( + 'v2/org/org-123/locales', + expect.objectContaining({ headers: { app_token: 'mock-app-token' } }) + ); + expect(result).toEqual(mockResponse); + }); + + it('should throw with message when getCall throws an Error', async () => { + mockGetCall.mockImplementation(() => { throw new Error('Timeout'); }); + await expect(getAllLocales('org-123')).rejects.toThrow('Error in userSession: Timeout'); + }); + + it('should throw generic message when getCall throws a non-Error', async () => { + mockGetCall.mockImplementation(() => { throw null; }); + await expect(getAllLocales('org-123')).rejects.toThrow('Unknown error in userSession'); + }); + }); + + describe('getUser - error handling', () => { + it('should throw with message when getCall throws an Error', async () => { + mockGetCall.mockImplementation(() => { throw new Error('Auth error'); }); + await expect(getUser()).rejects.toThrow('Error in userSession: Auth error'); + }); + + it('should throw generic message when getCall throws a non-Error', async () => { + mockGetCall.mockImplementation(() => { throw undefined; }); + await expect(getUser()).rejects.toThrow('Unknown error in userSession'); + }); + }); +}); diff --git a/ui/tests/unit/store/authMiddleware.test.ts b/ui/tests/unit/store/authMiddleware.test.ts new file mode 100644 index 000000000..39cfbf3a8 --- /dev/null +++ b/ui/tests/unit/store/authMiddleware.test.ts @@ -0,0 +1,53 @@ +import { describe, it, expect, vi, beforeEach } from 'vitest'; + +const { mockGetUserDetails } = vi.hoisted(() => ({ + mockGetUserDetails: vi.fn() +})); + +vi.mock('../../../src/store/slice/authSlice', () => ({ + getUserDetails: mockGetUserDetails, + default: (state = {}) => state +})); + +import authMiddleware from '../../../src/store/middleware/authMiddleware'; + +describe('store/middleware/authMiddleware', () => { + let dispatch: ReturnType; + let next: ReturnType; + + beforeEach(() => { + dispatch = vi.fn(); + next = vi.fn(); + vi.clearAllMocks(); + }); + + it('should dispatch getUserDetails on @@INIT action', () => { + const middleware = authMiddleware({ dispatch, getState: vi.fn() } as any)(next); + middleware({ type: '@@INIT' }); + + expect(dispatch).toHaveBeenCalledWith(mockGetUserDetails()); + }); + + it('should call next for any action', () => { + const middleware = authMiddleware({ dispatch, getState: vi.fn() } as any)(next); + const action = { type: 'SOME_ACTION' }; + + middleware(action); + expect(next).toHaveBeenCalledWith(action); + }); + + it('should not dispatch getUserDetails for non-INIT actions', () => { + const middleware = authMiddleware({ dispatch, getState: vi.fn() } as any)(next); + middleware({ type: 'OTHER_ACTION' }); + + expect(dispatch).not.toHaveBeenCalled(); + }); + + it('should always pass action to next', () => { + const middleware = authMiddleware({ dispatch, getState: vi.fn() } as any)(next); + const action = { type: '@@INIT' }; + + const result = middleware(action); + expect(next).toHaveBeenCalledWith(action); + }); +}); diff --git a/ui/tests/unit/store/authSlice.test.ts b/ui/tests/unit/store/authSlice.test.ts new file mode 100644 index 000000000..1c908c45c --- /dev/null +++ b/ui/tests/unit/store/authSlice.test.ts @@ -0,0 +1,187 @@ +import { describe, it, expect, vi, beforeEach } from 'vitest'; +import { configureStore } from '@reduxjs/toolkit'; + +vi.mock('../../../src/utilities/functions', () => ({ + getDataFromLocalStorage: vi.fn(() => null), + clearLocalStorage: vi.fn(() => true), + isEmptyString: vi.fn((str: string | undefined) => !str || str.trim().length < 1), + validateArray: vi.fn((arr: any[]) => Array.isArray(arr) && arr.length > 0) +})); + +vi.mock('../../../src/services/api/user.service', () => ({ + getUser: vi.fn() +})); + +import authReducer, { + setAuthToken, + reInitiliseState, + setOrganisationsList, + setSelectedOrganisation, + setUser, + clearOrganisationData, + clearAuthToken, + getUserDetails +} from '../../../src/store/slice/authSlice'; +import { getUser } from '../../../src/services/api/user.service'; + +const createTestStore = (preloadedState?: any) => + configureStore({ + reducer: { authentication: authReducer }, + preloadedState: preloadedState + ? { authentication: preloadedState } + : undefined + }); + +describe('store/slice/authSlice', () => { + describe('reducers', () => { + it('should return the initial state', () => { + const store = createTestStore(); + const state = store.getState().authentication; + + expect(state).toHaveProperty('authToken'); + expect(state).toHaveProperty('user'); + expect(state).toHaveProperty('isAuthenticated'); + expect(state).toHaveProperty('organisationsList'); + expect(state).toHaveProperty('selectedOrganisation'); + }); + + describe('setAuthToken', () => { + it('should set auth token and authentication flag', () => { + const store = createTestStore(); + store.dispatch(setAuthToken({ authToken: 'new-token', isAuthenticated: true })); + + const state = store.getState().authentication; + expect(state.authToken).toBe('new-token'); + expect(state.isAuthenticated).toBe(true); + }); + }); + + describe('setUser', () => { + it('should merge user data with existing state', () => { + const store = createTestStore(); + store.dispatch(setUser({ first_name: 'John', last_name: 'Doe' })); + + const state = store.getState().authentication; + expect(state.user.first_name).toBe('John'); + expect(state.user.last_name).toBe('Doe'); + }); + }); + + describe('reInitiliseState', () => { + it('should reset state to initial values', () => { + const store = createTestStore(); + store.dispatch(setAuthToken({ authToken: 'token', isAuthenticated: true })); + store.dispatch(reInitiliseState()); + + const state = store.getState().authentication; + expect(state.authToken).toBe(''); + expect(state.isAuthenticated).toBe(false); + expect(state.organisationsList).toEqual([]); + }); + }); + + describe('setOrganisationsList', () => { + it('should set the organisations list', () => { + const store = createTestStore(); + const orgs = [ + { uid: 'org-1', value: 'org-1', label: 'Org 1' }, + { uid: 'org-2', value: 'org-2', label: 'Org 2' } + ]; + store.dispatch(setOrganisationsList(orgs)); + + const state = store.getState().authentication; + expect(state.organisationsList).toEqual(orgs); + }); + }); + + describe('setSelectedOrganisation', () => { + it('should set the selected organisation', () => { + const store = createTestStore(); + const org = { uid: 'org-1', value: 'org-1', label: 'Org 1' }; + store.dispatch(setSelectedOrganisation(org)); + + const state = store.getState().authentication; + expect(state.selectedOrganisation).toEqual(org); + }); + }); + + describe('clearOrganisationData', () => { + it('should clear organisations list and selected organisation', () => { + const store = createTestStore(); + store.dispatch( + setOrganisationsList([{ uid: 'org-1', value: 'org-1', label: 'Org 1' }]) + ); + store.dispatch(clearOrganisationData()); + + const state = store.getState().authentication; + expect(state.organisationsList).toEqual([]); + expect(state.selectedOrganisation.value).toBe(''); + }); + }); + + describe('clearAuthToken', () => { + it('should clear auth token and set isAuthenticated to false', () => { + const store = createTestStore(); + store.dispatch(setAuthToken({ authToken: 'token', isAuthenticated: true })); + store.dispatch(clearAuthToken()); + + const state = store.getState().authentication; + expect(state.authToken).toBe(''); + expect(state.isAuthenticated).toBe(false); + }); + }); + }); + + describe('getUserDetails thunk', () => { + beforeEach(() => { + vi.clearAllMocks(); + }); + + it('should set user data on successful response', async () => { + const mockUser = { + email: 'test@example.com', + first_name: 'Test', + orgs: [ + { org_id: 'org-1', org_name: 'Test Org' } + ] + }; + vi.mocked(getUser).mockResolvedValue({ + status: 200, + data: { user: mockUser } + }); + + const store = createTestStore(); + await store.dispatch(getUserDetails()); + + const state = store.getState().authentication; + expect(state.user.email).toBe('test@example.com'); + expect(state.organisationsList).toHaveLength(1); + expect(state.organisationsList[0]).toEqual( + expect.objectContaining({ uid: 'org-1', label: 'Test Org' }) + ); + }); + + it('should reset state on 401 response', async () => { + vi.mocked(getUser).mockResolvedValue({ status: 401 }); + + const store = createTestStore(); + store.dispatch(setAuthToken({ authToken: 'old-token', isAuthenticated: true })); + await store.dispatch(getUserDetails()); + + const state = store.getState().authentication; + expect(state.authToken).toBe(''); + expect(state.isAuthenticated).toBe(false); + }); + + it('should reset state on error', async () => { + vi.mocked(getUser).mockRejectedValue(new Error('Network error')); + + const store = createTestStore(); + store.dispatch(setAuthToken({ authToken: 'old-token', isAuthenticated: true })); + await store.dispatch(getUserDetails()); + + const state = store.getState().authentication; + expect(state.authToken).toBe(''); + }); + }); +}); diff --git a/ui/tests/unit/store/networkSlice.test.ts b/ui/tests/unit/store/networkSlice.test.ts new file mode 100644 index 000000000..65019d73b --- /dev/null +++ b/ui/tests/unit/store/networkSlice.test.ts @@ -0,0 +1,61 @@ +import { describe, it, expect } from 'vitest'; +import { configureStore } from '@reduxjs/toolkit'; +import networkReducer, { + setOnline, + setOffline, + selectNetworkStatus +} from '../../../src/store/slice/networkSlice'; + +const createTestStore = () => + configureStore({ + reducer: { network: networkReducer } + }); + +describe('store/slice/networkSlice', () => { + describe('reducers', () => { + it('should return initial state with navigator.onLine', () => { + const store = createTestStore(); + const state = store.getState().network; + expect(state).toHaveProperty('isOnline'); + }); + + describe('setOnline', () => { + it('should set isOnline to true', () => { + const store = createTestStore(); + store.dispatch(setOffline()); + store.dispatch(setOnline()); + + expect(store.getState().network.isOnline).toBe(true); + }); + }); + + describe('setOffline', () => { + it('should set isOnline to false', () => { + const store = createTestStore(); + store.dispatch(setOffline()); + + expect(store.getState().network.isOnline).toBe(false); + }); + }); + }); + + describe('selectors', () => { + describe('selectNetworkStatus', () => { + it('should return the network online status', () => { + const store = createTestStore(); + store.dispatch(setOnline()); + + const state = store.getState(); + expect(selectNetworkStatus(state as any)).toBe(true); + }); + + it('should return false when offline', () => { + const store = createTestStore(); + store.dispatch(setOffline()); + + const state = store.getState(); + expect(selectNetworkStatus(state as any)).toBe(false); + }); + }); + }); +}); diff --git a/ui/tests/unit/store/storeIndex.test.ts b/ui/tests/unit/store/storeIndex.test.ts new file mode 100644 index 000000000..a58269d81 --- /dev/null +++ b/ui/tests/unit/store/storeIndex.test.ts @@ -0,0 +1,59 @@ +import { describe, it, expect, vi } from 'vitest'; + +vi.mock('../../../src/utilities/functions', () => ({ + getDataFromLocalStorage: vi.fn(() => null), + clearLocalStorage: vi.fn(() => true), + isEmptyString: vi.fn((str: string | undefined) => !str || str.trim().length < 1), + validateArray: vi.fn((arr: any[]) => Array.isArray(arr) && arr.length > 0) +})); + +vi.mock('../../../src/services/api/user.service', () => ({ + getUser: vi.fn() +})); + +import { store, persistor } from '../../../src/store/index'; +import type { RootState, AppDispatch } from '../../../src/store/index'; + +describe('store/index', () => { + it('should export a configured store', () => { + expect(store).toBeDefined(); + expect(store.getState).toBeDefined(); + expect(store.dispatch).toBeDefined(); + }); + + it('should export a persistor', () => { + expect(persistor).toBeDefined(); + }); + + it('should have all required reducer slices in state', () => { + const state = store.getState(); + expect(state).toHaveProperty('migration'); + expect(state).toHaveProperty('authentication'); + expect(state).toHaveProperty('network'); + }); + + it('should have the correct initial authentication state shape', () => { + const state = store.getState(); + expect(state.authentication).toHaveProperty('authToken'); + expect(state.authentication).toHaveProperty('user'); + expect(state.authentication).toHaveProperty('isAuthenticated'); + expect(state.authentication).toHaveProperty('organisationsList'); + expect(state.authentication).toHaveProperty('selectedOrganisation'); + }); + + it('should have the correct initial migration state shape', () => { + const state = store.getState(); + expect(state.migration).toHaveProperty('migrationData'); + expect(state.migration).toHaveProperty('newMigrationData'); + }); + + it('should have the correct initial network state shape', () => { + const state = store.getState(); + expect(state.network).toHaveProperty('isOnline'); + }); + + it('should allow dispatching actions', () => { + const dispatch: AppDispatch = store.dispatch; + expect(typeof dispatch).toBe('function'); + }); +}); diff --git a/ui/tests/unit/utilities/constants.test.ts b/ui/tests/unit/utilities/constants.test.ts new file mode 100644 index 000000000..ee2c057f0 --- /dev/null +++ b/ui/tests/unit/utilities/constants.test.ts @@ -0,0 +1,156 @@ +import { describe, it, expect } from 'vitest'; +import { + assetsRelativeUrl, + TOKEN_KEY, + API_VERSION, + AUTH_ROUTES, + LOGIN_SUCCESSFUL_MESSAGE, + TFA_MESSAGE, + TFA_VIA_SMS_MESSAGE, + API_METHOD, + REGIONS, + CS_URL, + HEADERS, + CS_ENTRIES, + PROJECT_STATUS, + NEW_PROJECT_STATUS, + CONTENT_MAPPING_STATUS, + STATUS_ICON_Mapping, + VALIDATION_DOCUMENTATION_URL, + auditLogsConstants, + HTTP_CODES, + EXECUTION_LOGS_UI_TEXT, + EXECUTION_LOGS_ERROR_TEXT +} from '../../../src/utilities/constants'; + +describe('utilities/constants', () => { + it('should export assetsRelativeUrl', () => { + expect(assetsRelativeUrl).toBe('v3/assets'); + }); + + it('should export TOKEN_KEY', () => { + expect(TOKEN_KEY).toBe('access_token'); + }); + + it('should export API_VERSION from env or default', () => { + expect(API_VERSION).toBeDefined(); + }); + + it('should export AUTH_ROUTES based on API_VERSION', () => { + expect(AUTH_ROUTES).toBe(`${API_VERSION}/auth`); + }); + + it('should export login messages', () => { + expect(LOGIN_SUCCESSFUL_MESSAGE).toBe('Login Successful.'); + expect(TFA_MESSAGE).toBe('Please login using the Two-Factor verification Token'); + expect(TFA_VIA_SMS_MESSAGE).toBe('Two-Factor Authentication Token sent via SMS.'); + }); + + it('should export API_METHOD with all HTTP methods', () => { + expect(API_METHOD).toEqual({ + GET: 'GET', + POST: 'POST', + PATCH: 'PATCH', + PUT: 'PUT', + DELETE: 'DELETE' + }); + }); + + it('should export all REGIONS', () => { + expect(REGIONS).toHaveProperty('NA'); + expect(REGIONS).toHaveProperty('EU'); + expect(REGIONS).toHaveProperty('AZURE_NA'); + expect(REGIONS).toHaveProperty('AZURE_EU'); + expect(REGIONS).toHaveProperty('GCP_NA'); + expect(REGIONS).toHaveProperty('GCP_EU'); + expect(REGIONS).toHaveProperty('AU'); + }); + + it('should export CS_URL with URLs for all regions', () => { + expect(CS_URL.NA).toContain('app.contentstack.com'); + expect(CS_URL.EU).toContain('eu-app.contentstack.com'); + expect(CS_URL.AU).toContain('au-app.contentstack.com'); + expect(Object.keys(CS_URL)).toHaveLength(7); + }); + + it('should export HEADERS with Content-Type', () => { + expect(HEADERS['Content-Type']).toBe('application/json'); + expect(HEADERS).toHaveProperty('Authorization'); + }); + + it('should export CS_ENTRIES with all content type keys', () => { + expect(CS_ENTRIES.HOME_PAGE).toBe('homepage'); + expect(CS_ENTRIES.LOGIN).toBe('login'); + expect(CS_ENTRIES.PROJECTS).toBe('projects'); + expect(CS_ENTRIES.LEGACY_CMS).toBe('legacy_cms'); + expect(CS_ENTRIES.DESTINATION_STACK).toBe('destination_stack'); + expect(CS_ENTRIES.CONTENT_MAPPING).toBe('content_mapping'); + expect(CS_ENTRIES.TEST_MIGRATION).toBe('test_migration'); + expect(CS_ENTRIES.MIGRATION_EXECUTION).toBe('migration_execution'); + expect(CS_ENTRIES.SETTING).toBe('settings'); + expect(CS_ENTRIES.ERROR_HANDLER).toBe('error_handler'); + expect(CS_ENTRIES.NOT_FOUND_ERROR).toEqual({ type: 'error_handler', url: '404' }); + expect(CS_ENTRIES.INTERNAL_SERVER_ERROR).toEqual({ type: 'error_handler', url: '500' }); + expect(CS_ENTRIES.ADD_STACK).toBe('add_stack'); + expect(CS_ENTRIES.UNMAPPED_LOCALE_KEY).toBe('undefined'); + }); + + it('should export PROJECT_STATUS with all statuses', () => { + expect(PROJECT_STATUS['0']).toBe('Draft'); + expect(PROJECT_STATUS['5']).toBe('Migration successful'); + expect(PROJECT_STATUS['6']).toBe('Migration terminated'); + }); + + it('should export NEW_PROJECT_STATUS with all statuses', () => { + expect(NEW_PROJECT_STATUS['0']).toBe('Draft'); + expect(NEW_PROJECT_STATUS['5']).toBe('Completed'); + expect(NEW_PROJECT_STATUS['6']).toBe('Failed'); + }); + + it('should export CONTENT_MAPPING_STATUS', () => { + expect(CONTENT_MAPPING_STATUS['1']).toBe('Mapped'); + expect(CONTENT_MAPPING_STATUS['2']).toBe('Updated'); + expect(CONTENT_MAPPING_STATUS['3']).toBe('Failed'); + expect(CONTENT_MAPPING_STATUS['4']).toBe('All'); + }); + + it('should export STATUS_ICON_Mapping', () => { + expect(STATUS_ICON_Mapping['1']).toBe('CheckedCircle'); + expect(STATUS_ICON_Mapping['2']).toBe('SuccessInverted'); + expect(STATUS_ICON_Mapping['3']).toBe('ErrorInverted'); + }); + + it('should export VALIDATION_DOCUMENTATION_URL', () => { + expect(VALIDATION_DOCUMENTATION_URL.sitecore).toContain('sitecore.pdf'); + expect(VALIDATION_DOCUMENTATION_URL.contentful).toContain('contentful.pdf'); + expect(VALIDATION_DOCUMENTATION_URL.wordpress).toBe(''); + expect(VALIDATION_DOCUMENTATION_URL.drupal).toContain('Drupal.pdf'); + expect(VALIDATION_DOCUMENTATION_URL.aem).toContain('AEM'); + }); + + it('should export auditLogsConstants', () => { + expect(auditLogsConstants.noLogs).toBe('No logs'); + expect(auditLogsConstants.noResult).toBe('No matching result found'); + expect(auditLogsConstants.placeholders.selectStack).toBe('Select Stack'); + expect(auditLogsConstants.filterModal.apply).toBe('Apply'); + }); + + it('should export HTTP_CODES', () => { + expect(HTTP_CODES.OK).toBe(200); + expect(HTTP_CODES.UNAUTHORIZED).toBe(401); + expect(HTTP_CODES.FORBIDDEN).toBe(403); + expect(HTTP_CODES.NOT_FOUND).toBe(404); + expect(HTTP_CODES.SERVER_ERROR).toBe(500); + expect(HTTP_CODES.UNPROCESSABLE_CONTENT).toBe(422); + }); + + it('should export EXECUTION_LOGS_UI_TEXT', () => { + expect(EXECUTION_LOGS_UI_TEXT.SEARCH_PLACEHOLDER).toBe('Search Execution Logs'); + expect(EXECUTION_LOGS_UI_TEXT.EMPTY_STATE_HEADING.NO_LOGS).toBe('No logs'); + expect(EXECUTION_LOGS_UI_TEXT.VIEW_LOG.VIEW_TEXT).toBe('View Log'); + }); + + it('should export EXECUTION_LOGS_ERROR_TEXT', () => { + expect(EXECUTION_LOGS_ERROR_TEXT.ERROR).toBe('Error in Getting Migration Logs'); + }); +}); diff --git a/ui/tests/unit/utilities/functions.test.ts b/ui/tests/unit/utilities/functions.test.ts new file mode 100644 index 000000000..28263c68d --- /dev/null +++ b/ui/tests/unit/utilities/functions.test.ts @@ -0,0 +1,431 @@ +import { describe, it, expect, vi, beforeEach } from 'vitest'; +import { + Locales, + getLocaleCode, + validateObject, + validateArray, + validateImage, + validateLink, + imageWithSiteDomainUrl, + addDomainInPath, + failtureNotification, + clearMarks, + clearMeasures, + extractWindowObj, + clearLocalStorage, + getDataFromLocalStorage, + setDataInLocalStorage, + getStateFromLocalStorage, + saveStateToLocalStorage, + getDays, + isEmptyString, + shortName, + returnFileSize, + isValidPrefix, + getFileExtension, + getSafeRouterPath +} from '../../../src/utilities/functions'; + +vi.mock('../../../src/utilities/constants', () => ({ + WEBSITE_BASE_URL: 'https://test.contentstack.com' +})); + +vi.mock('@contentstack/venus-components', () => ({ + Notification: vi.fn() +})); + +describe('utilities/functions', () => { + describe('Locales', () => { + it('should contain all supported locale mappings', () => { + expect(Locales.en).toBe('en-us'); + expect(Locales.fr).toBe('fr-fr'); + expect(Locales.de).toBe('de-de'); + expect(Locales.jp).toBe('ja-jp'); + expect(Locales.kr).toBe('ko-kr'); + expect(Locales.cn).toBe('zh-cn'); + expect(Locales.es).toBe('es-mx'); + expect(Locales.pt).toBe('pt-br'); + }); + }); + + describe('getLocaleCode', () => { + it('should return the locale code for a valid key', () => { + expect(getLocaleCode('en')).toBe('en-us'); + expect(getLocaleCode('fr')).toBe('fr-fr'); + }); + + it('should default to "en" when no argument is provided', () => { + expect(getLocaleCode()).toBe('en-us'); + }); + + it('should return undefined for an invalid key', () => { + expect(getLocaleCode('invalid')).toBeUndefined(); + }); + }); + + describe('validateObject', () => { + it('should return true for a non-empty object', () => { + expect(validateObject({ key: 'value' })).toBe(true); + }); + + it('should return false for an empty object', () => { + expect(validateObject({})).toBe(false); + }); + + it('should return false for an array', () => { + expect(validateObject([] as any)).toBe(false); + }); + }); + + describe('validateArray', () => { + it('should return true for a non-empty array', () => { + expect(validateArray([1, 2, 3])).toBe(true); + }); + + it('should return false for an empty array', () => { + expect(validateArray([])).toBe(false); + }); + + it('should return false for a non-array value', () => { + expect(validateArray('string' as any)).toBe(false); + }); + }); + + describe('validateImage', () => { + it('should return a truthy value when image has a url', () => { + expect(validateImage({ url: 'https://example.com/img.png' })).toBeTruthy(); + }); + + it('should return a falsy value when image has no url', () => { + expect(validateImage({ url: '' })).toBeFalsy(); + }); + + it('should return a falsy value for null/undefined', () => { + expect(validateImage(null as any)).toBeFalsy(); + expect(validateImage(undefined as any)).toBeFalsy(); + }); + }); + + describe('validateLink', () => { + it('should return a truthy value when link has a url', () => { + expect(validateLink({ url: 'https://example.com' })).toBeTruthy(); + }); + + it('should return a falsy value when link has no url', () => { + expect(validateLink({ url: '' })).toBeFalsy(); + }); + }); + + describe('imageWithSiteDomainUrl', () => { + it('should replace images.contentstack.io domain', () => { + const url = 'https://images.contentstack.io/v3/assets/image.png'; + const result = imageWithSiteDomainUrl(url); + expect(result).toBe('https://test.contentstack.com/v3/assets/image.png'); + }); + + it('should replace assets.contentstack.io domain', () => { + const url = 'https://assets.contentstack.io/v3/file.pdf'; + const result = imageWithSiteDomainUrl(url); + expect(result).toBe('https://test.contentstack.com/v3/file.pdf'); + }); + + it('should return the original url if no match', () => { + const url = 'https://other-domain.com/image.png'; + expect(imageWithSiteDomainUrl(url)).toBe(url); + }); + }); + + describe('failtureNotification', () => { + it('should call Notification with error type and message', async () => { + const { Notification } = await import('@contentstack/venus-components'); + failtureNotification('Something went wrong'); + + expect(Notification).toHaveBeenCalledWith( + expect.objectContaining({ + text: 'Something went wrong', + type: 'error' + }) + ); + }); + }); + + describe('addDomainInPath', () => { + it('should prepend WEBSITE_BASE_URL to the path', () => { + expect(addDomainInPath('/v3/assets/img.png')).toBe( + 'https://test.contentstack.com/v3/assets/img.png' + ); + }); + }); + + describe('clearMarks', () => { + it('should call performance.clearMarks with a specific name', () => { + const spy = vi.spyOn(performance, 'clearMarks'); + clearMarks('test-mark'); + expect(spy).toHaveBeenCalledWith('test-mark'); + }); + + it('should call performance.clearMarks without arguments when clearAll is true', () => { + const spy = vi.spyOn(performance, 'clearMarks'); + clearMarks('test-mark', true); + expect(spy).toHaveBeenCalledWith(); + }); + }); + + describe('clearMeasures', () => { + it('should call performance.clearMeasures with a specific name', () => { + const spy = vi.spyOn(performance, 'clearMeasures'); + clearMeasures('test-measure'); + expect(spy).toHaveBeenCalledWith('test-measure'); + }); + + it('should call performance.clearMeasures without arguments when clearAll is true', () => { + const spy = vi.spyOn(performance, 'clearMeasures'); + clearMeasures('test-measure', true); + expect(spy).toHaveBeenCalledWith(); + }); + }); + + describe('extractWindowObj', () => { + it('should extract script content containing window.sso', () => { + const html = ''; + expect(extractWindowObj(html)).toBe('window.sso = { token: "abc" };'); + }); + + it('should return null when no script tags are found', () => { + expect(extractWindowObj('no scripts here')).toBeNull(); + }); + + it('should return null when no window.sso script is found', () => { + const html = ''; + expect(extractWindowObj(html)).toBeNull(); + }); + + it('should handle multiple script tags and find the correct one', () => { + const html = + ''; + expect(extractWindowObj(html)).toBe('window.sso = { id: 123 };'); + }); + }); + + describe('localStorage helpers', () => { + describe('clearLocalStorage', () => { + it('should clear localStorage and return true', () => { + localStorage.setItem('key', 'value'); + expect(clearLocalStorage()).toBe(true); + expect(localStorage.length).toBe(0); + }); + }); + + describe('getDataFromLocalStorage', () => { + it('should return the value for an existing key', () => { + localStorage.setItem('testKey', 'testValue'); + expect(getDataFromLocalStorage('testKey')).toBe('testValue'); + }); + + it('should return null for a non-existent key', () => { + expect(getDataFromLocalStorage('nonExistent')).toBeNull(); + }); + }); + + describe('setDataInLocalStorage', () => { + it('should store data and return true', () => { + expect(setDataInLocalStorage('key', 'value')).toBe(true); + expect(localStorage.getItem('key')).toBe('value'); + }); + + it('should return false when localStorage.getItem returns null after setItem', () => { + const getItemSpy = vi.spyOn(Storage.prototype, 'getItem'); + const setItemSpy = vi.spyOn(Storage.prototype, 'setItem'); + + setItemSpy.mockImplementation(() => {}); + getItemSpy.mockReturnValue(null); + + expect(setDataInLocalStorage('key', 'value')).toBe(false); + + setItemSpy.mockRestore(); + getItemSpy.mockRestore(); + }); + }); + }); + + describe('sessionStorage helpers', () => { + describe('getStateFromLocalStorage', () => { + it('should parse and return stored JSON', () => { + sessionStorage.setItem('state', JSON.stringify({ active: true })); + expect(getStateFromLocalStorage('state')).toEqual({ active: true }); + }); + + it('should return null when key does not exist', () => { + expect(getStateFromLocalStorage('missing')).toBeNull(); + }); + }); + + describe('saveStateToLocalStorage', () => { + it('should save state as JSON string', () => { + saveStateToLocalStorage('state', { step1: true }); + expect(sessionStorage.getItem('state')).toBe(JSON.stringify({ step1: true })); + }); + }); + }); + + describe('getDays', () => { + it('should return "X seconds ago" for a recent date', () => { + const recent = new Date(Date.now() - 30 * 1000); + expect(getDays(recent)).toMatch(/seconds? ago/); + }); + + it('should return "X minutes ago" for dates within the last hour', () => { + const minsAgo = new Date(Date.now() - 10 * 60 * 1000); + expect(getDays(minsAgo)).toMatch(/minutes? ago/); + }); + + it('should return "X hours ago" for dates within the last day', () => { + const hoursAgo = new Date(Date.now() - 5 * 3600 * 1000); + expect(getDays(hoursAgo)).toMatch(/hours? ago/); + }); + + it('should return "X days ago" for dates within the last week', () => { + const daysAgo = new Date(Date.now() - 3 * 24 * 3600 * 1000); + expect(getDays(daysAgo)).toMatch(/days? ago/); + }); + + it('should return a formatted date for dates older than a week', () => { + const oldDate = new Date('2023-01-15'); + const result = getDays(oldDate); + expect(result).toMatch(/Jan/); + expect(result).toMatch(/2023/); + }); + }); + + describe('isEmptyString', () => { + it('should return true for undefined', () => { + expect(isEmptyString(undefined)).toBe(true); + }); + + it('should return true for an empty string', () => { + expect(isEmptyString('')).toBe(true); + }); + + it('should return true for a whitespace-only string', () => { + expect(isEmptyString(' ')).toBe(true); + }); + + it('should return false for a non-empty string', () => { + expect(isEmptyString('hello')).toBe(false); + }); + }); + + describe('shortName', () => { + it('should return the name unchanged if <= 25 characters', () => { + expect(shortName('short name')).toBe('short name'); + }); + + it('should truncate and add ellipsis for names > 25 characters', () => { + const longName = 'this-is-a-very-long-project-name-that-exceeds'; + const result = shortName(longName); + expect(result).toContain('...'); + expect(result.length).toBeLessThan(longName.length); + }); + + it('should return falsy for falsy input', () => { + expect(shortName('')).toBe(''); + }); + }); + + describe('returnFileSize', () => { + it('should return bytes for small files', () => { + expect(returnFileSize(500)).toBe('500bytes'); + }); + + it('should return KB for files >= 1024 bytes', () => { + expect(returnFileSize(2048)).toBe('2.0KB'); + }); + + it('should return MB for files >= 1MB', () => { + expect(returnFileSize(2097152)).toBe('2.0MB'); + }); + }); + + describe('isValidPrefix', () => { + it('should return true for valid prefixes (2-5 alpha chars)', () => { + expect(isValidPrefix('cs')).toBe(true); + expect(isValidPrefix('abc')).toBe(true); + expect(isValidPrefix('ABCDE')).toBe(true); + }); + + it('should return false for too short prefix', () => { + expect(isValidPrefix('a')).toBe(false); + }); + + it('should return false for too long prefix', () => { + expect(isValidPrefix('abcdef')).toBe(false); + }); + + it('should return false for prefix with numbers', () => { + expect(isValidPrefix('ab1')).toBe(false); + }); + + it('should return false for prefix with special characters', () => { + expect(isValidPrefix('ab!')).toBe(false); + }); + }); + + describe('getFileExtension', () => { + it('should extract json extension', () => { + expect(getFileExtension('/path/to/file.json')).toBe('json'); + }); + + it('should extract pdf extension', () => { + expect(getFileExtension('file.pdf')).toBe('pdf'); + }); + + it('should extract zip extension', () => { + expect(getFileExtension('archive.zip')).toBe('zip'); + }); + + it('should extract xml extension', () => { + expect(getFileExtension('data.xml')).toBe('xml'); + }); + + it('should return empty string for unsupported extensions', () => { + expect(getFileExtension('file.txt')).toBe(''); + }); + + it('should return empty string for files without extension', () => { + expect(getFileExtension('noextension')).toBe(''); + }); + + it('should handle backslash paths', () => { + expect(getFileExtension('C:\\path\\to\\file.json')).toBe('json'); + }); + + it('should be case insensitive', () => { + expect(getFileExtension('file.JSON')).toBe('json'); + }); + }); + + describe('getSafeRouterPath', () => { + it('should return pathname only by default', () => { + const location = { pathname: '/projects', search: '?id=1', hash: '#top' }; + expect(getSafeRouterPath(location)).toBe('/projects'); + }); + + it('should return full path when includeSearchAndHash is true', () => { + const location = { pathname: '/projects', search: '?id=1', hash: '#section' }; + expect(getSafeRouterPath(location, true)).toBe('/projects?id=1#section'); + }); + + it('should default pathname to "/" when missing', () => { + expect(getSafeRouterPath({} as any)).toBe('/'); + }); + + it('should handle missing search and hash gracefully', () => { + const location = { pathname: '/home' }; + expect(getSafeRouterPath(location, true)).toBe('/home'); + }); + + it('should handle null location', () => { + expect(getSafeRouterPath(null as any)).toBe('/'); + }); + }); +}); diff --git a/ui/tests/unit/utilities/projectDBMapper.test.ts b/ui/tests/unit/utilities/projectDBMapper.test.ts new file mode 100644 index 000000000..01c824590 --- /dev/null +++ b/ui/tests/unit/utilities/projectDBMapper.test.ts @@ -0,0 +1,86 @@ +import { describe, it, expect } from 'vitest'; +import { createObject } from '../../../src/utilities/projectDBMapper'; +import { createMockProject } from '../../fixtures/user.fixture'; + +describe('utilities/projectDBMapper', () => { + describe('createObject', () => { + it('should map project data to the correct structure', () => { + const projectData = createMockProject(); + const result = createObject(projectData); + + expect(result).toHaveProperty('legacy_cms'); + expect(result).toHaveProperty('destination_stack'); + expect(result).toHaveProperty('content_mapping'); + expect(result).toHaveProperty('stackDetails'); + expect(result).toHaveProperty('mapperKeys'); + }); + + it('should correctly map legacy_cms fields', () => { + const projectData = createMockProject(); + const result = createObject(projectData); + + expect(result.legacy_cms.selectedCms.cms_id).toBe('wordpress'); + expect(result.legacy_cms.selectedCms.allowed_file_formats).toEqual(['json']); + expect(result.legacy_cms.affix).toBe('cs'); + expect(result.legacy_cms.file_format).toBe('json'); + }); + + it('should correctly map uploadedFile details', () => { + const projectData = createMockProject(); + const result = createObject(projectData); + + expect(result.legacy_cms.uploadedFile.file_details.localPath).toBe('/path/to/file'); + expect(result.legacy_cms.uploadedFile.file_details.isLocalPath).toBe(true); + expect(result.legacy_cms.uploadedFile.isValidated).toBe(true); + }); + + it('should correctly map AWS details', () => { + const projectData = createMockProject(); + const result = createObject(projectData); + + const awsData = result.legacy_cms.uploadedFile.file_details.awsData; + expect(awsData.awsRegion).toBe('us-east-1'); + expect(awsData.bucketName).toBe('test-bucket'); + expect(awsData.bucketKey).toBe('test-key'); + }); + + it('should correctly map destination_stack fields', () => { + const projectData = createMockProject(); + const result = createObject(projectData); + + expect(result.destination_stack.selectedOrg.value).toBe('org-123'); + expect(result.destination_stack.selectedOrg.label).toBe('Test Org'); + expect(result.destination_stack.selectedStack.value).toBe('stack-123'); + expect(result.destination_stack.selectedStack.label).toBe('Test Stack'); + expect(result.destination_stack.selectedStack.master_locale).toBe('en-us'); + }); + + it('should correctly map stackArray', () => { + const projectData = createMockProject(); + const result = createObject(projectData); + + expect(result.destination_stack.stackArray.value).toBe('stack-123'); + expect(result.destination_stack.stackArray.created_at).toBe('2024-01-01'); + }); + + it('should handle missing/undefined project data gracefully', () => { + const result = createObject({}); + + expect(result.legacy_cms.selectedCms.cms_id).toBeUndefined(); + expect(result.legacy_cms.affix).toBeUndefined(); + expect(result.destination_stack.selectedOrg.value).toBeUndefined(); + expect(result.content_mapping).toBeUndefined(); + }); + + it('should pass through content_mapping and mapperKeys directly', () => { + const projectData = createMockProject({ + content_mapping: { ct1: 'mapped1' }, + mapperKeys: { key1: 'val1' } + }); + const result = createObject(projectData); + + expect(result.content_mapping).toEqual({ ct1: 'mapped1' }); + expect(result.mapperKeys).toEqual({ key1: 'val1' }); + }); + }); +}); diff --git a/ui/vitest.config.ts b/ui/vitest.config.ts new file mode 100644 index 000000000..c841b02fa --- /dev/null +++ b/ui/vitest.config.ts @@ -0,0 +1,40 @@ +import { defineConfig } from 'vitest/config'; +import react from '@vitejs/plugin-react-swc'; +import tsconfigPaths from 'vite-tsconfig-paths'; + +export default defineConfig({ + plugins: [react(), tsconfigPaths()], + test: { + globals: true, + environment: 'jsdom', + setupFiles: ['./tests/setup.ts'], + coverage: { + provider: 'v8', + reporter: ['text', 'lcov', 'html'], + include: ['src/**/*.ts', 'src/**/*.tsx'], + exclude: [ + '**/node_modules/**', + '**/tests/**', + 'src/index.tsx', + 'src/App.tsx', + 'src/vite-env.d.ts', + 'src/setupTests.js', + 'src/**/*.interface.ts', + 'src/**/*.d.ts', + 'src/types/**', + 'src/scss/**', + 'src/common/assets/**', + 'src/pages/**', + 'src/components/**', + 'src/context/app/app.context.tsx', + 'src/context/app/app.provider.tsx', + ], + thresholds: { + lines: 80, + functions: 80, + branches: 60, + statements: 80, + }, + }, + }, +}); diff --git a/upload-api/package-lock.json b/upload-api/package-lock.json index b92c93ee4..453e1aea3 100644 --- a/upload-api/package-lock.json +++ b/upload-api/package-lock.json @@ -54,13 +54,15 @@ "@types/wordpress__blocks": "^12.5.18", "@types/xml2js": "^0.4.14", "@typescript-eslint/eslint-plugin": "^8.0.0", + "@vitest/coverage-v8": "^4.0.18", "eslint": "^9.0.0", "eslint-config-prettier": "^9.1.0", "eslint-plugin-prettier": "^5.1.3", "nodemon": "^3.1.10", "rimraf": "^6.0.1", "ts-node": "^10.9.2", - "typescript": "^5.3.3" + "typescript": "^5.3.3", + "vitest": "^4.0.18" } }, "migration-aem": { @@ -1681,6 +1683,16 @@ } } }, + "node_modules/@bcoe/v8-coverage": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-1.0.2.tgz", + "integrity": "sha512-6zABk/ECA/QYSCQ1NGiVwwbQerUCZ+TQbp64Q3AgmfNvurHH0j8TtXa1qbShXA6qqkpAj4V5W8pP6mLe1mcMqA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, "node_modules/@colors/colors": { "version": "1.6.0", "license": "MIT", @@ -1998,8 +2010,451 @@ "resolved": "https://registry.npmjs.org/@emotion/weak-memoize/-/weak-memoize-0.4.0.tgz", "integrity": "sha512-snKqtPW01tN0ui7yu9rGv69aJXr/a/Ywvl11sUjNtEcRc+ng/mQriFL0wLXMef74iHa/EkftbDzU9F8iFbH+zg==" }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.27.3.tgz", + "integrity": "sha512-9fJMTNFTWZMh5qwrBItuziu834eOCUcEqymSH7pY+zoMVEZg3gcPuBNxH1EvfVYe9h0x/Ptw8KBzv7qxb7l8dg==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.27.3.tgz", + "integrity": "sha512-i5D1hPY7GIQmXlXhs2w8AWHhenb00+GxjxRncS2ZM7YNVGNfaMxgzSGuO8o8SJzRc/oZwU2bcScvVERk03QhzA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.27.3.tgz", + "integrity": "sha512-YdghPYUmj/FX2SYKJ0OZxf+iaKgMsKHVPF1MAq/P8WirnSpCStzKJFjOjzsW0QQ7oIAiccHdcqjbHmJxRb/dmg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.27.3.tgz", + "integrity": "sha512-IN/0BNTkHtk8lkOM8JWAYFg4ORxBkZQf9zXiEOfERX/CzxW3Vg1ewAhU7QSWQpVIzTW+b8Xy+lGzdYXV6UZObQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.27.3.tgz", + "integrity": "sha512-Re491k7ByTVRy0t3EKWajdLIr0gz2kKKfzafkth4Q8A5n1xTHrkqZgLLjFEHVD+AXdUGgQMq+Godfq45mGpCKg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.27.3.tgz", + "integrity": "sha512-vHk/hA7/1AckjGzRqi6wbo+jaShzRowYip6rt6q7VYEDX4LEy1pZfDpdxCBnGtl+A5zq8iXDcyuxwtv3hNtHFg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.27.3.tgz", + "integrity": "sha512-ipTYM2fjt3kQAYOvo6vcxJx3nBYAzPjgTCk7QEgZG8AUO3ydUhvelmhrbOheMnGOlaSFUoHXB6un+A7q4ygY9w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.27.3.tgz", + "integrity": "sha512-dDk0X87T7mI6U3K9VjWtHOXqwAMJBNN2r7bejDsc+j03SEjtD9HrOl8gVFByeM0aJksoUuUVU9TBaZa2rgj0oA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.27.3.tgz", + "integrity": "sha512-s6nPv2QkSupJwLYyfS+gwdirm0ukyTFNl3KTgZEAiJDd+iHZcbTPPcWCcRYH+WlNbwChgH2QkE9NSlNrMT8Gfw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.27.3.tgz", + "integrity": "sha512-sZOuFz/xWnZ4KH3YfFrKCf1WyPZHakVzTiqji3WDc0BCl2kBwiJLCXpzLzUBLgmp4veFZdvN5ChW4Eq/8Fc2Fg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.27.3.tgz", + "integrity": "sha512-yGlQYjdxtLdh0a3jHjuwOrxQjOZYD/C9PfdbgJJF3TIZWnm/tMd/RcNiLngiu4iwcBAOezdnSLAwQDPqTmtTYg==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.27.3.tgz", + "integrity": "sha512-WO60Sn8ly3gtzhyjATDgieJNet/KqsDlX5nRC5Y3oTFcS1l0KWba+SEa9Ja1GfDqSF1z6hif/SkpQJbL63cgOA==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.27.3.tgz", + "integrity": "sha512-APsymYA6sGcZ4pD6k+UxbDjOFSvPWyZhjaiPyl/f79xKxwTnrn5QUnXR5prvetuaSMsb4jgeHewIDCIWljrSxw==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.27.3.tgz", + "integrity": "sha512-eizBnTeBefojtDb9nSh4vvVQ3V9Qf9Df01PfawPcRzJH4gFSgrObw+LveUyDoKU3kxi5+9RJTCWlj4FjYXVPEA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.27.3.tgz", + "integrity": "sha512-3Emwh0r5wmfm3ssTWRQSyVhbOHvqegUDRd0WhmXKX2mkHJe1SFCMJhagUleMq+Uci34wLSipf8Lagt4LlpRFWQ==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.27.3.tgz", + "integrity": "sha512-pBHUx9LzXWBc7MFIEEL0yD/ZVtNgLytvx60gES28GcWMqil8ElCYR4kvbV2BDqsHOvVDRrOxGySBM9Fcv744hw==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.27.3.tgz", + "integrity": "sha512-Czi8yzXUWIQYAtL/2y6vogER8pvcsOsk5cpwL4Gk5nJqH5UZiVByIY8Eorm5R13gq+DQKYg0+JyQoytLQas4dA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.27.3.tgz", + "integrity": "sha512-sDpk0RgmTCR/5HguIZa9n9u+HVKf40fbEUt+iTzSnCaGvY9kFP0YKBWZtJaraonFnqef5SlJ8/TiPAxzyS+UoA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.27.3.tgz", + "integrity": "sha512-P14lFKJl/DdaE00LItAukUdZO5iqNH7+PjoBm+fLQjtxfcfFE20Xf5CrLsmZdq5LFFZzb5JMZ9grUwvtVYzjiA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.27.3.tgz", + "integrity": "sha512-AIcMP77AvirGbRl/UZFTq5hjXK+2wC7qFRGoHSDrZ5v5b8DK/GYpXW3CPRL53NkvDqb9D+alBiC/dV0Fb7eJcw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.27.3.tgz", + "integrity": "sha512-DnW2sRrBzA+YnE70LKqnM3P+z8vehfJWHXECbwBmH/CU51z6FiqTQTHFenPlHmo3a8UgpLyH3PT+87OViOh1AQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openharmony-arm64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.27.3.tgz", + "integrity": "sha512-NinAEgr/etERPTsZJ7aEZQvvg/A6IsZG/LgZy+81wON2huV7SrK3e63dU0XhyZP4RKGyTm7aOgmQk0bGp0fy2g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.27.3.tgz", + "integrity": "sha512-PanZ+nEz+eWoBJ8/f8HKxTTD172SKwdXebZ0ndd953gt1HRBbhMsaNqjTyYLGLPdoWHy4zLU7bDVJztF5f3BHA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.27.3.tgz", + "integrity": "sha512-B2t59lWWYrbRDw/tjiWOuzSsFh1Y/E95ofKz7rIVYSQkUYBjfSgf6oeYPNWHToFRr2zx52JKApIcAS/D5TUBnA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.27.3.tgz", + "integrity": "sha512-QLKSFeXNS8+tHW7tZpMtjlNb7HKau0QDpwm49u0vUp9y1WOF+PEzkU84y9GqYaAVW8aH8f3GcBck26jh54cX4Q==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.27.3.tgz", + "integrity": "sha512-4uJGhsxuptu3OcpVAzli+/gWusVGwZZHTlS63hh++ehExkVT8SgiEf7/uC/PclrPPkLhZqGgCTjd0VWLo6xMqA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, "node_modules/@eslint-community/eslint-utils": { "version": "4.9.1", + "dev": true, "license": "MIT", "dependencies": { "eslint-visitor-keys": "^3.4.3" @@ -2016,6 +2471,7 @@ }, "node_modules/@eslint-community/regexpp": { "version": "4.12.2", + "dev": true, "license": "MIT", "engines": { "node": "^12.0.0 || ^14.0.0 || >=16.0.0" @@ -2025,6 +2481,7 @@ "version": "0.21.1", "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.21.1.tgz", "integrity": "sha512-aw1gNayWpdI/jSYVgzN5pL0cfzU02GT3NBpeT/DXbx1/1x7ZKxFPd9bwrzygx/qiwIQiJ1sw/zD8qY/kRvlGHA==", + "dev": true, "license": "Apache-2.0", "dependencies": { "@eslint/object-schema": "^2.1.7", @@ -2039,6 +2496,7 @@ "version": "1.1.12", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "dev": true, "license": "MIT", "dependencies": { "balanced-match": "^1.0.0", @@ -2049,6 +2507,7 @@ "version": "3.1.2", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, "license": "ISC", "dependencies": { "brace-expansion": "^1.1.7" @@ -2061,6 +2520,7 @@ "version": "0.4.2", "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.4.2.tgz", "integrity": "sha512-gBrxN88gOIf3R7ja5K9slwNayVcZgK6SOUORm2uBzTeIEfeVaIhOpCtTox3P6R7o2jLFwLFTLnC7kU/RGcYEgw==", + "dev": true, "license": "Apache-2.0", "dependencies": { "@eslint/core": "^0.17.0" @@ -2073,6 +2533,7 @@ "version": "0.17.0", "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.17.0.tgz", "integrity": "sha512-yL/sLrpmtDaFEiUj1osRP4TI2MDz1AddJL+jZ7KSqvBuliN4xqYY54IfdN8qD8Toa6g1iloph1fxQNkjOxrrpQ==", + "dev": true, "license": "Apache-2.0", "dependencies": { "@types/json-schema": "^7.0.15" @@ -2085,6 +2546,7 @@ "version": "3.3.3", "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.3.3.tgz", "integrity": "sha512-Kr+LPIUVKz2qkx1HAMH8q1q6azbqBAsXJUxBl/ODDuVPX45Z9DfwB8tPjTi6nNZ8BuM3nbJxC5zCAg5elnBUTQ==", + "dev": true, "license": "MIT", "dependencies": { "ajv": "^6.12.4", @@ -2108,6 +2570,7 @@ "version": "6.12.6", "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, "license": "MIT", "dependencies": { "fast-deep-equal": "^3.1.1", @@ -2124,6 +2587,7 @@ "version": "1.1.12", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "dev": true, "license": "MIT", "dependencies": { "balanced-match": "^1.0.0", @@ -2134,6 +2598,7 @@ "version": "5.3.2", "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", + "dev": true, "license": "MIT", "engines": { "node": ">= 4" @@ -2143,12 +2608,14 @@ "version": "0.4.1", "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true, "license": "MIT" }, "node_modules/@eslint/eslintrc/node_modules/minimatch": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, "license": "ISC", "dependencies": { "brace-expansion": "^1.1.7" @@ -2161,6 +2628,7 @@ "version": "9.39.2", "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.39.2.tgz", "integrity": "sha512-q1mjIoW1VX4IvSocvM/vbTiveKC4k9eLrajNEuSsmjymSDEbpGddtpfOoN7YGAqBK3NG+uqo8ia4PDTt8buCYA==", + "dev": true, "license": "MIT", "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -2173,6 +2641,7 @@ "version": "2.1.7", "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-2.1.7.tgz", "integrity": "sha512-VtAOaymWVfZcmZbp6E2mympDIHvyjXs/12LqWYjVw6qjrfF+VK+fyG33kChz3nnK+SU5/NeHOqrTEHS8sXO3OA==", + "dev": true, "license": "Apache-2.0", "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -2182,6 +2651,7 @@ "version": "0.4.1", "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.4.1.tgz", "integrity": "sha512-43/qtrDUokr7LJqoF2c3+RInu/t4zfrpYdoSDfYyhg52rwLV6TnOvdG4fXm7IkSB3wErkcmJS9iEhjVtOSEjjA==", + "dev": true, "license": "Apache-2.0", "dependencies": { "@eslint/core": "^0.17.0", @@ -2227,6 +2697,7 @@ }, "node_modules/@humanfs/core": { "version": "0.19.1", + "dev": true, "license": "Apache-2.0", "engines": { "node": ">=18.18.0" @@ -2234,6 +2705,7 @@ }, "node_modules/@humanfs/node": { "version": "0.16.7", + "dev": true, "license": "Apache-2.0", "dependencies": { "@humanfs/core": "^0.19.1", @@ -2245,6 +2717,7 @@ }, "node_modules/@humanwhocodes/module-importer": { "version": "1.0.1", + "dev": true, "license": "Apache-2.0", "engines": { "node": ">=12.22" @@ -2256,6 +2729,7 @@ }, "node_modules/@humanwhocodes/retry": { "version": "0.4.3", + "dev": true, "license": "Apache-2.0", "engines": { "node": ">=18.18" @@ -2869,59 +3343,409 @@ "react": "^16.8.0 || ^17.0.0 || ^18.0.0" } }, - "node_modules/@react-spring/core": { - "version": "9.7.5", - "resolved": "https://registry.npmjs.org/@react-spring/core/-/core-9.7.5.tgz", - "integrity": "sha512-rmEqcxRcu7dWh7MnCcMXLvrf6/SDlSokLaLTxiPlAYi11nN3B5oiCUAblO72o+9z/87j2uzxa2Inm8UbLjXA+w==", - "dependencies": { - "@react-spring/animated": "~9.7.5", - "@react-spring/shared": "~9.7.5", - "@react-spring/types": "~9.7.5" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/react-spring/donate" - }, - "peerDependencies": { - "react": "^16.8.0 || ^17.0.0 || ^18.0.0" - } + "node_modules/@react-spring/core": { + "version": "9.7.5", + "resolved": "https://registry.npmjs.org/@react-spring/core/-/core-9.7.5.tgz", + "integrity": "sha512-rmEqcxRcu7dWh7MnCcMXLvrf6/SDlSokLaLTxiPlAYi11nN3B5oiCUAblO72o+9z/87j2uzxa2Inm8UbLjXA+w==", + "dependencies": { + "@react-spring/animated": "~9.7.5", + "@react-spring/shared": "~9.7.5", + "@react-spring/types": "~9.7.5" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/react-spring/donate" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0" + } + }, + "node_modules/@react-spring/rafz": { + "version": "9.7.5", + "resolved": "https://registry.npmjs.org/@react-spring/rafz/-/rafz-9.7.5.tgz", + "integrity": "sha512-5ZenDQMC48wjUzPAm1EtwQ5Ot3bLIAwwqP2w2owG5KoNdNHpEJV263nGhCeKKmuA3vG2zLLOdu3or6kuDjA6Aw==" + }, + "node_modules/@react-spring/shared": { + "version": "9.7.5", + "resolved": "https://registry.npmjs.org/@react-spring/shared/-/shared-9.7.5.tgz", + "integrity": "sha512-wdtoJrhUeeyD/PP/zo+np2s1Z820Ohr/BbuVYv+3dVLW7WctoiN7std8rISoYoHpUXtbkpesSKuPIw/6U1w1Pw==", + "dependencies": { + "@react-spring/rafz": "~9.7.5", + "@react-spring/types": "~9.7.5" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0" + } + }, + "node_modules/@react-spring/types": { + "version": "9.7.5", + "resolved": "https://registry.npmjs.org/@react-spring/types/-/types-9.7.5.tgz", + "integrity": "sha512-HVj7LrZ4ReHWBimBvu2SKND3cDVUPWKLqRTmWe/fNY6o1owGOX0cAHbdPDTMelgBlVbrTKrre6lFkhqGZErK/g==" + }, + "node_modules/@react-spring/web": { + "version": "9.7.5", + "resolved": "https://registry.npmjs.org/@react-spring/web/-/web-9.7.5.tgz", + "integrity": "sha512-lmvqGwpe+CSttsWNZVr+Dg62adtKhauGwLyGE/RRyZ8AAMLgb9x3NDMA5RMElXo+IMyTkPp7nxTB8ZQlmhb6JQ==", + "dependencies": { + "@react-spring/animated": "~9.7.5", + "@react-spring/core": "~9.7.5", + "@react-spring/shared": "~9.7.5", + "@react-spring/types": "~9.7.5" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0", + "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0" + } + }, + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.57.1.tgz", + "integrity": "sha512-A6ehUVSiSaaliTxai040ZpZ2zTevHYbvu/lDoeAteHI8QnaosIzm4qwtezfRg1jOYaUmnzLX1AOD6Z+UJjtifg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.57.1.tgz", + "integrity": "sha512-dQaAddCY9YgkFHZcFNS/606Exo8vcLHwArFZ7vxXq4rigo2bb494/xKMMwRRQW6ug7Js6yXmBZhSBRuBvCCQ3w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.57.1.tgz", + "integrity": "sha512-crNPrwJOrRxagUYeMn/DZwqN88SDmwaJ8Cvi/TN1HnWBU7GwknckyosC2gd0IqYRsHDEnXf328o9/HC6OkPgOg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.57.1.tgz", + "integrity": "sha512-Ji8g8ChVbKrhFtig5QBV7iMaJrGtpHelkB3lsaKzadFBe58gmjfGXAOfI5FV0lYMH8wiqsxKQ1C9B0YTRXVy4w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-freebsd-arm64": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.57.1.tgz", + "integrity": "sha512-R+/WwhsjmwodAcz65guCGFRkMb4gKWTcIeLy60JJQbXrJ97BOXHxnkPFrP+YwFlaS0m+uWJTstrUA9o+UchFug==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-freebsd-x64": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.57.1.tgz", + "integrity": "sha512-IEQTCHeiTOnAUC3IDQdzRAGj3jOAYNr9kBguI7MQAAZK3caezRrg0GxAb6Hchg4lxdZEI5Oq3iov/w/hnFWY9Q==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.57.1.tgz", + "integrity": "sha512-F8sWbhZ7tyuEfsmOxwc2giKDQzN3+kuBLPwwZGyVkLlKGdV1nvnNwYD0fKQ8+XS6hp9nY7B+ZeK01EBUE7aHaw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.57.1.tgz", + "integrity": "sha512-rGfNUfn0GIeXtBP1wL5MnzSj98+PZe/AXaGBCRmT0ts80lU5CATYGxXukeTX39XBKsxzFpEeK+Mrp9faXOlmrw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.57.1.tgz", + "integrity": "sha512-MMtej3YHWeg/0klK2Qodf3yrNzz6CGjo2UntLvk2RSPlhzgLvYEB3frRvbEF2wRKh1Z2fDIg9KRPe1fawv7C+g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.57.1.tgz", + "integrity": "sha512-1a/qhaaOXhqXGpMFMET9VqwZakkljWHLmZOX48R0I/YLbhdxr1m4gtG1Hq7++VhVUmf+L3sTAf9op4JlhQ5u1Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-gnu": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.57.1.tgz", + "integrity": "sha512-QWO6RQTZ/cqYtJMtxhkRkidoNGXc7ERPbZN7dVW5SdURuLeVU7lwKMpo18XdcmpWYd0qsP1bwKPf7DNSUinhvA==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-musl": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.57.1.tgz", + "integrity": "sha512-xpObYIf+8gprgWaPP32xiN5RVTi/s5FCR+XMXSKmhfoJjrpRAjCuuqQXyxUa/eJTdAE6eJ+KDKaoEqjZQxh3Gw==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-gnu": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.57.1.tgz", + "integrity": "sha512-4BrCgrpZo4hvzMDKRqEaW1zeecScDCR+2nZ86ATLhAoJ5FQ+lbHVD3ttKe74/c7tNT9c6F2viwB3ufwp01Oh2w==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-musl": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.57.1.tgz", + "integrity": "sha512-NOlUuzesGauESAyEYFSe3QTUguL+lvrN1HtwEEsU2rOwdUDeTMJdO5dUYl/2hKf9jWydJrO9OL/XSSf65R5+Xw==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.57.1.tgz", + "integrity": "sha512-ptA88htVp0AwUUqhVghwDIKlvJMD/fmL/wrQj99PRHFRAG6Z5nbWoWG4o81Nt9FT+IuqUQi+L31ZKAFeJ5Is+A==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-musl": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.57.1.tgz", + "integrity": "sha512-S51t7aMMTNdmAMPpBg7OOsTdn4tySRQvklmL3RpDRyknk87+Sp3xaumlatU+ppQ+5raY7sSTcC2beGgvhENfuw==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.57.1.tgz", + "integrity": "sha512-Bl00OFnVFkL82FHbEqy3k5CUCKH6OEJL54KCyx2oqsmZnFTR8IoNqBF+mjQVcRCT5sB6yOvK8A37LNm/kPJiZg==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.57.1.tgz", + "integrity": "sha512-ABca4ceT4N+Tv/GtotnWAeXZUZuM/9AQyCyKYyKnpk4yoA7QIAuBt6Hkgpw8kActYlew2mvckXkvx0FfoInnLg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.57.1.tgz", + "integrity": "sha512-HFps0JeGtuOR2convgRRkHCekD7j+gdAuXM+/i6kGzQtFhlCtQkpwtNzkNj6QhCDp7DRJ7+qC/1Vg2jt5iSOFw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-openbsd-x64": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.57.1.tgz", + "integrity": "sha512-H+hXEv9gdVQuDTgnqD+SQffoWoc0Of59AStSzTEj/feWTBAnSfSD3+Dql1ZruJQxmykT/JVY0dE8Ka7z0DH1hw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ] + }, + "node_modules/@rollup/rollup-openharmony-arm64": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.57.1.tgz", + "integrity": "sha512-4wYoDpNg6o/oPximyc/NG+mYUejZrCU2q+2w6YZqrAs2UcNUChIZXjtafAiiZSUc7On8v5NyNj34Kzj/Ltk6dQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ] }, - "node_modules/@react-spring/rafz": { - "version": "9.7.5", - "resolved": "https://registry.npmjs.org/@react-spring/rafz/-/rafz-9.7.5.tgz", - "integrity": "sha512-5ZenDQMC48wjUzPAm1EtwQ5Ot3bLIAwwqP2w2owG5KoNdNHpEJV263nGhCeKKmuA3vG2zLLOdu3or6kuDjA6Aw==" + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.57.1.tgz", + "integrity": "sha512-O54mtsV/6LW3P8qdTcamQmuC990HDfR71lo44oZMZlXU4tzLrbvTii87Ni9opq60ds0YzuAlEr/GNwuNluZyMQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] }, - "node_modules/@react-spring/shared": { - "version": "9.7.5", - "resolved": "https://registry.npmjs.org/@react-spring/shared/-/shared-9.7.5.tgz", - "integrity": "sha512-wdtoJrhUeeyD/PP/zo+np2s1Z820Ohr/BbuVYv+3dVLW7WctoiN7std8rISoYoHpUXtbkpesSKuPIw/6U1w1Pw==", - "dependencies": { - "@react-spring/rafz": "~9.7.5", - "@react-spring/types": "~9.7.5" - }, - "peerDependencies": { - "react": "^16.8.0 || ^17.0.0 || ^18.0.0" - } + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.57.1.tgz", + "integrity": "sha512-P3dLS+IerxCT/7D2q2FYcRdWRl22dNbrbBEtxdWhXrfIMPP9lQhb5h4Du04mdl5Woq05jVCDPCMF7Ub0NAjIew==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] }, - "node_modules/@react-spring/types": { - "version": "9.7.5", - "resolved": "https://registry.npmjs.org/@react-spring/types/-/types-9.7.5.tgz", - "integrity": "sha512-HVj7LrZ4ReHWBimBvu2SKND3cDVUPWKLqRTmWe/fNY6o1owGOX0cAHbdPDTMelgBlVbrTKrre6lFkhqGZErK/g==" + "node_modules/@rollup/rollup-win32-x64-gnu": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.57.1.tgz", + "integrity": "sha512-VMBH2eOOaKGtIJYleXsi2B8CPVADrh+TyNxJ4mWPnKfLB/DBUmzW+5m1xUrcwWoMfSLagIRpjUFeW5CO5hyciQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] }, - "node_modules/@react-spring/web": { - "version": "9.7.5", - "resolved": "https://registry.npmjs.org/@react-spring/web/-/web-9.7.5.tgz", - "integrity": "sha512-lmvqGwpe+CSttsWNZVr+Dg62adtKhauGwLyGE/RRyZ8AAMLgb9x3NDMA5RMElXo+IMyTkPp7nxTB8ZQlmhb6JQ==", - "dependencies": { - "@react-spring/animated": "~9.7.5", - "@react-spring/core": "~9.7.5", - "@react-spring/shared": "~9.7.5", - "@react-spring/types": "~9.7.5" - }, - "peerDependencies": { - "react": "^16.8.0 || ^17.0.0 || ^18.0.0", - "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0" - } + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.57.1.tgz", + "integrity": "sha512-mxRFDdHIWRxg3UfIIAwCm6NzvxG0jDX/wBN6KsQFTvKFqqg9vTrWUE68qEjHt19A5wwx5X5aUi2zuZT7YR0jrA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] }, "node_modules/@smithy/abort-controller": { "version": "4.2.8", @@ -3561,6 +4385,13 @@ "text-hex": "1.0.x" } }, + "node_modules/@standard-schema/spec": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.1.0.tgz", + "integrity": "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==", + "dev": true, + "license": "MIT" + }, "node_modules/@tannin/compile": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@tannin/compile/-/compile-1.1.0.tgz", @@ -3634,6 +4465,17 @@ "@types/node": "*" } }, + "node_modules/@types/chai": { + "version": "5.2.3", + "resolved": "https://registry.npmjs.org/@types/chai/-/chai-5.2.3.tgz", + "integrity": "sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/deep-eql": "*", + "assertion-error": "^2.0.1" + } + }, "node_modules/@types/connect": { "version": "3.4.38", "dev": true, @@ -3650,10 +4492,16 @@ "@types/node": "*" } }, + "node_modules/@types/deep-eql": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@types/deep-eql/-/deep-eql-4.0.2.tgz", + "integrity": "sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/estree": { "version": "1.0.8", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", - "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", + "dev": true, "license": "MIT" }, "node_modules/@types/express": { @@ -3717,6 +4565,7 @@ "version": "7.0.15", "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", + "dev": true, "license": "MIT" }, "node_modules/@types/lodash": { @@ -3761,7 +4610,7 @@ }, "node_modules/@types/node": { "version": "20.19.19", - "devOptional": true, + "dev": true, "license": "MIT", "dependencies": { "undici-types": "~6.21.0" @@ -4092,6 +4941,148 @@ "react": ">= 16.8.0" } }, + "node_modules/@vitest/coverage-v8": { + "version": "4.0.18", + "resolved": "https://registry.npmjs.org/@vitest/coverage-v8/-/coverage-v8-4.0.18.tgz", + "integrity": "sha512-7i+N2i0+ME+2JFZhfuz7Tg/FqKtilHjGyGvoHYQ6iLV0zahbsJ9sljC9OcFcPDbhYKCet+sG8SsVqlyGvPflZg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@bcoe/v8-coverage": "^1.0.2", + "@vitest/utils": "4.0.18", + "ast-v8-to-istanbul": "^0.3.10", + "istanbul-lib-coverage": "^3.2.2", + "istanbul-lib-report": "^3.0.1", + "istanbul-reports": "^3.2.0", + "magicast": "^0.5.1", + "obug": "^2.1.1", + "std-env": "^3.10.0", + "tinyrainbow": "^3.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "@vitest/browser": "4.0.18", + "vitest": "4.0.18" + }, + "peerDependenciesMeta": { + "@vitest/browser": { + "optional": true + } + } + }, + "node_modules/@vitest/expect": { + "version": "4.0.18", + "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-4.0.18.tgz", + "integrity": "sha512-8sCWUyckXXYvx4opfzVY03EOiYVxyNrHS5QxX3DAIi5dpJAAkyJezHCP77VMX4HKA2LDT/Jpfo8i2r5BE3GnQQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@standard-schema/spec": "^1.0.0", + "@types/chai": "^5.2.2", + "@vitest/spy": "4.0.18", + "@vitest/utils": "4.0.18", + "chai": "^6.2.1", + "tinyrainbow": "^3.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/mocker": { + "version": "4.0.18", + "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-4.0.18.tgz", + "integrity": "sha512-HhVd0MDnzzsgevnOWCBj5Otnzobjy5wLBe4EdeeFGv8luMsGcYqDuFRMcttKWZA5vVO8RFjexVovXvAM4JoJDQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/spy": "4.0.18", + "estree-walker": "^3.0.3", + "magic-string": "^0.30.21" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "msw": "^2.4.9", + "vite": "^6.0.0 || ^7.0.0-0" + }, + "peerDependenciesMeta": { + "msw": { + "optional": true + }, + "vite": { + "optional": true + } + } + }, + "node_modules/@vitest/pretty-format": { + "version": "4.0.18", + "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-4.0.18.tgz", + "integrity": "sha512-P24GK3GulZWC5tz87ux0m8OADrQIUVDPIjjj65vBXYG17ZeU3qD7r+MNZ1RNv4l8CGU2vtTRqixrOi9fYk/yKw==", + "dev": true, + "license": "MIT", + "dependencies": { + "tinyrainbow": "^3.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/runner": { + "version": "4.0.18", + "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-4.0.18.tgz", + "integrity": "sha512-rpk9y12PGa22Jg6g5M3UVVnTS7+zycIGk9ZNGN+m6tZHKQb7jrP7/77WfZy13Y/EUDd52NDsLRQhYKtv7XfPQw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/utils": "4.0.18", + "pathe": "^2.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/snapshot": { + "version": "4.0.18", + "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-4.0.18.tgz", + "integrity": "sha512-PCiV0rcl7jKQjbgYqjtakly6T1uwv/5BQ9SwBLekVg/EaYeQFPiXcgrC2Y7vDMA8dM1SUEAEV82kgSQIlXNMvA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/pretty-format": "4.0.18", + "magic-string": "^0.30.21", + "pathe": "^2.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/spy": { + "version": "4.0.18", + "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-4.0.18.tgz", + "integrity": "sha512-cbQt3PTSD7P2OARdVW3qWER5EGq7PHlvE+QfzSC0lbwO+xnt7+XH06ZzFjFRgzUX//JmpxrCu92VdwvEPlWSNw==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/utils": { + "version": "4.0.18", + "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-4.0.18.tgz", + "integrity": "sha512-msMRKLMVLWygpK3u2Hybgi4MNjcYJvwTb0Ru09+fOyCXIgT5raYP041DRRdiJiI3k/2U6SEbAETB3YtBrUkCFA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/pretty-format": "4.0.18", + "tinyrainbow": "^3.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, "node_modules/@wordpress/a11y": { "version": "4.39.0", "resolved": "https://registry.npmjs.org/@wordpress/a11y/-/a11y-4.39.0.tgz", @@ -8044,6 +9035,7 @@ "version": "5.3.2", "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "dev": true, "license": "MIT", "peerDependencies": { "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" @@ -8235,6 +9227,35 @@ "util": "^0.12.5" } }, + "node_modules/assertion-error": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-2.0.1.tgz", + "integrity": "sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + } + }, + "node_modules/ast-v8-to-istanbul": { + "version": "0.3.11", + "resolved": "https://registry.npmjs.org/ast-v8-to-istanbul/-/ast-v8-to-istanbul-0.3.11.tgz", + "integrity": "sha512-Qya9fkoofMjCBNVdWINMjB5KZvkYfaO9/anwkWnjxibpWUxo5iHl2sOdP7/uAqaRuUYuoo8rDwnbaaKVFxoUvw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/trace-mapping": "^0.3.31", + "estree-walker": "^3.0.3", + "js-tokens": "^10.0.0" + } + }, + "node_modules/ast-v8-to-istanbul/node_modules/js-tokens": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-10.0.0.tgz", + "integrity": "sha512-lM/UBzQmfJRo9ABXbPWemivdCW8V2G8FHaHdypQaIy523snUjog0W71ayWXTjiR+ixeMyVHN2XcpnTd/liPg/Q==", + "dev": true, + "license": "MIT" + }, "node_modules/async": { "version": "3.2.6", "license": "MIT" @@ -8560,6 +9581,16 @@ "upper-case-first": "^2.0.2" } }, + "node_modules/chai": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/chai/-/chai-6.2.2.tgz", + "integrity": "sha512-NUPRluOfOiTKBKvWPtSD4PhFvWCqOi0BGStNWs57X9js7XGTprSmFoz5F0tWhR4WPjNeR9jXqdC7/UpSJTnlRg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, "node_modules/chalk": { "version": "4.1.2", "license": "MIT", @@ -8916,7 +9947,8 @@ "node_modules/concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==" + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "dev": true }, "node_modules/concat-stream": { "version": "2.0.0", @@ -9049,6 +10081,7 @@ }, "node_modules/cross-spawn": { "version": "7.0.6", + "dev": true, "license": "MIT", "dependencies": { "path-key": "^3.1.0", @@ -9275,6 +10308,7 @@ }, "node_modules/deep-is": { "version": "0.1.4", + "dev": true, "license": "MIT" }, "node_modules/deepmerge": { @@ -9739,6 +10773,48 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/esbuild": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.27.3.tgz", + "integrity": "sha512-8VwMnyGCONIs6cWue2IdpHxHnAjzxnw2Zr7MkVxB2vjmQ2ivqGFb4LEG3SMnv0Gb2F/G/2yA8zUaiL1gywDCCg==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.27.3", + "@esbuild/android-arm": "0.27.3", + "@esbuild/android-arm64": "0.27.3", + "@esbuild/android-x64": "0.27.3", + "@esbuild/darwin-arm64": "0.27.3", + "@esbuild/darwin-x64": "0.27.3", + "@esbuild/freebsd-arm64": "0.27.3", + "@esbuild/freebsd-x64": "0.27.3", + "@esbuild/linux-arm": "0.27.3", + "@esbuild/linux-arm64": "0.27.3", + "@esbuild/linux-ia32": "0.27.3", + "@esbuild/linux-loong64": "0.27.3", + "@esbuild/linux-mips64el": "0.27.3", + "@esbuild/linux-ppc64": "0.27.3", + "@esbuild/linux-riscv64": "0.27.3", + "@esbuild/linux-s390x": "0.27.3", + "@esbuild/linux-x64": "0.27.3", + "@esbuild/netbsd-arm64": "0.27.3", + "@esbuild/netbsd-x64": "0.27.3", + "@esbuild/openbsd-arm64": "0.27.3", + "@esbuild/openbsd-x64": "0.27.3", + "@esbuild/openharmony-arm64": "0.27.3", + "@esbuild/sunos-x64": "0.27.3", + "@esbuild/win32-arm64": "0.27.3", + "@esbuild/win32-ia32": "0.27.3", + "@esbuild/win32-x64": "0.27.3" + } + }, "node_modules/escalade": { "version": "3.2.0", "license": "MIT", @@ -9793,6 +10869,7 @@ "version": "9.39.2", "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.39.2.tgz", "integrity": "sha512-LEyamqS7W5HB3ujJyvi0HQK/dtVINZvd5mAAp9eT5S/ujByGjiZLCzPcHVzuXbpJDJF/cxwHlfceVUDZ2lnSTw==", + "dev": true, "license": "MIT", "dependencies": { "@eslint-community/eslint-utils": "^4.8.0", @@ -9892,6 +10969,7 @@ "version": "8.4.0", "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.4.0.tgz", "integrity": "sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg==", + "dev": true, "license": "BSD-2-Clause", "dependencies": { "esrecurse": "^4.3.0", @@ -9906,6 +10984,7 @@ }, "node_modules/eslint-visitor-keys": { "version": "3.4.3", + "dev": true, "license": "Apache-2.0", "engines": { "node": "^12.22.0 || ^14.17.0 || >=16.0.0" @@ -9916,6 +10995,7 @@ }, "node_modules/eslint/node_modules/ajv": { "version": "6.12.6", + "dev": true, "license": "MIT", "dependencies": { "fast-deep-equal": "^3.1.1", @@ -9932,6 +11012,7 @@ "version": "1.1.12", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "dev": true, "license": "MIT", "dependencies": { "balanced-match": "^1.0.0", @@ -9942,6 +11023,7 @@ "version": "4.2.1", "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz", "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==", + "dev": true, "license": "Apache-2.0", "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -9952,6 +11034,7 @@ }, "node_modules/eslint/node_modules/ignore": { "version": "5.3.2", + "dev": true, "license": "MIT", "engines": { "node": ">= 4" @@ -9959,12 +11042,14 @@ }, "node_modules/eslint/node_modules/json-schema-traverse": { "version": "0.4.1", + "dev": true, "license": "MIT" }, "node_modules/eslint/node_modules/minimatch": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, "license": "ISC", "dependencies": { "brace-expansion": "^1.1.7" @@ -9977,6 +11062,7 @@ "version": "10.4.0", "resolved": "https://registry.npmjs.org/espree/-/espree-10.4.0.tgz", "integrity": "sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==", + "dev": true, "license": "BSD-2-Clause", "dependencies": { "acorn": "^8.15.0", @@ -9994,6 +11080,7 @@ "version": "4.2.1", "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz", "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==", + "dev": true, "license": "Apache-2.0", "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -10018,6 +11105,7 @@ "version": "1.7.0", "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.7.0.tgz", "integrity": "sha512-Ap6G0WQwcU/LHsvLwON1fAQX9Zp0A2Y6Y/cJBl9r/JbW90Zyg4/zbG6zzKa2OTALELarYHmKu0GhpM5EO+7T0g==", + "dev": true, "dependencies": { "estraverse": "^5.1.0" }, @@ -10029,6 +11117,7 @@ "version": "4.3.0", "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "dev": true, "license": "BSD-2-Clause", "dependencies": { "estraverse": "^5.2.0" @@ -10044,6 +11133,16 @@ "node": ">=4.0" } }, + "node_modules/estree-walker": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz", + "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0" + } + }, "node_modules/esutils": { "version": "2.0.3", "license": "BSD-2-Clause", @@ -10058,6 +11157,16 @@ "node": ">= 0.6" } }, + "node_modules/expect-type": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/expect-type/-/expect-type-1.3.0.tgz", + "integrity": "sha512-knvyeauYhqjOYvQ66MznSMs83wmHrCycNEN6Ao+2AeYEfxUIkuiVxdEa1qlGEPK+We3n0THiDciYSsCcgW/DoA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=12.0.0" + } + }, "node_modules/express": { "version": "5.2.1", "license": "MIT", @@ -10131,10 +11240,12 @@ }, "node_modules/fast-json-stable-stringify": { "version": "2.1.0", + "dev": true, "license": "MIT" }, "node_modules/fast-levenshtein": { "version": "2.0.6", + "dev": true, "license": "MIT" }, "node_modules/fast-uri": { @@ -10167,6 +11278,23 @@ "fxparser": "src/cli/cli.js" } }, + "node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, "node_modules/fecha": { "version": "4.2.3", "license": "MIT" @@ -10193,6 +11321,7 @@ }, "node_modules/file-entry-cache": { "version": "8.0.0", + "dev": true, "license": "MIT", "dependencies": { "flat-cache": "^4.0.0" @@ -10252,6 +11381,7 @@ }, "node_modules/find-up": { "version": "5.0.0", + "dev": true, "license": "MIT", "dependencies": { "locate-path": "^6.0.0", @@ -10266,6 +11396,7 @@ }, "node_modules/flat-cache": { "version": "4.0.1", + "dev": true, "license": "MIT", "dependencies": { "flatted": "^3.2.9", @@ -10277,6 +11408,7 @@ }, "node_modules/flatted": { "version": "3.3.3", + "dev": true, "license": "ISC" }, "node_modules/fn.name": { @@ -10565,6 +11697,7 @@ }, "node_modules/glob-parent": { "version": "6.0.2", + "dev": true, "license": "ISC", "dependencies": { "is-glob": "^4.0.3" @@ -10616,6 +11749,7 @@ "version": "14.0.0", "resolved": "https://registry.npmjs.org/globals/-/globals-14.0.0.tgz", "integrity": "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==", + "dev": true, "license": "MIT", "engines": { "node": ">=18" @@ -10853,6 +11987,13 @@ "node": ">=12" } }, + "node_modules/html-escaper": { + "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" + }, "node_modules/html-react-parser": { "version": "5.2.11", "resolved": "https://registry.npmjs.org/html-react-parser/-/html-react-parser-5.2.11.tgz", @@ -11026,6 +12167,7 @@ }, "node_modules/imurmurhash": { "version": "0.1.4", + "dev": true, "license": "MIT", "engines": { "node": ">=0.8.19" @@ -11654,6 +12796,7 @@ }, "node_modules/is-extglob": { "version": "2.1.1", + "dev": true, "license": "MIT", "engines": { "node": ">=0.10.0" @@ -11698,6 +12841,7 @@ }, "node_modules/is-glob": { "version": "4.0.3", + "dev": true, "license": "MIT", "dependencies": { "is-extglob": "^2.1.1" @@ -11952,6 +13096,7 @@ }, "node_modules/isexe": { "version": "2.0.0", + "dev": true, "license": "ISC" }, "node_modules/isomorphic.js": { @@ -11963,6 +13108,58 @@ "url": "https://github.com/sponsors/dmonad" } }, + "node_modules/istanbul-lib-coverage": { + "version": "3.2.2", + "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", + "engines": { + "node": ">=8" + } + }, + "node_modules/istanbul-lib-report": { + "version": "3.0.1", + "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", + "dependencies": { + "istanbul-lib-coverage": "^3.0.0", + "make-dir": "^4.0.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/istanbul-lib-report/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/istanbul-reports": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.2.0.tgz", + "integrity": "sha512-HGYWWS/ehqTV3xN10i23tkPkpH46MLCIMFNCaaKNavAXTF1RkqxawEPtnjnGZ6XKSInBKkiOA5BKS+aZiY3AvA==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "html-escaper": "^2.0.0", + "istanbul-lib-report": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/jackspeak": { "version": "4.2.3", "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-4.2.3.tgz", @@ -12102,6 +13299,7 @@ }, "node_modules/json-buffer": { "version": "3.0.1", + "dev": true, "license": "MIT" }, "node_modules/json-parse-even-better-errors": { @@ -12119,6 +13317,7 @@ }, "node_modules/json-stable-stringify-without-jsonify": { "version": "1.0.1", + "dev": true, "license": "MIT" }, "node_modules/jszip": { @@ -12133,6 +13332,7 @@ }, "node_modules/keyv": { "version": "4.5.4", + "dev": true, "license": "MIT", "dependencies": { "json-buffer": "3.0.1" @@ -12158,6 +13358,7 @@ }, "node_modules/levn": { "version": "0.4.1", + "dev": true, "license": "MIT", "dependencies": { "prelude-ls": "^1.2.1", @@ -12221,6 +13422,7 @@ }, "node_modules/locate-path": { "version": "6.0.0", + "dev": true, "license": "MIT", "dependencies": { "p-locate": "^5.0.0" @@ -12244,6 +13446,7 @@ "version": "4.6.2", "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", + "dev": true, "license": "MIT" }, "node_modules/log-symbols": { @@ -12320,6 +13523,44 @@ "url": "https://github.com/sponsors/wellwelwel" } }, + "node_modules/magic-string": { + "version": "0.30.21", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz", + "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.5" + } + }, + "node_modules/magicast": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/magicast/-/magicast-0.5.2.tgz", + "integrity": "sha512-E3ZJh4J3S9KfwdjZhe2afj6R9lGIN5Pher1pF39UGrXRqq/VDaGVIGN13BjHd2u8B61hArAGOnso7nBOouW3TQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.29.0", + "@babel/types": "^7.29.0", + "source-map-js": "^1.2.1" + } + }, + "node_modules/make-dir": { + "version": "4.0.0", + "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", + "dependencies": { + "semver": "^7.5.3" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "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", @@ -12611,6 +13852,7 @@ }, "node_modules/natural-compare": { "version": "1.4.0", + "dev": true, "license": "MIT" }, "node_modules/negotiator": { @@ -12785,6 +14027,17 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/obug": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/obug/-/obug-2.1.1.tgz", + "integrity": "sha512-uTqF9MuPraAQ+IsnPf366RG4cP9RtUi7MLO1N3KEc+wb0a6yKpeL0lmk2IB1jY5KHPAlTc6T/JRdC/YqxHNwkQ==", + "dev": true, + "funding": [ + "https://github.com/sponsors/sxzz", + "https://opencollective.com/debug" + ], + "license": "MIT" + }, "node_modules/on-finished": { "version": "2.4.1", "license": "MIT", @@ -12846,6 +14099,7 @@ }, "node_modules/optionator": { "version": "0.9.4", + "dev": true, "license": "MIT", "dependencies": { "deep-is": "^0.1.3", @@ -12906,6 +14160,7 @@ }, "node_modules/p-limit": { "version": "3.1.0", + "dev": true, "license": "MIT", "dependencies": { "yocto-queue": "^0.1.0" @@ -12919,6 +14174,7 @@ }, "node_modules/p-locate": { "version": "5.0.0", + "dev": true, "license": "MIT", "dependencies": { "p-limit": "^3.0.2" @@ -13078,6 +14334,7 @@ }, "node_modules/path-key": { "version": "3.1.1", + "dev": true, "license": "MIT", "engines": { "node": ">=8" @@ -13120,6 +14377,13 @@ "node": ">=8" } }, + "node_modules/pathe": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz", + "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==", + "dev": true, + "license": "MIT" + }, "node_modules/php-serialize": { "version": "5.1.3", "license": "MIT", @@ -13273,6 +14537,7 @@ }, "node_modules/prelude-ls": { "version": "1.2.1", + "dev": true, "license": "MIT", "engines": { "node": ">= 0.8.0" @@ -13770,6 +15035,51 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/rollup": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.57.1.tgz", + "integrity": "sha512-oQL6lgK3e2QZeQ7gcgIkS2YZPg5slw37hYufJ3edKlfQSGGm8ICoxswK15ntSzF/a8+h7ekRy7k7oWc3BQ7y8A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "1.0.8" + }, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@rollup/rollup-android-arm-eabi": "4.57.1", + "@rollup/rollup-android-arm64": "4.57.1", + "@rollup/rollup-darwin-arm64": "4.57.1", + "@rollup/rollup-darwin-x64": "4.57.1", + "@rollup/rollup-freebsd-arm64": "4.57.1", + "@rollup/rollup-freebsd-x64": "4.57.1", + "@rollup/rollup-linux-arm-gnueabihf": "4.57.1", + "@rollup/rollup-linux-arm-musleabihf": "4.57.1", + "@rollup/rollup-linux-arm64-gnu": "4.57.1", + "@rollup/rollup-linux-arm64-musl": "4.57.1", + "@rollup/rollup-linux-loong64-gnu": "4.57.1", + "@rollup/rollup-linux-loong64-musl": "4.57.1", + "@rollup/rollup-linux-ppc64-gnu": "4.57.1", + "@rollup/rollup-linux-ppc64-musl": "4.57.1", + "@rollup/rollup-linux-riscv64-gnu": "4.57.1", + "@rollup/rollup-linux-riscv64-musl": "4.57.1", + "@rollup/rollup-linux-s390x-gnu": "4.57.1", + "@rollup/rollup-linux-x64-gnu": "4.57.1", + "@rollup/rollup-linux-x64-musl": "4.57.1", + "@rollup/rollup-openbsd-x64": "4.57.1", + "@rollup/rollup-openharmony-arm64": "4.57.1", + "@rollup/rollup-win32-arm64-msvc": "4.57.1", + "@rollup/rollup-win32-ia32-msvc": "4.57.1", + "@rollup/rollup-win32-x64-gnu": "4.57.1", + "@rollup/rollup-win32-x64-msvc": "4.57.1", + "fsevents": "~2.3.2" + } + }, "node_modules/router": { "version": "2.2.0", "license": "MIT", @@ -14026,6 +15336,7 @@ }, "node_modules/shebang-command": { "version": "2.0.0", + "dev": true, "license": "MIT", "dependencies": { "shebang-regex": "^3.0.0" @@ -14036,6 +15347,7 @@ }, "node_modules/shebang-regex": { "version": "3.0.0", + "dev": true, "license": "MIT", "engines": { "node": ">=8" @@ -14296,6 +15608,13 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/siginfo": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/siginfo/-/siginfo-2.0.0.tgz", + "integrity": "sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==", + "dev": true, + "license": "ISC" + }, "node_modules/simple-html-tokenizer": { "version": "0.5.11", "resolved": "https://registry.npmjs.org/simple-html-tokenizer/-/simple-html-tokenizer-0.5.11.tgz", @@ -14464,6 +15783,13 @@ "node": "*" } }, + "node_modules/stackback": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/stackback/-/stackback-0.0.2.tgz", + "integrity": "sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==", + "dev": true, + "license": "MIT" + }, "node_modules/statuses": { "version": "2.0.2", "license": "MIT", @@ -14471,6 +15797,13 @@ "node": ">= 0.8" } }, + "node_modules/std-env": { + "version": "3.10.0", + "resolved": "https://registry.npmjs.org/std-env/-/std-env-3.10.0.tgz", + "integrity": "sha512-5GS12FdOZNliM5mAOxFRg7Ir0pWz8MdpYm6AY6VPkGpbA7ZzmbzNcBJQ0GPvvyWgcY7QAhCgf9Uy89I03faLkg==", + "dev": true, + "license": "MIT" + }, "node_modules/stop-iteration-iterator": { "version": "1.1.0", "license": "MIT", @@ -14602,6 +15935,7 @@ "version": "3.1.1", "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "dev": true, "license": "MIT", "engines": { "node": ">=8" @@ -14724,6 +16058,23 @@ "resolved": "https://registry.npmjs.org/tiny-emitter/-/tiny-emitter-2.1.0.tgz", "integrity": "sha512-NB6Dk1A9xgQPMoGqC5CVXn123gWyte215ONT5Pp5a0yt4nlEoO1ZWeCwpncaekPHXO60i47ihFnZPiRPjRMq4Q==" }, + "node_modules/tinybench": { + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/tinybench/-/tinybench-2.9.0.tgz", + "integrity": "sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==", + "dev": true, + "license": "MIT" + }, + "node_modules/tinyexec": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-1.0.2.tgz", + "integrity": "sha512-W/KYk+NFhkmsYpuHq5JykngiOCnxeVL8v8dFnqxSD8qEEdRfXk1SDM6JzNqcERbcGYj9tMrDQBYV9cjgnunFIg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, "node_modules/tinyglobby": { "version": "0.2.15", "license": "MIT", @@ -14738,21 +16089,6 @@ "url": "https://github.com/sponsors/SuperchupuDev" } }, - "node_modules/tinyglobby/node_modules/fdir": { - "version": "6.5.0", - "license": "MIT", - "engines": { - "node": ">=12.0.0" - }, - "peerDependencies": { - "picomatch": "^3 || ^4" - }, - "peerDependenciesMeta": { - "picomatch": { - "optional": true - } - } - }, "node_modules/tinyglobby/node_modules/picomatch": { "version": "4.0.3", "license": "MIT", @@ -14763,6 +16099,16 @@ "url": "https://github.com/sponsors/jonschlinkert" } }, + "node_modules/tinyrainbow": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/tinyrainbow/-/tinyrainbow-3.0.3.tgz", + "integrity": "sha512-PSkbLUoxOFRzJYjjxHJt9xro7D+iilgMX/C9lawzVuYiIdcihh9DXmVibBe8lmcFrRi/VzlPjBxbN7rH24q8/Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, "node_modules/tmp": { "version": "0.2.5", "license": "MIT", @@ -14935,6 +16281,7 @@ }, "node_modules/type-check": { "version": "0.4.0", + "dev": true, "license": "MIT", "dependencies": { "prelude-ls": "^1.2.1" @@ -15065,6 +16412,7 @@ }, "node_modules/typescript": { "version": "5.9.3", + "dev": true, "license": "Apache-2.0", "bin": { "tsc": "bin/tsc", @@ -15105,7 +16453,7 @@ }, "node_modules/undici-types": { "version": "6.21.0", - "devOptional": true, + "dev": true, "license": "MIT" }, "node_modules/unique-string": { @@ -15151,6 +16499,7 @@ }, "node_modules/uri-js": { "version": "4.4.1", + "dev": true, "license": "BSD-2-Clause", "dependencies": { "punycode": "^2.1.0" @@ -15274,6 +16623,185 @@ "node": ">= 0.8" } }, + "node_modules/vite": { + "version": "7.3.1", + "resolved": "https://registry.npmjs.org/vite/-/vite-7.3.1.tgz", + "integrity": "sha512-w+N7Hifpc3gRjZ63vYBXA56dvvRlNWRczTdmCBBa+CotUzAPf5b7YMdMR/8CQoeYE5LX3W4wj6RYTgonm1b9DA==", + "dev": true, + "license": "MIT", + "dependencies": { + "esbuild": "^0.27.0", + "fdir": "^6.5.0", + "picomatch": "^4.0.3", + "postcss": "^8.5.6", + "rollup": "^4.43.0", + "tinyglobby": "^0.2.15" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^20.19.0 || >=22.12.0", + "jiti": ">=1.21.0", + "less": "^4.0.0", + "lightningcss": "^1.21.0", + "sass": "^1.70.0", + "sass-embedded": "^1.70.0", + "stylus": ">=0.54.8", + "sugarss": "^5.0.0", + "terser": "^5.16.0", + "tsx": "^4.8.1", + "yaml": "^2.4.2" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "jiti": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { + "optional": true + } + } + }, + "node_modules/vite/node_modules/picomatch": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", + "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/vitest": { + "version": "4.0.18", + "resolved": "https://registry.npmjs.org/vitest/-/vitest-4.0.18.tgz", + "integrity": "sha512-hOQuK7h0FGKgBAas7v0mSAsnvrIgAvWmRFjmzpJ7SwFHH3g1k2u37JtYwOwmEKhK6ZO3v9ggDBBm0La1LCK4uQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/expect": "4.0.18", + "@vitest/mocker": "4.0.18", + "@vitest/pretty-format": "4.0.18", + "@vitest/runner": "4.0.18", + "@vitest/snapshot": "4.0.18", + "@vitest/spy": "4.0.18", + "@vitest/utils": "4.0.18", + "es-module-lexer": "^1.7.0", + "expect-type": "^1.2.2", + "magic-string": "^0.30.21", + "obug": "^2.1.1", + "pathe": "^2.0.3", + "picomatch": "^4.0.3", + "std-env": "^3.10.0", + "tinybench": "^2.9.0", + "tinyexec": "^1.0.2", + "tinyglobby": "^0.2.15", + "tinyrainbow": "^3.0.3", + "vite": "^6.0.0 || ^7.0.0", + "why-is-node-running": "^2.3.0" + }, + "bin": { + "vitest": "vitest.mjs" + }, + "engines": { + "node": "^20.0.0 || ^22.0.0 || >=24.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "@edge-runtime/vm": "*", + "@opentelemetry/api": "^1.9.0", + "@types/node": "^20.0.0 || ^22.0.0 || >=24.0.0", + "@vitest/browser-playwright": "4.0.18", + "@vitest/browser-preview": "4.0.18", + "@vitest/browser-webdriverio": "4.0.18", + "@vitest/ui": "4.0.18", + "happy-dom": "*", + "jsdom": "*" + }, + "peerDependenciesMeta": { + "@edge-runtime/vm": { + "optional": true + }, + "@opentelemetry/api": { + "optional": true + }, + "@types/node": { + "optional": true + }, + "@vitest/browser-playwright": { + "optional": true + }, + "@vitest/browser-preview": { + "optional": true + }, + "@vitest/browser-webdriverio": { + "optional": true + }, + "@vitest/ui": { + "optional": true + }, + "happy-dom": { + "optional": true + }, + "jsdom": { + "optional": true + } + } + }, + "node_modules/vitest/node_modules/picomatch": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", + "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, "node_modules/w3c-hr-time": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/w3c-hr-time/-/w3c-hr-time-1.0.2.tgz", @@ -15350,6 +16878,7 @@ }, "node_modules/which": { "version": "2.0.2", + "dev": true, "license": "ISC", "dependencies": { "isexe": "^2.0.0" @@ -15446,6 +16975,23 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/why-is-node-running": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/why-is-node-running/-/why-is-node-running-2.3.0.tgz", + "integrity": "sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==", + "dev": true, + "license": "MIT", + "dependencies": { + "siginfo": "^2.0.0", + "stackback": "0.0.2" + }, + "bin": { + "why-is-node-running": "cli.js" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/widest-line": { "version": "3.1.0", "license": "MIT", @@ -15514,6 +17060,7 @@ }, "node_modules/word-wrap": { "version": "1.2.5", + "dev": true, "license": "MIT", "engines": { "node": ">=0.10.0" @@ -15708,6 +17255,7 @@ }, "node_modules/yocto-queue": { "version": "0.1.0", + "dev": true, "license": "MIT", "engines": { "node": ">=10" diff --git a/upload-api/package.json b/upload-api/package.json index 3432c547f..4559fc729 100644 --- a/upload-api/package.json +++ b/upload-api/package.json @@ -5,7 +5,10 @@ "main": "index.js", "scripts": { "build": "for d in migration-*; do [ -f \"$d/package.json\" ] && npm --prefix $d run build; done && tsc", - "test": "echo \"Error: no test specified\" && exit 1", + "test": "vitest run", + "test:watch": "vitest", + "test:coverage": "vitest run --coverage", + "coverage:ui": "npx serve coverage -l 3939", "start": "npm run build &&tsc && node build/index.js", "startNew": "tsc && node build/index.js", "validation": "tsc && node build/main.js", @@ -31,13 +34,15 @@ "@types/wordpress__blocks": "^12.5.18", "@types/xml2js": "^0.4.14", "@typescript-eslint/eslint-plugin": "^8.0.0", + "@vitest/coverage-v8": "^4.0.18", "eslint": "^9.0.0", "eslint-config-prettier": "^9.1.0", "eslint-plugin-prettier": "^5.1.3", "nodemon": "^3.1.10", "rimraf": "^6.0.1", "ts-node": "^10.9.2", - "typescript": "^5.3.3" + "typescript": "^5.3.3", + "vitest": "^4.0.18" }, "dependencies": { "@aws-sdk/client-s3": "^3.985.0", diff --git a/upload-api/tests/__mocks__/migration-aem.ts b/upload-api/tests/__mocks__/migration-aem.ts new file mode 100644 index 000000000..97068b06d --- /dev/null +++ b/upload-api/tests/__mocks__/migration-aem.ts @@ -0,0 +1,9 @@ +import { vi } from 'vitest'; + +export const validator = vi.fn().mockResolvedValue([true]); +export const contentTypes = vi.fn().mockReturnValue({ + convertAndCreate: vi.fn().mockResolvedValue([]), +}); +export const locales = vi.fn().mockReturnValue({ + processAndSave: vi.fn().mockResolvedValue([]), +}); diff --git a/upload-api/tests/__mocks__/migration-contentful.ts b/upload-api/tests/__mocks__/migration-contentful.ts new file mode 100644 index 000000000..21764d913 --- /dev/null +++ b/upload-api/tests/__mocks__/migration-contentful.ts @@ -0,0 +1,5 @@ +import { vi } from 'vitest'; + +export const extractLocale = vi.fn().mockResolvedValue([]); +export const extractContentTypes = vi.fn().mockResolvedValue(undefined); +export const createInitialMapper = vi.fn().mockResolvedValue({ contentTypes: [] }); diff --git a/upload-api/tests/__mocks__/migration-drupal.ts b/upload-api/tests/__mocks__/migration-drupal.ts new file mode 100644 index 000000000..386318efd --- /dev/null +++ b/upload-api/tests/__mocks__/migration-drupal.ts @@ -0,0 +1,5 @@ +import { vi } from 'vitest'; + +export const extractLocale = vi.fn().mockResolvedValue(new Set()); +export const extractTaxonomy = vi.fn().mockResolvedValue(undefined); +export const createInitialMapper = vi.fn().mockResolvedValue({ contentTypes: [] }); diff --git a/upload-api/tests/__mocks__/migration-sitecore.ts b/upload-api/tests/__mocks__/migration-sitecore.ts new file mode 100644 index 000000000..cbd04d9a5 --- /dev/null +++ b/upload-api/tests/__mocks__/migration-sitecore.ts @@ -0,0 +1,7 @@ +import { vi } from 'vitest'; + +export const ExtractFiles = vi.fn().mockResolvedValue(undefined); +export const extractLocales = vi.fn().mockResolvedValue([]); +export const ExtractConfiguration = vi.fn().mockResolvedValue(undefined); +export const contentTypes = vi.fn().mockResolvedValue(undefined); +export const reference = vi.fn().mockResolvedValue({ contentTypeUids: [], path: '' }); diff --git a/upload-api/tests/__mocks__/migration-wordpress.ts b/upload-api/tests/__mocks__/migration-wordpress.ts new file mode 100644 index 000000000..7c925b6cc --- /dev/null +++ b/upload-api/tests/__mocks__/migration-wordpress.ts @@ -0,0 +1,4 @@ +import { vi } from 'vitest'; + +export const extractLocale = vi.fn().mockResolvedValue([]); +export const extractContentTypes = vi.fn().mockResolvedValue(null); diff --git a/upload-api/tests/fixtures/config.fixture.ts b/upload-api/tests/fixtures/config.fixture.ts new file mode 100644 index 000000000..42078f068 --- /dev/null +++ b/upload-api/tests/fixtures/config.fixture.ts @@ -0,0 +1,59 @@ +import { vi } from 'vitest'; + +export const createMockConfig = (overrides: Record = {}) => ({ + plan: { dropdown: { optionLimit: 100 } }, + cmsType: 'wordpress', + isLocalPath: true, + awsData: { + awsRegion: 'us-east-2', + awsAccessKeyId: 'test-key', + awsSecretAccessKey: 'test-secret', + awsSessionToken: 'test-token', + bucketName: 'test-bucket', + bucketKey: 'test-key.zip', + }, + mysql: { + host: 'localhost', + user: 'root', + password: 'password', + database: 'drupal_db', + port: '3306', + }, + assetsConfig: { + base_url: 'http://localhost:8080', + public_path: '/sites/default/files', + }, + localPath: '/tmp/test-uploads', + ...overrides, +}); + +export const createMockRequest = (overrides: Record = {}) => ({ + headers: { + projectid: 'project-123', + app_token: 'mock-token', + affix: 'csm', + ...overrides.headers, + }, + file: overrides.file || undefined, + body: overrides.body || {}, + ...overrides, +}); + +export const createMockResponse = () => { + const res: any = { + headersSent: false, + status: vi.fn().mockReturnThis(), + json: vi.fn().mockReturnThis(), + send: vi.fn().mockReturnThis(), + }; + return res; +}; + +export const createMockMySQLData = (overrides: Record = {}) => ({ + host: 'localhost', + user: 'root', + password: 'password', + database: 'drupal_db', + port: 3306, + ...overrides, +}); diff --git a/upload-api/tests/setup.ts b/upload-api/tests/setup.ts new file mode 100644 index 000000000..d730764e4 --- /dev/null +++ b/upload-api/tests/setup.ts @@ -0,0 +1,18 @@ +import { vi, beforeAll, afterAll, afterEach } from 'vitest'; + +beforeAll(() => { + vi.stubEnv('PORT', '5002'); + vi.stubEnv('CMS_TYPE', 'wordpress'); + vi.stubEnv('CONTAINER_PATH', '/tmp/test-uploads'); + vi.stubEnv('NODE_BACKEND_API', 'http://localhost:5001'); + vi.stubEnv('DRUPAL_ASSETS_BASE_URL', 'http://localhost:8080'); + vi.stubEnv('DRUPAL_ASSETS_PUBLIC_PATH', '/sites/default/files'); +}); + +afterEach(() => { + vi.restoreAllMocks(); +}); + +afterAll(() => { + vi.unstubAllEnvs(); +}); diff --git a/upload-api/tests/unit/config/index.config.test.ts b/upload-api/tests/unit/config/index.config.test.ts new file mode 100644 index 000000000..73f05093f --- /dev/null +++ b/upload-api/tests/unit/config/index.config.test.ts @@ -0,0 +1,72 @@ +import { describe, it, expect, vi, beforeEach } from 'vitest'; + +describe('config/index', () => { + beforeEach(() => { + vi.resetModules(); + }); + + it('should export default configuration object', async () => { + const config = (await import('../../../src/config/index')).default; + + expect(config).toHaveProperty('plan'); + expect(config).toHaveProperty('cmsType'); + expect(config).toHaveProperty('isLocalPath'); + expect(config).toHaveProperty('awsData'); + expect(config).toHaveProperty('mysql'); + expect(config).toHaveProperty('assetsConfig'); + expect(config).toHaveProperty('localPath'); + }); + + it('should have plan with dropdown optionLimit', async () => { + const config = (await import('../../../src/config/index')).default; + expect(config.plan.dropdown.optionLimit).toBe(100); + }); + + it('should have isLocalPath as true', async () => { + const config = (await import('../../../src/config/index')).default; + expect(config.isLocalPath).toBe(true); + }); + + it('should use CMS_TYPE env var when set', async () => { + vi.stubEnv('CMS_TYPE', 'sitecore'); + const config = (await import('../../../src/config/index')).default; + expect(config.cmsType).toBe('sitecore'); + }); + + it('should use CONTAINER_PATH env var when set', async () => { + vi.stubEnv('CONTAINER_PATH', '/custom/path'); + const config = (await import('../../../src/config/index')).default; + expect(config.localPath).toBe('/custom/path'); + }); + + it('should use DRUPAL_ASSETS_BASE_URL env var when set', async () => { + vi.stubEnv('DRUPAL_ASSETS_BASE_URL', 'https://example.com'); + const config = (await import('../../../src/config/index')).default; + expect(config.assetsConfig.base_url).toBe('https://example.com'); + }); + + it('should use DRUPAL_ASSETS_PUBLIC_PATH env var when set', async () => { + vi.stubEnv('DRUPAL_ASSETS_PUBLIC_PATH', '/custom/files'); + const config = (await import('../../../src/config/index')).default; + expect(config.assetsConfig.public_path).toBe('/custom/files'); + }); + + it('should have default AWS data', async () => { + const config = (await import('../../../src/config/index')).default; + expect(config.awsData).toEqual({ + awsRegion: 'us-east-2', + awsAccessKeyId: '', + awsSecretAccessKey: '', + awsSessionToken: '', + bucketName: '', + bucketKey: '', + }); + }); + + it('should have default MySQL configuration', async () => { + const config = (await import('../../../src/config/index')).default; + expect(config.mysql.host).toBe('host_name'); + expect(config.mysql.user).toBe('user_name'); + expect(config.mysql.database).toBe('database_name'); + }); +}); diff --git a/upload-api/tests/unit/constants/index.test.ts b/upload-api/tests/unit/constants/index.test.ts new file mode 100644 index 000000000..4475cf4c7 --- /dev/null +++ b/upload-api/tests/unit/constants/index.test.ts @@ -0,0 +1,91 @@ +import { describe, it, expect } from 'vitest'; +import { HTTP_CODES, HTTP_TEXTS, HTTP_RESPONSE_HEADERS, MIGRATION_DATA_CONFIG, MACOSX_FOLDER } from '../../../src/constants/index'; + +describe('constants', () => { + describe('HTTP_CODES', () => { + it('should have standard HTTP status codes', () => { + expect(HTTP_CODES.OK).toBe(200); + expect(HTTP_CODES.BAD_REQUEST).toBe(400); + expect(HTTP_CODES.UNAUTHORIZED).toBe(401); + expect(HTTP_CODES.FORBIDDEN).toBe(403); + expect(HTTP_CODES.NOT_FOUND).toBe(404); + expect(HTTP_CODES.SERVER_ERROR).toBe(500); + }); + + it('should have additional status codes', () => { + expect(HTTP_CODES.TOO_MANY_REQS).toBe(429); + expect(HTTP_CODES.SOMETHING_WRONG).toBe(501); + expect(HTTP_CODES.MOVED_PERMANENTLY).toBe(301); + expect(HTTP_CODES.SUPPORT_DOC).toBe(294); + expect(HTTP_CODES.UNPROCESSABLE_CONTENT).toBe(422); + }); + }); + + describe('HTTP_TEXTS', () => { + it('should have error message strings', () => { + expect(HTTP_TEXTS.UNAUTHORIZED).toContain('unauthorized'); + expect(HTTP_TEXTS.INTERNAL_ERROR).toContain('Internal server error'); + expect(HTTP_TEXTS.ROUTE_ERROR).toContain('not available'); + expect(HTTP_TEXTS.SOMETHING_WENT_WRONG).toContain('Something went wrong'); + }); + + it('should have validation messages', () => { + expect(HTTP_TEXTS.VALIDATION_ERROR).toContain('validation failed'); + expect(HTTP_TEXTS.VALIDATION_SUCCESSFULL).toContain('validated successfully'); + }); + + it('should have file operation messages', () => { + expect(HTTP_TEXTS.ZIP_FILE_SAVE).toBeDefined(); + expect(HTTP_TEXTS.XML_FILE_SAVE).toBeDefined(); + expect(HTTP_TEXTS.S3_ERROR).toBeDefined(); + }); + + it('should have mapper and locale messages', () => { + expect(HTTP_TEXTS.MAPPER_SAVED).toContain('completed'); + expect(HTTP_TEXTS.LOCALE_SAVED).toContain('locales'); + expect(HTTP_TEXTS.LOCALE_FAILED).toContain('Unable'); + }); + }); + + describe('HTTP_RESPONSE_HEADERS', () => { + it('should have CORS and content-type headers', () => { + expect(HTTP_RESPONSE_HEADERS['Access-Control-Allow-Origin']).toBe('*'); + expect(HTTP_RESPONSE_HEADERS['Content-Type']).toBe('application/json'); + expect(HTTP_RESPONSE_HEADERS.Connection).toBe('close'); + }); + }); + + describe('MIGRATION_DATA_CONFIG', () => { + it('should have data folder configurations', () => { + expect(MIGRATION_DATA_CONFIG.DATA).toBe('cmsMigrationData'); + expect(MIGRATION_DATA_CONFIG.BACKUP_DATA).toBe('migration-data'); + }); + + it('should have locale configurations', () => { + expect(MIGRATION_DATA_CONFIG.LOCALE_DIR_NAME).toBe('locale'); + expect(MIGRATION_DATA_CONFIG.LOCALE_FILE_NAME).toBe('locales.json'); + expect(MIGRATION_DATA_CONFIG.LOCALE_MASTER_LOCALE).toBe('master-locale.json'); + }); + + it('should have content type configurations', () => { + expect(MIGRATION_DATA_CONFIG.CONTENT_TYPES_DIR_NAME).toBe('content_types'); + expect(MIGRATION_DATA_CONFIG.CONTENT_TYPES_FILE_NAME).toBe('contenttype.json'); + }); + + it('should have asset configurations', () => { + expect(MIGRATION_DATA_CONFIG.ASSETS_DIR_NAME).toBe('assets'); + expect(MIGRATION_DATA_CONFIG.ASSETS_FILE_NAME).toBe('assets.json'); + }); + + it('should have entries and global field configurations', () => { + expect(MIGRATION_DATA_CONFIG.ENTRIES_DIR_NAME).toBe('entries'); + expect(MIGRATION_DATA_CONFIG.GLOBAL_FIELDS_DIR_NAME).toBe('global_fields'); + }); + }); + + describe('MACOSX_FOLDER', () => { + it('should be __MACOSX', () => { + expect(MACOSX_FOLDER).toBe('__MACOSX'); + }); + }); +}); diff --git a/upload-api/tests/unit/controllers/aem.controller.test.ts b/upload-api/tests/unit/controllers/aem.controller.test.ts new file mode 100644 index 000000000..578797d1b --- /dev/null +++ b/upload-api/tests/unit/controllers/aem.controller.test.ts @@ -0,0 +1,55 @@ +import { describe, it, expect, vi, beforeEach } from 'vitest'; + +const { mockAxiosRequest, mockContentTypes, mockLocales } = vi.hoisted(() => ({ + mockAxiosRequest: vi.fn(), + mockContentTypes: vi.fn(), + mockLocales: vi.fn(), +})); + +vi.mock('migration-aem', () => ({ + contentTypes: mockContentTypes, + locales: mockLocales, + validator: vi.fn(), +})); + +vi.mock('axios', () => ({ default: { request: mockAxiosRequest } })); +vi.mock('../../../src/utils/logger', () => ({ default: { info: vi.fn(), error: vi.fn(), warn: vi.fn(), debug: vi.fn() } })); + +import { createAemMapper } from '../../../src/controllers/aem/index'; + +describe('createAemMapper', () => { + beforeEach(() => { + vi.clearAllMocks(); + }); + + it('should create AEM mapper and send to API', async () => { + const mockConvertAndCreate = vi.fn().mockResolvedValue([{ uid: 'page' }]); + const mockProcessAndSave = vi.fn().mockResolvedValue(['en-US']); + mockContentTypes.mockReturnValue({ convertAndCreate: mockConvertAndCreate }); + mockLocales.mockReturnValue({ processAndSave: mockProcessAndSave }); + mockAxiosRequest + .mockResolvedValueOnce({ status: 200 }) + .mockResolvedValueOnce({ data: { data: { content_mapper: [1] } } }); + + await createAemMapper('/path/to/aem', 'proj-1', 'token', 'csm'); + + expect(mockContentTypes).toHaveBeenCalled(); + expect(mockLocales).toHaveBeenCalled(); + expect(mockAxiosRequest).toHaveBeenCalledTimes(2); + }); + + it('should handle error gracefully', async () => { + mockContentTypes.mockImplementation(() => { throw new Error('AEM error'); }); + await expect(createAemMapper('/path', 'proj-1', 'token', 'csm')).resolves.toBeUndefined(); + }); + + it('should handle API send failure', async () => { + const mockConvertAndCreate = vi.fn().mockResolvedValue([]); + const mockProcessAndSave = vi.fn().mockResolvedValue([]); + mockContentTypes.mockReturnValue({ convertAndCreate: mockConvertAndCreate }); + mockLocales.mockReturnValue({ processAndSave: mockProcessAndSave }); + mockAxiosRequest.mockRejectedValue(new Error('Network error')); + + await expect(createAemMapper('/path', 'proj-1', 'token')).resolves.toBeUndefined(); + }, 30000); +}); diff --git a/upload-api/tests/unit/controllers/awsBucket.controller.test.ts b/upload-api/tests/unit/controllers/awsBucket.controller.test.ts new file mode 100644 index 000000000..6ee1d26e0 --- /dev/null +++ b/upload-api/tests/unit/controllers/awsBucket.controller.test.ts @@ -0,0 +1,73 @@ +import { describe, it, expect, vi, beforeEach } from 'vitest'; +import { Readable } from 'stream'; + +const { mockSend, MockGetObjectCommand } = vi.hoisted(() => ({ + mockSend: vi.fn(), + MockGetObjectCommand: vi.fn().mockImplementation(function (this: any, params: any) { + Object.assign(this, params); + }), +})); + +vi.mock('../../../src/services/aws/client', () => ({ + client: { send: mockSend }, +})); + +vi.mock('../../../src/config', () => ({ + default: { + awsData: { + awsRegion: 'us-east-2', + awsAccessKeyId: '', + awsSecretAccessKey: '', + awsSessionToken: '', + }, + }, +})); + +vi.mock('@aws-sdk/client-s3', () => ({ + GetObjectCommand: MockGetObjectCommand, + S3Client: vi.fn(), +})); + +import getBuketObject from '../../../src/controllers/awsBucket/index'; + +describe('getBuketObject', () => { + beforeEach(() => { + vi.clearAllMocks(); + }); + + it('should return readable stream from S3', async () => { + const mockStream = new Readable({ read() {} }); + mockSend.mockResolvedValue({ Body: mockStream }); + + const result = await getBuketObject({ Key: 'test-key', Bucket: 'test-bucket' }); + expect(result).toBe(mockStream); + expect(mockSend).toHaveBeenCalled(); + }); + + it('should throw error when S3 body is empty', async () => { + mockSend.mockResolvedValue({ Body: null }); + + await expect( + getBuketObject({ Key: 'test-key', Bucket: 'test-bucket' }) + ).rejects.toThrow('Empty response body from S3'); + }); + + it('should throw error when S3 send fails', async () => { + mockSend.mockRejectedValue(new Error('S3 access denied')); + + await expect( + getBuketObject({ Key: 'test-key', Bucket: 'test-bucket' }) + ).rejects.toThrow('S3 access denied'); + }); + + it('should pass correct params to GetObjectCommand', async () => { + const mockStream = new Readable({ read() {} }); + mockSend.mockResolvedValue({ Body: mockStream }); + + await getBuketObject({ Key: 'my/path/file.zip', Bucket: 'my-bucket' }); + expect(MockGetObjectCommand).toHaveBeenCalledWith({ + Key: 'my/path/file.zip', + Bucket: 'my-bucket', + }); + }); +}); diff --git a/upload-api/tests/unit/controllers/sitecore.controller.test.ts b/upload-api/tests/unit/controllers/sitecore.controller.test.ts new file mode 100644 index 000000000..b8f57817c --- /dev/null +++ b/upload-api/tests/unit/controllers/sitecore.controller.test.ts @@ -0,0 +1,48 @@ +import { describe, it, expect, vi, beforeEach } from 'vitest'; + +const { mockAxiosRequest, mockDeleteFolderSync } = vi.hoisted(() => ({ + mockAxiosRequest: vi.fn(), + mockDeleteFolderSync: vi.fn(), +})); + +vi.mock('axios', () => ({ default: { request: mockAxiosRequest } })); +vi.mock('../../../src/helper', () => ({ deleteFolderSync: mockDeleteFolderSync, fileOperationLimiter: vi.fn() })); +vi.mock('../../../src/utils/logger', () => ({ default: { info: vi.fn(), error: vi.fn(), warn: vi.fn(), debug: vi.fn() } })); + +import createSitecoreMapper from '../../../src/controllers/sitecore/index'; + +describe('createSitecoreMapper', () => { + beforeEach(() => { + vi.clearAllMocks(); + }); + + it('should be a function', () => { + expect(typeof createSitecoreMapper).toBe('function'); + }); + + it('should handle error gracefully when extraction fails', async () => { + mockAxiosRequest.mockResolvedValue({ status: 200 }); + await expect( + createSitecoreMapper('/nonexistent', 'proj-1', 'token', 'csm', {}) + ).resolves.toBeUndefined(); + }); + + it('should not throw on empty path', async () => { + mockAxiosRequest.mockResolvedValue({ status: 200 }); + await expect( + createSitecoreMapper('', 'proj-1', 'token', 'csm', {}) + ).resolves.toBeUndefined(); + }); + + it('should handle API request failure', async () => { + mockAxiosRequest.mockRejectedValue(new Error('Network error')); + await expect( + createSitecoreMapper('/fake', 'proj-1', 'token', 'csm', {}) + ).resolves.toBeUndefined(); + }); + + it('should accept all required parameters', async () => { + mockAxiosRequest.mockResolvedValue({ status: 200 }); + await createSitecoreMapper('/path', 'project-id', 'app-token', 'csm', { option: true }); + }); +}); diff --git a/upload-api/tests/unit/controllers/wordpress.controller.test.ts b/upload-api/tests/unit/controllers/wordpress.controller.test.ts new file mode 100644 index 000000000..86abbc044 --- /dev/null +++ b/upload-api/tests/unit/controllers/wordpress.controller.test.ts @@ -0,0 +1,73 @@ +import { describe, it, expect, vi, beforeEach } from 'vitest'; + +const { mockAxiosRequest, mockDeleteFolderSync, mockExtractLocale, mockExtractContentTypes } = vi.hoisted(() => ({ + mockAxiosRequest: vi.fn(), + mockDeleteFolderSync: vi.fn(), + mockExtractLocale: vi.fn().mockResolvedValue([]), + mockExtractContentTypes: vi.fn().mockResolvedValue(null), +})); + +vi.mock('migration-wordpress', () => ({ + extractLocale: mockExtractLocale, + extractContentTypes: mockExtractContentTypes, +})); + +vi.mock('axios', () => ({ default: { request: mockAxiosRequest } })); +vi.mock('../../../src/helper', () => ({ deleteFolderSync: mockDeleteFolderSync, fileOperationLimiter: vi.fn() })); +vi.mock('../../../src/utils/logger', () => ({ default: { info: vi.fn(), error: vi.fn(), warn: vi.fn(), debug: vi.fn() } })); + +import createWordpressMapper from '../../../src/controllers/wordpress/index'; + +describe('createWordpressMapper', () => { + beforeEach(() => { + vi.clearAllMocks(); + }); + + it('should extract content types and send mapper to API', async () => { + mockExtractLocale.mockResolvedValue(['en']); + mockExtractContentTypes.mockResolvedValue([{ uid: 'post', title: 'Post' }]); + mockAxiosRequest + .mockResolvedValueOnce({ data: { data: { content_mapper: [1] } } }) + .mockResolvedValueOnce({ status: 200 }); + + await createWordpressMapper('/path', 'proj-1', 'token', 'csm', {}); + + expect(mockExtractLocale).toHaveBeenCalledWith('/path'); + expect(mockAxiosRequest).toHaveBeenCalledTimes(2); + expect(mockDeleteFolderSync).toHaveBeenCalled(); + }); + + it('should not send mapper when contentTypeData is falsy', async () => { + mockExtractLocale.mockResolvedValue([]); + mockExtractContentTypes.mockResolvedValue(null); + + await createWordpressMapper('/path', 'proj-1', 'token', 'csm', {}); + expect(mockAxiosRequest).not.toHaveBeenCalled(); + }); + + it('should handle error gracefully', async () => { + mockExtractLocale.mockRejectedValue(new Error('Extract failed')); + await expect(createWordpressMapper('/path', 'proj-1', 'token', 'csm', {})).resolves.toBeUndefined(); + }); + + it('should handle API error gracefully', async () => { + mockExtractLocale.mockResolvedValue(['en']); + mockExtractContentTypes.mockResolvedValue([{ uid: 'page' }]); + mockAxiosRequest.mockRejectedValue({ response: { data: 'API error' } }); + await expect(createWordpressMapper('/path', 'proj-1', 'token', 'csm', {})).resolves.toBeUndefined(); + }); + + it('should set type=content_type on each content type', async () => { + mockExtractLocale.mockResolvedValue(['en']); + mockExtractContentTypes.mockResolvedValue([{ uid: 'post' }, { uid: 'page' }]); + mockAxiosRequest + .mockResolvedValueOnce({ data: { data: { content_mapper: [1] } } }) + .mockResolvedValueOnce({ status: 200 }); + + await createWordpressMapper('/path', 'proj-1', 'token', 'csm', {}); + + const payload = JSON.parse(mockAxiosRequest.mock.calls[0][0].data); + expect(payload.contentTypes[0].type).toBe('content_type'); + expect(payload.contentTypes[1].type).toBe('content_type'); + }); +}); diff --git a/upload-api/tests/unit/helper/index.test.ts b/upload-api/tests/unit/helper/index.test.ts new file mode 100644 index 000000000..66232c178 --- /dev/null +++ b/upload-api/tests/unit/helper/index.test.ts @@ -0,0 +1,249 @@ +import { describe, it, expect, vi, beforeEach } from 'vitest'; + +const { + mockMkdir, mockWriteFile, mockExistsSync, mockReaddirSync, + mockLstatSync, mockUnlinkSync, mockRmdirSync, mockParseStringPromise, +} = vi.hoisted(() => ({ + mockMkdir: vi.fn().mockResolvedValue(undefined), + mockWriteFile: vi.fn().mockResolvedValue(undefined), + mockExistsSync: vi.fn(), + mockReaddirSync: vi.fn(), + mockLstatSync: vi.fn(), + mockUnlinkSync: vi.fn(), + mockRmdirSync: vi.fn(), + mockParseStringPromise: vi.fn().mockResolvedValue({ rss: { channel: { item: [] } } }), +})); + +vi.mock('../../../src/utils/logger', () => ({ + default: { info: vi.fn(), error: vi.fn(), warn: vi.fn(), debug: vi.fn() }, +})); + +vi.mock('fs', () => ({ + default: { + existsSync: (...a: any[]) => mockExistsSync(...a), + readdirSync: (...a: any[]) => mockReaddirSync(...a), + lstatSync: (...a: any[]) => mockLstatSync(...a), + unlinkSync: (...a: any[]) => mockUnlinkSync(...a), + rmdirSync: (...a: any[]) => mockRmdirSync(...a), + promises: { + mkdir: (...a: any[]) => mockMkdir(...a), + writeFile: (...a: any[]) => mockWriteFile(...a), + }, + }, + existsSync: (...a: any[]) => mockExistsSync(...a), + readdirSync: (...a: any[]) => mockReaddirSync(...a), + lstatSync: (...a: any[]) => mockLstatSync(...a), + unlinkSync: (...a: any[]) => mockUnlinkSync(...a), + rmdirSync: (...a: any[]) => mockRmdirSync(...a), + promises: { + mkdir: (...a: any[]) => mockMkdir(...a), + writeFile: (...a: any[]) => mockWriteFile(...a), + }, +})); + +vi.mock('xml2js', () => ({ + default: { + Parser: class MockParser { parseStringPromise = mockParseStringPromise; }, + }, +})); + +vi.mock('jszip', () => { + const Cls = vi.fn().mockImplementation(function (this: any) { + this.files = {}; + this.loadAsync = vi.fn().mockResolvedValue({ files: {} }); + }); + return { default: Cls, __esModule: true }; +}); + +import { getFileName, saveJson, parseXmlToJson, deleteFolderSync, saveZip } from '../../../src/helper/index'; + +describe('helper/index', () => { + beforeEach(() => { + vi.clearAllMocks(); + mockParseStringPromise.mockResolvedValue({ rss: { channel: { item: [] } } }); + }); + + describe('getFileName', () => { + it('should extract filename and extension from S3 key', () => { + const result = (getFileName as any)({ Key: 'folder/subfolder/test.zip' }); + expect(result.fileName).toBe('test.zip'); + expect(result.fileExt).toBe('zip'); + }); + + it('should handle keys without slashes', () => { + const result = (getFileName as any)({ Key: 'myfile.xml' }); + expect(result.fileName).toBe('myfile.xml'); + expect(result.fileExt).toBe('xml'); + }); + + it('should handle keys with multiple dots', () => { + const result = (getFileName as any)({ Key: 'path/file.v2.tar.gz' }); + expect(result.fileName).toBe('file.v2.tar.gz'); + expect(result.fileExt).toBe('gz'); + }); + + it('should handle empty Key', () => { + const result = (getFileName as any)({ Key: '' }); + expect(result.fileName).toBe(''); + }); + }); + + describe('saveJson', () => { + it('should save JSON content to file', async () => { + const result = await saveJson('{"key":"value"}', 'test.json'); + expect(result).toBe(true); + expect(mockMkdir).toHaveBeenCalled(); + expect(mockWriteFile).toHaveBeenCalled(); + }); + + it('should stringify object content', async () => { + const result = await saveJson({ key: 'value' } as any, 'test.json'); + expect(result).toBe(true); + expect(mockWriteFile).toHaveBeenCalledWith( + expect.any(String), JSON.stringify({ key: 'value' }, null, 4), 'utf8' + ); + }); + + it('should handle falsy content with empty JSON fallback', async () => { + const result = await saveJson('' as any, 'test.json'); + expect(result).toBe(true); + expect(mockWriteFile).toHaveBeenCalledWith(expect.any(String), '{}', 'utf8'); + }); + + it('should return false on write error', async () => { + mockMkdir.mockRejectedValueOnce(new Error('write fail')); + const result = await saveJson('data', 'test.json'); + expect(result).toBe(false); + }); + }); + + describe('parseXmlToJson', () => { + it('should parse valid XML to JSON', async () => { + const result = await parseXmlToJson(''); + expect(result).toEqual({ rss: { channel: { item: [] } } }); + }); + + it('should strip XML comments before parsing', async () => { + const result = await parseXmlToJson(''); + expect(result).toBeDefined(); + }); + + it('should strip DOCTYPE before parsing', async () => { + const result = await parseXmlToJson(''); + expect(result).toBeDefined(); + }); + + it('should return false when parsing fails', async () => { + mockParseStringPromise.mockRejectedValueOnce(new Error('parse error')); + const result = await parseXmlToJson('bad'); + expect(result).toBe(false); + }); + }); + + describe('deleteFolderSync', () => { + it('should do nothing if folder does not exist', () => { + mockExistsSync.mockReturnValue(false); + deleteFolderSync('/fake/path'); + expect(mockReaddirSync).not.toHaveBeenCalled(); + }); + + it('should delete files and folder recursively', () => { + mockExistsSync.mockReturnValue(true); + mockReaddirSync.mockReturnValueOnce(['file1.txt', 'subdir']).mockReturnValueOnce([]); + mockLstatSync + .mockReturnValueOnce({ isDirectory: () => false }) + .mockReturnValueOnce({ isDirectory: () => true }); + + deleteFolderSync('/test/folder'); + expect(mockUnlinkSync).toHaveBeenCalled(); + expect(mockRmdirSync).toHaveBeenCalled(); + }); + }); + + describe('saveZip', () => { + it('should extract regular files from zip', async () => { + const zip = { + files: { + 'folder/file.txt': { dir: false, async: vi.fn().mockResolvedValue(Buffer.from('content')) }, + }, + }; + + const result = await saveZip(zip, 'test-project'); + expect(result.isSaved).toBe(true); + expect(mockMkdir).toHaveBeenCalled(); + expect(mockWriteFile).toHaveBeenCalled(); + }); + + it('should skip directory entries', async () => { + const zip = { files: { 'folder/': { dir: true } } }; + const result = await saveZip(zip, 'test-project'); + expect(result.isSaved).toBe(true); + expect(mockWriteFile).not.toHaveBeenCalled(); + }); + + it('should skip __MACOSX files', async () => { + const zip = { + files: { + '__MACOSX/file.txt': { dir: false, async: vi.fn().mockResolvedValue(Buffer.from('x')) }, + }, + }; + const result = await saveZip(zip, 'test-project'); + expect(result.isSaved).toBe(true); + expect(mockWriteFile).not.toHaveBeenCalled(); + }); + + it('should set filePath for non-sitecore folder files', async () => { + const zip = { + files: { + 'customfolder/page.json': { dir: false, async: vi.fn().mockResolvedValue(Buffer.from('{}')) }, + }, + }; + const result = await saveZip(zip, 'test-project'); + expect(result.isSaved).toBe(true); + expect(result.filePath).toBe('customfolder'); + }); + + it('should not set filePath for sitecore folder files', async () => { + const zip = { + files: { + 'items/master/test.json': { dir: false, async: vi.fn().mockResolvedValue(Buffer.from('{}')) }, + }, + }; + const result = await saveZip(zip, 'test-project'); + expect(result.isSaved).toBe(true); + expect(result.filePath).toBeUndefined(); + }); + + it('should skip filePath for files already under the main folder', async () => { + const zip = { + files: { + 'test-project/file.txt': { dir: false, async: vi.fn().mockResolvedValue(Buffer.from('x')) }, + }, + }; + const result = await saveZip(zip, 'test-project'); + expect(result.isSaved).toBe(true); + }); + + it('should handle nested zip files (non-sitecore)', async () => { + const zip = { + files: { + 'nested.zip': { dir: false, async: vi.fn().mockResolvedValue(Buffer.from('zipdata')) }, + }, + }; + const result = await saveZip(zip, 'test-project'); + expect(result.isSaved).toBe(true); + }); + + it('should return isSaved false on error', async () => { + const result = await saveZip(null as any, 'test'); + expect(result.isSaved).toBe(false); + expect(result.filePath).toBeUndefined(); + }); + + it('should handle empty zip files object', async () => { + const zip = { files: {} }; + const result = await saveZip(zip, 'test-project'); + expect(result.isSaved).toBe(true); + }); + }); +}); diff --git a/upload-api/tests/unit/routes/index.routes.test.ts b/upload-api/tests/unit/routes/index.routes.test.ts new file mode 100644 index 000000000..0d82a1072 --- /dev/null +++ b/upload-api/tests/unit/routes/index.routes.test.ts @@ -0,0 +1,530 @@ +import { describe, it, expect, vi, beforeEach } from 'vitest'; +import { EventEmitter } from 'events'; + +const { + mockStatSync, + mockCreateReadStream, + mockClientSend, + mockHandleFileProcessing, + mockCreateMapper, + mockConfig, +} = vi.hoisted(() => ({ + mockStatSync: vi.fn(), + mockCreateReadStream: vi.fn(), + mockClientSend: vi.fn(), + mockHandleFileProcessing: vi.fn(), + mockCreateMapper: vi.fn(), + mockConfig: { + cmsType: 'wordpress', + isLocalPath: true, + localPath: 'sql', + awsData: { + awsRegion: 'us-east-2', + bucketName: 'test-bucket', + bucketKey: 'project/test.zip', + }, + mysql: { host: 'localhost', user: 'root', password: 'pw', database: 'drupal', port: '3306' }, + assetsConfig: { base_url: 'http://test.com', public_path: '/files' }, + plan: { dropdown: { optionLimit: 100 } }, + } as any, +})); + +vi.mock('fs', () => ({ + createReadStream: (...args: any[]) => mockCreateReadStream(...args), + statSync: (...args: any[]) => mockStatSync(...args), + default: { + createReadStream: (...args: any[]) => mockCreateReadStream(...args), + statSync: (...args: any[]) => mockStatSync(...args), + }, +})); + +vi.mock('../../../src/services/aws/client', () => ({ + client: { send: (...args: any[]) => mockClientSend(...args) }, +})); + +vi.mock('../../../src/helper', () => ({ + fileOperationLimiter: (_req: any, _res: any, next: any) => next(), + deleteFolderSync: vi.fn(), +})); + +vi.mock('../../../src/services/fileProcessing', () => ({ + default: (...args: any[]) => mockHandleFileProcessing(...args), +})); + +vi.mock('../../../src/services/createMapper', () => ({ + default: (...args: any[]) => mockCreateMapper(...args), +})); + +vi.mock('../../../src/config/index', () => ({ default: mockConfig })); + +vi.mock('@aws-sdk/client-s3', () => ({ + GetObjectCommand: vi.fn().mockImplementation(function (this: any, p: any) { Object.assign(this, p); }), + CreateMultipartUploadCommand: vi.fn().mockImplementation(function (this: any, p: any) { Object.assign(this, p); }), + UploadPartCommand: vi.fn().mockImplementation(function (this: any, p: any) { Object.assign(this, p); }), + CompleteMultipartUploadCommand: vi.fn().mockImplementation(function (this: any, p: any) { Object.assign(this, p); }), +})); + +function getHandler(router: any, method: string, routePath: string) { + const layer = router.stack.find( + (l: any) => l.route?.path === routePath && l.route?.methods?.[method] + ); + if (!layer) throw new Error(`Route ${method.toUpperCase()} ${routePath} not found`); + const handlers = layer.route.stack; + return handlers[handlers.length - 1].handle; +} + +function mockReq(overrides: any = {}) { + return { headers: { projectid: 'proj-1', app_token: 'tk', affix: 'csm' }, ...overrides }; +} + +function mockRes() { + return { + headersSent: false, + status: vi.fn().mockReturnThis(), + json: vi.fn().mockReturnThis(), + send: vi.fn().mockReturnThis(), + }; +} + +async function waitFor(fn: () => boolean, ms = 300) { + const t = Date.now(); + while (Date.now() - t < ms) { + if (fn()) return; + await new Promise((r) => setTimeout(r, 10)); + } +} + +describe('routes/index', () => { + let router: any; + + beforeEach(async () => { + vi.clearAllMocks(); + Object.assign(mockConfig, { + cmsType: 'wordpress', + isLocalPath: true, + localPath: 'sql', + awsData: { awsRegion: 'us-east-2', bucketName: 'test-bucket', bucketKey: 'project/test.zip' }, + mysql: { host: 'localhost', user: 'root', password: 'pw', database: 'drupal', port: '3306' }, + assetsConfig: { base_url: 'http://test.com', public_path: '/files' }, + plan: { dropdown: { optionLimit: 100 } }, + }); + const mod = await import('../../../src/routes/index'); + router = mod.default; + }); + + it('should export a router with 3 routes', () => { + expect(router).toBeDefined(); + const routes = router.stack.filter((l: any) => l.route); + expect(routes.length).toBe(3); + }); + + describe('GET /config', () => { + it('should return config without mysql password', async () => { + const handler = getHandler(router, 'get', '/config'); + const res = mockRes(); + await handler(mockReq(), res); + + expect(res.json).toHaveBeenCalledTimes(1); + const sent = res.json.mock.calls[0][0]; + expect(sent.mysql.password).toBeUndefined(); + expect(sent.mysql.host).toBe('localhost'); + expect(sent.cmsType).toBe('wordpress'); + }); + }); + + describe('POST /upload', () => { + it('should upload file via multipart', async () => { + const handler = getHandler(router, 'post', '/upload'); + mockClientSend + .mockResolvedValueOnce({ UploadId: 'uid-1' }) + .mockResolvedValueOnce({ ETag: 'etag-1' }) + .mockResolvedValueOnce({}); + + const req = mockReq({ file: { originalname: 'test.zip', buffer: Buffer.from('data') } }); + const res = mockRes(); + await handler(req, res); + expect(res.send).toHaveBeenCalledWith('file uploaded sucessfully.'); + }); + + it('should skip upload when no file buffer', async () => { + const handler = getHandler(router, 'post', '/upload'); + const res = mockRes(); + await handler(mockReq({ file: null }), res); + expect(res.send).toHaveBeenCalledWith('file uploaded sucessfully.'); + expect(mockClientSend).not.toHaveBeenCalled(); + }); + + it('should handle upload S3 error', async () => { + const handler = getHandler(router, 'post', '/upload'); + mockClientSend.mockRejectedValueOnce(new Error('S3 fail')); + const res = mockRes(); + await handler(mockReq({ file: { originalname: 'f.zip', buffer: Buffer.from('x') } }), res); + }); + }); + + describe('GET /validator — SQL path', () => { + it('should process SQL and create mapper on 200', async () => { + mockConfig.localPath = 'sql'; + mockHandleFileProcessing.mockResolvedValue({ status: 200, message: 'OK', file_details: {} }); + + const handler = getHandler(router, 'get', '/validator'); + const res = mockRes(); + await handler(mockReq(), res); + + expect(mockHandleFileProcessing).toHaveBeenCalledWith('sql', null, 'wordpress', 'sql'); + expect(mockCreateMapper).toHaveBeenCalled(); + expect(res.status).toHaveBeenCalledWith(200); + }); + + it('should not call mapper when status is not 200', async () => { + mockConfig.localPath = 'SQL'; + mockHandleFileProcessing.mockResolvedValue({ status: 400, message: 'Bad', file_details: {} }); + + const handler = getHandler(router, 'get', '/validator'); + const res = mockRes(); + await handler(mockReq(), res); + + expect(mockCreateMapper).not.toHaveBeenCalled(); + expect(res.status).toHaveBeenCalledWith(400); + }); + + it('should return 500 when file processing returns null', async () => { + mockConfig.localPath = 'sql'; + mockHandleFileProcessing.mockResolvedValue(null); + + const handler = getHandler(router, 'get', '/validator'); + const res = mockRes(); + await handler(mockReq(), res); + + expect(res.status).toHaveBeenCalledWith(500); + }); + + it('should strip mysql password from SQL response', async () => { + mockConfig.localPath = 'sql'; + mockHandleFileProcessing.mockResolvedValue({ status: 200, message: 'OK', file_details: {} }); + + const handler = getHandler(router, 'get', '/validator'); + const res = mockRes(); + await handler(mockReq(), res); + + const sent = res.json.mock.calls[0][0]; + expect(sent.file_details.mySQLDetails.password).toBeUndefined(); + }); + }); + + describe('GET /validator — directory path', () => { + it('should handle directory and call mapper on 200', async () => { + mockConfig.localPath = '/tmp/content-dir'; + mockStatSync.mockReturnValue({ isDirectory: () => true }); + mockHandleFileProcessing.mockResolvedValue({ status: 200, message: 'OK' }); + + const handler = getHandler(router, 'get', '/validator'); + const res = mockRes(); + await handler(mockReq(), res); + + expect(mockHandleFileProcessing).toHaveBeenCalledWith('folder', '/tmp/content-dir', 'wordpress', 'content-dir'); + expect(mockCreateMapper).toHaveBeenCalled(); + expect(res.status).toHaveBeenCalledWith(200); + }); + + it('should not call mapper when directory validation fails', async () => { + mockConfig.localPath = '/tmp/bad'; + mockStatSync.mockReturnValue({ isDirectory: () => true }); + mockHandleFileProcessing.mockResolvedValue({ status: 400, message: 'Bad' }); + + const handler = getHandler(router, 'get', '/validator'); + const res = mockRes(); + await handler(mockReq(), res); + + expect(mockCreateMapper).not.toHaveBeenCalled(); + }); + + it('should return 500 on stat error', async () => { + mockConfig.localPath = '/nonexistent'; + mockStatSync.mockImplementation(() => { throw new Error('ENOENT'); }); + + const handler = getHandler(router, 'get', '/validator'); + const res = mockRes(); + await handler(mockReq(), res); + + expect(res.status).toHaveBeenCalledWith(500); + expect(res.json).toHaveBeenCalledWith(expect.objectContaining({ status: 'error' })); + }); + }); + + describe('GET /validator — file path (XML)', () => { + it('should process XML file stream', async () => { + mockConfig.localPath = '/tmp/data.xml'; + mockStatSync.mockReturnValue({ isDirectory: () => false }); + + const stream = new EventEmitter(); + mockCreateReadStream.mockReturnValue(stream); + mockHandleFileProcessing.mockResolvedValue({ status: 200, message: 'Valid' }); + + const handler = getHandler(router, 'get', '/validator'); + const res = mockRes(); + handler(mockReq(), res); + + await new Promise((r) => setTimeout(r, 20)); + stream.emit('data', 'xml'); + stream.emit('end'); + await waitFor(() => res.status.mock.calls.length > 0); + + expect(mockHandleFileProcessing).toHaveBeenCalledWith('xml', 'xml', 'wordpress', 'data'); + expect(res.status).toHaveBeenCalledWith(200); + }); + + it('should return 400 when XML stream is empty', async () => { + mockConfig.localPath = '/tmp/empty.xml'; + mockStatSync.mockReturnValue({ isDirectory: () => false }); + + const stream = new EventEmitter(); + mockCreateReadStream.mockReturnValue(stream); + + const handler = getHandler(router, 'get', '/validator'); + const res = mockRes(); + handler(mockReq(), res); + + await new Promise((r) => setTimeout(r, 20)); + stream.emit('end'); + await waitFor(() => res.status.mock.calls.length > 0); + + expect(res.status).toHaveBeenCalledWith(400); + }); + + it('should call createMapper for valid XML with 200', async () => { + mockConfig.localPath = '/tmp/data.xml'; + mockStatSync.mockReturnValue({ isDirectory: () => false }); + + const stream = new EventEmitter(); + mockCreateReadStream.mockReturnValue(stream); + mockHandleFileProcessing.mockResolvedValue({ status: 200, message: 'OK' }); + + const handler = getHandler(router, 'get', '/validator'); + const res = mockRes(); + handler(mockReq(), res); + + await new Promise((r) => setTimeout(r, 20)); + stream.emit('data', ''); + stream.emit('end'); + await waitFor(() => res.json.mock.calls.length > 0); + + expect(mockCreateMapper).toHaveBeenCalled(); + }); + + it('should handle XML processing error', async () => { + mockConfig.localPath = '/tmp/data.xml'; + mockStatSync.mockReturnValue({ isDirectory: () => false }); + + const stream = new EventEmitter(); + mockCreateReadStream.mockReturnValue(stream); + mockHandleFileProcessing.mockRejectedValue(new Error('parse fail')); + + const handler = getHandler(router, 'get', '/validator'); + const res = mockRes(); + handler(mockReq(), res); + + await new Promise((r) => setTimeout(r, 20)); + stream.emit('data', 'bad data'); + stream.emit('end'); + await waitFor(() => res.status.mock.calls.length > 0); + + expect(res.status).toHaveBeenCalledWith(500); + }); + }); + + describe('GET /validator — file path (ZIP)', () => { + it('should process ZIP file stream', async () => { + mockConfig.localPath = '/tmp/data.zip'; + mockStatSync.mockReturnValue({ isDirectory: () => false }); + + const stream = new EventEmitter(); + mockCreateReadStream.mockReturnValue(stream); + mockHandleFileProcessing.mockResolvedValue({ status: 200, message: 'OK', file: 'inner' }); + + const handler = getHandler(router, 'get', '/validator'); + const res = mockRes(); + handler(mockReq(), res); + + await new Promise((r) => setTimeout(r, 20)); + stream.emit('data', Buffer.from('zipdata')); + stream.emit('end'); + await waitFor(() => res.status.mock.calls.length > 0); + + expect(mockHandleFileProcessing).toHaveBeenCalled(); + expect(res.status).toHaveBeenCalledWith(200); + expect(mockCreateMapper).toHaveBeenCalled(); + }); + + it('should return 400 when ZIP stream is empty', async () => { + mockConfig.localPath = '/tmp/data.zip'; + mockStatSync.mockReturnValue({ isDirectory: () => false }); + + const stream = new EventEmitter(); + mockCreateReadStream.mockReturnValue(stream); + + const handler = getHandler(router, 'get', '/validator'); + const res = mockRes(); + handler(mockReq(), res); + + await new Promise((r) => setTimeout(r, 20)); + stream.emit('end'); + await waitFor(() => res.status.mock.calls.length > 0); + + expect(res.status).toHaveBeenCalledWith(400); + }); + + it('should handle stream error', async () => { + mockConfig.localPath = '/tmp/data.zip'; + mockStatSync.mockReturnValue({ isDirectory: () => false }); + + const stream = new EventEmitter(); + mockCreateReadStream.mockReturnValue(stream); + + const handler = getHandler(router, 'get', '/validator'); + const res = mockRes(); + handler(mockReq(), res); + + await new Promise((r) => setTimeout(r, 20)); + stream.emit('error', new Error('read error')); + await waitFor(() => res.status.mock.calls.length > 0); + + expect(res.status).toHaveBeenCalledWith(500); + }); + + it('should handle ZIP processing error in end callback', async () => { + mockConfig.localPath = '/tmp/data.zip'; + mockStatSync.mockReturnValue({ isDirectory: () => false }); + + const stream = new EventEmitter(); + mockCreateReadStream.mockReturnValue(stream); + mockHandleFileProcessing.mockRejectedValue(new Error('zip fail')); + + const handler = getHandler(router, 'get', '/validator'); + const res = mockRes(); + handler(mockReq(), res); + + await new Promise((r) => setTimeout(r, 20)); + stream.emit('data', Buffer.from('zipdata')); + stream.emit('end'); + await waitFor(() => res.status.mock.calls.length > 0); + + expect(res.status).toHaveBeenCalledWith(500); + }); + }); + + describe('GET /validator — S3 path', () => { + it('should process S3 file', async () => { + mockConfig.isLocalPath = false; + + const bodyStream = new EventEmitter(); + mockClientSend.mockResolvedValue({ Body: bodyStream }); + mockHandleFileProcessing.mockResolvedValue({ status: 200, message: 'OK' }); + + const handler = getHandler(router, 'get', '/validator'); + const res = mockRes(); + handler(mockReq(), res); + + await new Promise((r) => setTimeout(r, 50)); + bodyStream.emit('data', Buffer.from('s3data')); + bodyStream.emit('end'); + await waitFor(() => res.status.mock.calls.length > 0); + + expect(mockHandleFileProcessing).toHaveBeenCalled(); + expect(res.status).toHaveBeenCalledWith(200); + }); + + it('should handle S3 with file-specific path', async () => { + mockConfig.isLocalPath = false; + + const bodyStream = new EventEmitter(); + mockClientSend.mockResolvedValue({ Body: bodyStream }); + mockHandleFileProcessing.mockResolvedValue({ status: 200, message: 'OK', file: 'nested' }); + + const handler = getHandler(router, 'get', '/validator'); + const res = mockRes(); + handler(mockReq(), res); + + await new Promise((r) => setTimeout(r, 50)); + bodyStream.emit('data', Buffer.from('s3zip')); + bodyStream.emit('end'); + await waitFor(() => res.status.mock.calls.length > 0); + + expect(mockCreateMapper).toHaveBeenCalled(); + }); + + it('should handle empty S3 body', async () => { + mockConfig.isLocalPath = false; + mockClientSend.mockResolvedValue({ Body: null }); + + const handler = getHandler(router, 'get', '/validator'); + const res = mockRes(); + await handler(mockReq(), res); + await new Promise((r) => setTimeout(r, 50)); + + expect(res.status).toHaveBeenCalledWith(500); + }); + + it('should handle S3 error', async () => { + mockConfig.isLocalPath = false; + mockClientSend.mockRejectedValue(new Error('S3 error')); + + const handler = getHandler(router, 'get', '/validator'); + const res = mockRes(); + await handler(mockReq(), res); + + expect(res.status).toHaveBeenCalledWith(500); + }); + + it('should handle S3 stream error event', async () => { + mockConfig.isLocalPath = false; + + const bodyStream = new EventEmitter(); + mockClientSend.mockResolvedValue({ Body: bodyStream }); + + const handler = getHandler(router, 'get', '/validator'); + const res = mockRes(); + handler(mockReq(), res); + + await new Promise((r) => setTimeout(r, 50)); + bodyStream.emit('error', new Error('stream fail')); + await waitFor(() => res.status.mock.calls.length > 0); + + expect(res.status).toHaveBeenCalledWith(500); + }); + + it('should handle empty S3 buffer in end callback', async () => { + mockConfig.isLocalPath = false; + + const bodyStream = new EventEmitter(); + mockClientSend.mockResolvedValue({ Body: bodyStream }); + + const handler = getHandler(router, 'get', '/validator'); + const res = mockRes(); + handler(mockReq(), res); + + await new Promise((r) => setTimeout(r, 50)); + bodyStream.emit('end'); + await waitFor(() => res.status.mock.calls.length > 0 || res.json.mock.calls.length > 0, 500); + }); + + it('should handle S3 processing error in end callback', async () => { + mockConfig.isLocalPath = false; + + const bodyStream = new EventEmitter(); + mockClientSend.mockResolvedValue({ Body: bodyStream }); + mockHandleFileProcessing.mockRejectedValue(new Error('process fail')); + + const handler = getHandler(router, 'get', '/validator'); + const res = mockRes(); + handler(mockReq(), res); + + await new Promise((r) => setTimeout(r, 50)); + bodyStream.emit('data', Buffer.from('data')); + bodyStream.emit('end'); + await waitFor(() => res.status.mock.calls.length > 0); + + expect(res.status).toHaveBeenCalledWith(500); + }); + }); +}); diff --git a/upload-api/tests/unit/services/aws-client.test.ts b/upload-api/tests/unit/services/aws-client.test.ts new file mode 100644 index 000000000..3bab12d4a --- /dev/null +++ b/upload-api/tests/unit/services/aws-client.test.ts @@ -0,0 +1,40 @@ +import { describe, it, expect, vi } from 'vitest'; + +const { mockS3Client } = vi.hoisted(() => ({ + mockS3Client: vi.fn().mockImplementation(function (this: any, config: any) { + this.config = config; + this.send = vi.fn(); + }), +})); + +vi.mock('../../../src/config', () => ({ + default: { + awsData: { + awsRegion: 'us-east-2', + awsAccessKeyId: 'test-key', + awsSecretAccessKey: 'test-secret', + awsSessionToken: 'test-token', + }, + }, +})); + +vi.mock('@aws-sdk/client-s3', () => ({ + S3Client: mockS3Client, + GetObjectCommand: vi.fn(), +})); + +describe('aws/client', () => { + it('should create S3Client with config from environment', async () => { + const { client } = await import('../../../src/services/aws/client'); + + expect(mockS3Client).toHaveBeenCalledWith({ + region: 'us-east-2', + credentials: { + accessKeyId: 'test-key', + secretAccessKey: 'test-secret', + sessionToken: 'test-token', + }, + }); + expect(client).toBeDefined(); + }); +}); diff --git a/upload-api/tests/unit/services/contentful.service.test.ts b/upload-api/tests/unit/services/contentful.service.test.ts new file mode 100644 index 000000000..8c1aa57ec --- /dev/null +++ b/upload-api/tests/unit/services/contentful.service.test.ts @@ -0,0 +1,49 @@ +import { describe, it, expect, vi, beforeEach } from 'vitest'; + +const { mockAxiosRequest } = vi.hoisted(() => ({ + mockAxiosRequest: vi.fn(), +})); + +vi.mock('axios', () => ({ default: { request: mockAxiosRequest } })); +vi.mock('../../../src/utils/logger', () => ({ default: { info: vi.fn(), error: vi.fn(), warn: vi.fn(), debug: vi.fn() } })); + +import createContentfulMapper from '../../../src/services/contentful/index'; + +describe('createContentfulMapper', () => { + beforeEach(() => { + vi.clearAllMocks(); + }); + + it('should be a function', () => { + expect(typeof createContentfulMapper).toBe('function'); + }); + + it('should handle error gracefully when extractLocale fails', async () => { + const config: any = { localPath: '/nonexistent/path' }; + await expect( + createContentfulMapper('proj-1', 'token', 'csm', config) + ).resolves.toBeUndefined(); + }); + + it('should handle empty localPath', async () => { + const config: any = { localPath: '' }; + await expect( + createContentfulMapper('proj-1', 'token', 'csm', config) + ).resolves.toBeUndefined(); + }); + + it('should handle undefined config values', async () => { + const config: any = {}; + await expect( + createContentfulMapper('proj-1', 'token', 'csm', config) + ).resolves.toBeUndefined(); + }); + + it('should handle API error response', async () => { + const config: any = { localPath: '/tmp/nonexistent' }; + mockAxiosRequest.mockRejectedValue({ response: { data: 'API Error' } }); + await expect( + createContentfulMapper('proj-1', 'token', 'csm', config) + ).resolves.toBeUndefined(); + }); +}); diff --git a/upload-api/tests/unit/services/createMapper.test.ts b/upload-api/tests/unit/services/createMapper.test.ts new file mode 100644 index 000000000..69f7f7fc2 --- /dev/null +++ b/upload-api/tests/unit/services/createMapper.test.ts @@ -0,0 +1,160 @@ +import { describe, it, expect, vi, beforeEach } from 'vitest'; + +const { + mockCreateSitecoreMapper, mockCreateContentfulMapper, + mockCreateWordpressMapper, mockCreateAemMapper, mockCreateDrupalMapper, + mockDeleteFolderSync, mockReaddirSync, mockExistsSync, mockStatSync, +} = vi.hoisted(() => ({ + mockCreateSitecoreMapper: vi.fn(), + mockCreateContentfulMapper: vi.fn(), + mockCreateWordpressMapper: vi.fn(), + mockCreateAemMapper: vi.fn(), + mockCreateDrupalMapper: vi.fn(), + mockDeleteFolderSync: vi.fn(), + mockReaddirSync: vi.fn().mockReturnValue([]), + mockExistsSync: vi.fn().mockReturnValue(false), + mockStatSync: vi.fn().mockReturnValue({ isDirectory: () => false }), +})); + +vi.mock('../../../src/controllers/sitecore', () => ({ default: mockCreateSitecoreMapper })); +vi.mock('../../../src/services/contentful', () => ({ default: mockCreateContentfulMapper })); +vi.mock('../../../src/controllers/wordpress', () => ({ default: mockCreateWordpressMapper })); +vi.mock('../../../src/controllers/aem', () => ({ createAemMapper: mockCreateAemMapper })); +vi.mock('../../../src/services/drupal', () => ({ default: mockCreateDrupalMapper })); +vi.mock('../../../src/helper', () => ({ + deleteFolderSync: mockDeleteFolderSync, + fileOperationLimiter: vi.fn(), +})); +vi.mock('../../../src/utils/logger', () => ({ + default: { info: vi.fn(), error: vi.fn(), warn: vi.fn(), debug: vi.fn() }, +})); + +vi.mock('fs', () => ({ + default: { + readdirSync: (...a: any[]) => mockReaddirSync(...a), + existsSync: (...a: any[]) => mockExistsSync(...a), + statSync: (...a: any[]) => mockStatSync(...a), + }, + readdirSync: (...a: any[]) => mockReaddirSync(...a), + existsSync: (...a: any[]) => mockExistsSync(...a), + statSync: (...a: any[]) => mockStatSync(...a), +})); + +import createMapper from '../../../src/services/createMapper'; + +describe('createMapper', () => { + const defaultConfig: any = { cmsType: 'wordpress', localPath: '/tmp/test' }; + + beforeEach(() => { + vi.clearAllMocks(); + mockReaddirSync.mockReturnValue([]); + mockExistsSync.mockReturnValue(false); + mockStatSync.mockReturnValue({ isDirectory: () => false }); + }); + + it('should dispatch to sitecore mapper', async () => { + mockCreateSitecoreMapper.mockResolvedValue('sitecore-result'); + const config = { ...defaultConfig, cmsType: 'sitecore' }; + const result = await createMapper('/path', 'proj-1', 'token', 'csm', config); + expect(mockCreateSitecoreMapper).toHaveBeenCalledWith('/path', 'proj-1', 'token', 'csm', config); + expect(result).toBe('sitecore-result'); + }); + + it('should dispatch to contentful mapper', async () => { + mockCreateContentfulMapper.mockResolvedValue('contentful-result'); + const config = { ...defaultConfig, cmsType: 'contentful' }; + const result = await createMapper('/path', 'proj-1', 'token', 'csm', config); + expect(mockCreateContentfulMapper).toHaveBeenCalledWith('proj-1', 'token', 'csm', config); + expect(result).toBe('contentful-result'); + }); + + it('should dispatch to wordpress mapper', async () => { + mockCreateWordpressMapper.mockResolvedValue('wordpress-result'); + const config = { ...defaultConfig, cmsType: 'wordpress' }; + const result = await createMapper('/path', 'proj-1', 'token', 'csm', config); + expect(mockCreateWordpressMapper).toHaveBeenCalledWith('/path', 'proj-1', 'token', 'csm', config); + expect(result).toBe('wordpress-result'); + }); + + it('should dispatch to aem mapper', async () => { + mockCreateAemMapper.mockResolvedValue('aem-result'); + const config = { ...defaultConfig, cmsType: 'aem' }; + const result = await createMapper('/path', 'proj-1', 'token', 'csm', config); + expect(mockCreateAemMapper).toHaveBeenCalledWith('/path', 'proj-1', 'token', 'csm'); + expect(result).toBe('aem-result'); + }); + + it('should dispatch to drupal mapper', async () => { + mockCreateDrupalMapper.mockResolvedValue('drupal-result'); + const config = { ...defaultConfig, cmsType: 'drupal' }; + const result = await createMapper('/path', 'proj-1', 'token', 'csm', config); + expect(mockCreateDrupalMapper).toHaveBeenCalledWith(config, 'proj-1', 'token', 'csm'); + expect(result).toBe('drupal-result'); + }); + + it('should return false for unknown CMS type', async () => { + const config = { ...defaultConfig, cmsType: 'unknown' }; + const result = await createMapper('/path', 'proj-1', 'token', 'csm', config); + expect(result).toBe(false); + }); + + it('should handle case-insensitive CMS types', async () => { + mockCreateWordpressMapper.mockResolvedValue('ok'); + const config = { ...defaultConfig, cmsType: 'WordPress' }; + const result = await createMapper('/path', 'proj-1', 'token', 'csm', config); + expect(mockCreateWordpressMapper).toHaveBeenCalled(); + expect(result).toBe('ok'); + }); + + describe('clearAllMigrationData', () => { + it('should delete folders ending with MigrationData', async () => { + mockReaddirSync.mockReturnValue(['sitecoreMigrationData', 'drupalMigrationData', 'other']); + mockExistsSync.mockReturnValue(true); + mockStatSync.mockReturnValue({ isDirectory: () => true }); + + mockCreateWordpressMapper.mockResolvedValue(undefined); + await createMapper('/path', 'proj-1', 'token', 'csm', defaultConfig); + + expect(mockDeleteFolderSync).toHaveBeenCalledTimes(2); + expect(mockDeleteFolderSync).toHaveBeenCalledWith(expect.stringContaining('sitecoreMigrationData')); + expect(mockDeleteFolderSync).toHaveBeenCalledWith(expect.stringContaining('drupalMigrationData')); + }); + + it('should skip non-directory items', async () => { + mockReaddirSync.mockReturnValue(['contentfulMigrationData']); + mockExistsSync.mockReturnValue(true); + mockStatSync.mockReturnValue({ isDirectory: () => false }); + + mockCreateWordpressMapper.mockResolvedValue(undefined); + await createMapper('/path', 'proj-1', 'token', 'csm', defaultConfig); + + expect(mockDeleteFolderSync).not.toHaveBeenCalled(); + }); + + it('should handle no migration folders', async () => { + mockReaddirSync.mockReturnValue(['src', 'node_modules', 'package.json']); + + mockCreateWordpressMapper.mockResolvedValue(undefined); + await createMapper('/path', 'proj-1', 'token', 'csm', defaultConfig); + + expect(mockDeleteFolderSync).not.toHaveBeenCalled(); + }); + + it('should handle delete error gracefully', async () => { + mockReaddirSync.mockReturnValue(['sitecoreMigrationData']); + mockExistsSync.mockReturnValue(true); + mockStatSync.mockReturnValue({ isDirectory: () => true }); + mockDeleteFolderSync.mockImplementation(() => { throw new Error('delete err'); }); + + mockCreateWordpressMapper.mockResolvedValue(undefined); + await createMapper('/path', 'proj-1', 'token', 'csm', defaultConfig); + }); + + it('should handle readdirSync error gracefully', async () => { + mockReaddirSync.mockImplementation(() => { throw new Error('read err'); }); + + mockCreateWordpressMapper.mockResolvedValue(undefined); + await createMapper('/path', 'proj-1', 'token', 'csm', defaultConfig); + }); + }); +}); diff --git a/upload-api/tests/unit/services/drupal.service.test.ts b/upload-api/tests/unit/services/drupal.service.test.ts new file mode 100644 index 000000000..774ddeccf --- /dev/null +++ b/upload-api/tests/unit/services/drupal.service.test.ts @@ -0,0 +1,57 @@ +import { describe, it, expect, vi, beforeEach } from 'vitest'; + +const { mockAxiosRequest } = vi.hoisted(() => ({ + mockAxiosRequest: vi.fn(), +})); + +vi.mock('axios', () => ({ default: { request: mockAxiosRequest } })); +vi.mock('../../../src/utils/logger', () => ({ default: { info: vi.fn(), error: vi.fn(), warn: vi.fn(), debug: vi.fn() } })); +vi.mock('fs', () => ({ + default: { existsSync: vi.fn().mockReturnValue(false), promises: { readFile: vi.fn() } }, + existsSync: vi.fn().mockReturnValue(false), + promises: { readFile: vi.fn() }, +})); + +import createDrupalMapper from '../../../src/services/drupal/index'; + +describe('createDrupalMapper', () => { + const config: any = { + mysql: { host: 'localhost', user: 'root', password: 'pw', database: 'drupal' }, + assetsConfig: { base_url: 'http://test.com', public_path: '/files' }, + }; + + beforeEach(() => { + vi.clearAllMocks(); + }); + + it('should be a function', () => { + expect(typeof createDrupalMapper).toBe('function'); + }); + + it('should handle errors gracefully when extraction fails', async () => { + await expect( + createDrupalMapper(config, 'proj-1', 'token', 'csm') + ).resolves.toBeUndefined(); + }); + + it('should handle missing mysql config gracefully', async () => { + const badConfig: any = { mysql: {} }; + await expect( + createDrupalMapper(badConfig, 'proj-1', 'token', 'csm') + ).resolves.toBeUndefined(); + }); + + it('should handle null config values', async () => { + const nullConfig: any = { mysql: null }; + await expect( + createDrupalMapper(nullConfig, 'proj-1', 'token', 'csm') + ).resolves.toBeUndefined(); + }); + + it('should handle API error response', async () => { + mockAxiosRequest.mockRejectedValue({ response: { data: 'API Error' } }); + await expect( + createDrupalMapper(config, 'proj-1', 'token', 'csm') + ).resolves.toBeUndefined(); + }); +}); diff --git a/upload-api/tests/unit/services/fileProcessing.test.ts b/upload-api/tests/unit/services/fileProcessing.test.ts new file mode 100644 index 000000000..2f5be5d54 --- /dev/null +++ b/upload-api/tests/unit/services/fileProcessing.test.ts @@ -0,0 +1,183 @@ +import { describe, it, expect, vi, beforeEach } from 'vitest'; + +const { mockValidator, mockSaveZip, mockSaveJson, mockParseXmlToJson } = vi.hoisted(() => ({ + mockValidator: vi.fn(), + mockSaveZip: vi.fn(), + mockSaveJson: vi.fn(), + mockParseXmlToJson: vi.fn(), +})); + +vi.mock('../../../src/validators/index', () => ({ default: mockValidator })); +vi.mock('../../../src/helper/index', () => ({ + saveZip: mockSaveZip, + saveJson: mockSaveJson, + parseXmlToJson: mockParseXmlToJson, + fileOperationLimiter: vi.fn(), + deleteFolderSync: vi.fn(), + getFileName: vi.fn(), +})); + +vi.mock('../../../src/utils/logger', () => ({ + default: { info: vi.fn(), error: vi.fn(), warn: vi.fn(), debug: vi.fn() }, +})); + +vi.mock('../../../src/config/index', () => ({ + default: { + cmsType: 'wordpress', + mysql: { host: 'localhost', user: 'root', password: 'pw', database: 'db', port: '3306' }, + assetsConfig: { base_url: 'http://test.com', public_path: '/files' }, + }, +})); + +vi.mock('jszip', () => ({ + default: vi.fn().mockImplementation(function (this: any) { + this.loadAsync = vi.fn().mockResolvedValue(this); + }), +})); + +import handleFileProcessing from '../../../src/services/fileProcessing'; + +describe('handleFileProcessing', () => { + beforeEach(() => { + vi.clearAllMocks(); + }); + + describe('zip files', () => { + it('should return OK on valid zip with successful save', async () => { + mockValidator.mockResolvedValue(true); + mockSaveZip.mockResolvedValue({ isSaved: true, filePath: 'extracted' }); + + const result = await handleFileProcessing('zip', Buffer.from('fake-zip'), 'sitecore', 'test'); + expect(result?.status).toBe(200); + expect(result?.message).toContain('validated successfully'); + expect(result?.file).toBe('extracted'); + }); + + it('should return UNAUTHORIZED on invalid zip', async () => { + mockValidator.mockResolvedValue(false); + + const result = await handleFileProcessing('zip', Buffer.from('bad-zip'), 'sitecore', 'test'); + expect(result?.status).toBe(401); + expect(result?.message).toContain('validation failed'); + }); + + it('should return undefined when zip is valid but save fails', async () => { + mockValidator.mockResolvedValue(true); + mockSaveZip.mockResolvedValue({ isSaved: false }); + + const result = await handleFileProcessing('zip', Buffer.from('fake-zip'), 'sitecore', 'test'); + expect(result).toBeUndefined(); + }); + }); + + describe('xml files', () => { + it('should return OK for valid wordpress XML', async () => { + mockValidator.mockResolvedValue(true); + mockParseXmlToJson.mockResolvedValue({ rss: {} }); + mockSaveJson.mockResolvedValue(true); + + const result = await handleFileProcessing('xml', Buffer.from(''), 'wordpress', 'test'); + expect(result?.status).toBe(200); + }); + + it('should return UNAUTHORIZED when XML save fails', async () => { + mockValidator.mockResolvedValue(true); + mockParseXmlToJson.mockResolvedValue({ rss: {} }); + mockSaveJson.mockResolvedValue(false); + + const result = await handleFileProcessing('xml', Buffer.from(''), 'wordpress', 'test'); + expect(result?.status).toBe(401); + }); + + it('should return undefined when XML validation fails', async () => { + mockValidator.mockResolvedValue(false); + + const result = await handleFileProcessing('xml', Buffer.from(''), 'wordpress', 'test'); + expect(result).toBeUndefined(); + }); + + it('should handle drupal XML', async () => { + mockValidator.mockResolvedValue(true); + mockParseXmlToJson.mockResolvedValue({ data: {} }); + mockSaveJson.mockResolvedValue(true); + + const result = await handleFileProcessing('xml', Buffer.from(''), 'drupal', 'test'); + expect(result?.status).toBe(200); + }); + }); + + describe('folder', () => { + it('should return OK for valid folder', async () => { + mockValidator.mockResolvedValue(true); + + const result = await handleFileProcessing('folder', '/path/to/aem', 'aem', 'aem-folder'); + expect(result?.status).toBe(200); + }); + + it('should return UNAUTHORIZED for invalid folder', async () => { + mockValidator.mockResolvedValue(false); + + const result = await handleFileProcessing('folder', '/path/to/aem', 'aem', 'aem-folder'); + expect(result?.status).toBe(401); + }); + }); + + describe('sql', () => { + it('should return OK on valid SQL connection (boolean true)', async () => { + mockValidator.mockResolvedValue(true); + + const result = await handleFileProcessing('sql', null, 'drupal', 'sql'); + expect(result?.status).toBe(200); + expect(result?.message).toBe('File validated successfully'); + }); + + it('should return OK on valid SQL connection (object { success: true })', async () => { + mockValidator.mockResolvedValue({ success: true }); + + const result = await handleFileProcessing('sql', null, 'drupal', 'sql'); + expect(result?.status).toBe(200); + }); + + it('should return UNAUTHORIZED on failed SQL connection', async () => { + mockValidator.mockResolvedValue({ success: false, error: 'DB error' }); + + const result = await handleFileProcessing('sql', null, 'drupal', 'sql'); + expect(result?.status).toBe(401); + expect(result?.message).toBe('DB error'); + }); + + it('should return SERVER_ERROR on exception', async () => { + mockValidator.mockRejectedValue(new Error('Connection timeout')); + + const result = await handleFileProcessing('sql', null, 'drupal', 'sql'); + expect(result?.status).toBe(500); + }); + }); + + describe('other file types (json/default)', () => { + it('should return OK for valid JSON file', async () => { + mockValidator.mockResolvedValue(true); + + const result = await handleFileProcessing( + 'json', + Buffer.from('{"contentTypes":[]}'), + 'contentful', + 'test' + ); + expect(result?.status).toBe(200); + }); + + it('should return UNAUTHORIZED for invalid JSON file', async () => { + mockValidator.mockResolvedValue(false); + + const result = await handleFileProcessing('json', Buffer.from('bad'), 'contentful', 'test'); + expect(result?.status).toBe(401); + }); + + it('should return UNAUTHORIZED when buffer is null for non-SQL file', async () => { + const result = await handleFileProcessing('json', null, 'contentful', 'test'); + expect(result?.status).toBe(401); + expect(result?.message).toBe('File data is missing'); + }); + }); +}); diff --git a/upload-api/tests/unit/utils/index.test.ts b/upload-api/tests/unit/utils/index.test.ts new file mode 100644 index 000000000..75227a22a --- /dev/null +++ b/upload-api/tests/unit/utils/index.test.ts @@ -0,0 +1,71 @@ +import { describe, it, expect } from 'vitest'; +import { getLogMessage, safePromise } from '../../../src/utils/index'; + +describe('utils/index', () => { + describe('getLogMessage', () => { + it('should return a log message object with methodName and message', () => { + const result = getLogMessage('testMethod', 'test message'); + expect(result.methodName).toBe('testMethod'); + expect(result.message).toBe('test message'); + }); + + it('should include user when provided', () => { + const user = { id: 'user-1', name: 'Test' }; + const result = getLogMessage('testMethod', 'test message', user); + expect(result.user).toEqual(user); + }); + + it('should include error when provided', () => { + const error = new Error('test error'); + const result = getLogMessage('testMethod', 'test message', {}, error); + expect(result.error).toBe(error); + expect(result.methodName).toBe('testMethod'); + }); + + it('should include both user and error when provided', () => { + const user = { id: 'user-1' }; + const error = new Error('fail'); + const result = getLogMessage('testMethod', 'test message', user, error); + expect(result.user).toEqual(user); + expect(result.error).toBe(error); + }); + + it('should include user with default empty object', () => { + const result = getLogMessage('testMethod', 'msg'); + expect(result).toHaveProperty('methodName', 'testMethod'); + expect(result).toHaveProperty('message', 'msg'); + }); + + it('should not include error when not provided', () => { + const result = getLogMessage('method', 'msg'); + expect(result).not.toHaveProperty('error'); + }); + }); + + describe('safePromise', () => { + it('should return [null, result] on resolved promise', async () => { + const [err, result] = await safePromise(Promise.resolve('success')); + expect(err).toBeNull(); + expect(result).toBe('success'); + }); + + it('should return [error] on rejected promise', async () => { + const error = new Error('fail'); + const [err, result] = await safePromise(Promise.reject(error)); + expect(err).toBe(error); + expect(result).toBeUndefined(); + }); + + it('should handle promise resolving with undefined', async () => { + const [err, result] = await safePromise(Promise.resolve(undefined)); + expect(err).toBeNull(); + expect(result).toBeUndefined(); + }); + + it('should handle promise resolving with null', async () => { + const [err, result] = await safePromise(Promise.resolve(null)); + expect(err).toBeNull(); + expect(result).toBeNull(); + }); + }); +}); diff --git a/upload-api/tests/unit/utils/sanitize-path.utils.test.ts b/upload-api/tests/unit/utils/sanitize-path.utils.test.ts new file mode 100644 index 000000000..bc17367e1 --- /dev/null +++ b/upload-api/tests/unit/utils/sanitize-path.utils.test.ts @@ -0,0 +1,178 @@ +import { describe, it, expect } from 'vitest'; +import path from 'path'; +import { + sanitizeFilename, + isValidPathSegment, + sanitizeId, + isPathWithinBase, + getSafePath, +} from '../../../src/utils/sanitize-path.utils'; + +describe('sanitize-path.utils', () => { + describe('sanitizeFilename', () => { + it('should return basename of a safe filename', () => { + expect(sanitizeFilename('file.txt')).toBe('file.txt'); + }); + + it('should strip directory components', () => { + expect(sanitizeFilename('/path/to/file.txt')).toBe('file.txt'); + }); + + it('should remove unsafe characters', () => { + expect(sanitizeFilename('file@name!.txt')).toBe('filename.txt'); + }); + + it('should return empty string for null/undefined', () => { + expect(sanitizeFilename(null as any)).toBe(''); + expect(sanitizeFilename(undefined as any)).toBe(''); + }); + + it('should return empty string for non-string input', () => { + expect(sanitizeFilename(123 as any)).toBe(''); + }); + + it('should return empty string for empty string', () => { + expect(sanitizeFilename('')).toBe(''); + }); + + it('should allow spaces, hyphens, underscores, and dots', () => { + expect(sanitizeFilename('my file_v2.0-final.txt')).toBe('my file_v2.0-final.txt'); + }); + + it('should handle path traversal attempts', () => { + const result = sanitizeFilename('../../../etc/passwd'); + expect(result).toBe('passwd'); + }); + + it('should handle filenames with mixed path separators', () => { + const result = sanitizeFilename('some/path/file.txt'); + expect(result).toBe('file.txt'); + }); + }); + + describe('isValidPathSegment', () => { + it('should return true for alphanumeric strings', () => { + expect(isValidPathSegment('abc123')).toBe(true); + }); + + it('should allow underscores, hyphens, and dots', () => { + expect(isValidPathSegment('my-file_v2.0')).toBe(true); + }); + + it('should return false for empty string', () => { + expect(isValidPathSegment('')).toBe(false); + }); + + it('should return false for null/undefined', () => { + expect(isValidPathSegment(null as any)).toBe(false); + expect(isValidPathSegment(undefined as any)).toBe(false); + }); + + it('should return false for strings with slashes', () => { + expect(isValidPathSegment('path/to/file')).toBe(false); + }); + + it('should return false for strings with special characters', () => { + expect(isValidPathSegment('file@name!')).toBe(false); + }); + + it('should return false for strings with spaces', () => { + expect(isValidPathSegment('has space')).toBe(false); + }); + + it('should return false for non-string input', () => { + expect(isValidPathSegment(123 as any)).toBe(false); + }); + }); + + describe('sanitizeId', () => { + it('should return sanitized string for valid input', () => { + expect(sanitizeId('blt1234abcd')).toBe('blt1234abcd'); + }); + + it('should handle array input by using first element', () => { + expect(sanitizeId(['id-123', 'id-456'])).toBe('id-123'); + }); + + it('should strip directory components', () => { + expect(sanitizeId('path/to/id')).toBe('id'); + }); + + it('should remove special characters', () => { + expect(sanitizeId('id@special!')).toBe('idspecial'); + }); + + it('should return empty string for null/undefined', () => { + expect(sanitizeId(null as any)).toBe(''); + expect(sanitizeId(undefined as any)).toBe(''); + }); + + it('should return empty string for empty string', () => { + expect(sanitizeId('')).toBe(''); + }); + + it('should return empty string for empty array', () => { + expect(sanitizeId([])).toBe(''); + }); + + it('should allow dots, hyphens, and underscores', () => { + expect(sanitizeId('stack-id_v2.0')).toBe('stack-id_v2.0'); + }); + }); + + describe('isPathWithinBase', () => { + it('should return true when path is within base', () => { + expect(isPathWithinBase('/tmp/base/file.txt', '/tmp/base')).toBe(true); + }); + + it('should return true for nested paths', () => { + expect(isPathWithinBase('/tmp/base/sub/dir/file.txt', '/tmp/base')).toBe(true); + }); + + it('should return false for path traversal', () => { + expect(isPathWithinBase('/tmp/base/../other/file.txt', '/tmp/base')).toBe(false); + }); + + it('should return false for paths outside base', () => { + expect(isPathWithinBase('/other/path/file.txt', '/tmp/base')).toBe(false); + }); + + it('should return true when path equals base', () => { + expect(isPathWithinBase('/tmp/base', '/tmp/base')).toBe(true); + }); + }); + + describe('getSafePath', () => { + it('should return an absolute path', () => { + const result = getSafePath('/tmp/test.log'); + expect(path.isAbsolute(result)).toBe(true); + }); + + it('should sanitize the filename', () => { + const result = getSafePath('/tmp/test@file!.log'); + expect(result).not.toContain('@'); + expect(result).not.toContain('!'); + }); + + it('should prevent directory escape when baseDir is provided', () => { + const result = getSafePath('../../etc/passwd', '/tmp/logs'); + expect(result).toContain('/tmp/logs'); + }); + + it('should handle relative paths with baseDir', () => { + const result = getSafePath('subdir/file.log', '/tmp/logs'); + expect(result).toContain('file.log'); + expect(path.isAbsolute(result)).toBe(true); + }); + + it('should return default.log on empty input with baseDir', () => { + const result = getSafePath('', '/tmp/logs'); + expect(path.isAbsolute(result)).toBe(true); + }); + + it('should return default.log when path is outside baseDir', () => { + const result = getSafePath('/outside/path.log', '/tmp/logs'); + expect(result).toContain('default.log'); + }); + }); +}); diff --git a/upload-api/tests/unit/validators/aem.validator.test.ts b/upload-api/tests/unit/validators/aem.validator.test.ts new file mode 100644 index 000000000..6fa81cf90 --- /dev/null +++ b/upload-api/tests/unit/validators/aem.validator.test.ts @@ -0,0 +1,53 @@ +import { describe, it, expect, vi, beforeEach } from 'vitest'; + +const { mockValidator } = vi.hoisted(() => ({ + mockValidator: vi.fn(), +})); + +vi.mock('migration-aem', () => ({ + validator: mockValidator, +})); + +import aemValidator from '../../../src/validators/aem/index'; + +describe('aemValidator', () => { + beforeEach(() => { + vi.clearAllMocks(); + }); + + it('should return true when at least one validation passes', async () => { + mockValidator.mockResolvedValue([false, true, false]); + const result = await aemValidator({ data: '/path/to/aem' }); + expect(result).toBe(true); + }); + + it('should return false when all validations fail', async () => { + mockValidator.mockResolvedValue([false, false, false]); + const result = await aemValidator({ data: '/path/to/aem' }); + expect(result).toBe(false); + }); + + it('should return false when validation returns empty array', async () => { + mockValidator.mockResolvedValue([]); + const result = await aemValidator({ data: '/path/to/aem' }); + expect(result).toBe(false); + }); + + it('should return false when validation returns non-array', async () => { + mockValidator.mockResolvedValue(null); + const result = await aemValidator({ data: '/path/to/aem' }); + expect(result).toBe(false); + }); + + it('should return false on error', async () => { + mockValidator.mockRejectedValue(new Error('AEM validation error')); + const result = await aemValidator({ data: '/path/to/aem' }); + expect(result).toBe(false); + }); + + it('should return true when all validations pass', async () => { + mockValidator.mockResolvedValue([true, true, true]); + const result = await aemValidator({ data: '/path/to/aem' }); + expect(result).toBe(true); + }); +}); diff --git a/upload-api/tests/unit/validators/contentful.validator.test.ts b/upload-api/tests/unit/validators/contentful.validator.test.ts new file mode 100644 index 000000000..670939bc6 --- /dev/null +++ b/upload-api/tests/unit/validators/contentful.validator.test.ts @@ -0,0 +1,74 @@ +import { describe, it, expect, vi } from 'vitest'; + +vi.mock('../../../src/models/contentful.json', () => ({ + default: { + contentTypes: { name: 'contentTypes', required: 'true' }, + entries: { name: 'entries', required: 'true' }, + assets: { name: 'assets', required: 'true' }, + locales: { name: 'locales', required: 'true' }, + editorInterfaces: { name: 'editorInterfaces', required: 'true' }, + tags: { name: 'tags', required: 'false' }, + webhooks: { name: 'webhooks', required: 'false' }, + roles: { name: 'roles', required: 'false' }, + }, +})); + +import contentfulValidator from '../../../src/validators/contentful/index'; + +describe('contentfulValidator', () => { + it('should return true for valid JSON with all required properties', () => { + const data = JSON.stringify({ + contentTypes: [], + entries: [], + assets: [], + locales: [], + editorInterfaces: [], + }); + expect(contentfulValidator(data)).toBe(true); + }); + + it('should return true when optional properties are missing', () => { + const data = JSON.stringify({ + contentTypes: [], + entries: [], + assets: [], + locales: [], + editorInterfaces: [], + }); + expect(contentfulValidator(data)).toBe(true); + }); + + it('should return false when required property is missing', () => { + const data = JSON.stringify({ + contentTypes: [], + entries: [], + }); + expect(contentfulValidator(data)).toBe(false); + }); + + it('should return false for invalid JSON string', () => { + expect(contentfulValidator('not-json')).toBe(false); + }); + + it('should return false for empty string', () => { + expect(contentfulValidator('')).toBe(false); + }); + + it('should return true when all required and optional properties present', () => { + const data = JSON.stringify({ + contentTypes: [], + entries: [], + assets: [], + locales: [], + editorInterfaces: [], + tags: [], + webhooks: [], + roles: [], + }); + expect(contentfulValidator(data)).toBe(true); + }); + + it('should return false for null input', () => { + expect(contentfulValidator(null as any)).toBe(false); + }); +}); diff --git a/upload-api/tests/unit/validators/drupal.validator.test.ts b/upload-api/tests/unit/validators/drupal.validator.test.ts new file mode 100644 index 000000000..7a3237a61 --- /dev/null +++ b/upload-api/tests/unit/validators/drupal.validator.test.ts @@ -0,0 +1,322 @@ +import { describe, it, expect, vi, beforeEach } from 'vitest'; + +const { mockCreateConnection, mockExecute, mockEnd, mockAxiosHead } = vi.hoisted(() => ({ + mockCreateConnection: vi.fn(), + mockExecute: vi.fn(), + mockEnd: vi.fn().mockResolvedValue(undefined), + mockAxiosHead: vi.fn(), +})); + +vi.mock('mysql2/promise', () => ({ + default: { createConnection: (...a: any[]) => mockCreateConnection(...a) }, + createConnection: (...a: any[]) => mockCreateConnection(...a), +})); + +vi.mock('axios', () => ({ + default: { head: mockAxiosHead }, +})); + +vi.mock('../../../src/utils/logger', () => ({ + default: { info: vi.fn(), error: vi.fn(), warn: vi.fn(), debug: vi.fn() }, +})); + +import drupalValidator from '../../../src/validators/drupal/index'; + +describe('drupalValidator', () => { + const validData = { + host: 'localhost', + user: 'root', + password: 'password', + database: 'drupal_db', + port: 3306, + }; + + beforeEach(() => { + vi.clearAllMocks(); + mockEnd.mockResolvedValue(undefined); + mockCreateConnection.mockResolvedValue({ execute: mockExecute, end: mockEnd }); + }); + + it('should return success when DB validation passes', async () => { + mockExecute + .mockResolvedValueOnce([{ count: 10 }]) + .mockResolvedValueOnce([[{ name: 'field.field.node.article' }]]); + + const result = await drupalValidator({ data: validData }); + expect(result).toEqual({ success: true }); + }); + + it('should return error when host is missing', async () => { + const result = await drupalValidator({ data: { ...validData, host: '' } }); + expect(result).toEqual(expect.objectContaining({ success: false, error: expect.stringContaining('Missing') })); + }); + + it('should return error when user is missing', async () => { + const result = await drupalValidator({ data: { ...validData, user: '' } }); + expect(result).toEqual(expect.objectContaining({ success: false })); + }); + + it('should return error when database is missing', async () => { + const result = await drupalValidator({ data: { ...validData, database: '' } }); + expect(result).toEqual(expect.objectContaining({ success: false })); + }); + + it('should return error when node_field_data table does not exist', async () => { + mockExecute.mockRejectedValueOnce({ message: 'Table not found', code: 'ER_NO_SUCH_TABLE' }); + const result = await drupalValidator({ data: validData }); + expect(result).toEqual(expect.objectContaining({ success: false, error: expect.stringContaining('Required Drupal table') })); + }); + + it('should return error when config query returns empty', async () => { + mockExecute.mockResolvedValueOnce([{ count: 10 }]).mockResolvedValueOnce([[]]); + const result = await drupalValidator({ data: validData }); + expect(result).toEqual(expect.objectContaining({ success: false, error: expect.stringContaining('schema validation failed') })); + }); + + it('should return error when config table query fails with ER_NO_SUCH_TABLE', async () => { + mockExecute.mockResolvedValueOnce([{ count: 10 }]).mockRejectedValueOnce({ message: 'no table', code: 'ER_NO_SUCH_TABLE' }); + const result = await drupalValidator({ data: validData }); + expect(result).toEqual(expect.objectContaining({ success: false, error: expect.stringContaining('Required Drupal tables not found') })); + }); + + it('should return error when config table query fails with other code', async () => { + mockExecute.mockResolvedValueOnce([{ count: 10 }]).mockRejectedValueOnce({ message: 'syntax error', code: 'ER_PARSE_ERROR' }); + const result = await drupalValidator({ data: validData }); + expect(result).toEqual(expect.objectContaining({ success: false, error: expect.stringContaining('syntax error') })); + }); + + it('should handle ECONNREFUSED error', async () => { + mockCreateConnection.mockRejectedValue({ code: 'ECONNREFUSED', message: 'Connection refused' }); + const result = await drupalValidator({ data: validData }); + expect(result).toEqual(expect.objectContaining({ success: false, error: expect.stringContaining('Cannot connect') })); + }); + + it('should handle ER_ACCESS_DENIED_ERROR', async () => { + mockCreateConnection.mockRejectedValue({ code: 'ER_ACCESS_DENIED_ERROR', message: 'Denied' }); + const result = await drupalValidator({ data: validData }); + expect(result).toEqual(expect.objectContaining({ success: false, error: expect.stringContaining('Access denied') })); + }); + + it('should handle ER_BAD_DB_ERROR', async () => { + mockCreateConnection.mockRejectedValue({ code: 'ER_BAD_DB_ERROR', message: 'Bad DB' }); + const result = await drupalValidator({ data: validData }); + expect(result).toEqual(expect.objectContaining({ success: false, error: expect.stringContaining('does not exist') })); + }); + + it('should handle ETIMEDOUT error', async () => { + mockCreateConnection.mockRejectedValue({ code: 'ETIMEDOUT', message: 'Timeout' }); + const result = await drupalValidator({ data: validData }); + expect(result).toEqual(expect.objectContaining({ success: false, error: expect.stringContaining('Cannot reach') })); + }); + + it('should handle ENOTFOUND error', async () => { + mockCreateConnection.mockRejectedValue({ code: 'ENOTFOUND', message: 'Not found' }); + const result = await drupalValidator({ data: validData }); + expect(result).toEqual(expect.objectContaining({ success: false, error: expect.stringContaining('Cannot reach') })); + }); + + it('should handle generic connection error', async () => { + mockCreateConnection.mockRejectedValue({ code: 'UNKNOWN', message: 'Unknown error' }); + const result = await drupalValidator({ data: validData }); + expect(result).toEqual(expect.objectContaining({ success: false, error: expect.stringContaining('Unknown error') })); + }); + + it('should use default port 3306 when port is NaN', async () => { + mockExecute.mockResolvedValueOnce([{ count: 10 }]).mockResolvedValueOnce([[{ name: 'f' }]]); + await drupalValidator({ data: { ...validData, port: 'invalid' as any } }); + expect(mockCreateConnection).toHaveBeenCalledWith(expect.objectContaining({ port: 3306 })); + }); + + it('should close connection in finally block', async () => { + mockExecute.mockResolvedValueOnce([{ count: 10 }]).mockResolvedValueOnce([[{ name: 'f' }]]); + await drupalValidator({ data: validData }); + expect(mockEnd).toHaveBeenCalled(); + }); + + it('should handle connection close error gracefully', async () => { + mockExecute.mockResolvedValueOnce([{ count: 10 }]).mockResolvedValueOnce([[{ name: 'f' }]]); + mockEnd.mockRejectedValueOnce(new Error('close error')); + const result = await drupalValidator({ data: validData }); + expect(result).toEqual({ success: true }); + }); + + describe('asset validation', () => { + it('should skip validation when assetsConfig has no base_url', async () => { + mockExecute.mockResolvedValueOnce([{ count: 10 }]).mockResolvedValueOnce([[{ name: 'f' }]]); + const result = await drupalValidator({ + data: validData, + assetsConfig: { base_url: '', public_path: '/files' }, + }); + expect(result).toEqual({ success: true }); + expect(mockAxiosHead).not.toHaveBeenCalled(); + }); + + it('should validate assets when base_url is provided', async () => { + mockExecute + .mockResolvedValueOnce([{ count: 10 }]) + .mockResolvedValueOnce([[{ name: 'field.field.node.article' }]]) + .mockResolvedValueOnce([[ + { fid: 1, filename: 'test.jpg', uri: 'public://images/test.jpg', filesize: 1024, filemime: 'image/jpeg' }, + ]]); + + mockAxiosHead.mockResolvedValue({ + status: 200, + headers: { 'content-type': 'image/jpeg' }, + }); + + const result = await drupalValidator({ + data: validData, + assetsConfig: { base_url: 'https://example.com', public_path: '/sites/default/files/' }, + }); + + expect(result).toEqual({ success: true }); + expect(mockAxiosHead).toHaveBeenCalled(); + }); + + it('should fail when assets return HTML content type', async () => { + mockExecute + .mockResolvedValueOnce([{ count: 10 }]) + .mockResolvedValueOnce([[{ name: 'f' }]]) + .mockResolvedValueOnce([[ + { fid: 1, filename: 'test.jpg', uri: 'public://images/test.jpg', filesize: 1024, filemime: 'image/jpeg' }, + ]]); + + mockAxiosHead.mockResolvedValue({ + status: 200, + headers: { 'content-type': 'text/html; charset=utf-8' }, + }); + + const result = await drupalValidator({ + data: validData, + assetsConfig: { base_url: 'https://example.com', public_path: '/files/' }, + }); + + expect(result).toEqual(expect.objectContaining({ success: false, error: expect.stringContaining('Assets validation failed') })); + }); + + it('should fail when all asset requests fail', async () => { + mockExecute + .mockResolvedValueOnce([{ count: 10 }]) + .mockResolvedValueOnce([[{ name: 'f' }]]) + .mockResolvedValueOnce([[ + { fid: 1, filename: 'test.jpg', uri: 'public://img.jpg', filesize: 100, filemime: 'image/jpeg' }, + ]]); + + mockAxiosHead.mockRejectedValue({ response: { status: 404 }, message: 'Not found' }); + + const result = await drupalValidator({ + data: validData, + assetsConfig: { base_url: 'https://example.com', public_path: '/files/' }, + }); + + expect(result).toEqual(expect.objectContaining({ success: false })); + }); + + it('should handle no assets in database', async () => { + mockExecute + .mockResolvedValueOnce([{ count: 10 }]) + .mockResolvedValueOnce([[{ name: 'f' }]]) + .mockResolvedValueOnce([[]]); + + const result = await drupalValidator({ + data: validData, + assetsConfig: { base_url: 'https://example.com', public_path: '/files/' }, + }); + + expect(result).toEqual({ success: true }); + }); + + it('should construct URL for public:// scheme', async () => { + mockExecute + .mockResolvedValueOnce([{ count: 10 }]) + .mockResolvedValueOnce([[{ name: 'f' }]]) + .mockResolvedValueOnce([[ + { fid: 1, filename: 'test.pdf', uri: 'public://docs/test.pdf', filesize: 100, filemime: 'application/pdf' }, + ]]); + + mockAxiosHead.mockResolvedValue({ + status: 200, + headers: { 'content-type': 'application/pdf' }, + }); + + const result = await drupalValidator({ + data: validData, + assetsConfig: { base_url: 'https://example.com', public_path: '/sites/default/files/' }, + }); + + expect(result).toEqual({ success: true }); + expect(mockAxiosHead).toHaveBeenCalledWith( + expect.stringContaining('https://example.com/sites/default/files/docs/test.pdf'), + expect.any(Object) + ); + }); + + it('should normalize base_url by adding protocol when missing', async () => { + mockExecute + .mockResolvedValueOnce([{ count: 10 }]) + .mockResolvedValueOnce([[{ name: 'f' }]]) + .mockResolvedValueOnce([[ + { fid: 1, filename: 'a.jpg', uri: 'public://a.jpg', filesize: 100, filemime: 'image/jpeg' }, + ]]); + + mockAxiosHead.mockResolvedValue({ + status: 200, + headers: { 'content-type': 'image/jpeg' }, + }); + + await drupalValidator({ + data: validData, + assetsConfig: { base_url: 'example.com', public_path: '/files/' }, + }); + + expect(mockAxiosHead).toHaveBeenCalledWith( + expect.stringContaining('https://example.com'), + expect.any(Object) + ); + }); + + it('should handle asset validation error', async () => { + mockExecute + .mockResolvedValueOnce([{ count: 10 }]) + .mockResolvedValueOnce([[{ name: 'f' }]]) + .mockRejectedValueOnce(new Error('query fail')); + + const result = await drupalValidator({ + data: validData, + assetsConfig: { base_url: 'https://example.com', public_path: '/files/' }, + }); + + expect(result).toEqual(expect.objectContaining({ success: false })); + }); + + it('should validate various content types as valid assets', async () => { + const validTypes = ['image/png', 'application/pdf', 'video/mp4', 'audio/mpeg', 'text/plain', 'text/csv', 'application/zip', 'application/octet-stream', 'application/msword', 'application/vnd.openxmlformats']; + + for (const ct of validTypes) { + vi.clearAllMocks(); + mockCreateConnection.mockResolvedValue({ execute: mockExecute, end: mockEnd }); + mockEnd.mockResolvedValue(undefined); + + mockExecute + .mockResolvedValueOnce([{ count: 10 }]) + .mockResolvedValueOnce([[{ name: 'f' }]]) + .mockResolvedValueOnce([[{ fid: 1, filename: 'f', uri: 'public://f', filesize: 1, filemime: ct }]]); + + mockAxiosHead.mockResolvedValue({ status: 200, headers: { 'content-type': ct } }); + + const result = await drupalValidator({ + data: validData, + assetsConfig: { base_url: 'https://x.com', public_path: '/f/' }, + }); + + expect(result).toEqual({ success: true }); + } + }); + + it('should skip asset validation when assetsConfig has no values', async () => { + mockExecute.mockResolvedValueOnce([{ count: 10 }]).mockResolvedValueOnce([[{ name: 'f' }]]); + const result = await drupalValidator({ data: validData, assetsConfig: {} }); + expect(result).toEqual({ success: true }); + }); + }); +}); diff --git a/upload-api/tests/unit/validators/index.test.ts b/upload-api/tests/unit/validators/index.test.ts new file mode 100644 index 000000000..9639c2742 --- /dev/null +++ b/upload-api/tests/unit/validators/index.test.ts @@ -0,0 +1,70 @@ +import { describe, it, expect, vi, beforeEach } from 'vitest'; + +const { mockSitecoreValidator, mockContentfulValidator, mockWordpressValidator, mockAemValidator, mockDrupalValidator } = vi.hoisted(() => ({ + mockSitecoreValidator: vi.fn(), + mockContentfulValidator: vi.fn(), + mockWordpressValidator: vi.fn(), + mockAemValidator: vi.fn(), + mockDrupalValidator: vi.fn(), +})); + +vi.mock('migration-aem', () => ({ validator: vi.fn() })); +vi.mock('../../../src/validators/sitecore', () => ({ default: mockSitecoreValidator })); +vi.mock('../../../src/validators/contentful', () => ({ default: mockContentfulValidator })); +vi.mock('../../../src/validators/wordpress', () => ({ default: mockWordpressValidator })); +vi.mock('../../../src/validators/aem', () => ({ default: mockAemValidator })); +vi.mock('../../../src/validators/drupal', () => ({ default: mockDrupalValidator })); + +import validator from '../../../src/validators/index'; + +describe('validators/index', () => { + beforeEach(() => { + vi.clearAllMocks(); + }); + + it('should dispatch to sitecore validator for sitecore-zip', () => { + mockSitecoreValidator.mockReturnValue(true); + const result = validator({ data: 'zipData', type: 'sitecore', extension: 'zip' }); + expect(mockSitecoreValidator).toHaveBeenCalledWith({ data: 'zipData' }); + expect(result).toBe(true); + }); + + it('should dispatch to contentful validator for contentful-json', () => { + mockContentfulValidator.mockReturnValue(true); + const result = validator({ data: '{"contentTypes":[]}', type: 'contentful', extension: 'json' }); + expect(mockContentfulValidator).toHaveBeenCalledWith('{"contentTypes":[]}'); + expect(result).toBe(true); + }); + + it('should dispatch to wordpress validator for wordpress-xml', () => { + mockWordpressValidator.mockReturnValue(true); + const result = validator({ data: '', type: 'wordpress', extension: 'xml' }); + expect(mockWordpressValidator).toHaveBeenCalledWith(''); + expect(result).toBe(true); + }); + + it('should dispatch to aem validator for aem-folder', () => { + mockAemValidator.mockReturnValue(true); + const result = validator({ data: '/path', type: 'aem', extension: 'folder' }); + expect(mockAemValidator).toHaveBeenCalledWith({ data: '/path' }); + expect(result).toBe(true); + }); + + it('should dispatch to drupal validator for drupal-sql', () => { + const assetsConfig = { base_url: 'http://test.com', public_path: '/files' }; + mockDrupalValidator.mockReturnValue({ success: true }); + const result = validator({ data: {}, type: 'drupal', extension: 'sql', assetsConfig }); + expect(mockDrupalValidator).toHaveBeenCalledWith({ data: {}, assetsConfig }); + expect(result).toEqual({ success: true }); + }); + + it('should return false for unknown CMS type', () => { + const result = validator({ data: 'data', type: 'unknown', extension: 'txt' }); + expect(result).toBe(false); + }); + + it('should return false for empty type and extension', () => { + const result = validator({ data: 'data', type: '', extension: '' }); + expect(result).toBe(false); + }); +}); diff --git a/upload-api/tests/unit/validators/sitecore.validator.test.ts b/upload-api/tests/unit/validators/sitecore.validator.test.ts new file mode 100644 index 000000000..47a1985a8 --- /dev/null +++ b/upload-api/tests/unit/validators/sitecore.validator.test.ts @@ -0,0 +1,115 @@ +import { describe, it, expect, vi, beforeEach } from 'vitest'; + +const { mockJSZip } = vi.hoisted(() => ({ + mockJSZip: vi.fn().mockImplementation(function (this: any) { + this.loadAsync = vi.fn().mockResolvedValue(this); + this.files = {}; + }), +})); + +vi.mock('jszip', () => ({ + default: mockJSZip, +})); + +import sitecoreValidator from '../../../src/validators/sitecore/index'; + +describe('sitecoreValidator', () => { + beforeEach(() => { + vi.clearAllMocks(); + }); + + it('should return true when items and metadata folders are present', async () => { + const data = { + files: { + 'items/master/sitecore/content': {}, + 'metadata/config.yml': {}, + }, + }; + const result = await sitecoreValidator({ data }); + expect(result).toBe(true); + }); + + it('should return true when items and metadata are nested under path', async () => { + const data = { + files: { + 'project/items/master/content': {}, + 'project/metadata/config.yml': {}, + }, + }; + const result = await sitecoreValidator({ data }); + expect(result).toBe(true); + }); + + it('should return false when required folders are missing', async () => { + const data = { + files: { + 'installer/setup.exe': {}, + 'properties/config.txt': {}, + }, + }; + const result = await sitecoreValidator({ data }); + expect(result).toBe(false); + }); + + it('should return false for empty zip', async () => { + const data = { files: {} }; + const result = await sitecoreValidator({ data }); + expect(result).toBe(false); + }); + + it('should return false on error', async () => { + const result = await sitecoreValidator({ data: null as any }); + expect(result).toBe(false); + }); + + it('should handle mixed content with items and metadata among other files', async () => { + const data = { + files: { + 'project/items/master/content.yml': {}, + 'project/metadata/config.yml': {}, + 'project/installer/setup.exe': {}, + 'project/properties/config.txt': {}, + 'readme.txt': {}, + }, + }; + const result = await sitecoreValidator({ data }); + expect(result).toBe(true); + }); + + it('should return false when nested zip does not contain Sitecore folders', async () => { + const nestedFiles = { + 'random/file.txt': { dir: false }, + }; + + mockJSZip.mockImplementation(function (this: any) { + this.loadAsync = vi.fn().mockResolvedValue(this); + this.files = nestedFiles; + }); + + const data = { + files: { + 'archive.zip': { + dir: false, + async: vi.fn().mockResolvedValue(Buffer.from('fake-zip')), + }, + }, + }; + + const result = await sitecoreValidator({ data }); + expect(result).toBe(false); + }); + + it('should handle error when processing nested zip fails', async () => { + const data = { + files: { + 'bad.zip': { + dir: false, + async: vi.fn().mockRejectedValue(new Error('corrupt zip')), + }, + }, + }; + + const result = await sitecoreValidator({ data }); + expect(result).toBe(false); + }); +}); diff --git a/upload-api/tests/unit/validators/wordpress.validator.test.ts b/upload-api/tests/unit/validators/wordpress.validator.test.ts new file mode 100644 index 000000000..9cef16fdb --- /dev/null +++ b/upload-api/tests/unit/validators/wordpress.validator.test.ts @@ -0,0 +1,40 @@ +import { describe, it, expect, vi } from 'vitest'; + +vi.mock('../../../src/models/wordpress.json', () => ({ + default: { + item: { name: 'item', required: 'true' }, + author: { name: 'wp\\:author', required: 'false' }, + category: { name: 'wp\\:category', required: 'false' }, + }, +})); + +import wordpressValidator from '../../../src/validators/wordpress/index'; + +describe('wordpressValidator', () => { + it('should return true for valid WordPress XML with required tags', () => { + const xml = 'Test'; + expect(wordpressValidator(xml)).toBe(true); + }); + + it('should return false when required tag is missing', () => { + const xml = 'Test'; + expect(wordpressValidator(xml)).toBe(false); + }); + + it('should return true when optional tags are missing but required present', () => { + const xml = 'Content'; + expect(wordpressValidator(xml)).toBe(true); + }); + + it('should return false for empty XML', () => { + expect(wordpressValidator('')).toBe(false); + }); + + it('should return false for invalid XML that causes error', () => { + expect(wordpressValidator(null as any)).toBe(false); + }); + + it('should return false for non-XML string', () => { + expect(wordpressValidator('not xml at all')).toBe(false); + }); +}); diff --git a/upload-api/vitest.config.ts b/upload-api/vitest.config.ts new file mode 100644 index 000000000..73a275aa7 --- /dev/null +++ b/upload-api/vitest.config.ts @@ -0,0 +1,40 @@ +import { defineConfig } from 'vitest/config'; +import path from 'path'; + +export default defineConfig({ + resolve: { + alias: { + 'migration-aem': path.resolve(__dirname, 'tests/__mocks__/migration-aem.ts'), + 'migration-sitecore': path.resolve(__dirname, 'tests/__mocks__/migration-sitecore.ts'), + 'migration-wordpress': path.resolve(__dirname, 'tests/__mocks__/migration-wordpress.ts'), + 'migration-contentful': path.resolve(__dirname, 'tests/__mocks__/migration-contentful.ts'), + 'migration-drupal': path.resolve(__dirname, 'tests/__mocks__/migration-drupal.ts'), + }, + }, + test: { + globals: true, + environment: 'node', + setupFiles: ['./tests/setup.ts'], + coverage: { + provider: 'v8', + reporter: ['text', 'lcov', 'html'], + include: ['src/**/*.ts'], + exclude: [ + '**/node_modules/**', + '**/tests/**', + 'src/index.ts', + 'src/main.ts', + 'src/config/index.ts', + 'src/utils/logger.ts', + 'src/models/types.ts', + 'src/generate-schema.d.ts', + ], + thresholds: { + lines: 80, + functions: 80, + branches: 60, + statements: 80, + }, + }, + }, +});