diff --git a/.prettierrc b/.prettierrc new file mode 100644 index 0000000..4a1222f --- /dev/null +++ b/.prettierrc @@ -0,0 +1,7 @@ +{ + "semi": true, + "singleQuote": false, + "trailingComma": "all", + "printWidth": 100, + "tabWidth": 2 +} diff --git a/eslint.config.mjs b/eslint.config.mjs new file mode 100644 index 0000000..a7c6da3 --- /dev/null +++ b/eslint.config.mjs @@ -0,0 +1,24 @@ +import eslint from "@eslint/js"; +import tseslint from "typescript-eslint"; +import reactHooks from "eslint-plugin-react-hooks"; + +export default tseslint.config( + eslint.configs.recommended, + ...tseslint.configs.recommended, + { + plugins: { + "react-hooks": reactHooks, + }, + rules: { + ...reactHooks.configs.recommended.rules, + "@typescript-eslint/no-unused-vars": [ + "warn", + { argsIgnorePattern: "^_", varsIgnorePattern: "^_" }, + ], + "@typescript-eslint/no-explicit-any": "warn", + }, + }, + { + ignores: ["build/", "node_modules/", ".plasmo/", "docs/"], + }, +); diff --git a/package-lock.json b/package-lock.json index 4b02e54..82500a6 100644 --- a/package-lock.json +++ b/package-lock.json @@ -17,11 +17,16 @@ "remeda": "^2.32.0" }, "devDependencies": { + "@eslint/js": "^9.39.4", "@types/chrome": "^0.1.22", "@types/react": "^19.2.0", "@types/react-dom": "^19.2.0", + "eslint": "^9.39.4", + "eslint-plugin-react-hooks": "^5.2.0", "plasmo": "^0.90.5", - "typescript": "^5.9.3" + "prettier": "^3.8.1", + "typescript": "^5.9.3", + "typescript-eslint": "^8.57.2" } }, "node_modules/@ampproject/remapping": { @@ -693,6 +698,234 @@ "node": ">=12" } }, + "node_modules/@eslint-community/eslint-utils": { + "version": "4.9.1", + "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" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" + } + }, + "node_modules/@eslint-community/eslint-utils/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, + "license": "Apache-2.0", + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint-community/regexpp": { + "version": "4.12.2", + "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" + } + }, + "node_modules/@eslint/config-array": { + "version": "0.21.2", + "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.21.2.tgz", + "integrity": "sha512-nJl2KGTlrf9GjLimgIru+V/mzgSK0ABCDQRvxw5BjURL7WfH5uoWmizbH7QB6MmnMBd8cIC9uceWnezL1VZWWw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/object-schema": "^2.1.7", + "debug": "^4.3.1", + "minimatch": "^3.1.5" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/config-array/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" + } + }, + "node_modules/@eslint/config-array/node_modules/minimatch": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/@eslint/config-helpers": { + "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" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/core": { + "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" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/eslintrc": { + "version": "3.3.5", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.3.5.tgz", + "integrity": "sha512-4IlJx0X0qftVsN5E+/vGujTRIFtwuLbNsVUe7TO6zYPDR1O6nFwvwhIKEKSrl6dZchmYBITazxKoUYOjdtjlRg==", + "dev": true, + "license": "MIT", + "dependencies": { + "ajv": "^6.14.0", + "debug": "^4.3.2", + "espree": "^10.0.1", + "globals": "^14.0.0", + "ignore": "^5.2.0", + "import-fresh": "^3.2.1", + "js-yaml": "^4.1.1", + "minimatch": "^3.1.5", + "strip-json-comments": "^3.1.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint/eslintrc/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" + } + }, + "node_modules/@eslint/eslintrc/node_modules/globals": { + "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" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@eslint/eslintrc/node_modules/ignore": { + "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" + } + }, + "node_modules/@eslint/eslintrc/node_modules/minimatch": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/@eslint/eslintrc/node_modules/strip-json-comments": { + "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" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@eslint/js": { + "version": "9.39.4", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.39.4.tgz", + "integrity": "sha512-nE7DEIchvtiFTwBw4Lfbu59PG+kCofhjsKaCWzxTpt4lfRjRMqG6uMBzKXuEcyXhOHoUp9riAm7/aWYGhXZ9cw==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://eslint.org/donate" + } + }, + "node_modules/@eslint/object-schema": { + "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" + } + }, + "node_modules/@eslint/plugin-kit": { + "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", + "levn": "^0.4.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, "node_modules/@expo/spawn-async": { "version": "1.7.2", "resolved": "https://registry.npmjs.org/@expo/spawn-async/-/spawn-async-1.7.2.tgz", @@ -706,6 +939,58 @@ "node": ">=12" } }, + "node_modules/@humanfs/core": { + "version": "0.19.1", + "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz", + "integrity": "sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@humanfs/node": { + "version": "0.16.7", + "resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.7.tgz", + "integrity": "sha512-/zUx+yOsIrG4Y43Eh2peDeKCxlRt/gET6aHfaKpuq267qXdYDFViVHfMaLyygZOnl0kGWxFIgsBy8QFuTLUXEQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@humanfs/core": "^0.19.1", + "@humanwhocodes/retry": "^0.4.0" + }, + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@humanwhocodes/module-importer": { + "version": "1.0.1", + "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" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@humanwhocodes/retry": { + "version": "0.4.3", + "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.3.tgz", + "integrity": "sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, "node_modules/@img/sharp-darwin-arm64": { "version": "0.33.5", "resolved": "https://registry.npmjs.org/@img/sharp-darwin-arm64/-/sharp-darwin-arm64-0.33.5.tgz", @@ -5209,49 +5494,6 @@ "node": ">=10" } }, - "node_modules/@plasmohq/parcel-transformer-vue/node_modules/react": { - "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==", - "dev": true, - "license": "MIT", - "optional": true, - "peer": true, - "dependencies": { - "loose-envify": "^1.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/@plasmohq/parcel-transformer-vue/node_modules/react-dom": { - "version": "18.3.1", - "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz", - "integrity": "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==", - "dev": true, - "license": "MIT", - "optional": true, - "peer": true, - "dependencies": { - "loose-envify": "^1.1.0", - "scheduler": "^0.23.2" - }, - "peerDependencies": { - "react": "^18.3.1" - } - }, - "node_modules/@plasmohq/parcel-transformer-vue/node_modules/scheduler": { - "version": "0.23.2", - "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.2.tgz", - "integrity": "sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==", - "dev": true, - "license": "MIT", - "optional": true, - "peer": true, - "dependencies": { - "loose-envify": "^1.1.0" - } - }, "node_modules/@plasmohq/parcel-transformer-vue/node_modules/semver": { "version": "7.5.4", "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", @@ -6011,6 +6253,13 @@ "dev": true, "license": "MIT" }, + "node_modules/@types/json-schema": { + "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/parse-json": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/@types/parse-json/-/parse-json-4.0.2.tgz", @@ -6052,72 +6301,367 @@ "dev": true, "license": "MIT" }, - "node_modules/@vue/compiler-core": { - "version": "3.3.4", - "resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.3.4.tgz", - "integrity": "sha512-cquyDNvZ6jTbf/+x+AgM2Arrp6G4Dzbb0R64jiG804HRMfRiFXWI6kqUVqZ6ZR0bQhIoQjB4+2bhNtVwndW15g==", + "node_modules/@typescript-eslint/eslint-plugin": { + "version": "8.57.2", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.57.2.tgz", + "integrity": "sha512-NZZgp0Fm2IkD+La5PR81sd+g+8oS6JwJje+aRWsDocxHkjyRw0J5L5ZTlN3LI1LlOcGL7ph3eaIUmTXMIjLk0w==", "dev": true, "license": "MIT", "dependencies": { - "@babel/parser": "^7.21.3", - "@vue/shared": "3.3.4", - "estree-walker": "^2.0.2", - "source-map-js": "^1.0.2" + "@eslint-community/regexpp": "^4.12.2", + "@typescript-eslint/scope-manager": "8.57.2", + "@typescript-eslint/type-utils": "8.57.2", + "@typescript-eslint/utils": "8.57.2", + "@typescript-eslint/visitor-keys": "8.57.2", + "ignore": "^7.0.5", + "natural-compare": "^1.4.0", + "ts-api-utils": "^2.4.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "@typescript-eslint/parser": "^8.57.2", + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.0.0" } }, - "node_modules/@vue/compiler-dom": { - "version": "3.3.4", - "resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.3.4.tgz", - "integrity": "sha512-wyM+OjOVpuUukIq6p5+nwHYtj9cFroz9cwkfmP9O1nzH68BenTTv0u7/ndggT8cIQlnBeOo6sUT/gvHcIkLA5w==", + "node_modules/@typescript-eslint/eslint-plugin/node_modules/ignore": { + "version": "7.0.5", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.5.tgz", + "integrity": "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==", "dev": true, "license": "MIT", - "dependencies": { - "@vue/compiler-core": "3.3.4", - "@vue/shared": "3.3.4" + "engines": { + "node": ">= 4" } }, - "node_modules/@vue/compiler-sfc": { - "version": "3.3.4", - "resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.3.4.tgz", - "integrity": "sha512-6y/d8uw+5TkCuzBkgLS0v3lSM3hJDntFEiUORM11pQ/hKvkhSKZrXW6i69UyXlJQisJxuUEJKAWEqWbWsLeNKQ==", + "node_modules/@typescript-eslint/parser": { + "version": "8.57.2", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.57.2.tgz", + "integrity": "sha512-30ScMRHIAD33JJQkgfGW1t8CURZtjc2JpTrq5n2HFhOefbAhb7ucc7xJwdWcrEtqUIYJ73Nybpsggii6GtAHjA==", "dev": true, "license": "MIT", "dependencies": { - "@babel/parser": "^7.20.15", - "@vue/compiler-core": "3.3.4", - "@vue/compiler-dom": "3.3.4", - "@vue/compiler-ssr": "3.3.4", - "@vue/reactivity-transform": "3.3.4", - "@vue/shared": "3.3.4", - "estree-walker": "^2.0.2", - "magic-string": "^0.30.0", - "postcss": "^8.1.10", - "source-map-js": "^1.0.2" + "@typescript-eslint/scope-manager": "8.57.2", + "@typescript-eslint/types": "8.57.2", + "@typescript-eslint/typescript-estree": "8.57.2", + "@typescript-eslint/visitor-keys": "8.57.2", + "debug": "^4.4.3" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.0.0" } }, - "node_modules/@vue/compiler-ssr": { - "version": "3.3.4", - "resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.3.4.tgz", - "integrity": "sha512-m0v6oKpup2nMSehwA6Uuu+j+wEwcy7QmwMkVNVfrV9P2qE5KshC6RwOCq8fjGS/Eak/uNb8AaWekfiXxbBB6gQ==", + "node_modules/@typescript-eslint/project-service": { + "version": "8.57.2", + "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.57.2.tgz", + "integrity": "sha512-FuH0wipFywXRTHf+bTTjNyuNQQsQC3qh/dYzaM4I4W0jrCqjCVuUh99+xd9KamUfmCGPvbO8NDngo/vsnNVqgw==", "dev": true, "license": "MIT", "dependencies": { - "@vue/compiler-dom": "3.3.4", - "@vue/shared": "3.3.4" + "@typescript-eslint/tsconfig-utils": "^8.57.2", + "@typescript-eslint/types": "^8.57.2", + "debug": "^4.4.3" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.0.0" } }, - "node_modules/@vue/reactivity": { - "version": "3.3.4", - "resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.3.4.tgz", - "integrity": "sha512-kLTDLwd0B1jG08NBF3R5rqULtv/f8x3rOFByTDz4J53ttIQEDmALqKqXY0J+XQeN0aV2FBxY8nJDf88yvOPAqQ==", + "node_modules/@typescript-eslint/scope-manager": { + "version": "8.57.2", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.57.2.tgz", + "integrity": "sha512-snZKH+W4WbWkrBqj4gUNRIGb/jipDW3qMqVJ4C9rzdFc+wLwruxk+2a5D+uoFcKPAqyqEnSb4l2ULuZf95eSkw==", "dev": true, "license": "MIT", "dependencies": { - "@vue/shared": "3.3.4" + "@typescript-eslint/types": "8.57.2", + "@typescript-eslint/visitor-keys": "8.57.2" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" } }, - "node_modules/@vue/reactivity-transform": { - "version": "3.3.4", + "node_modules/@typescript-eslint/tsconfig-utils": { + "version": "8.57.2", + "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.57.2.tgz", + "integrity": "sha512-3Lm5DSM+DCowsUOJC+YqHHnKEfFh5CoGkj5Z31NQSNF4l5wdOwqGn99wmwN/LImhfY3KJnmordBq/4+VDe2eKw==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/@typescript-eslint/type-utils": { + "version": "8.57.2", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.57.2.tgz", + "integrity": "sha512-Co6ZCShm6kIbAM/s+oYVpKFfW7LBc6FXoPXjTRQ449PPNBY8U0KZXuevz5IFuuUj2H9ss40atTaf9dlGLzbWZg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.57.2", + "@typescript-eslint/typescript-estree": "8.57.2", + "@typescript-eslint/utils": "8.57.2", + "debug": "^4.4.3", + "ts-api-utils": "^2.4.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/@typescript-eslint/types": { + "version": "8.57.2", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.57.2.tgz", + "integrity": "sha512-/iZM6FnM4tnx9csuTxspMW4BOSegshwX5oBDznJ7S4WggL7Vczz5d2W11ecc4vRrQMQHXRSxzrCsyG5EsPPTbA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/typescript-estree": { + "version": "8.57.2", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.57.2.tgz", + "integrity": "sha512-2MKM+I6g8tJxfSmFKOnHv2t8Sk3T6rF20A1Puk0svLK+uVapDZB/4pfAeB7nE83uAZrU6OxW+HmOd5wHVdXwXA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/project-service": "8.57.2", + "@typescript-eslint/tsconfig-utils": "8.57.2", + "@typescript-eslint/types": "8.57.2", + "@typescript-eslint/visitor-keys": "8.57.2", + "debug": "^4.4.3", + "minimatch": "^10.2.2", + "semver": "^7.7.3", + "tinyglobby": "^0.2.15", + "ts-api-utils": "^2.4.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/balanced-match": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", + "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "18 || 20 || >=22" + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/brace-expansion": { + "version": "5.0.5", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.5.tgz", + "integrity": "sha512-VZznLgtwhn+Mact9tfiwx64fA9erHH/MCXEUfB/0bX/6Fz6ny5EGTXYltMocqg4xFAQZtnO3DHWWXi8RiuN7cQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^4.0.2" + }, + "engines": { + "node": "18 || 20 || >=22" + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/minimatch": { + "version": "10.2.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.4.tgz", + "integrity": "sha512-oRjTw/97aTBN0RHbYCdtF1MQfvusSIBQM0IZEgzl6426+8jSC0nF1a/GmnVLpfB9yyr6g6FTqWqiZVbxrtaCIg==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "brace-expansion": "^5.0.2" + }, + "engines": { + "node": "18 || 20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/@typescript-eslint/typescript-estree/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/@typescript-eslint/utils": { + "version": "8.57.2", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.57.2.tgz", + "integrity": "sha512-krRIbvPK1ju1WBKIefiX+bngPs+odIQUtR7kymzPfo1POVw3jlF+nLkmexdSSd4UCbDcQn+wMBATOOmpBbqgKg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.9.1", + "@typescript-eslint/scope-manager": "8.57.2", + "@typescript-eslint/types": "8.57.2", + "@typescript-eslint/typescript-estree": "8.57.2" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/@typescript-eslint/visitor-keys": { + "version": "8.57.2", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.57.2.tgz", + "integrity": "sha512-zhahknjobV2FiD6Ee9iLbS7OV9zi10rG26odsQdfBO/hjSzUQbkIYgda+iNKK1zNiW2ey+Lf8MU5btN17V3dUw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.57.2", + "eslint-visitor-keys": "^5.0.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/visitor-keys/node_modules/eslint-visitor-keys": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-5.0.1.tgz", + "integrity": "sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@vue/compiler-core": { + "version": "3.3.4", + "resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.3.4.tgz", + "integrity": "sha512-cquyDNvZ6jTbf/+x+AgM2Arrp6G4Dzbb0R64jiG804HRMfRiFXWI6kqUVqZ6ZR0bQhIoQjB4+2bhNtVwndW15g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.21.3", + "@vue/shared": "3.3.4", + "estree-walker": "^2.0.2", + "source-map-js": "^1.0.2" + } + }, + "node_modules/@vue/compiler-dom": { + "version": "3.3.4", + "resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.3.4.tgz", + "integrity": "sha512-wyM+OjOVpuUukIq6p5+nwHYtj9cFroz9cwkfmP9O1nzH68BenTTv0u7/ndggT8cIQlnBeOo6sUT/gvHcIkLA5w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vue/compiler-core": "3.3.4", + "@vue/shared": "3.3.4" + } + }, + "node_modules/@vue/compiler-sfc": { + "version": "3.3.4", + "resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.3.4.tgz", + "integrity": "sha512-6y/d8uw+5TkCuzBkgLS0v3lSM3hJDntFEiUORM11pQ/hKvkhSKZrXW6i69UyXlJQisJxuUEJKAWEqWbWsLeNKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.20.15", + "@vue/compiler-core": "3.3.4", + "@vue/compiler-dom": "3.3.4", + "@vue/compiler-ssr": "3.3.4", + "@vue/reactivity-transform": "3.3.4", + "@vue/shared": "3.3.4", + "estree-walker": "^2.0.2", + "magic-string": "^0.30.0", + "postcss": "^8.1.10", + "source-map-js": "^1.0.2" + } + }, + "node_modules/@vue/compiler-ssr": { + "version": "3.3.4", + "resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.3.4.tgz", + "integrity": "sha512-m0v6oKpup2nMSehwA6Uuu+j+wEwcy7QmwMkVNVfrV9P2qE5KshC6RwOCq8fjGS/Eak/uNb8AaWekfiXxbBB6gQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vue/compiler-dom": "3.3.4", + "@vue/shared": "3.3.4" + } + }, + "node_modules/@vue/reactivity": { + "version": "3.3.4", + "resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.3.4.tgz", + "integrity": "sha512-kLTDLwd0B1jG08NBF3R5rqULtv/f8x3rOFByTDz4J53ttIQEDmALqKqXY0J+XQeN0aV2FBxY8nJDf88yvOPAqQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vue/shared": "3.3.4" + } + }, + "node_modules/@vue/reactivity-transform": { + "version": "3.3.4", "resolved": "https://registry.npmjs.org/@vue/reactivity-transform/-/reactivity-transform-3.3.4.tgz", "integrity": "sha512-MXgwjako4nu5WFLAjpBnCj/ieqcjE2aJBINUNQzkZQfzIZA4xn+0fV1tIYBJvvva3N3OvKGofRLvQIwEQPpaXw==", "dev": true, @@ -6194,6 +6738,33 @@ "node": ">=0.4.0" } }, + "node_modules/acorn-jsx": { + "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" + } + }, + "node_modules/ajv": { + "version": "6.14.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.14.0.tgz", + "integrity": "sha512-IWrosm/yrn43eiKqkfkHis7QioDleaXQHdDVPKg0FSwwd/DuvyX79TZnFOnYpB7dcsFAMmtFztZuXPDvSePkFw==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, "node_modules/ansi-escapes": { "version": "4.3.2", "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz", @@ -6718,6 +7289,13 @@ "node": ">= 10" } }, + "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, + "license": "MIT" + }, "node_modules/config-chain": { "version": "1.1.13", "resolved": "https://registry.npmjs.org/config-chain/-/config-chain-1.1.13.tgz", @@ -6820,77 +7398,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/css-select": { - "version": "5.2.2", - "resolved": "https://registry.npmjs.org/css-select/-/css-select-5.2.2.tgz", - "integrity": "sha512-TizTzUddG/xYLA3NXodFM0fSbNizXjOKhqiQQwvhlspadZokn1KDy0NZFS0wuEubIYAV5/c1/lAr0TaaFXEXzw==", - "dev": true, - "license": "BSD-2-Clause", - "optional": true, - "peer": true, - "dependencies": { - "boolbase": "^1.0.0", - "css-what": "^6.1.0", - "domhandler": "^5.0.2", - "domutils": "^3.0.1", - "nth-check": "^2.0.1" - }, - "funding": { - "url": "https://github.com/sponsors/fb55" - } - }, - "node_modules/css-select/node_modules/dom-serializer": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-2.0.0.tgz", - "integrity": "sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==", - "dev": true, - "license": "MIT", - "optional": true, - "peer": true, - "dependencies": { - "domelementtype": "^2.3.0", - "domhandler": "^5.0.2", - "entities": "^4.2.0" - }, - "funding": { - "url": "https://github.com/cheeriojs/dom-serializer?sponsor=1" - } - }, - "node_modules/css-select/node_modules/domhandler": { - "version": "5.0.3", - "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-5.0.3.tgz", - "integrity": "sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==", - "dev": true, - "license": "BSD-2-Clause", - "optional": true, - "peer": true, - "dependencies": { - "domelementtype": "^2.3.0" - }, - "engines": { - "node": ">= 4" - }, - "funding": { - "url": "https://github.com/fb55/domhandler?sponsor=1" - } - }, - "node_modules/css-select/node_modules/domutils": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/domutils/-/domutils-3.2.2.tgz", - "integrity": "sha512-6kZKyUajlDuqlHKVX1w7gyslj9MPIXzIFiz/rGu35uC1wMi+kMhQwGhl4lt9unC9Vb9INnY9Z3/ZA3+FhASLaw==", - "dev": true, - "license": "BSD-2-Clause", - "optional": true, - "peer": true, - "dependencies": { - "dom-serializer": "^2.0.0", - "domelementtype": "^2.3.0", - "domhandler": "^5.0.3" - }, - "funding": { - "url": "https://github.com/fb55/domutils?sponsor=1" - } - }, "node_modules/css-tree": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-2.3.1.tgz", @@ -6918,48 +7425,6 @@ "url": "https://github.com/sponsors/fb55" } }, - "node_modules/csso": { - "version": "5.0.5", - "resolved": "https://registry.npmjs.org/csso/-/csso-5.0.5.tgz", - "integrity": "sha512-0LrrStPOdJj+SPCCrGhzryycLjwcgUSHBtxNA8aIDxf0GLsRh1cKYhB00Gd1lDOS4yGH69+SNn13+TWbVHETFQ==", - "dev": true, - "license": "MIT", - "optional": true, - "peer": true, - "dependencies": { - "css-tree": "~2.2.0" - }, - "engines": { - "node": "^10 || ^12.20.0 || ^14.13.0 || >=15.0.0", - "npm": ">=7.0.0" - } - }, - "node_modules/csso/node_modules/css-tree": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-2.2.1.tgz", - "integrity": "sha512-OA0mILzGc1kCOCSJerOeqDxDQ4HOh+G8NbOJFOTgOCzpw7fCBubk0fEyxp8AgOL/jvLgYA/uV0cMbe43ElF1JA==", - "dev": true, - "license": "MIT", - "optional": true, - "peer": true, - "dependencies": { - "mdn-data": "2.0.28", - "source-map-js": "^1.0.1" - }, - "engines": { - "node": "^10 || ^12.20.0 || ^14.13.0 || >=15.0.0", - "npm": ">=7.0.0" - } - }, - "node_modules/csso/node_modules/mdn-data": { - "version": "2.0.28", - "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.28.tgz", - "integrity": "sha512-aylIc7Z9y4yzHYAJNuESG3hfhC+0Ibp/MAMiaOZgNv4pmEdFyfZhhhny4MNiAfWdBQ1RQ2mfDWmM1x8SvGyp8g==", - "dev": true, - "license": "CC0-1.0", - "optional": true, - "peer": true - }, "node_modules/csstype": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", @@ -7024,6 +7489,13 @@ "node": ">=4.0.0" } }, + "node_modules/deep-is": { + "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, + "license": "MIT" + }, "node_modules/deepmerge": { "version": "4.3.1", "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz", @@ -7284,6 +7756,240 @@ "node": ">=6" } }, + "node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint": { + "version": "9.39.4", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.39.4.tgz", + "integrity": "sha512-XoMjdBOwe/esVgEvLmNsD3IRHkm7fbKIUGvrleloJXUZgDHig2IPWNniv+GwjyJXzuNqVjlr5+4yVUZjycJwfQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.8.0", + "@eslint-community/regexpp": "^4.12.1", + "@eslint/config-array": "^0.21.2", + "@eslint/config-helpers": "^0.4.2", + "@eslint/core": "^0.17.0", + "@eslint/eslintrc": "^3.3.5", + "@eslint/js": "9.39.4", + "@eslint/plugin-kit": "^0.4.1", + "@humanfs/node": "^0.16.6", + "@humanwhocodes/module-importer": "^1.0.1", + "@humanwhocodes/retry": "^0.4.2", + "@types/estree": "^1.0.6", + "ajv": "^6.14.0", + "chalk": "^4.0.0", + "cross-spawn": "^7.0.6", + "debug": "^4.3.2", + "escape-string-regexp": "^4.0.0", + "eslint-scope": "^8.4.0", + "eslint-visitor-keys": "^4.2.1", + "espree": "^10.4.0", + "esquery": "^1.5.0", + "esutils": "^2.0.2", + "fast-deep-equal": "^3.1.3", + "file-entry-cache": "^8.0.0", + "find-up": "^5.0.0", + "glob-parent": "^6.0.2", + "ignore": "^5.2.0", + "imurmurhash": "^0.1.4", + "is-glob": "^4.0.0", + "json-stable-stringify-without-jsonify": "^1.0.1", + "lodash.merge": "^4.6.2", + "minimatch": "^3.1.5", + "natural-compare": "^1.4.0", + "optionator": "^0.9.3" + }, + "bin": { + "eslint": "bin/eslint.js" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://eslint.org/donate" + }, + "peerDependencies": { + "jiti": "*" + }, + "peerDependenciesMeta": { + "jiti": { + "optional": true + } + } + }, + "node_modules/eslint-plugin-react-hooks": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-5.2.0.tgz", + "integrity": "sha512-+f15FfK64YQwZdJNELETdn5ibXEUQmW1DZL6KXhNnc2heoy/sg9VJJeT7n8TlMWouzWqSWavFkIhHyIbIAEapg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "eslint": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 || ^9.0.0" + } + }, + "node_modules/eslint-scope": { + "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", + "estraverse": "^5.2.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint-visitor-keys": { + "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" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint/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" + } + }, + "node_modules/eslint/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" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "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, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/eslint/node_modules/ignore": { + "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" + } + }, + "node_modules/eslint/node_modules/minimatch": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/espree": { + "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", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^4.2.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/esquery": { + "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, + "license": "BSD-3-Clause", + "dependencies": { + "estraverse": "^5.1.0" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/esrecurse": { + "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" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estraverse": { + "version": "5.3.0", + "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": "2.0.2", "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz", @@ -7291,6 +7997,16 @@ "dev": true, "license": "MIT" }, + "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" + } + }, "node_modules/events": { "version": "3.3.0", "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz", @@ -7358,6 +8074,13 @@ "dev": true, "license": "ISC" }, + "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==", + "dev": true, + "license": "MIT" + }, "node_modules/fast-glob": { "version": "3.3.3", "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz", @@ -7375,6 +8098,20 @@ "node": ">=8.6.0" } }, + "node_modules/fast-json-stable-stringify": { + "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, + "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, + "license": "MIT" + }, "node_modules/fastq": { "version": "1.19.1", "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.19.1.tgz", @@ -7392,6 +8129,19 @@ "dev": true, "license": "MIT" }, + "node_modules/file-entry-cache": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz", + "integrity": "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "flat-cache": "^4.0.0" + }, + "engines": { + "node": ">=16.0.0" + } + }, "node_modules/fill-range": { "version": "7.1.1", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", @@ -7405,6 +8155,44 @@ "node": ">=8" } }, + "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, + "license": "MIT", + "dependencies": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/flat-cache": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-4.0.1.tgz", + "integrity": "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==", + "dev": true, + "license": "MIT", + "dependencies": { + "flatted": "^3.2.9", + "keyv": "^4.5.4" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/flatted": { + "version": "3.4.2", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.4.2.tgz", + "integrity": "sha512-PjDse7RzhcPkIJwy5t7KPWQSZ9cAbzQXcafsetQoD7sOJRQlGikNbx7yZp2OotDnJyrDcbyRq3Ttb18iYOqkxA==", + "dev": true, + "license": "ISC" + }, "node_modules/foreground-child": { "version": "3.3.1", "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.1.tgz", @@ -7884,6 +8672,16 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.8.19" + } + }, "node_modules/ini": { "version": "1.3.8", "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", @@ -8085,9 +8883,9 @@ "license": "MIT" }, "node_modules/js-yaml": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", - "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "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": { @@ -8138,6 +8936,20 @@ "node": ">=16" } }, + "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, + "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, + "license": "MIT" + }, "node_modules/json5": { "version": "2.2.3", "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", @@ -8228,6 +9040,20 @@ "node": ">=4" } }, + "node_modules/levn": { + "version": "0.4.1", + "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" + }, + "engines": { + "node": ">= 0.8.0" + } + }, "node_modules/lightningcss": { "version": "1.30.2", "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.30.2.tgz", @@ -8579,6 +9405,29 @@ "dev": true, "license": "MIT" }, + "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, + "license": "MIT", + "dependencies": { + "p-locate": "^5.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "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, + "license": "MIT" + }, "node_modules/lodash.sortby": { "version": "4.7.0", "resolved": "https://registry.npmjs.org/lodash.sortby/-/lodash.sortby-4.7.0.tgz", @@ -8586,21 +9435,6 @@ "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==", - "dev": true, - "license": "MIT", - "optional": true, - "peer": true, - "dependencies": { - "js-tokens": "^3.0.0 || ^4.0.0" - }, - "bin": { - "loose-envify": "cli.js" - } - }, "node_modules/lowercase-keys": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-3.0.0.tgz", @@ -8896,6 +9730,13 @@ "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", + "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", + "dev": true, + "license": "MIT" + }, "node_modules/needle": { "version": "3.3.1", "resolved": "https://registry.npmjs.org/needle/-/needle-3.3.1.tgz", @@ -9047,6 +9888,24 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/optionator": { + "version": "0.9.4", + "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", + "levn": "^0.4.1", + "prelude-ls": "^1.2.1", + "type-check": "^0.4.0", + "word-wrap": "^1.2.5" + }, + "engines": { + "node": ">= 0.8.0" + } + }, "node_modules/ordered-binary": { "version": "1.6.0", "resolved": "https://registry.npmjs.org/ordered-binary/-/ordered-binary-1.6.0.tgz", @@ -9064,6 +9923,38 @@ "node": ">=14.16" } }, + "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, + "license": "MIT", + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "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==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-limit": "^3.0.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/package-json": { "version": "10.0.1", "resolved": "https://registry.npmjs.org/package-json/-/package-json-10.0.1.tgz", @@ -9132,6 +10023,16 @@ "node": ">= 0.10" } }, + "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==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, "node_modules/path-key": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", @@ -9434,6 +10335,32 @@ "node": ">=12" } }, + "node_modules/prelude-ls": { + "version": "1.2.1", + "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.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" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/prettier/prettier?sponsor=1" + } + }, "node_modules/process": { "version": "0.11.10", "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz", @@ -9930,21 +10857,6 @@ "node": ">=0.10.0" } }, - "node_modules/srcset": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/srcset/-/srcset-5.0.1.tgz", - "integrity": "sha512-/P1UYbGfJVlxZag7aABNRrulEXAwCSDo7fklafOQrantuPTDmYgijJMks2zusPCVzgW9+4P69mq7w6pYuZpgxw==", - "dev": true, - "license": "MIT", - "optional": true, - "peer": true, - "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/stable": { "version": "0.1.8", "resolved": "https://registry.npmjs.org/stable/-/stable-0.1.8.tgz", @@ -10165,34 +11077,6 @@ "dev": true, "license": "MIT" }, - "node_modules/svgo": { - "version": "3.3.2", - "resolved": "https://registry.npmjs.org/svgo/-/svgo-3.3.2.tgz", - "integrity": "sha512-OoohrmuUlBs8B8o6MB2Aevn+pRIH9zDALSR+6hhqVfa6fRwG/Qw9VUMSMW9VNg2CFc/MTIfabtdOVl9ODIJjpw==", - "dev": true, - "license": "MIT", - "optional": true, - "peer": true, - "dependencies": { - "@trysound/sax": "0.2.0", - "commander": "^7.2.0", - "css-select": "^5.1.0", - "css-tree": "^2.3.1", - "css-what": "^6.1.0", - "csso": "^5.0.5", - "picocolors": "^1.0.0" - }, - "bin": { - "svgo": "bin/svgo" - }, - "engines": { - "node": ">=14.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/svgo" - } - }, "node_modules/temp-dir": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/temp-dir/-/temp-dir-3.0.0.tgz", @@ -10271,6 +11155,54 @@ "node": ">=0.8" } }, + "node_modules/tinyglobby": { + "version": "0.2.15", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz", + "integrity": "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "fdir": "^6.5.0", + "picomatch": "^4.0.3" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, + "node_modules/tinyglobby/node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/tinyglobby/node_modules/picomatch": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", + "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, "node_modules/to-regex-range": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", @@ -10311,6 +11243,19 @@ "dev": true, "license": "MIT" }, + "node_modules/ts-api-utils": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.5.0.tgz", + "integrity": "sha512-OJ/ibxhPlqrMM0UiNHJ/0CKQkoKF243/AEmplt3qpRgkW8VG7IfOS41h7V8TjITqdByHzrjcS/2si+y4lIh8NA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18.12" + }, + "peerDependencies": { + "typescript": ">=4.8.4" + } + }, "node_modules/ts-interface-checker": { "version": "0.1.13", "resolved": "https://registry.npmjs.org/ts-interface-checker/-/ts-interface-checker-0.1.13.tgz", @@ -10433,6 +11378,19 @@ "node": ">= 8" } }, + "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" + }, + "engines": { + "node": ">= 0.8.0" + } + }, "node_modules/type-fest": { "version": "4.41.0", "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-4.41.0.tgz", @@ -10459,6 +11417,30 @@ "node": ">=14.17" } }, + "node_modules/typescript-eslint": { + "version": "8.57.2", + "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.57.2.tgz", + "integrity": "sha512-VEPQ0iPgWO/sBaZOU1xo4nuNdODVOajPnTIbog2GKYr31nIlZ0fWPoCQgGfF3ETyBl1vn63F/p50Um9Z4J8O8A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/eslint-plugin": "8.57.2", + "@typescript-eslint/parser": "8.57.2", + "@typescript-eslint/typescript-estree": "8.57.2", + "@typescript-eslint/utils": "8.57.2" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.0.0" + } + }, "node_modules/unique-string": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/unique-string/-/unique-string-3.0.0.tgz", @@ -10516,6 +11498,16 @@ "browserslist": ">= 4.21.0" } }, + "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" + } + }, "node_modules/utility-types": { "version": "3.11.0", "resolved": "https://registry.npmjs.org/utility-types/-/utility-types-3.11.0.tgz", @@ -10582,6 +11574,16 @@ "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" + } + }, "node_modules/wrap-ansi": { "version": "6.2.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", @@ -10730,6 +11732,19 @@ "node": ">= 6" } }, + "node_modules/yocto-queue": { + "version": "0.1.0", + "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" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/yoctocolors-cjs": { "version": "2.1.3", "resolved": "https://registry.npmjs.org/yoctocolors-cjs/-/yoctocolors-cjs-2.1.3.tgz", diff --git a/package.json b/package.json index f935d43..b5eb764 100644 --- a/package.json +++ b/package.json @@ -8,7 +8,11 @@ "dev": "plasmo dev --verbose", "build": "plasmo build", "package": "plasmo package", - "lint": "tsc --noEmit" + "typecheck": "tsc --noEmit", + "lint": "eslint src/", + "lint:fix": "eslint src/ --fix", + "format": "prettier --write \"src/**/*.{ts,tsx}\"", + "format:check": "prettier --check \"src/**/*.{ts,tsx}\"" }, "keywords": [], "author": "", @@ -23,11 +27,16 @@ "remeda": "^2.32.0" }, "devDependencies": { + "@eslint/js": "^9.39.4", "@types/chrome": "^0.1.22", "@types/react": "^19.2.0", "@types/react-dom": "^19.2.0", + "eslint": "^9.39.4", + "eslint-plugin-react-hooks": "^5.2.0", "plasmo": "^0.90.5", - "typescript": "^5.9.3" + "prettier": "^3.8.1", + "typescript": "^5.9.3", + "typescript-eslint": "^8.57.2" }, "type": "module", "displayName": "Fireclaw - Firecrawl Extension", diff --git a/src/background/index.ts b/src/background/index.ts index 4ab647e..6c5ceea 100644 --- a/src/background/index.ts +++ b/src/background/index.ts @@ -1,3 +1,3 @@ -export {} +export {}; -console.info('[firecrawl-extension] background initialized') +console.info("[firecrawl-extension] background initialized"); diff --git a/src/background/messages/crawl-start.ts b/src/background/messages/crawl-start.ts index 84761ec..c27d5ea 100644 --- a/src/background/messages/crawl-start.ts +++ b/src/background/messages/crawl-start.ts @@ -1,65 +1,65 @@ -import type { PlasmoMessaging } from '@plasmohq/messaging' -import * as R from 'remeda' +import type { PlasmoMessaging } from "@plasmohq/messaging"; +import * as R from "remeda"; import { performCrawlStart, type CrawlStartRequestBody, - type CrawlStartResponseBody -} from '~lib/firecrawl' -import { getStoredApiKey } from '~lib/storage' + type CrawlStartResponseBody, +} from "~lib/firecrawl"; +import { getStoredApiKey } from "~lib/storage"; const sanitizeOptions = (options?: Record) => - R.pickBy(options ?? {}, (value) => value !== undefined) + R.pickBy(options ?? {}, (value) => value !== undefined); const resolveApiKey = async (provided?: string) => { if (provided?.trim()) { - return provided.trim() + return provided.trim(); } - return getStoredApiKey() -} + return getStoredApiKey(); +}; -const handler: PlasmoMessaging.MessageHandler = async ( - req, - res -) => { +const handler: PlasmoMessaging.MessageHandler< + CrawlStartRequestBody, + CrawlStartResponseBody +> = async (req, res) => { try { - const { url, options, apiKey: providedKey } = req.body ?? {} + const { url, options, apiKey: providedKey } = req.body ?? {}; if (!url) { res.send({ ok: false, - error: 'Missing URL to crawl' - }) - return + error: "Missing URL to crawl", + }); + return; } - const apiKey = await resolveApiKey(providedKey) + const apiKey = await resolveApiKey(providedKey); if (!apiKey) { res.send({ ok: false, - error: 'Missing Firecrawl API key' - }) - return + error: "Missing Firecrawl API key", + }); + return; } - const effectiveOptions = sanitizeOptions(options) + const effectiveOptions = sanitizeOptions(options); const { result } = await performCrawlStart(apiKey, { url, options: effectiveOptions, - apiKey - }) + apiKey, + }); - res.send(result) + res.send(result); } catch (error) { - console.error('Crawl start handler crashed', error) + console.error("Crawl start handler crashed", error); res.send({ ok: false, - error: error instanceof Error ? error.message : 'Unexpected crawl start error' - }) + error: error instanceof Error ? error.message : "Unexpected crawl start error", + }); } -} +}; -export default handler +export default handler; diff --git a/src/background/messages/crawl-status.ts b/src/background/messages/crawl-status.ts index 2b3dc48..4cd0041 100644 --- a/src/background/messages/crawl-status.ts +++ b/src/background/messages/crawl-status.ts @@ -1,54 +1,54 @@ -import type { PlasmoMessaging } from '@plasmohq/messaging' +import type { PlasmoMessaging } from "@plasmohq/messaging"; import { performCrawlStatus, type CrawlStatusRequestBody, - type CrawlStatusResponseBody -} from '~lib/firecrawl' -import { getStoredApiKey } from '~lib/storage' + type CrawlStatusResponseBody, +} from "~lib/firecrawl"; +import { getStoredApiKey } from "~lib/storage"; const resolveApiKey = async (provided?: string) => { if (provided?.trim()) { - return provided.trim() + return provided.trim(); } - return getStoredApiKey() -} + return getStoredApiKey(); +}; -const handler: PlasmoMessaging.MessageHandler = async ( - req, - res -) => { +const handler: PlasmoMessaging.MessageHandler< + CrawlStatusRequestBody, + CrawlStatusResponseBody +> = async (req, res) => { try { - const { id, apiKey: providedKey } = req.body ?? {} + const { id, apiKey: providedKey } = req.body ?? {}; if (!id) { res.send({ ok: false, - error: 'Missing crawl job id' - }) - return + error: "Missing crawl job id", + }); + return; } - const apiKey = await resolveApiKey(providedKey) + const apiKey = await resolveApiKey(providedKey); if (!apiKey) { res.send({ ok: false, - error: 'Missing Firecrawl API key' - }) - return + error: "Missing Firecrawl API key", + }); + return; } - const { result } = await performCrawlStatus(apiKey, id) - res.send(result) + const { result } = await performCrawlStatus(apiKey, id); + res.send(result); } catch (error) { - console.error('Crawl status handler crashed', error) + console.error("Crawl status handler crashed", error); res.send({ ok: false, - error: error instanceof Error ? error.message : 'Unexpected crawl status error' - }) + error: error instanceof Error ? error.message : "Unexpected crawl status error", + }); } -} +}; -export default handler +export default handler; diff --git a/src/background/messages/credit-usage.ts b/src/background/messages/credit-usage.ts index 5b80b4e..a5bb50d 100644 --- a/src/background/messages/credit-usage.ts +++ b/src/background/messages/credit-usage.ts @@ -1,42 +1,42 @@ -import type { PlasmoMessaging } from '@plasmohq/messaging' +import type { PlasmoMessaging } from "@plasmohq/messaging"; -import { fetchCreditUsage, type CreditUsageResponse } from '~lib/firecrawl' -import { getStoredApiKey } from '~lib/storage' +import { fetchCreditUsage, type CreditUsageResponse } from "~lib/firecrawl"; +import { getStoredApiKey } from "~lib/storage"; -type RequestBody = { apiKey?: string } +type RequestBody = { apiKey?: string }; const resolveApiKey = async (provided?: string) => { if (provided?.trim()) { - return provided.trim() + return provided.trim(); } - return getStoredApiKey() -} + return getStoredApiKey(); +}; const handler: PlasmoMessaging.MessageHandler = async ( req, - res + res, ) => { try { - const apiKey = await resolveApiKey(req.body?.apiKey) + const apiKey = await resolveApiKey(req.body?.apiKey); if (!apiKey) { res.send({ success: false, - error: 'Missing Firecrawl API key' - }) - return + error: "Missing Firecrawl API key", + }); + return; } - const result = await fetchCreditUsage(apiKey) - res.send(result) + const result = await fetchCreditUsage(apiKey); + res.send(result); } catch (error) { - console.error('Credit usage handler crashed', error) + console.error("Credit usage handler crashed", error); res.send({ success: false, - error: error instanceof Error ? error.message : 'Failed to fetch credit usage' - }) + error: error instanceof Error ? error.message : "Failed to fetch credit usage", + }); } -} +}; -export default handler +export default handler; diff --git a/src/background/messages/scrape.ts b/src/background/messages/scrape.ts index 1b2d368..6992959 100644 --- a/src/background/messages/scrape.ts +++ b/src/background/messages/scrape.ts @@ -1,82 +1,82 @@ -import type { PlasmoMessaging } from '@plasmohq/messaging' -import * as R from 'remeda' +import type { PlasmoMessaging } from "@plasmohq/messaging"; +import * as R from "remeda"; import { performScrape, type FirecrawlScrapeOptions, type ScrapeRequestBody, - type ScrapeResponseBody -} from '~lib/firecrawl' -import { getStoredApiKey, getStoredPreferences, preferencesToScrapeOptions } from '~lib/storage' + type ScrapeResponseBody, +} from "~lib/firecrawl"; +import { getStoredApiKey, getStoredPreferences, preferencesToScrapeOptions } from "~lib/storage"; const assembleOptions = async ( - options: FirecrawlScrapeOptions | undefined + options: FirecrawlScrapeOptions | undefined, ): Promise => { - const stored = preferencesToScrapeOptions(await getStoredPreferences()) + const stored = preferencesToScrapeOptions(await getStoredPreferences()); const merged = { ...stored, - ...(options ?? {}) - } + ...(options ?? {}), + }; - const formats = options?.formats ?? stored.formats + const formats = options?.formats ?? stored.formats; return R.pipe( { ...merged, - ...(formats ? { formats } : {}) + ...(formats ? { formats } : {}), }, - (value) => R.pickBy(value, (entry) => entry !== undefined) - ) as FirecrawlScrapeOptions -} + (value) => R.pickBy(value, (entry) => entry !== undefined), + ) as FirecrawlScrapeOptions; +}; const resolveApiKey = async (provided?: string) => { if (provided?.trim()) { - return provided.trim() + return provided.trim(); } - return getStoredApiKey() -} + return getStoredApiKey(); +}; const handler: PlasmoMessaging.MessageHandler = async ( req, - res + res, ) => { try { - const { url, options, apiKey: providedKey } = req.body ?? {} + const { url, options, apiKey: providedKey } = req.body ?? {}; if (!url) { res.send({ ok: false, - error: 'Missing URL to scrape' - }) - return + error: "Missing URL to scrape", + }); + return; } - const apiKey = await resolveApiKey(providedKey) + const apiKey = await resolveApiKey(providedKey); if (!apiKey) { res.send({ ok: false, - error: 'Missing Firecrawl API key' - }) - return + error: "Missing Firecrawl API key", + }); + return; } - const preparedOptions = await assembleOptions(options) + const preparedOptions = await assembleOptions(options); const { result } = await performScrape(apiKey, { url, - ...preparedOptions - }) + ...preparedOptions, + }); - res.send(result) + res.send(result); } catch (error) { - console.error('Scrape handler crashed', error) + console.error("Scrape handler crashed", error); res.send({ ok: false, - error: error instanceof Error ? error.message : 'Unexpected scrape error' - }) + error: error instanceof Error ? error.message : "Unexpected scrape error", + }); } -} +}; -export default handler +export default handler; diff --git a/src/lib/download.ts b/src/lib/download.ts index 43f0c96..25bebb5 100644 --- a/src/lib/download.ts +++ b/src/lib/download.ts @@ -1,44 +1,38 @@ -import * as R from 'remeda' - -import type { FirecrawlFormatKey } from './firecrawl' +import type { FirecrawlFormatKey } from "./firecrawl"; const MIME_EXTENSION_MAP: Record = { - 'text/markdown': 'md', - 'text/html': 'html', - 'application/json': 'json', - 'text/plain': 'txt' -} + "text/markdown": "md", + "text/html": "html", + "application/json": "json", + "text/plain": "txt", +}; const sanitizePathSegment = (input: string) => input - .normalize('NFKD') - .replace(/[^\w\-]+/g, '-') - .replace(/-{2,}/g, '-') - .replace(/^-+|-+$/g, '') + .normalize("NFKD") + .replace(/[^\w-]+/g, "-") + .replace(/-{2,}/g, "-") + .replace(/^-+|-+$/g, ""); const baseNameFromUrl = (rawUrl: string) => { try { - const url = new URL(rawUrl) - const path = url.pathname === '/' ? '' : url.pathname - const composed = `${url.hostname}${path}` - const sanitized = sanitizePathSegment(composed) - return sanitized.length > 0 ? sanitized : 'firecrawl-export' + const url = new URL(rawUrl); + const path = url.pathname === "/" ? "" : url.pathname; + const composed = `${url.hostname}${path}`; + const sanitized = sanitizePathSegment(composed); + return sanitized.length > 0 ? sanitized : "firecrawl-export"; } catch (error) { - console.warn('Unable to derive filename from URL', error) - return 'firecrawl-export' + console.warn("Unable to derive filename from URL", error); + return "firecrawl-export"; } -} +}; -const filenameForDownload = ( - sourceUrl: string, - format: FirecrawlFormatKey, - mimeType: string -) => { - const extension = MIME_EXTENSION_MAP[mimeType] ?? 'txt' - const base = baseNameFromUrl(sourceUrl) - const timestamp = new Date().toISOString().replace(/[:.]/g, '-') - return `${base}.${format}.${timestamp}.${extension}` -} +const filenameForDownload = (sourceUrl: string, format: FirecrawlFormatKey, mimeType: string) => { + const extension = MIME_EXTENSION_MAP[mimeType] ?? "txt"; + const base = baseNameFromUrl(sourceUrl); + const timestamp = new Date().toISOString().replace(/[:.]/g, "-"); + return `${base}.${format}.${timestamp}.${extension}`; +}; const chromeDownload = (url: string, filename: string) => new Promise((resolve, reject) => { @@ -46,54 +40,54 @@ const chromeDownload = (url: string, filename: string) => { url, filename, - saveAs: false + saveAs: false, }, (downloadId) => { - const error = chrome.runtime.lastError + const error = chrome.runtime.lastError; if (error) { - reject(new Error(error.message)) - return + reject(new Error(error.message)); + return; } if (downloadId === undefined || downloadId === null) { - reject(new Error('Download failed to start')) - return + reject(new Error("Download failed to start")); + return; } - resolve(downloadId) - } - ) - }) + resolve(downloadId); + }, + ); + }); export type DownloadRequest = { - sourceUrl: string - content: string - mimeType: string - format: FirecrawlFormatKey -} + sourceUrl: string; + content: string; + mimeType: string; + format: FirecrawlFormatKey; +}; export const triggerDownload = async ({ sourceUrl, content, mimeType, - format + format, }: DownloadRequest) => { - const extension = MIME_EXTENSION_MAP[mimeType] ?? 'txt' - const blob = new Blob([content], { type: mimeType || 'text/plain' }) - const objectUrl = URL.createObjectURL(blob) - const filename = filenameForDownload(sourceUrl, format, mimeType) + const extension = MIME_EXTENSION_MAP[mimeType] ?? "txt"; + const blob = new Blob([content], { type: mimeType || "text/plain" }); + const objectUrl = URL.createObjectURL(blob); + const filename = filenameForDownload(sourceUrl, format, mimeType); try { - await chromeDownload(objectUrl, filename) + await chromeDownload(objectUrl, filename); } finally { // Allow Chrome to consume the URL before revoking. setTimeout(() => { - URL.revokeObjectURL(objectUrl) - }, 1_000) + URL.revokeObjectURL(objectUrl); + }, 1_000); } return { filename, - extension - } -} + extension, + }; +}; diff --git a/src/lib/firecrawl.ts b/src/lib/firecrawl.ts index bfd3d5e..8372f97 100644 --- a/src/lib/firecrawl.ts +++ b/src/lib/firecrawl.ts @@ -1,315 +1,322 @@ -import type { PlasmoMessaging } from '@plasmohq/messaging' -import * as R from 'remeda' +import type { PlasmoMessaging } from "@plasmohq/messaging"; +import * as R from "remeda"; -const FIRECRAWL_BASE_URL = 'https://api.firecrawl.dev/v2' +const FIRECRAWL_BASE_URL = "https://api.firecrawl.dev/v2"; -export type FirecrawlFormatKey = 'markdown' | 'links' | 'html' | 'rawHtml' | 'summary' | 'json' +export type FirecrawlFormatKey = "markdown" | "links" | "html" | "rawHtml" | "summary" | "json"; -export type FirecrawlFormatPrimitive = Exclude +export type FirecrawlFormatPrimitive = Exclude; export type FirecrawlJsonFormat = { - type: 'json' - prompt?: string - schema?: unknown -} + type: "json"; + prompt?: string; + schema?: unknown; +}; -export type FirecrawlFormatSpec = FirecrawlFormatPrimitive | FirecrawlJsonFormat +export type FirecrawlFormatSpec = FirecrawlFormatPrimitive | FirecrawlJsonFormat; export const formatKeyToSpec = (key: FirecrawlFormatKey): FirecrawlFormatSpec => - key === 'json' ? { type: 'json' } : key + key === "json" ? { type: "json" } : key; export const formatKeysToSpecs = (keys: FirecrawlFormatKey[]): FirecrawlFormatSpec[] => - keys.map(formatKeyToSpec) + keys.map(formatKeyToSpec); export type FirecrawlScrapeOptions = { - formats?: FirecrawlFormatSpec[] - onlyMainContent?: boolean - includeTags?: string[] - excludeTags?: string[] - maxAge?: number - waitFor?: number - timeout?: number - actions?: unknown[] - headers?: Record - mobile?: boolean - removeBase64Images?: boolean - blockAds?: boolean - storeInCache?: boolean -} + formats?: FirecrawlFormatSpec[]; + onlyMainContent?: boolean; + includeTags?: string[]; + excludeTags?: string[]; + maxAge?: number; + waitFor?: number; + timeout?: number; + actions?: unknown[]; + headers?: Record; + mobile?: boolean; + removeBase64Images?: boolean; + blockAds?: boolean; + storeInCache?: boolean; +}; export type FirecrawlScrapeRequestPayload = { - url: string -} & FirecrawlScrapeOptions + url: string; +} & FirecrawlScrapeOptions; export type FirecrawlScrapeSuccessPayload = { - success: true + success: true; data: { - markdown?: string - html?: string - rawHtml?: string - summary?: string - links?: unknown - [key: string]: unknown - } -} + markdown?: string; + html?: string; + rawHtml?: string; + summary?: string; + links?: unknown; + [key: string]: unknown; + }; +}; export type FirecrawlScrapeFailurePayload = { - success: false - error?: string - message?: string -} + success: false; + error?: string; + message?: string; +}; -export type FirecrawlScrapePayload = - | FirecrawlScrapeSuccessPayload - | FirecrawlScrapeFailurePayload +export type FirecrawlScrapePayload = FirecrawlScrapeSuccessPayload | FirecrawlScrapeFailurePayload; export type FirecrawlCrawlStartResponse = { - id?: string - success?: boolean - error?: string - message?: string -} + id?: string; + success?: boolean; + error?: string; + message?: string; +}; export type FirecrawlCrawlStatus = { - success: boolean - status?: 'pending' | 'processing' | 'completed' | 'failed' - data?: unknown - error?: string - message?: string - next?: string -} + success: boolean; + status?: "pending" | "processing" | "completed" | "failed"; + total?: number; + completed?: number; + data?: unknown; + error?: string; + message?: string; + next?: string; +}; export type ScrapeRequestBody = { - url: string - options?: FirecrawlScrapeOptions - apiKey?: string -} + url: string; + options?: FirecrawlScrapeOptions; + apiKey?: string; +}; export type ScrapeResponseBody = | { - ok: true - mimeType: 'text/markdown' | 'application/json' | 'text/html' | 'text/plain' - primary: string - primaryFormat: FirecrawlFormatKey - raw: FirecrawlScrapePayload + ok: true; + mimeType: "text/markdown" | "application/json" | "text/html" | "text/plain"; + primary: string; + primaryFormat: FirecrawlFormatKey; + raw: FirecrawlScrapePayload; } | { - ok: false - error: string - status?: number - raw?: FirecrawlScrapePayload - } + ok: false; + error: string; + status?: number; + raw?: FirecrawlScrapePayload; + }; export type CrawlStartRequestBody = { - url: string - options?: Record - apiKey?: string -} + url: string; + options?: Record; + apiKey?: string; +}; export type CrawlStartResponseBody = | { - ok: true - id: string + ok: true; + id: string; } | { - ok: false - error: string - status?: number - raw?: FirecrawlCrawlStartResponse - } + ok: false; + error: string; + status?: number; + raw?: FirecrawlCrawlStartResponse; + }; export type CrawlStatusRequestBody = { - id: string - apiKey?: string -} + id: string; + apiKey?: string; +}; export type CrawlStatusResponseBody = | { - ok: true - status: FirecrawlCrawlStatus['status'] - payload: FirecrawlCrawlStatus + ok: true; + status: FirecrawlCrawlStatus["status"]; + payload: FirecrawlCrawlStatus; } | { - ok: false - error: string - status?: number - payload?: FirecrawlCrawlStatus - } + ok: false; + error: string; + status?: number; + payload?: FirecrawlCrawlStatus; + }; export type CreditUsageResponse = { - success: boolean + success: boolean; data?: { - remainingCredits: number - planCredits: number - billingPeriodStart: string | null - billingPeriodEnd: string | null - } - error?: string -} + remainingCredits: number; + planCredits: number; + billingPeriodStart: string | null; + billingPeriodEnd: string | null; + }; + error?: string; +}; -export type FirecrawlMessageHandler = PlasmoMessaging.MessageHandler +export type FirecrawlMessageHandler = PlasmoMessaging.MessageHandler; -export const DEFAULT_FORMAT_KEYS: FirecrawlFormatKey[] = ['markdown'] +export const DEFAULT_FORMAT_KEYS: FirecrawlFormatKey[] = ["markdown"]; -const defaultFormats: FirecrawlFormatSpec[] = formatKeysToSpecs(DEFAULT_FORMAT_KEYS) +const defaultFormats: FirecrawlFormatSpec[] = formatKeysToSpecs(DEFAULT_FORMAT_KEYS); const formatSpecToKey = (spec: FirecrawlFormatSpec): FirecrawlFormatKey => - typeof spec === 'string' ? spec : spec.type + typeof spec === "string" ? spec : spec.type; const isSuccessful = (payload: FirecrawlScrapePayload): payload is FirecrawlScrapeSuccessPayload => - payload?.success === true + payload?.success === true; const safeJson = async (response: Response): Promise => { try { - return (await response.json()) as T + return (await response.json()) as T; } catch (error) { - console.warn('Failed to parse response JSON', error) - return undefined + console.warn("Failed to parse response JSON", error); + return undefined; } -} +}; export type FirecrawlRequestConfig = { - apiKey: string - path: string - init?: RequestInit -} - -const requestFirecrawl = async ({ apiKey, path, init }: FirecrawlRequestConfig): Promise<{ - data?: T - status: number + apiKey: string; + path: string; + init?: RequestInit; +}; + +const requestFirecrawl = async ({ + apiKey, + path, + init, +}: FirecrawlRequestConfig): Promise<{ + data?: T; + status: number; }> => { const response = await fetch(`${FIRECRAWL_BASE_URL}${path}`, { ...init, headers: { - 'Content-Type': 'application/json', + "Content-Type": "application/json", Authorization: `Bearer ${apiKey}`, - ...(init?.headers ?? {}) - } - }) + ...(init?.headers ?? {}), + }, + }); - const data = await safeJson(response) + const data = await safeJson(response); return { data, - status: response.status - } -} + status: response.status, + }; +}; export const performScrape = async ( apiKey: string, - payload: FirecrawlScrapeRequestPayload + payload: FirecrawlScrapeRequestPayload, ): Promise<{ - result: ScrapeResponseBody - status: number + result: ScrapeResponseBody; + status: number; }> => { - const sanitizedPayload = R.pickBy(payload, (value) => value !== undefined) + const sanitizedPayload = R.pickBy(payload, (value) => value !== undefined); const { data, status } = await requestFirecrawl({ apiKey, - path: '/scrape', + path: "/scrape", init: { - method: 'POST', + method: "POST", body: JSON.stringify({ ...sanitizedPayload, - formats: payload.formats?.length ? payload.formats : defaultFormats - }) - } - }) + formats: payload.formats?.length ? payload.formats : defaultFormats, + }), + }, + }); if (!data) { return { result: { ok: false, - error: 'Empty Firecrawl response', - status + error: "Empty Firecrawl response", + status, }, - status - } + status, + }; } if (!isSuccessful(data)) { - const error = data.error ?? data.message ?? 'Firecrawl scrape failed' + const error = data.error ?? data.message ?? "Firecrawl scrape failed"; return { result: { ok: false, error, status, - raw: data + raw: data, }, - status - } + status, + }; } - const formats = payload.formats ?? defaultFormats + const formats = payload.formats ?? defaultFormats; - const primaryFormatSpec = R.find(formats, (format) => { - if (typeof format === 'string') { - return format === 'markdown' - } - return format.type === 'json' - }) ?? formats[0] ?? 'markdown' + const primaryFormatSpec = + R.find(formats, (format) => { + if (typeof format === "string") { + return format === "markdown"; + } + return format.type === "json"; + }) ?? + formats[0] ?? + "markdown"; - const primaryFormatKey = formatSpecToKey(primaryFormatSpec) + const primaryFormatKey = formatSpecToKey(primaryFormatSpec); const primaryCandidate = (() => { - if (typeof primaryFormatSpec === 'string') { + if (typeof primaryFormatSpec === "string") { switch (primaryFormatSpec) { - case 'markdown': - return data.data.markdown - case 'html': - return data.data.html - case 'rawHtml': - return data.data.rawHtml - case 'summary': - return data.data.summary - case 'links': - return JSON.stringify(data.data.links ?? [], null, 2) + case "markdown": + return data.data.markdown; + case "html": + return data.data.html; + case "rawHtml": + return data.data.rawHtml; + case "summary": + return data.data.summary; + case "links": + return JSON.stringify(data.data.links ?? [], null, 2); default: - return undefined + return undefined; } } - if (primaryFormatSpec.type === 'json') { - const jsonPayload = data.data.json ?? data.data.data ?? data.data - return JSON.stringify(jsonPayload, null, 2) + if (primaryFormatSpec.type === "json") { + const jsonPayload = data.data.json ?? data.data.data ?? data.data; + return JSON.stringify(jsonPayload, null, 2); } - return undefined - })() + return undefined; + })(); - if (typeof primaryCandidate !== 'string') { + if (typeof primaryCandidate !== "string") { return { result: { ok: false, - error: 'Unable to resolve scrape content', + error: "Unable to resolve scrape content", status, - raw: data + raw: data, }, - status - } + status, + }; } const mimeType = (() => { - if (typeof primaryFormatSpec === 'string') { + if (typeof primaryFormatSpec === "string") { switch (primaryFormatSpec) { - case 'markdown': - return 'text/markdown' - case 'html': - case 'rawHtml': - return 'text/html' - case 'summary': - return 'text/plain' - case 'links': - return 'application/json' + case "markdown": + return "text/markdown"; + case "html": + case "rawHtml": + return "text/html"; + case "summary": + return "text/plain"; + case "links": + return "application/json"; default: - return 'text/plain' + return "text/plain"; } } - if (primaryFormatSpec.type === 'json') { - return 'application/json' + if (primaryFormatSpec.type === "json") { + return "application/json"; } - return 'text/plain' - })() + return "text/plain"; + })(); return { result: { @@ -317,129 +324,129 @@ export const performScrape = async ( mimeType, primary: primaryCandidate, primaryFormat: primaryFormatKey, - raw: data + raw: data, }, - status - } -} + status, + }; +}; export const performCrawlStart = async ( apiKey: string, - payload: CrawlStartRequestBody + payload: CrawlStartRequestBody, ): Promise<{ - result: CrawlStartResponseBody - status: number + result: CrawlStartResponseBody; + status: number; }> => { - const sanitizedPayload = R.pickBy(payload.options ?? {}, (value) => value !== undefined) + const sanitizedPayload = R.pickBy(payload.options ?? {}, (value) => value !== undefined); const { data, status } = await requestFirecrawl({ apiKey, - path: '/crawl', + path: "/crawl", init: { - method: 'POST', + method: "POST", body: JSON.stringify({ url: payload.url, - ...sanitizedPayload - }) - } - }) + ...sanitizedPayload, + }), + }, + }); if (!data) { return { result: { ok: false, - error: 'Empty Firecrawl response', - status + error: "Empty Firecrawl response", + status, }, - status - } + status, + }; } - const id = data.id + const id = data.id; if (!id) { - const error = data.error ?? data.message ?? 'Missing crawl job id' + const error = data.error ?? data.message ?? "Missing crawl job id"; return { result: { ok: false, error, status, - raw: data + raw: data, }, - status - } + status, + }; } return { result: { ok: true, - id + id, }, - status - } -} + status, + }; +}; export const performCrawlStatus = async ( apiKey: string, - id: string + id: string, ): Promise<{ - result: CrawlStatusResponseBody - status: number + result: CrawlStatusResponseBody; + status: number; }> => { const { data, status } = await requestFirecrawl({ apiKey, path: `/crawl/${id}`, init: { - method: 'GET' - } - }) + method: "GET", + }, + }); if (!data) { return { result: { ok: false, - error: 'Empty Firecrawl response', - status + error: "Empty Firecrawl response", + status, }, - status - } + status, + }; } if (!data.success) { - const error = data.error ?? data.message ?? 'Crawl status failed' + const error = data.error ?? data.message ?? "Crawl status failed"; return { result: { ok: false, error, status, - payload: data + payload: data, }, - status - } + status, + }; } return { result: { ok: true, status: data.status, - payload: data + payload: data, }, - status - } -} + status, + }; +}; export const fetchCreditUsage = async (apiKey: string): Promise => { - const { data, status } = await requestFirecrawl({ + const { data } = await requestFirecrawl({ apiKey, - path: '/team/credit-usage', - init: { method: 'GET' } - }) + path: "/team/credit-usage", + init: { method: "GET" }, + }); if (!data || !data.success) { return { success: false, - error: data?.error ?? 'Failed to fetch credit usage' - } + error: data?.error ?? "Failed to fetch credit usage", + }; } - return data -} + return data; +}; diff --git a/src/lib/messaging.ts b/src/lib/messaging.ts index 0170d2d..ab8a03f 100644 --- a/src/lib/messaging.ts +++ b/src/lib/messaging.ts @@ -1,5 +1,5 @@ -import { sendToBackground } from '@plasmohq/messaging' -import * as R from 'remeda' +import { sendToBackground } from "@plasmohq/messaging"; +import * as R from "remeda"; import type { CrawlStartRequestBody, @@ -8,38 +8,35 @@ import type { CrawlStatusResponseBody, CreditUsageResponse, ScrapeRequestBody, - ScrapeResponseBody -} from './firecrawl' + ScrapeResponseBody, +} from "./firecrawl"; -type MessageName = 'scrape' | 'crawl-start' | 'crawl-status' | 'credit-usage' +type MessageName = "scrape" | "crawl-start" | "crawl-status" | "credit-usage"; -type SendFx = (input: { - name: MessageName - body: Req -}) => Promise +type SendFx = (input: { name: MessageName; body: Req }) => Promise; -const sendMessage = sendToBackground as unknown as SendFx +const sendMessage = sendToBackground as unknown as SendFx; export const requestScrape = (body: ScrapeRequestBody) => sendMessage({ - name: 'scrape', - body: R.clone(body) - }) + name: "scrape", + body: R.clone(body), + }); export const requestCrawlStart = (body: CrawlStartRequestBody) => sendMessage({ - name: 'crawl-start', - body: R.clone(body) - }) + name: "crawl-start", + body: R.clone(body), + }); export const requestCrawlStatus = (body: CrawlStatusRequestBody) => sendMessage({ - name: 'crawl-status', - body: R.clone(body) - }) + name: "crawl-status", + body: R.clone(body), + }); export const requestCreditUsage = (body: { apiKey?: string }) => sendMessage<{ apiKey?: string }, CreditUsageResponse>({ - name: 'credit-usage', - body: R.clone(body) - }) + name: "credit-usage", + body: R.clone(body), + }); diff --git a/src/lib/storage.ts b/src/lib/storage.ts index 8081183..7ca8ee0 100644 --- a/src/lib/storage.ts +++ b/src/lib/storage.ts @@ -1,67 +1,68 @@ -import type { FirecrawlFormatKey, FirecrawlScrapeOptions } from './firecrawl' -import { DEFAULT_FORMAT_KEYS, formatKeysToSpecs } from './firecrawl' -import { Storage } from '@plasmohq/storage' -import * as R from 'remeda' +import type { FirecrawlFormatKey, FirecrawlScrapeOptions } from "./firecrawl"; +import { DEFAULT_FORMAT_KEYS, formatKeysToSpecs } from "./firecrawl"; +import { Storage } from "@plasmohq/storage"; +import * as R from "remeda"; -const storage = new Storage({ area: 'local' }) +const storage = new Storage({ area: "local" }); -export const API_KEY_STORAGE_KEY = 'firecrawl/api-key' -export const PREFS_STORAGE_KEY = 'firecrawl/preferences' +export const API_KEY_STORAGE_KEY = "firecrawl/api-key"; +export const PREFS_STORAGE_KEY = "firecrawl/preferences"; export type StoredPreferences = { - formats?: FirecrawlFormatKey[] - onlyMainContent?: boolean - includeTags?: string[] - excludeTags?: string[] -} + formats?: FirecrawlFormatKey[]; + onlyMainContent?: boolean; + includeTags?: string[]; + excludeTags?: string[]; +}; export const getStoredApiKey = async (): Promise => { - const value = await storage.get(API_KEY_STORAGE_KEY) - return R.isString(value) && value.trim().length > 0 ? value : undefined -} + const value = await storage.get(API_KEY_STORAGE_KEY); + return R.isString(value) && value.trim().length > 0 ? value : undefined; +}; export const setStoredApiKey = async (value: string) => { - const sanitized = value.trim() + const sanitized = value.trim(); if (sanitized.length === 0) { - await storage.remove(API_KEY_STORAGE_KEY) - return + await storage.remove(API_KEY_STORAGE_KEY); + return; } - await storage.set(API_KEY_STORAGE_KEY, sanitized) -} + await storage.set(API_KEY_STORAGE_KEY, sanitized); +}; export const getStoredPreferences = async (): Promise => { - const value = await storage.get(PREFS_STORAGE_KEY) + const value = await storage.get(PREFS_STORAGE_KEY); if (!value) { - return undefined + return undefined; } - return R.pickBy(value, (option) => option !== undefined) -} + return R.pickBy(value, (option) => option !== undefined); +}; export const setStoredPreferences = async (prefs: StoredPreferences) => { - await storage.set(PREFS_STORAGE_KEY, R.pickBy(prefs, (option) => option !== undefined)) -} + await storage.set( + PREFS_STORAGE_KEY, + R.pickBy(prefs, (option) => option !== undefined), + ); +}; -export const preferencesToScrapeOptions = ( - prefs?: StoredPreferences -): FirecrawlScrapeOptions => { +export const preferencesToScrapeOptions = (prefs?: StoredPreferences): FirecrawlScrapeOptions => { if (!prefs) { return { - formats: formatKeysToSpecs(DEFAULT_FORMAT_KEYS) - } + formats: formatKeysToSpecs(DEFAULT_FORMAT_KEYS), + }; } const formats = prefs.formats?.length ? formatKeysToSpecs(prefs.formats) - : formatKeysToSpecs(DEFAULT_FORMAT_KEYS) + : formatKeysToSpecs(DEFAULT_FORMAT_KEYS); return R.pipe( { formats, onlyMainContent: prefs.onlyMainContent, includeTags: prefs.includeTags, - excludeTags: prefs.excludeTags + excludeTags: prefs.excludeTags, }, - (value) => R.pickBy(value, (entry) => entry !== undefined) - ) as FirecrawlScrapeOptions -} + (value) => R.pickBy(value, (entry) => entry !== undefined), + ) as FirecrawlScrapeOptions; +}; diff --git a/src/popup/components/ApiKeyEditor.tsx b/src/popup/components/ApiKeyEditor.tsx new file mode 100644 index 0000000..fc34814 --- /dev/null +++ b/src/popup/components/ApiKeyEditor.tsx @@ -0,0 +1,122 @@ +import { useState } from "react"; +import { requestCreditUsage } from "~lib/messaging"; +import { styles } from "../styles"; +import { colors, font, radii, space } from "../design"; + +type ValidationState = "idle" | "validating" | "valid" | "invalid"; + +type Props = { + apiKeyDraft: string; + isApiConfigured: boolean; + onDraftChange: (value: string) => void; + onSave: (validatedKey: string) => void; + onCancel: () => void; +}; + +export const ApiKeyEditor = ({ + apiKeyDraft, + isApiConfigured, + onDraftChange, + onSave, + onCancel, +}: Props) => { + const [validation, setValidation] = useState("idle"); + const [validationError, setValidationError] = useState(undefined); + + const handleSave = async () => { + const trimmed = apiKeyDraft.trim(); + if (!trimmed) { + setValidation("invalid"); + setValidationError("API key cannot be empty."); + return; + } + + setValidation("validating"); + setValidationError(undefined); + + try { + const result = await requestCreditUsage({ apiKey: trimmed }); + if (result.success) { + setValidation("valid"); + onSave(trimmed); + } else { + setValidation("invalid"); + setValidationError(result.error ?? "Invalid API key. Check and try again."); + } + } catch { + setValidation("invalid"); + setValidationError("Could not reach Firecrawl. Check your connection."); + } + }; + + const handleDraftChange = (value: string) => { + onDraftChange(value); + if (validation !== "idle") { + setValidation("idle"); + setValidationError(undefined); + } + }; + + const isValidating = validation === "validating"; + + return ( +
+ + handleDraftChange(event.target.value)} + disabled={isValidating} + /> +

Stored locally in your browser.

+ {validationError && ( +

+ {validationError} +

+ )} +
+ + {isApiConfigured ? ( + + ) : null} +
+
+ ); +}; diff --git a/src/popup/components/CrawlView.tsx b/src/popup/components/CrawlView.tsx new file mode 100644 index 0000000..db33d7d --- /dev/null +++ b/src/popup/components/CrawlView.tsx @@ -0,0 +1,203 @@ +import type { CrawlStatusResponseBody } from "~lib/firecrawl"; + +import { + DEFAULT_CRAWL_FORM, + type CrawlFormState, + type CrawlStage, + type SitemapMode, +} from "../types"; +import { styles } from "../styles"; +import { getStatusDotStyle } from "../utils"; + +type Props = { + isCrawling: boolean; + tabUrl: string; + crawlStage: CrawlStage; + crawlForm: CrawlFormState; + crawlStatus: CrawlStatusResponseBody | undefined; + crawlStatusSummary: string | undefined; + crawlJobId: string | undefined; + isPolling: boolean; + pagesCrawled: number; + onCrawlStart: () => void; + onUpdateForm: (field: F, value: CrawlFormState[F]) => void; +}; + +export const CrawlView = ({ + isCrawling, + tabUrl, + crawlStage, + crawlForm, + crawlStatus, + crawlStatusSummary, + crawlJobId, + isPolling, + pagesCrawled, + onCrawlStart, + onUpdateForm, +}: Props) => ( +
+ +

{"\u2318"}Enter

+
+