diff --git a/composer.json b/composer.json index d71453e9..1d382236 100644 --- a/composer.json +++ b/composer.json @@ -53,6 +53,12 @@ "config": { "sort-packages": true }, + "scripts": { + "test": "phpunit", + "test:unit": "phpunit --testsuite Unit", + "test:integration": "phpunit --testsuite Integration", + "test:coverage": "XDEBUG_MODE=coverage phpunit --coverage-text --coverage-html coverage/php" + }, "minimum-stability": "dev", "prefer-stable": true } diff --git a/composer.lock b/composer.lock index 7c0a0f88..f5573287 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "4864e1957f662ffa0e5ccd515a3621ac", + "content-hash": "283743803b19fa2ffb434b79a3d1f739", "packages": [ { "name": "brick/math", @@ -8661,9 +8661,9 @@ } ], "aliases": [], - "minimum-stability": "stable", + "minimum-stability": "dev", "stability-flags": {}, - "prefer-stable": false, + "prefer-stable": true, "prefer-lowest": false, "platform": { "php": "^8.2" diff --git a/docker/e2e/setup-panel.sh b/docker/e2e/setup-panel.sh index 27393c61..ce02c7e1 100644 --- a/docker/e2e/setup-panel.sh +++ b/docker/e2e/setup-panel.sh @@ -57,6 +57,47 @@ php artisan migrate --path=vendor/notur/notur/database/migrations --force rm -rf /tmp/hello-world-src /tmp/hello-world.notur cp -R /opt/notur/examples/hello-world /tmp/hello-world-src php -r 'require "vendor/autoload.php"; \Notur\Support\NoturArchive::pack("/tmp/hello-world-src", "/tmp/hello-world.notur");' +mkdir -p public/notur/e2e-registry storage/notur +cp /tmp/hello-world.notur public/notur/e2e-registry/hello-world.notur +php <<'PHP' + gmdate('Y-m-d\TH:i:s\Z'), + 'registry' => [ + 'version' => '1.0.0', + 'extensions' => [[ + 'id' => 'notur/hello-world', + 'name' => 'Hello World', + 'description' => 'E2E registry fixture for archive-backed installs.', + 'version' => '1.0.0', + 'archive_url' => 'http://127.0.0.1/notur/e2e-registry/hello-world.notur', + 'sha256' => $checksum, + 'tags' => ['e2e', 'fixture'], + ]], + ], +]; + +$json = json_encode($registry, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES); +if ($json === false) { + fwrite(STDERR, 'Failed to encode E2E registry cache: ' . json_last_error_msg() . "\n"); + exit(1); +} + +if (file_put_contents($cachePath, $json . PHP_EOL) === false) { + fwrite(STDERR, "Failed to write {$cachePath}\n"); + exit(1); +} +PHP php artisan notur:add /tmp/hello-world.notur --force rm -rf notur/extensions/notur/full-extension diff --git a/examples/hello-world/package-lock.json b/examples/hello-world/package-lock.json new file mode 100644 index 00000000..7c5db2d8 --- /dev/null +++ b/examples/hello-world/package-lock.json @@ -0,0 +1,1859 @@ +{ + "name": "notur-hello-world-example", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "notur-hello-world-example", + "version": "1.0.0", + "devDependencies": { + "@notur/sdk": "^1.2.0", + "@types/react": "^16.14.0", + "@types/react-dom": "^16.9.0", + "ts-loader": "^9.5.0", + "typescript": "^5.3.0", + "webpack": "^5.90.0", + "webpack-cli": "^6.0.0" + }, + "peerDependencies": { + "react": "^16.14.0", + "react-dom": "^16.14.0" + } + }, + "node_modules/@discoveryjs/json-ext": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/@discoveryjs/json-ext/-/json-ext-0.6.3.tgz", + "integrity": "sha512-4B4OijXeVNOPZlYA2oEwWOTkzyltLao+xbotHQeqN++Rv27Y6s818+n2Qkp8q+Fxhn0t/5lA5X1Mxktud8eayQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.17.0" + } + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.13", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", + "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.0", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/source-map": { + "version": "0.3.11", + "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.11.tgz", + "integrity": "sha512-ZMp1V8ZFcPG5dIWnQLr3NSI1MiCU7UETdS/A0G8V/XWHvJv3ZsFqutJn1Y5RPmAPX6F3BiE397OqveU/9NCuIA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.25" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", + "dev": true, + "license": "MIT" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.31", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", + "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@notur/sdk": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/@notur/sdk/-/sdk-1.3.2.tgz", + "integrity": "sha512-Sli5+QZmCLNPZWrXDM6yXpZ20b2wC6j7RYP2+eJPj5Q1d/sQuaJZMGdtal1eOVWY+qCh7iAYOY7rYJKsfXRhig==", + "dev": true, + "license": "MIT", + "dependencies": { + "libsodium-wrappers": "^0.7.13", + "yaml": "^2.3.0" + }, + "bin": { + "notur-keygen": "bin/notur-keygen.js", + "notur-pack": "bin/notur-pack.js" + }, + "peerDependencies": { + "react": "^16.14.0", + "react-dom": "^16.14.0" + } + }, + "node_modules/@types/eslint": { + "version": "9.6.1", + "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-9.6.1.tgz", + "integrity": "sha512-FXx2pKgId/WyYo2jXw63kk7/+TY7u7AziEJxJAnSFzHlqTAS3Ync6SvgYAN/k4/PQpnnVuzoMuVnByKK2qp0ag==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "*", + "@types/json-schema": "*" + } + }, + "node_modules/@types/eslint-scope": { + "version": "3.7.7", + "resolved": "https://registry.npmjs.org/@types/eslint-scope/-/eslint-scope-3.7.7.tgz", + "integrity": "sha512-MzMFlSLBqNF2gcHWO0G1vP/YQyfvrxZ0bF+u7mzUdZ1/xK4A4sru+nraZz5i3iEIk1l1uyicaDVTB4QbbEkAYg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/eslint": "*", + "@types/estree": "*" + } + }, + "node_modules/@types/estree": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", + "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/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/node": { + "version": "25.6.0", + "resolved": "https://registry.npmjs.org/@types/node/-/node-25.6.0.tgz", + "integrity": "sha512-+qIYRKdNYJwY3vRCZMdJbPLJAtGjQBudzZzdzwQYkEPQd+PJGixUL5QfvCLDaULoLv+RhT3LDkwEfKaAkgSmNQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~7.19.0" + } + }, + "node_modules/@types/prop-types": { + "version": "15.7.15", + "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.15.tgz", + "integrity": "sha512-F6bEyamV9jKGAFBEmlQnesRPGOQqS2+Uwi0Em15xenOxHaf2hv6L8YCVn3rPdPJOiJfPiCnLIRyvwVaqMY3MIw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/react": { + "version": "16.14.69", + "resolved": "https://registry.npmjs.org/@types/react/-/react-16.14.69.tgz", + "integrity": "sha512-NdnAamzkxLX9LBssSdt9Q0tQ3LR94hYxotI4/sRUs1vHKFXaDx9xDbK8S4wuw5bwrxiiXbTYyhKeITtFnwDvEA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/prop-types": "*", + "@types/scheduler": "^0.16", + "csstype": "^3.2.2" + } + }, + "node_modules/@types/react-dom": { + "version": "16.9.25", + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-16.9.25.tgz", + "integrity": "sha512-ZK//eAPhwft9Ul2/Zj+6O11YR6L4JX0J2sVeBC9Ft7x7HFN7xk7yUV/zDxqV6rjvqgl6r8Dq7oQImxtyf/Mzcw==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "@types/react": "^16.0.0" + } + }, + "node_modules/@types/scheduler": { + "version": "0.16.8", + "resolved": "https://registry.npmjs.org/@types/scheduler/-/scheduler-0.16.8.tgz", + "integrity": "sha512-WZLiwShhwLRmeV6zH+GkbOFT6Z6VklCItrDioxUnv+u4Ll+8vKeFySoFyK/0ctcRpOmwAicELfmys1sDc/Rw+A==", + "dev": true, + "license": "MIT" + }, + "node_modules/@webassemblyjs/ast": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.14.1.tgz", + "integrity": "sha512-nuBEDgQfm1ccRp/8bCQrx1frohyufl4JlbMMZ4P1wpeOfDhF6FQkxZJ1b/e+PLwr6X1Nhw6OLme5usuBWYBvuQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@webassemblyjs/helper-numbers": "1.13.2", + "@webassemblyjs/helper-wasm-bytecode": "1.13.2" + } + }, + "node_modules/@webassemblyjs/floating-point-hex-parser": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.13.2.tgz", + "integrity": "sha512-6oXyTOzbKxGH4steLbLNOu71Oj+C8Lg34n6CqRvqfS2O71BxY6ByfMDRhBytzknj9yGUPVJ1qIKhRlAwO1AovA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@webassemblyjs/helper-api-error": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-api-error/-/helper-api-error-1.13.2.tgz", + "integrity": "sha512-U56GMYxy4ZQCbDZd6JuvvNV/WFildOjsaWD3Tzzvmw/mas3cXzRJPMjP83JqEsgSbyrmaGjBfDtV7KDXV9UzFQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@webassemblyjs/helper-buffer": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-buffer/-/helper-buffer-1.14.1.tgz", + "integrity": "sha512-jyH7wtcHiKssDtFPRB+iQdxlDf96m0E39yb0k5uJVhFGleZFoNw1c4aeIcVUPPbXUVJ94wwnMOAqUHyzoEPVMA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@webassemblyjs/helper-numbers": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-numbers/-/helper-numbers-1.13.2.tgz", + "integrity": "sha512-FE8aCmS5Q6eQYcV3gI35O4J789wlQA+7JrqTTpJqn5emA4U2hvwJmvFRC0HODS+3Ye6WioDklgd6scJ3+PLnEA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@webassemblyjs/floating-point-hex-parser": "1.13.2", + "@webassemblyjs/helper-api-error": "1.13.2", + "@xtuc/long": "4.2.2" + } + }, + "node_modules/@webassemblyjs/helper-wasm-bytecode": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.13.2.tgz", + "integrity": "sha512-3QbLKy93F0EAIXLh0ogEVR6rOubA9AoZ+WRYhNbFyuB70j3dRdwH9g+qXhLAO0kiYGlg3TxDV+I4rQTr/YNXkA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@webassemblyjs/helper-wasm-section": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.14.1.tgz", + "integrity": "sha512-ds5mXEqTJ6oxRoqjhWDU83OgzAYjwsCV8Lo/N+oRsNDmx/ZDpqalmrtgOMkHwxsG0iI//3BwWAErYRHtgn0dZw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@webassemblyjs/ast": "1.14.1", + "@webassemblyjs/helper-buffer": "1.14.1", + "@webassemblyjs/helper-wasm-bytecode": "1.13.2", + "@webassemblyjs/wasm-gen": "1.14.1" + } + }, + "node_modules/@webassemblyjs/ieee754": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/ieee754/-/ieee754-1.13.2.tgz", + "integrity": "sha512-4LtOzh58S/5lX4ITKxnAK2USuNEvpdVV9AlgGQb8rJDHaLeHciwG4zlGr0j/SNWlr7x3vO1lDEsuePvtcDNCkw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@xtuc/ieee754": "^1.2.0" + } + }, + "node_modules/@webassemblyjs/leb128": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/leb128/-/leb128-1.13.2.tgz", + "integrity": "sha512-Lde1oNoIdzVzdkNEAWZ1dZ5orIbff80YPdHx20mrHwHrVNNTjNr8E3xz9BdpcGqRQbAEa+fkrCb+fRFTl/6sQw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@xtuc/long": "4.2.2" + } + }, + "node_modules/@webassemblyjs/utf8": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@webassemblyjs/utf8/-/utf8-1.13.2.tgz", + "integrity": "sha512-3NQWGjKTASY1xV5m7Hr0iPeXD9+RDobLll3T9d2AO+g3my8xy5peVyjSag4I50mR1bBSN/Ct12lo+R9tJk0NZQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@webassemblyjs/wasm-edit": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-edit/-/wasm-edit-1.14.1.tgz", + "integrity": "sha512-RNJUIQH/J8iA/1NzlE4N7KtyZNHi3w7at7hDjvRNm5rcUXa00z1vRz3glZoULfJ5mpvYhLybmVcwcjGrC1pRrQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@webassemblyjs/ast": "1.14.1", + "@webassemblyjs/helper-buffer": "1.14.1", + "@webassemblyjs/helper-wasm-bytecode": "1.13.2", + "@webassemblyjs/helper-wasm-section": "1.14.1", + "@webassemblyjs/wasm-gen": "1.14.1", + "@webassemblyjs/wasm-opt": "1.14.1", + "@webassemblyjs/wasm-parser": "1.14.1", + "@webassemblyjs/wast-printer": "1.14.1" + } + }, + "node_modules/@webassemblyjs/wasm-gen": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-gen/-/wasm-gen-1.14.1.tgz", + "integrity": "sha512-AmomSIjP8ZbfGQhumkNvgC33AY7qtMCXnN6bL2u2Js4gVCg8fp735aEiMSBbDR7UQIj90n4wKAFUSEd0QN2Ukg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@webassemblyjs/ast": "1.14.1", + "@webassemblyjs/helper-wasm-bytecode": "1.13.2", + "@webassemblyjs/ieee754": "1.13.2", + "@webassemblyjs/leb128": "1.13.2", + "@webassemblyjs/utf8": "1.13.2" + } + }, + "node_modules/@webassemblyjs/wasm-opt": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-opt/-/wasm-opt-1.14.1.tgz", + "integrity": "sha512-PTcKLUNvBqnY2U6E5bdOQcSM+oVP/PmrDY9NzowJjislEjwP/C4an2303MCVS2Mg9d3AJpIGdUFIQQWbPds0Sw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@webassemblyjs/ast": "1.14.1", + "@webassemblyjs/helper-buffer": "1.14.1", + "@webassemblyjs/wasm-gen": "1.14.1", + "@webassemblyjs/wasm-parser": "1.14.1" + } + }, + "node_modules/@webassemblyjs/wasm-parser": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-parser/-/wasm-parser-1.14.1.tgz", + "integrity": "sha512-JLBl+KZ0R5qB7mCnud/yyX08jWFw5MsoalJ1pQ4EdFlgj9VdXKGuENGsiCIjegI1W7p91rUlcB/LB5yRJKNTcQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@webassemblyjs/ast": "1.14.1", + "@webassemblyjs/helper-api-error": "1.13.2", + "@webassemblyjs/helper-wasm-bytecode": "1.13.2", + "@webassemblyjs/ieee754": "1.13.2", + "@webassemblyjs/leb128": "1.13.2", + "@webassemblyjs/utf8": "1.13.2" + } + }, + "node_modules/@webassemblyjs/wast-printer": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wast-printer/-/wast-printer-1.14.1.tgz", + "integrity": "sha512-kPSSXE6De1XOR820C90RIo2ogvZG+c3KiHzqUoO/F34Y2shGzesfqv7o57xrxovZJH/MetF5UjroJ/R/3isoiw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@webassemblyjs/ast": "1.14.1", + "@xtuc/long": "4.2.2" + } + }, + "node_modules/@webpack-cli/configtest": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@webpack-cli/configtest/-/configtest-3.0.1.tgz", + "integrity": "sha512-u8d0pJ5YFgneF/GuvEiDA61Tf1VDomHHYMjv/wc9XzYj7nopltpG96nXN5dJRstxZhcNpV1g+nT6CydO7pHbjA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18.12.0" + }, + "peerDependencies": { + "webpack": "^5.82.0", + "webpack-cli": "6.x.x" + } + }, + "node_modules/@webpack-cli/info": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@webpack-cli/info/-/info-3.0.1.tgz", + "integrity": "sha512-coEmDzc2u/ffMvuW9aCjoRzNSPDl/XLuhPdlFRpT9tZHmJ/039az33CE7uH+8s0uL1j5ZNtfdv0HkfaKRBGJsQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18.12.0" + }, + "peerDependencies": { + "webpack": "^5.82.0", + "webpack-cli": "6.x.x" + } + }, + "node_modules/@webpack-cli/serve": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@webpack-cli/serve/-/serve-3.0.1.tgz", + "integrity": "sha512-sbgw03xQaCLiT6gcY/6u3qBDn01CWw/nbaXl3gTdTFuJJ75Gffv3E3DBpgvY2fkkrdS1fpjaXNOmJlnbtKauKg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18.12.0" + }, + "peerDependencies": { + "webpack": "^5.82.0", + "webpack-cli": "6.x.x" + }, + "peerDependenciesMeta": { + "webpack-dev-server": { + "optional": true + } + } + }, + "node_modules/@xtuc/ieee754": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@xtuc/ieee754/-/ieee754-1.2.0.tgz", + "integrity": "sha512-DX8nKgqcGwsc0eJSqYt5lwP4DH5FlHnmuWWBRy7X0NcaGR0ZtuyeESgMwTYVEtxmsNGY+qit4QYT/MIYTOTPeA==", + "dev": true, + "license": "BSD-3-Clause" + }, + "node_modules/@xtuc/long": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@xtuc/long/-/long-4.2.2.tgz", + "integrity": "sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==", + "dev": true, + "license": "Apache-2.0" + }, + "node_modules/acorn": { + "version": "8.16.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.16.0.tgz", + "integrity": "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==", + "dev": true, + "license": "MIT", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-import-phases": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/acorn-import-phases/-/acorn-import-phases-1.0.4.tgz", + "integrity": "sha512-wKmbr/DDiIXzEOiWrTTUcDm24kQ2vGfZQvM2fwg2vXqR5uW6aapr7ObPtj1th32b9u90/Pf4AItvdTh42fBmVQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10.13.0" + }, + "peerDependencies": { + "acorn": "^8.14.0" + } + }, + "node_modules/ajv": { + "version": "8.20.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.20.0.tgz", + "integrity": "sha512-Thbli+OlOj+iMPYFBVBfJ3OmCAnaSyNn4M1vz9T6Gka5Jt9ba/HIR56joy65tY6kx/FCF5VXNB819Y7/GUrBGA==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.3", + "fast-uri": "^3.0.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ajv-formats": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-2.1.1.tgz", + "integrity": "sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ajv": "^8.0.0" + }, + "peerDependencies": { + "ajv": "^8.0.0" + }, + "peerDependenciesMeta": { + "ajv": { + "optional": true + } + } + }, + "node_modules/ajv-keywords": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-5.1.0.tgz", + "integrity": "sha512-YCS/JNFAUyr5vAuhk1DWm1CBxRHW9LbJ2ozWeemrIqpbsqKjHVxYPyi5GC0rjZIT5JxJ3virVTS8wk4i/Z+krw==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.3" + }, + "peerDependencies": { + "ajv": "^8.8.2" + } + }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/baseline-browser-mapping": { + "version": "2.10.27", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.10.27.tgz", + "integrity": "sha512-zEs/ufmZoUd7WftKpKyXaT6RFxpQ5Qm9xytKRHvJfxFV9DFJkZph9RvJ1LcOUi0Z1ZVijMte65JbILeV+8QQEA==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "baseline-browser-mapping": "dist/cli.cjs" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/braces": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", + "dev": true, + "license": "MIT", + "dependencies": { + "fill-range": "^7.1.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/browserslist": { + "version": "4.28.2", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.2.tgz", + "integrity": "sha512-48xSriZYYg+8qXna9kwqjIVzuQxi+KYWp2+5nCYnYKPTr0LvD89Jqk2Or5ogxz0NUMfIjhh2lIUX/LyX9B4oIg==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "baseline-browser-mapping": "^2.10.12", + "caniuse-lite": "^1.0.30001782", + "electron-to-chromium": "^1.5.328", + "node-releases": "^2.0.36", + "update-browserslist-db": "^1.2.3" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, + "node_modules/buffer-from": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", + "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001791", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001791.tgz", + "integrity": "sha512-yk0l/YSrOnFZk3UROpDLQD9+kC1l4meK/wed583AXrzoarMGJcbRi2Q4RaUYbKxYAsZ8sWmaSa/DsLmdBeI1vQ==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "CC-BY-4.0" + }, + "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/chrome-trace-event": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/chrome-trace-event/-/chrome-trace-event-1.0.4.tgz", + "integrity": "sha512-rNjApaLzuwaOTjCiT8lSDdGN1APCiqkChLMJxJPWLunPAt5fy8xgU9/jNOchV84wfIxrA0lRQB7oCT8jrn/wrQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.0" + } + }, + "node_modules/clone-deep": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/clone-deep/-/clone-deep-4.0.1.tgz", + "integrity": "sha512-neHB9xuzh/wk0dIHweyAXv2aPGZIVk3pLMe+/RNzINf17fe0OG96QroktYAUm7SM1PBnzTabaLboqqxDyMU+SQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-plain-object": "^2.0.4", + "kind-of": "^6.0.2", + "shallow-clone": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true, + "license": "MIT" + }, + "node_modules/colorette": { + "version": "2.0.20", + "resolved": "https://registry.npmjs.org/colorette/-/colorette-2.0.20.tgz", + "integrity": "sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==", + "dev": true, + "license": "MIT" + }, + "node_modules/commander": { + "version": "2.20.3", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", + "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/cross-spawn": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/csstype": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", + "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/electron-to-chromium": { + "version": "1.5.349", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.349.tgz", + "integrity": "sha512-QsWVGyRuY07Aqb234QytTfwd5d9AJlfNIQ5wIOl1L+PZDzI9d9+Fn0FRale/QYlFxt/bUnB0/nLd1jFPGxGK1A==", + "dev": true, + "license": "ISC" + }, + "node_modules/enhanced-resolve": { + "version": "5.21.0", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.21.0.tgz", + "integrity": "sha512-otxSQPw4lkOZWkHpB3zaEQs6gWYEsmX4xQF68ElXC/TWvGxGMSGOvoNbaLXm6/cS/fSfHtsEdw90y20PCd+sCA==", + "dev": true, + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.4", + "tapable": "^2.3.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/envinfo": { + "version": "7.21.0", + "resolved": "https://registry.npmjs.org/envinfo/-/envinfo-7.21.0.tgz", + "integrity": "sha512-Lw7I8Zp5YKHFCXL7+Dz95g4CcbMEpgvqZNNq3AmlT5XAV6CgAAk6gyAMqn2zjw08K9BHfcNuKrMiCPLByGafow==", + "dev": true, + "license": "MIT", + "bin": { + "envinfo": "dist/cli.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-module-lexer": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-2.1.0.tgz", + "integrity": "sha512-n27zTYMjYu1aj4MjCWzSP7G9r75utsaoc8m61weK+W8JMBGGQybd43GstCXZ3WNmSFtGT9wi59qQTW6mhTR5LQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/escalade": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/eslint-scope": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", + "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^4.1.1" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "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/esrecurse/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/estraverse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", + "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=4.0" + } + }, + "node_modules/events": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz", + "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.8.x" + } + }, + "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-uri": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.0.tgz", + "integrity": "sha512-iPeeDKJSWf4IEOasVVrknXpaBV0IApz/gp7S2bb7Z4Lljbl2MGJRqInZiUrQwV16cpzw/D3S5j5Julj/gT52AA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "BSD-3-Clause" + }, + "node_modules/fastest-levenshtein": { + "version": "1.0.16", + "resolved": "https://registry.npmjs.org/fastest-levenshtein/-/fastest-levenshtein-1.0.16.tgz", + "integrity": "sha512-eRnCtTTtGZFpQCwhJiUOuxPQWRXVKYDn0b2PeHfXL6/Zi53SLAzAHfVhVWK2AryC/WH05kGfxhFIPvTF0SXQzg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4.9.1" + } + }, + "node_modules/fill-range": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", + "dev": true, + "license": "MIT", + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "dev": true, + "license": "MIT", + "dependencies": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/flat": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/flat/-/flat-5.0.2.tgz", + "integrity": "sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ==", + "dev": true, + "license": "BSD-3-Clause", + "bin": { + "flat": "cli.js" + } + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/glob-to-regexp": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz", + "integrity": "sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==", + "dev": true, + "license": "BSD-2-Clause" + }, + "node_modules/graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/hasown": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.3.tgz", + "integrity": "sha512-ej4AhfhfL2Q2zpMmLo7U1Uv9+PyhIZpgQLGT1F9miIGmiCJIoCgSmczFdrc97mWT4kVY72KA+WnnhJ5pghSvSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/import-local": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/import-local/-/import-local-3.2.0.tgz", + "integrity": "sha512-2SPlun1JUPWoM6t3F0dw0FkCF/jWY8kttcY4f599GLTSjh2OCuuhdTkJQsEcZzBqbXZGKMK2OqW1oZsjtf/gQA==", + "dev": true, + "license": "MIT", + "dependencies": { + "pkg-dir": "^4.2.0", + "resolve-cwd": "^3.0.0" + }, + "bin": { + "import-local-fixture": "fixtures/cli.js" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/interpret": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/interpret/-/interpret-3.1.1.tgz", + "integrity": "sha512-6xwYfHbajpoF0xLW+iwLkhwgvLoZDfjYfoFNu8ftMoXINzwuymNLd9u/KmwtdT2GbR+/Cz66otEGEVVUHX9QLQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/is-core-module": { + "version": "2.16.1", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.1.tgz", + "integrity": "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==", + "dev": true, + "license": "MIT", + "dependencies": { + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/is-plain-object": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", + "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", + "dev": true, + "license": "MIT", + "dependencies": { + "isobject": "^3.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true, + "license": "ISC" + }, + "node_modules/isobject": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", + "integrity": "sha512-WhB9zCku7EGTj/HQQRz5aUQEUeoQZH2bWcltRErOpymJ4boYE6wL9Tbr23krRPSZ+C5zqNSrSw+Cc7sZZ4b7vg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/jest-worker": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-27.5.1.tgz", + "integrity": "sha512-7vuh85V5cdDofPyxn58nrPjBktZo0u9x1g8WtjQol+jZDaE+fhN+cIvTj11GndBnMnyfrUOG1sZQxCdjKh+DKg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*", + "merge-stream": "^2.0.0", + "supports-color": "^8.0.0" + }, + "engines": { + "node": ">= 10.13.0" + } + }, + "node_modules/jest-worker/node_modules/supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" + } + }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "license": "MIT", + "peer": true + }, + "node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "dev": true, + "license": "MIT" + }, + "node_modules/kind-of": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", + "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/libsodium": { + "version": "0.7.16", + "resolved": "https://registry.npmjs.org/libsodium/-/libsodium-0.7.16.tgz", + "integrity": "sha512-3HrzSPuzm6Yt9aTYCDxYEG8x8/6C0+ag655Y7rhhWZM9PT4NpdnbqlzXhGZlDnkgR6MeSTnOt/VIyHLs9aSf+Q==", + "dev": true, + "license": "ISC" + }, + "node_modules/libsodium-wrappers": { + "version": "0.7.16", + "resolved": "https://registry.npmjs.org/libsodium-wrappers/-/libsodium-wrappers-0.7.16.tgz", + "integrity": "sha512-Gtr/WBx4dKjvRL1pvfwZqu7gO6AfrQ0u9vFL+kXihtHf6NfkROR8pjYWn98MFDI3jN19Ii1ZUfPR9afGiPyfHg==", + "dev": true, + "license": "ISC", + "dependencies": { + "libsodium": "^0.7.16" + } + }, + "node_modules/loader-runner": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/loader-runner/-/loader-runner-4.3.2.tgz", + "integrity": "sha512-DFEqQ3ihfS9blba08cLfYf1NRAIEm+dDjic073DRDc3/JspI/8wYmtDsHwd3+4hwvdxSK7PGaElfTmm0awWJ4w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.11.5" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + } + }, + "node_modules/locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-locate": "^4.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/loose-envify": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", + "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", + "license": "MIT", + "peer": true, + "dependencies": { + "js-tokens": "^3.0.0 || ^4.0.0" + }, + "bin": { + "loose-envify": "cli.js" + } + }, + "node_modules/merge-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", + "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", + "dev": true, + "license": "MIT" + }, + "node_modules/micromatch": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", + "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", + "dev": true, + "license": "MIT", + "dependencies": { + "braces": "^3.0.3", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/mime-db": { + "version": "1.54.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz", + "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/neo-async": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz", + "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==", + "dev": true, + "license": "MIT" + }, + "node_modules/node-releases": { + "version": "2.0.38", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.38.tgz", + "integrity": "sha512-3qT/88Y3FbH/Kx4szpQQ4HzUbVrHPKTLVpVocKiLfoYvw9XSGOX2FmD2d6DrXbVYyAQTF2HeF6My8jmzx7/CRw==", + "dev": true, + "license": "MIT" + }, + "node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-try": "^2.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-limit": "^2.2.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/p-try": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "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", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", + "dev": true, + "license": "MIT" + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "dev": true, + "license": "ISC" + }, + "node_modules/picomatch": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.2.tgz", + "integrity": "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/pkg-dir": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", + "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "find-up": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/prop-types": { + "version": "15.8.1", + "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz", + "integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==", + "license": "MIT", + "peer": true, + "dependencies": { + "loose-envify": "^1.4.0", + "object-assign": "^4.1.1", + "react-is": "^16.13.1" + } + }, + "node_modules/react": { + "version": "16.14.0", + "resolved": "https://registry.npmjs.org/react/-/react-16.14.0.tgz", + "integrity": "sha512-0X2CImDkJGApiAlcf0ODKIneSwBPhqJawOa5wCtKbu7ZECrmS26NvtSILynQ66cgkT/RJ4LidJOc3bUESwmU8g==", + "license": "MIT", + "peer": true, + "dependencies": { + "loose-envify": "^1.1.0", + "object-assign": "^4.1.1", + "prop-types": "^15.6.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-dom": { + "version": "16.14.0", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-16.14.0.tgz", + "integrity": "sha512-1gCeQXDLoIqMgqD3IO2Ah9bnf0w9kzhwN5q4FGnHZ67hBm9yePzB5JJAIQCc8x3pFnNlwFq4RidZggNAAkzWWw==", + "license": "MIT", + "peer": true, + "dependencies": { + "loose-envify": "^1.1.0", + "object-assign": "^4.1.1", + "prop-types": "^15.6.2", + "scheduler": "^0.19.1" + }, + "peerDependencies": { + "react": "^16.14.0" + } + }, + "node_modules/react-is": { + "version": "16.13.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", + "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==", + "license": "MIT", + "peer": true + }, + "node_modules/rechoir": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/rechoir/-/rechoir-0.8.0.tgz", + "integrity": "sha512-/vxpCXddiX8NGfGO/mTafwjq4aFa/71pvamip0++IQk3zG8cbCj0fifNPrjjF1XMXUne91jL9OoxmdykoEtifQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "resolve": "^1.20.0" + }, + "engines": { + "node": ">= 10.13.0" + } + }, + "node_modules/require-from-string": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", + "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/resolve": { + "version": "1.22.12", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.12.tgz", + "integrity": "sha512-TyeJ1zif53BPfHootBGwPRYT1RUt6oGWsaQr8UyZW/eAm9bKoijtvruSDEmZHm92CwS9nj7/fWttqPCgzep8CA==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "is-core-module": "^2.16.1", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/resolve-cwd": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/resolve-cwd/-/resolve-cwd-3.0.0.tgz", + "integrity": "sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "resolve-from": "^5.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/resolve-from": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", + "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/scheduler": { + "version": "0.19.1", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.19.1.tgz", + "integrity": "sha512-n/zwRWRYSUj0/3g/otKDRPMh6qv2SYMWNq85IEa8iZyAv8od9zDYpGSnpBEjNgcMNq6Scbu5KfIPxNF72R/2EA==", + "license": "MIT", + "peer": true, + "dependencies": { + "loose-envify": "^1.1.0", + "object-assign": "^4.1.1" + } + }, + "node_modules/schema-utils": { + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.3.3.tgz", + "integrity": "sha512-eflK8wEtyOE6+hsaRVPxvUKYCpRgzLqDTb8krvAsRIwOGlHoSgYLgBXoubGgLd2fT41/OUYdb48v4k4WWHQurA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/json-schema": "^7.0.9", + "ajv": "^8.9.0", + "ajv-formats": "^2.1.1", + "ajv-keywords": "^5.1.0" + }, + "engines": { + "node": ">= 10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + } + }, + "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/shallow-clone": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/shallow-clone/-/shallow-clone-3.0.1.tgz", + "integrity": "sha512-/6KqX+GVUdqPuPPd2LxDDxzX6CAbjJehAAOKlNpqqUpAqPM6HeL8f+o3a+JsyGjn2lv0WY8UsTgUJjU9Ok55NA==", + "dev": true, + "license": "MIT", + "dependencies": { + "kind-of": "^6.0.2" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "license": "MIT", + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/source-map": { + "version": "0.7.6", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.6.tgz", + "integrity": "sha512-i5uvt8C3ikiWeNZSVZNWcfZPItFQOsYTUAOkcUPGd8DqDy1uOUikjt5dG+uRlwyvR108Fb9DOd4GvXfT0N2/uQ==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">= 12" + } + }, + "node_modules/source-map-support": { + "version": "0.5.21", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", + "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", + "dev": true, + "license": "MIT", + "dependencies": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + } + }, + "node_modules/source-map-support/node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/supports-preserve-symlinks-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/tapable": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.3.3.tgz", + "integrity": "sha512-uxc/zpqFg6x7C8vOE7lh6Lbda8eEL9zmVm/PLeTPBRhh1xCgdWaQ+J1CUieGpIfm2HdtsUpRv+HshiasBMcc6A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + } + }, + "node_modules/terser": { + "version": "5.46.2", + "resolved": "https://registry.npmjs.org/terser/-/terser-5.46.2.tgz", + "integrity": "sha512-uxfo9fPcSgLDYob/w1FuL0c99MWiJDnv+5qXSQc5+Ki5NjVNsYi66INnMFBjf6uFz6OnX12piJQPF4IpjJTNTw==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "@jridgewell/source-map": "^0.3.3", + "acorn": "^8.15.0", + "commander": "^2.20.0", + "source-map-support": "~0.5.20" + }, + "bin": { + "terser": "bin/terser" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/terser-webpack-plugin": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.5.0.tgz", + "integrity": "sha512-UYhptBwhWvfIjKd/UuFo6D8uq9xpGLDK+z8EDsj/zWhrTaH34cKEbrkMKfV5YWqGBvAYA3tlzZbs2R+qYrbQJA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/trace-mapping": "^0.3.25", + "jest-worker": "^27.4.5", + "schema-utils": "^4.3.0", + "terser": "^5.31.1" + }, + "engines": { + "node": ">= 10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "webpack": "^5.1.0" + }, + "peerDependenciesMeta": { + "@swc/core": { + "optional": true + }, + "esbuild": { + "optional": true + }, + "uglify-js": { + "optional": true + } + } + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/ts-loader": { + "version": "9.5.7", + "resolved": "https://registry.npmjs.org/ts-loader/-/ts-loader-9.5.7.tgz", + "integrity": "sha512-/ZNrKgA3K3PtpMYOC71EeMWIloGw3IYEa5/t1cyz2r5/PyUwTXGzYJvcD3kfUvmhlfpz1rhV8B2O6IVTQ0avsg==", + "dev": true, + "license": "MIT", + "dependencies": { + "chalk": "^4.1.0", + "enhanced-resolve": "^5.0.0", + "micromatch": "^4.0.0", + "semver": "^7.3.4", + "source-map": "^0.7.4" + }, + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "typescript": "*", + "webpack": "^5.0.0" + } + }, + "node_modules/typescript": { + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/undici-types": { + "version": "7.19.2", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.19.2.tgz", + "integrity": "sha512-qYVnV5OEm2AW8cJMCpdV20CDyaN3g0AjDlOGf1OW4iaDEx8MwdtChUp4zu4H0VP3nDRF/8RKWH+IPp9uW0YGZg==", + "dev": true, + "license": "MIT" + }, + "node_modules/update-browserslist-db": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz", + "integrity": "sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "escalade": "^3.2.0", + "picocolors": "^1.1.1" + }, + "bin": { + "update-browserslist-db": "cli.js" + }, + "peerDependencies": { + "browserslist": ">= 4.21.0" + } + }, + "node_modules/watchpack": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.5.1.tgz", + "integrity": "sha512-Zn5uXdcFNIA1+1Ei5McRd+iRzfhENPCe7LeABkJtNulSxjma+l7ltNx55BWZkRlwRnpOgHqxnjyaDgJnNXnqzg==", + "dev": true, + "license": "MIT", + "dependencies": { + "glob-to-regexp": "^0.4.1", + "graceful-fs": "^4.1.2" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/webpack": { + "version": "5.106.2", + "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.106.2.tgz", + "integrity": "sha512-wGN3qcrBQIFmQ/c0AiOAQBvrZ5lmY8vbbMv4Mxfgzqd/B6+9pXtLo73WuS1dSGXM5QYY3hZnIbvx+K1xxe6FyA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/eslint-scope": "^3.7.7", + "@types/estree": "^1.0.8", + "@types/json-schema": "^7.0.15", + "@webassemblyjs/ast": "^1.14.1", + "@webassemblyjs/wasm-edit": "^1.14.1", + "@webassemblyjs/wasm-parser": "^1.14.1", + "acorn": "^8.16.0", + "acorn-import-phases": "^1.0.3", + "browserslist": "^4.28.1", + "chrome-trace-event": "^1.0.2", + "enhanced-resolve": "^5.20.0", + "es-module-lexer": "^2.0.0", + "eslint-scope": "5.1.1", + "events": "^3.2.0", + "glob-to-regexp": "^0.4.1", + "graceful-fs": "^4.2.11", + "loader-runner": "^4.3.1", + "mime-db": "^1.54.0", + "neo-async": "^2.6.2", + "schema-utils": "^4.3.3", + "tapable": "^2.3.0", + "terser-webpack-plugin": "^5.3.17", + "watchpack": "^2.5.1", + "webpack-sources": "^3.3.4" + }, + "bin": { + "webpack": "bin/webpack.js" + }, + "engines": { + "node": ">=10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependenciesMeta": { + "webpack-cli": { + "optional": true + } + } + }, + "node_modules/webpack-cli": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/webpack-cli/-/webpack-cli-6.0.1.tgz", + "integrity": "sha512-MfwFQ6SfwinsUVi0rNJm7rHZ31GyTcpVE5pgVA3hwFRb7COD4TzjUUwhGWKfO50+xdc2MQPuEBBJoqIMGt3JDw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@discoveryjs/json-ext": "^0.6.1", + "@webpack-cli/configtest": "^3.0.1", + "@webpack-cli/info": "^3.0.1", + "@webpack-cli/serve": "^3.0.1", + "colorette": "^2.0.14", + "commander": "^12.1.0", + "cross-spawn": "^7.0.3", + "envinfo": "^7.14.0", + "fastest-levenshtein": "^1.0.12", + "import-local": "^3.0.2", + "interpret": "^3.1.1", + "rechoir": "^0.8.0", + "webpack-merge": "^6.0.1" + }, + "bin": { + "webpack-cli": "bin/cli.js" + }, + "engines": { + "node": ">=18.12.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "webpack": "^5.82.0" + }, + "peerDependenciesMeta": { + "webpack-bundle-analyzer": { + "optional": true + }, + "webpack-dev-server": { + "optional": true + } + } + }, + "node_modules/webpack-cli/node_modules/commander": { + "version": "12.1.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-12.1.0.tgz", + "integrity": "sha512-Vw8qHK3bZM9y/P10u3Vib8o/DdkvA2OtPtZvD871QKjy74Wj1WSKFILMPRPSdUSx5RFK1arlJzEtA4PkFgnbuA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/webpack-merge": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/webpack-merge/-/webpack-merge-6.0.1.tgz", + "integrity": "sha512-hXXvrjtx2PLYx4qruKl+kyRSLc52V+cCvMxRjmKwoA+CBbbF5GfIBtR6kCvl0fYGqTUPKB+1ktVmTHqMOzgCBg==", + "dev": true, + "license": "MIT", + "dependencies": { + "clone-deep": "^4.0.1", + "flat": "^5.0.2", + "wildcard": "^2.0.1" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/webpack-sources": { + "version": "3.4.1", + "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-3.4.1.tgz", + "integrity": "sha512-eACpxRN02yaawnt+uUNIF7Qje6A9zArxBbcAJjK1PK3S9Ycg5jIuJ8pW4q8EMnwNZCEGltcjkRx1QzOxOkKD8A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/wildcard": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/wildcard/-/wildcard-2.0.1.tgz", + "integrity": "sha512-CC1bOL87PIWSBhDcTrdeLo6eGT7mCFtrg0uIJtqJUFyK+eJnzl8A1niH56uu7KMa5XFrtiV+AQuHO3n7DsHnLQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/yaml": { + "version": "2.8.4", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.8.4.tgz", + "integrity": "sha512-ml/JPOj9fOQK8RNnWojA67GbZ0ApXAUlN2UQclwv2eVgTgn7O9gg9o7paZWKMp4g0H3nTLtS9LVzhkpOFIKzog==", + "dev": true, + "license": "ISC", + "bin": { + "yaml": "bin.mjs" + }, + "engines": { + "node": ">= 14.6" + }, + "funding": { + "url": "https://github.com/sponsors/eemeli" + } + } + } +} diff --git a/extensions/cs2-modframework/resources/frontend/dist/cs2-modframework.js b/extensions/cs2-modframework/resources/frontend/dist/cs2-modframework.js index 49a3f814..b820fa41 100644 --- a/extensions/cs2-modframework/resources/frontend/dist/cs2-modframework.js +++ b/extensions/cs2-modframework/resources/frontend/dist/cs2-modframework.js @@ -1 +1 @@ -!function(e,t){"object"==typeof exports&&"object"==typeof module?module.exports=t(require("react")):"function"==typeof define&&define.amd?define(["react"],t):"object"==typeof exports?exports.__NOTUR_EXT__=t(require("react")):e.__NOTUR_EXT__=t(e.React)}(self,e=>(()=>{"use strict";var t={36(t){t.exports=e}},n={},r=function e(r){var o=n[r];if(void 0!==o)return o.exports;var a=n[r]={exports:{}};return t[r](a,a.exports,e),a.exports}(36);function o(){const e=window.__NOTUR__;if(!e)throw new Error("[Notur SDK] Bridge runtime not found. Ensure bridge.js is loaded first.");return e}const a="notur/cs2-modframework",i={background:"var(--notur-bg-secondary, #1a1f2e)",borderRadius:"var(--notur-radius-md, 12px)",border:"1px solid var(--notur-border, rgba(255, 255, 255, 0.08))",padding:"1.25rem",display:"flex",flexDirection:"column",gap:"1rem",transition:"border-color 0.2s, box-shadow 0.2s"},l={display:"flex",alignItems:"center",gap:"0.75rem"},s={width:"44px",height:"44px",borderRadius:"10px",display:"flex",alignItems:"center",justifyContent:"center",fontSize:"1.5rem",flexShrink:0},d={color:"var(--notur-text-primary, #f0f0f0)",fontSize:"1rem",fontWeight:600,margin:0},c={color:"var(--notur-text-secondary, #a0a8b4)",fontSize:"0.8rem",margin:"2px 0 0 0",lineHeight:1.3},u={display:"flex",gap:"0.5rem",flexWrap:"wrap"},m={fontSize:"0.75rem",fontWeight:600,padding:"3px 10px",borderRadius:"6px",display:"inline-flex",alignItems:"center",gap:"4px"},p={...m,background:"rgba(34, 197, 94, 0.12)",color:"#22c55e",border:"1px solid rgba(34, 197, 94, 0.2)"},f={...m,background:"rgba(148, 163, 184, 0.1)",color:"#94a3b8",border:"1px solid rgba(148, 163, 184, 0.15)"},g={...m,background:"rgba(99, 102, 241, 0.1)",color:"#818cf8",border:"1px solid rgba(99, 102, 241, 0.2)"},y={color:"var(--notur-text-secondary, #a0a8b4)",fontSize:"0.78rem"},b={display:"flex",alignItems:"center",justifyContent:"space-between",marginTop:"auto"},v={padding:"0.5rem 1.25rem",borderRadius:"8px",border:"none",background:"#22c55e",color:"#fff",cursor:"pointer",fontSize:"0.85rem",fontWeight:600,transition:"opacity 0.15s",display:"flex",alignItems:"center",gap:"6px"},k={...v,background:"rgba(239, 68, 68, 0.15)",color:"#ef4444",border:"1px solid rgba(239, 68, 68, 0.25)"},x={...v,opacity:.5,cursor:"not-allowed",background:"rgba(148, 163, 184, 0.15)",color:"#94a3b8"},w={swiftly:"rgba(249, 115, 22, 0.15)",counterstrikesharp:"rgba(99, 102, 241, 0.15)",metamod:"rgba(14, 165, 233, 0.15)"},h={swiftly:"rgba(249, 115, 22, 0.25)",counterstrikesharp:"rgba(99, 102, 241, 0.25)",metamod:"rgba(14, 165, 233, 0.25)"},E=({framework:e,label:t,description:n,icon:o,status:a,version:E,gameInfoOk:S,dependency:C,dependencyInstalled:$,isLoading:I,onInstall:R,onUninstall:z})=>{var N;const j=null!==(N=null==a?void 0:a.installed)&&void 0!==N&&N,D={...s,background:w[e]||"rgba(148, 163, 184, 0.1)",border:`1px solid ${h[e]||"rgba(148, 163, 184, 0.15)"}`},_=C&&!$&&!j;return r.createElement("div",{style:i},r.createElement("div",{style:l},r.createElement("div",{style:D},o),r.createElement("div",null,r.createElement("h4",{style:d},t),r.createElement("p",{style:c},n))),r.createElement("div",{style:u},j?r.createElement("span",{style:p},"✓ Installed"):r.createElement("span",{style:f},"Not Installed"),C?r.createElement("span",{style:g},$?"✓ ":"","Requires ",C):null,j&&!1===S?r.createElement("span",{style:{...m,background:"rgba(245, 158, 11, 0.1)",color:"#f59e0b",border:"1px solid rgba(245, 158, 11, 0.2)"}},"gameinfo.gi entry missing"):null),r.createElement("div",{style:b},r.createElement("span",{style:y},E?`Latest: v${E.version}`:""),I?r.createElement("button",{style:x,disabled:!0},r.createElement("span",{style:{display:"inline-block",width:"14px",height:"14px",border:"2px solid rgba(148, 163, 184, 0.3)",borderTopColor:"#94a3b8",borderRadius:"50%",animation:"notur-spin 0.6s linear infinite"}}),"Working..."):j?r.createElement("button",{style:k,onClick:z},"Uninstall"):r.createElement("button",{style:v,onClick:R,title:_?`${C} will be auto-installed`:void 0},"Install")))},S={position:"fixed",inset:0,background:"rgba(0, 0, 0, 0.6)",display:"flex",alignItems:"center",justifyContent:"center",zIndex:9999,backdropFilter:"blur(2px)"},C={background:"var(--notur-bg-secondary, #1a1f2e)",borderRadius:"var(--notur-radius-md, 12px)",border:"1px solid var(--notur-border, rgba(255, 255, 255, 0.08))",padding:"1.5rem",maxWidth:"440px",width:"90%",boxShadow:"0 20px 60px rgba(0, 0, 0, 0.4)"},$={color:"var(--notur-text-primary, #f0f0f0)",fontSize:"1.1rem",fontWeight:600,margin:"0 0 0.75rem 0"},I={color:"var(--notur-text-secondary, #a0a8b4)",fontSize:"0.9rem",lineHeight:1.5,margin:"0 0 1rem 0"},R={background:"rgba(245, 158, 11, 0.1)",border:"1px solid rgba(245, 158, 11, 0.3)",borderRadius:"8px",padding:"0.75rem",color:"#f59e0b",fontSize:"0.85rem",lineHeight:1.4,margin:"0 0 1rem 0"},z={display:"flex",justifyContent:"flex-end",gap:"0.75rem"},N={padding:"0.5rem 1rem",borderRadius:"8px",border:"1px solid var(--notur-border, rgba(255, 255, 255, 0.1))",background:"transparent",color:"var(--notur-text-secondary, #a0a8b4)",cursor:"pointer",fontSize:"0.875rem",fontWeight:500},j={padding:"0.5rem 1.25rem",borderRadius:"8px",border:"none",color:"#fff",cursor:"pointer",fontSize:"0.875rem",fontWeight:600,minWidth:"100px",display:"flex",alignItems:"center",justifyContent:"center",gap:"0.5rem"},D=({open:e,title:t,message:n,warning:o,confirmLabel:a="Confirm",confirmColor:i="#ef4444",loading:l=!1,onConfirm:s,onCancel:d})=>{if(!e)return null;const c={...j,background:i,opacity:l?.7:1,pointerEvents:l?"none":"auto"};return r.createElement("div",{style:S,onClick:l?void 0:d},r.createElement("div",{style:C,onClick:e=>e.stopPropagation()},r.createElement("h3",{style:$},t),r.createElement("p",{style:I},n),o?r.createElement("div",{style:R},o):null,r.createElement("div",{style:z},r.createElement("button",{style:N,onClick:d,disabled:l},"Cancel"),r.createElement("button",{style:c,onClick:s,disabled:l},l?r.createElement("span",null,"Working..."):a))))},{useState:_,useCallback:W}=r,F=[{key:"swiftly",label:"SwiftlyS2",description:"High-performance CS2 plugin framework. Standalone — no Metamod required.",icon:"⚡"},{key:"counterstrikesharp",label:"CounterStrikeSharp",description:"C# plugin framework for CS2 servers. Requires Metamod:Source.",icon:"🔧",dependency:"metamod"},{key:"metamod",label:"Metamod:Source",description:"C++ plugin API for Source 2 engine. Required by CounterStrikeSharp.",icon:"📦"}],U={padding:"1.5rem",maxWidth:"900px",margin:"0 auto"},L={marginBottom:"1.5rem"},M={color:"var(--notur-text-primary, #f0f0f0)",fontSize:"1.4rem",fontWeight:700,margin:"0 0 0.35rem 0"},T={color:"var(--notur-text-secondary, #a0a8b4)",fontSize:"0.9rem",margin:0},K={display:"grid",gridTemplateColumns:"repeat(auto-fill, minmax(280px, 1fr))",gap:"1rem"},O={display:"flex",alignItems:"center",justifyContent:"center",padding:"4rem 1rem",color:"var(--notur-text-secondary, #a0a8b4)",fontSize:"0.9rem"},q={background:"rgba(239, 68, 68, 0.1)",border:"1px solid rgba(239, 68, 68, 0.25)",borderRadius:"10px",padding:"1rem",color:"#ef4444",fontSize:"0.875rem",marginBottom:"1rem",display:"flex",alignItems:"center",justifyContent:"space-between"},B={background:"transparent",border:"none",color:"#ef4444",cursor:"pointer",fontSize:"1.1rem",padding:"0 0.25rem"},H={background:"rgba(34, 197, 94, 0.1)",border:"1px solid rgba(34, 197, 94, 0.25)",borderRadius:"10px",padding:"1rem",color:"#22c55e",fontSize:"0.875rem",marginBottom:"1rem",display:"flex",alignItems:"center",justifyContent:"space-between"},P="notur-cs2-modframework-styles",A=()=>{r.useEffect(()=>{!function(){if(document.getElementById(P))return;const e=document.createElement("style");e.id=P,e.textContent="@keyframes notur-spin { to { transform: rotate(360deg); } }",document.head.appendChild(e)}()},[]);const e=function(){const[e,t]=(0,r.useState)(null);return(0,r.useEffect)(()=>{const e=document.getElementById("app");if(!e)return;const n=e.dataset.server;if(n)try{t(JSON.parse(n))}catch(e){}const r=window.location.pathname.match(/\/server\/([a-f0-9-]+)/);r&&t(e=>e||{uuid:r[1],name:"",node:"",isOwner:!1,status:null,permissions:[]})},[]),e}();return e?r.createElement(X,{serverUuid:e.uuid}):r.createElement("div",{style:O},"Loading server context...")},X=({serverUuid:e})=>{var t;const{status:n,versions:i,statusLoading:l,actionLoading:s,error:d,fetchStatus:c,installFramework:u,uninstallFramework:m}=function(e){const t=o().hooks.useExtensionApi({extensionId:a,baseUrl:`/api/client/notur/${a}/servers/${e}`}),[n,i]=(0,r.useState)(null),[l,s]=(0,r.useState)(null),[d,c]=(0,r.useState)(!0),[u,m]=(0,r.useState)(null),[p,f]=(0,r.useState)(null),g=(0,r.useRef)(t.get),y=(0,r.useRef)(t.post);g.current=t.get,y.current=t.post;const b=(0,r.useCallback)(async()=>{c(!0),f(null);try{const e=await g.current("/status");i(e.data)}catch(e){f(e.message||"Failed to fetch status")}finally{c(!1)}},[]),v=(0,r.useCallback)(async()=>{try{const e=await g.current("/versions");s(e.data)}catch(e){}},[]),k=(0,r.useCallback)(async e=>{m(e),f(null);try{const t=await y.current("/install",{framework:e});return await b(),t.data}catch(t){const n=t.message||`Failed to install ${e}`;return f(n),{success:!1,framework:e,message:n}}finally{m(null)}},[b]),x=(0,r.useCallback)(async e=>{m(e),f(null);try{const t=await y.current("/uninstall",{framework:e});return await b(),t.data}catch(t){const n=t.message||`Failed to uninstall ${e}`;return f(n),{success:!1,framework:e,message:n}}finally{m(null)}},[b]);return(0,r.useEffect)(()=>{e&&(b(),v())},[e,b,v]),{status:n,versions:l,statusLoading:d,actionLoading:u,error:p,fetchStatus:b,installFramework:k,uninstallFramework:x}}(e),[p,f]=_(null),[g,y]=_(null),b=W((e,t)=>{y(null),f({open:!0,action:e,framework:t})},[]),v=W(()=>f(null),[]),k=W(async()=>{if(!p)return;let e;e="install"===p.action?await u(p.framework):await m(p.framework),e.success&&y(e.message),v()},[p,u,m,v]);return l?r.createElement("div",{style:U},r.createElement("div",{style:L},r.createElement("h2",{style:M},"Mod Frameworks"),r.createElement("p",{style:T},"Manage CS2 modding frameworks on your server")),r.createElement("div",{style:O},"Loading framework status...")):r.createElement("div",{style:U},r.createElement("div",{style:L},r.createElement("h2",{style:M},"Mod Frameworks"),r.createElement("p",{style:T},"One-click install and manage CS2 modding frameworks. Stop your server before installing.")),d?r.createElement("div",{style:q},r.createElement("span",null,d),r.createElement("button",{style:B,onClick:c,title:"Retry"},"↻")):null,g?r.createElement("div",{style:H},r.createElement("span",null,g),r.createElement("button",{style:{...B,color:"#22c55e"},onClick:()=>y(null)},"✕")):null,r.createElement("div",{style:K},F.map(e=>{var t,o,a,l;const d=n?n[e.key]:null,c=i?i[e.key]:null,u=e.dependency?null!==(o=null===(t=null==n?void 0:n[e.dependency])||void 0===t?void 0:t.installed)&&void 0!==o&&o:void 0,m="counterstrikesharp"===e.key||null===(a=null==n?void 0:n.gameinfo_entries)||void 0===a?void 0:a[e.key];return r.createElement(E,{key:e.key,framework:e.key,label:e.label,description:e.description,icon:e.icon,status:d,version:c,gameInfoOk:m,dependency:e.dependency?null===(l=F.find(t=>t.key===e.dependency))||void 0===l?void 0:l.label:void 0,dependencyInstalled:u,isLoading:s===e.key||"metamod"===e.key&&"counterstrikesharp"===s,onInstall:()=>b("install",e.key),onUninstall:()=>b("uninstall",e.key)})})),r.createElement(D,{open:null!==(t=null==p?void 0:p.open)&&void 0!==t&&t,title:(()=>{if(!p)return"";const e=F.find(e=>e.key===p.framework),t=(null==e?void 0:e.label)||p.framework;return"install"===p.action?`Install ${t}`:`Uninstall ${t}`})(),message:(()=>{if(!p)return"";const e=F.find(e=>e.key===p.framework),t=(null==e?void 0:e.label)||p.framework;return"install"===p.action?`This will download and install the latest version of ${t} on your server. The server should be stopped before installing.`:`This will remove ${t} and its files from your server. Any plugins or configurations within ${t} will be lost.`})(),warning:(()=>{var e,t,r,o;if(p){if("install"===p.action&&"counterstrikesharp"===p.framework&&(null===(t=null===(e=null==n?void 0:n.metamod)||void 0===e?void 0:e.installed)||void 0===t||!t))return"Metamod:Source is not installed and will be automatically installed first, as CounterStrikeSharp requires it.";if("uninstall"===p.action&&"metamod"===p.framework&&null!==(o=null===(r=null==n?void 0:n.counterstrikesharp)||void 0===r?void 0:r.installed)&&void 0!==o&&o)return"CounterStrikeSharp is still installed and depends on Metamod. Please uninstall CounterStrikeSharp first."}})(),confirmLabel:"install"===(null==p?void 0:p.action)?"Install":"Uninstall",confirmColor:"install"===(null==p?void 0:p.action)?"#22c55e":"#ef4444",loading:null!==s,onConfirm:k,onCancel:v}))};return function(e){const t=function(e){return"id"in e&&!("config"in e)}(e)?{...e,config:{id:e.id}}:e,{config:n,slots:r=[],routes:a=[],onInit:i,onDestroy:l,cssIsolation:s}=t,d=o(),c=d.version;if(c&&"dev"!==c){const e=parseInt(c.split(".")[0],10);isNaN(e)||1===e||console.warn(`[Notur SDK] Bridge major version ${e} may be incompatible with SDK major version 1. Update @notur/sdk or the Notur bridge to match.`)}!function(e,t,n){/^[a-z0-9-]+\/[a-z0-9-]+$/.test(e)||console.warn(`[Notur SDK] Extension id "${e}" does not match vendor/name format (lowercase, hyphen-safe).`);const r=null!=t?t:[],o=null!=n?n:[],a=new Set;for(const t of r)t.slot&&"string"==typeof t.slot||console.warn(`[Notur SDK] Extension ${e} has a slot registration without a valid slot id.`),"function"!=typeof t.component&&console.warn(`[Notur SDK] Extension ${e} slot "${t.slot}" has a non-component value. Expected a React component.`);for(const t of o){t.path.startsWith("/")||console.warn(`[Notur SDK] Extension ${e} route "${t.name}" should start with "/". Received "${t.path}".`),"function"!=typeof t.component&&console.warn(`[Notur SDK] Extension ${e} route "${t.name}" has a non-component value. Expected a React component.`),t.permission&&!t.permission.includes(".")&&console.warn(`[Notur SDK] Extension ${e} route "${t.name}" uses permission "${t.permission}" without namespace (recommended: vendor.action).`);const n=`${t.area}:${t.path}`;a.has(n)?console.warn(`[Notur SDK] Extension ${e} registers duplicate route path "${t.path}" in area "${t.area}".`):a.add(n)}}(n.id,r,a);const u=function(e,t){var n,r,o;const a=null===(n=e.extensions)||void 0===n?void 0:n.find(e=>e.id===t.id),i=null!==(r=t.name)&&void 0!==r?r:null==a?void 0:a.name,l=null!==(o=t.version)&&void 0!==o?o:null==a?void 0:a.version;if(!i||!l)throw new Error(`[Notur] Extension ${t.id} is missing name/version. Provide them in createExtension() or in extension.yaml.`);return{id:t.id,name:i,version:l}}(d,n),m=function(e,t,n){var r,o;if(!0===n)return{mode:"root-class"};if(n&&"object"==typeof n)return n;const a=null===(o=null===(r=e.extensions)||void 0===r?void 0:r.find(e=>e.id===t))||void 0===o?void 0:o.cssIsolation;return a&&"object"==typeof a?"class"in a&&!a.className?{...a,className:a.class}:a:void 0}(d,n.id,s);if(d.registry.registerExtension({id:u.id,name:u.name,version:u.version,slots:r.map(e=>({...e,extensionId:n.id})),routes:a.map(e=>({...e,extensionId:n.id})),cssIsolation:m}),l&&d.registry.registerDestroyCallback(n.id,l),i)try{i()}catch(e){console.error(`[Notur] Extension ${n.id} init error:`,e)}console.log(`[Notur] Extension registered: ${u.id} v${u.version}`)}({id:"notur/cs2-modframework",slots:[{slot:"server.subnav.after",component:()=>{const e=window.location.pathname.match(/\/server\/([a-f0-9-]+)/),t=e?`/server/${e[1]}/mod-frameworks`:"#";return r.createElement("a",{href:t,onClick:n=>{e?(n.preventDefault(),window.location.href=t):n.preventDefault()},style:{display:"flex",alignItems:"center",gap:"0.5rem",padding:"0.5rem 0.75rem",color:"var(--notur-text-secondary, #a0a8b4)",textDecoration:"none",fontSize:"0.875rem",borderRadius:"6px",transition:"color 0.15s, background 0.15s"}},r.createElement("span",{style:{fontSize:"1rem"}},"🧩"),"Mod Frameworks")},order:50,when:{server:!0}}],routes:[{area:"server",path:"/mod-frameworks",name:"Mod Frameworks",component:A},{area:"server",path:"/server/:id/mod-frameworks",name:"Mod Frameworks (compat)",component:A}]}),{}})()); \ No newline at end of file +!function(e,n){"object"==typeof exports&&"object"==typeof module?module.exports=n(require("react")):"function"==typeof define&&define.amd?define(["react"],n):"object"==typeof exports?exports.__NOTUR_EXT__=n(require("react")):e.__NOTUR_EXT__=n(e.React)}(self,e=>(()=>{"use strict";var n={36(n){n.exports=e}},t={},r=function e(r){var o=t[r];if(void 0!==o)return o.exports;var a=t[r]={exports:{}};return n[r](a,a.exports,e),a.exports}(36);function o(){const e=window.__NOTUR__;if(!e)throw new Error("[Notur SDK] Bridge runtime not found. Ensure bridge.js is loaded first.");return e}const a="notur/cs2-modframework",i={background:"var(--notur-bg-secondary, #1a1f2e)",borderRadius:"var(--notur-radius-md, 12px)",border:"1px solid var(--notur-border, rgba(255, 255, 255, 0.08))",padding:"1.25rem",display:"flex",flexDirection:"column",gap:"1rem",transition:"border-color 0.2s, box-shadow 0.2s"},l={display:"flex",alignItems:"center",gap:"0.75rem"},s={width:"44px",height:"44px",borderRadius:"10px",display:"flex",alignItems:"center",justifyContent:"center",fontSize:"1.5rem",flexShrink:0},d={color:"var(--notur-text-primary, #f0f0f0)",fontSize:"1rem",fontWeight:600,margin:0},c={color:"var(--notur-text-secondary, #a0a8b4)",fontSize:"0.8rem",margin:"2px 0 0 0",lineHeight:1.3},u={display:"flex",gap:"0.5rem",flexWrap:"wrap"},m={fontSize:"0.75rem",fontWeight:600,padding:"3px 10px",borderRadius:"6px",display:"inline-flex",alignItems:"center",gap:"4px"},p={...m,background:"rgba(34, 197, 94, 0.12)",color:"#22c55e",border:"1px solid rgba(34, 197, 94, 0.2)"},f={...m,background:"rgba(148, 163, 184, 0.1)",color:"#94a3b8",border:"1px solid rgba(148, 163, 184, 0.15)"},g={...m,background:"rgba(99, 102, 241, 0.1)",color:"#818cf8",border:"1px solid rgba(99, 102, 241, 0.2)"},b={color:"var(--notur-text-secondary, #a0a8b4)",fontSize:"0.78rem"},v={width:"120px",padding:"0.4rem 0.55rem",borderRadius:"6px",border:"1px solid var(--notur-border, rgba(255, 255, 255, 0.12))",background:"rgba(15, 23, 42, 0.35)",color:"var(--notur-text-primary, #f0f0f0)",fontSize:"0.78rem"},y={display:"flex",alignItems:"center",justifyContent:"space-between",marginTop:"auto"},x={padding:"0.5rem 1.25rem",borderRadius:"8px",border:"none",background:"#22c55e",color:"#fff",cursor:"pointer",fontSize:"0.85rem",fontWeight:600,transition:"opacity 0.15s",display:"flex",alignItems:"center",gap:"6px"},k={...x,background:"rgba(239, 68, 68, 0.15)",color:"#ef4444",border:"1px solid rgba(239, 68, 68, 0.25)"},w={...x,opacity:.5,cursor:"not-allowed",background:"rgba(148, 163, 184, 0.15)",color:"#94a3b8"},h={swiftly:"rgba(249, 115, 22, 0.15)",counterstrikesharp:"rgba(99, 102, 241, 0.15)",metamod:"rgba(14, 165, 233, 0.15)"},E={swiftly:"rgba(249, 115, 22, 0.25)",counterstrikesharp:"rgba(99, 102, 241, 0.25)",metamod:"rgba(14, 165, 233, 0.25)"},S=({framework:e,label:n,description:t,icon:o,status:a,version:S,selectedVersion:C,gameInfoOk:$,dependency:I,dependencyInstalled:R,isLoading:z,disabledReason:N,onVersionChange:D,onInstall:j,onUninstall:_})=>{var W;const U=null!==(W=null==a?void 0:a.installed)&&void 0!==W&&W,F=Boolean(N),L={...s,background:h[e]||"rgba(148, 163, 184, 0.1)",border:`1px solid ${E[e]||"rgba(148, 163, 184, 0.15)"}`},T=I&&!R&&!U;return r.createElement("div",{style:i},r.createElement("div",{style:l},r.createElement("div",{style:L},o),r.createElement("div",null,r.createElement("h4",{style:d},n),r.createElement("p",{style:c},t))),r.createElement("div",{style:u},U?r.createElement("span",{style:p},"✓ Installed"):r.createElement("span",{style:f},"Not Installed"),I?r.createElement("span",{style:g},R?"✓ ":"","Requires ",I):null,U&&!1===$?r.createElement("span",{style:{...m,background:"rgba(245, 158, 11, 0.1)",color:"#f59e0b",border:"1px solid rgba(245, 158, 11, 0.2)"}},"gameinfo.gi entry missing"):null,U&&(null==a?void 0:a.restart_required)?r.createElement("span",{style:{...m,background:"rgba(14, 165, 233, 0.1)",color:"#38bdf8",border:"1px solid rgba(14, 165, 233, 0.2)"}},"Restart required"):null),r.createElement("div",{style:y},r.createElement("div",{style:{display:"flex",flexDirection:"column",gap:"0.35rem"}},r.createElement("span",{style:b},S?`Latest: v${S.version}`:"Latest unavailable"),!U&&S?r.createElement("input",{style:v,value:null!=C?C:S.version,onChange:e=>null==D?void 0:D(e.target.value),disabled:z||F,title:"Version to install","aria-label":`${n} version to install`}):null),z?r.createElement("button",{style:w,disabled:!0},r.createElement("span",{style:{display:"inline-block",width:"14px",height:"14px",border:"2px solid rgba(148, 163, 184, 0.3)",borderTopColor:"#94a3b8",borderRadius:"50%",animation:"notur-spin 0.6s linear infinite"}}),"Working..."):F?r.createElement("button",{style:w,disabled:!0,title:N},U?"Unavailable":"Install"):U?r.createElement("button",{style:k,onClick:_},"Uninstall"):r.createElement("button",{style:x,onClick:j,title:T?`${I} will be auto-installed`:void 0},"Install")))},C={position:"fixed",inset:0,background:"rgba(0, 0, 0, 0.6)",display:"flex",alignItems:"center",justifyContent:"center",zIndex:9999,backdropFilter:"blur(2px)"},$={background:"var(--notur-bg-secondary, #1a1f2e)",borderRadius:"var(--notur-radius-md, 12px)",border:"1px solid var(--notur-border, rgba(255, 255, 255, 0.08))",padding:"1.5rem",maxWidth:"440px",width:"90%",boxShadow:"0 20px 60px rgba(0, 0, 0, 0.4)"},I={color:"var(--notur-text-primary, #f0f0f0)",fontSize:"1.1rem",fontWeight:600,margin:"0 0 0.75rem 0"},R={color:"var(--notur-text-secondary, #a0a8b4)",fontSize:"0.9rem",lineHeight:1.5,margin:"0 0 1rem 0"},z={background:"rgba(245, 158, 11, 0.1)",border:"1px solid rgba(245, 158, 11, 0.3)",borderRadius:"8px",padding:"0.75rem",color:"#f59e0b",fontSize:"0.85rem",lineHeight:1.4,margin:"0 0 1rem 0"},N={display:"flex",justifyContent:"flex-end",gap:"0.75rem"},D={padding:"0.5rem 1rem",borderRadius:"8px",border:"1px solid var(--notur-border, rgba(255, 255, 255, 0.1))",background:"transparent",color:"var(--notur-text-secondary, #a0a8b4)",cursor:"pointer",fontSize:"0.875rem",fontWeight:500},j={padding:"0.5rem 1.25rem",borderRadius:"8px",border:"none",color:"#fff",cursor:"pointer",fontSize:"0.875rem",fontWeight:600,minWidth:"100px",display:"flex",alignItems:"center",justifyContent:"center",gap:"0.5rem"},_=({open:e,title:n,message:t,warning:o,confirmLabel:a="Confirm",confirmColor:i="#ef4444",loading:l=!1,onConfirm:s,onCancel:d})=>{if(!e)return null;const c={...j,background:i,opacity:l?.7:1,pointerEvents:l?"none":"auto"};return r.createElement("div",{style:C,onClick:l?void 0:d},r.createElement("div",{style:$,onClick:e=>e.stopPropagation()},r.createElement("h3",{style:I},n),r.createElement("p",{style:R},t),o?r.createElement("div",{style:z},o):null,r.createElement("div",{style:N},r.createElement("button",{style:D,onClick:d,disabled:l},"Cancel"),r.createElement("button",{style:c,onClick:s,disabled:l},l?r.createElement("span",null,"Working..."):a))))},{useState:W,useCallback:U}=r,F=[{key:"swiftly",label:"SwiftlyS2",description:"High-performance CS2 plugin framework. Standalone — no Metamod required.",icon:"⚡"},{key:"counterstrikesharp",label:"CounterStrikeSharp",description:"C# plugin framework for CS2 servers. Requires Metamod:Source.",icon:"🔧",dependency:"metamod"},{key:"metamod",label:"Metamod:Source",description:"C++ plugin API for Source 2 engine. Required by CounterStrikeSharp.",icon:"📦"}],L={padding:"1.5rem",maxWidth:"900px",margin:"0 auto"},T={marginBottom:"1.5rem"},K={color:"var(--notur-text-primary, #f0f0f0)",fontSize:"1.4rem",fontWeight:700,margin:"0 0 0.35rem 0"},M={color:"var(--notur-text-secondary, #a0a8b4)",fontSize:"0.9rem",margin:0},q={display:"grid",gridTemplateColumns:"repeat(auto-fill, minmax(280px, 1fr))",gap:"1rem"},B={display:"flex",alignItems:"center",justifyContent:"center",padding:"4rem 1rem",color:"var(--notur-text-secondary, #a0a8b4)",fontSize:"0.9rem"},O={background:"rgba(239, 68, 68, 0.1)",border:"1px solid rgba(239, 68, 68, 0.25)",borderRadius:"10px",padding:"1rem",color:"#ef4444",fontSize:"0.875rem",marginBottom:"1rem",display:"flex",alignItems:"center",justifyContent:"space-between"},V={background:"transparent",border:"none",color:"#ef4444",cursor:"pointer",fontSize:"1.1rem",padding:"0 0.25rem"},H={background:"rgba(34, 197, 94, 0.1)",border:"1px solid rgba(34, 197, 94, 0.25)",borderRadius:"10px",padding:"1rem",color:"#22c55e",fontSize:"0.875rem",marginBottom:"1rem",display:"flex",alignItems:"center",justifyContent:"space-between"},P={background:"rgba(245, 158, 11, 0.1)",border:"1px solid rgba(245, 158, 11, 0.25)",borderRadius:"10px",padding:"1rem",color:"#f59e0b",fontSize:"0.875rem",marginBottom:"1rem"},A="notur-cs2-modframework-styles",X=({serverUuid:e})=>{var n,t,i,l,s;const{status:d,versions:c,statusLoading:u,actionLoading:m,error:p,fetchStatus:f,installFramework:g,uninstallFramework:b}=function(e){const n=o().hooks.useExtensionApi({extensionId:a,baseUrl:`/api/client/notur/${a}/servers/${e}`}),[t,i]=(0,r.useState)(null),[l,s]=(0,r.useState)(null),[d,c]=(0,r.useState)(!0),[u,m]=(0,r.useState)(null),[p,f]=(0,r.useState)(null),g=(0,r.useRef)(n.get),b=(0,r.useRef)(n.post);g.current=n.get,b.current=n.post;const v=(0,r.useCallback)(async()=>{c(!0),f(null);try{const e=await g.current("/status");i(e.data)}catch(e){f(e.message||"Failed to fetch status")}finally{c(!1)}},[]),y=(0,r.useCallback)(async()=>{try{const e=await g.current("/versions");s(e.data)}catch(e){}},[]),x=(0,r.useCallback)(async(e,n)=>{m(e),f(null);try{const t=n&&""!==n.trim()?{framework:e,version:n.trim()}:{framework:e},r=await b.current("/install",t);return await v(),r.data}catch(n){const t=n.message||`Failed to install ${e}`;return f(t),{success:!1,framework:e,message:t}}finally{m(null)}},[v]),k=(0,r.useCallback)(async e=>{m(e),f(null);try{const n=await b.current("/uninstall",{framework:e});return await v(),n.data}catch(n){const t=n.message||`Failed to uninstall ${e}`;return f(t),{success:!1,framework:e,message:t}}finally{m(null)}},[v]);return(0,r.useEffect)(()=>{e&&(v(),y())},[e,v,y]),{status:t,versions:l,statusLoading:d,actionLoading:u,error:p,fetchStatus:v,installFramework:x,uninstallFramework:k}}(e),[v,y]=W(null),[x,k]=W(null),[w,h]=W({}),E=null===(t=null===(n=null==d?void 0:d.server)||void 0===n?void 0:n.supported)||void 0===t||t,C=null!==(l=null===(i=null==d?void 0:d.server)||void 0===i?void 0:i.reason)&&void 0!==l?l:"This server is not eligible for CS2 mod framework management.",$=U((e,n)=>{k(null),y({open:!0,action:e,framework:n})},[]),I=U(()=>y(null),[]),R=U(async()=>{if(!v)return;let e;e="install"===v.action?await g(v.framework,w[v.framework]):await b(v.framework),e.success&&k(e.message),I()},[v,w,g,b,I]);return u?r.createElement("div",{style:L},r.createElement("div",{style:T},r.createElement("h2",{style:K},"Mod Frameworks"),r.createElement("p",{style:M},"Manage CS2 modding frameworks on your server")),r.createElement("div",{style:B},"Loading framework status...")):r.createElement("div",{style:L},r.createElement("div",{style:T},r.createElement("h2",{style:K},"Mod Frameworks"),r.createElement("p",{style:M},"One-click install and manage CS2 modding frameworks. Stop your server before installing.")),p?r.createElement("div",{style:O},r.createElement("span",null,p),r.createElement("button",{style:V,onClick:f,title:"Retry"},"↻")):null,x?r.createElement("div",{style:H},r.createElement("span",null,x),r.createElement("button",{style:{...V,color:"#22c55e"},onClick:()=>k(null)},"✕")):null,E?null:r.createElement("div",{style:P},C),r.createElement("div",{style:q},F.map(e=>{var n,t,o,a,i;const l=d?d[e.key]:null,s=c?c[e.key]:null,u=e.dependency?null!==(t=null===(n=null==d?void 0:d[e.dependency])||void 0===n?void 0:n.installed)&&void 0!==t&&t:void 0,p="counterstrikesharp"===e.key||null===(o=null==d?void 0:d.gameinfo_entries)||void 0===o?void 0:o[e.key];return r.createElement(S,{key:e.key,framework:e.key,label:e.label,description:e.description,icon:e.icon,status:l,version:s,selectedVersion:null!==(a=w[e.key])&&void 0!==a?a:null==s?void 0:s.version,gameInfoOk:p,dependency:e.dependency?null===(i=F.find(n=>n.key===e.dependency))||void 0===i?void 0:i.label:void 0,dependencyInstalled:u,isLoading:m===e.key||"metamod"===e.key&&"counterstrikesharp"===m,disabledReason:E?void 0:C,onVersionChange:n=>h(t=>({...t,[e.key]:n})),onInstall:()=>$("install",e.key),onUninstall:()=>$("uninstall",e.key)})})),r.createElement(_,{open:null!==(s=null==v?void 0:v.open)&&void 0!==s&&s,title:(()=>{if(!v)return"";const e=F.find(e=>e.key===v.framework),n=(null==e?void 0:e.label)||v.framework;return"install"===v.action?`Install ${n}`:`Uninstall ${n}`})(),message:(()=>{if(!v)return"";const e=F.find(e=>e.key===v.framework),n=(null==e?void 0:e.label)||v.framework;return"install"===v.action?`This will download and install the latest version of ${n} on your server. The server should be stopped before installing.`:`This will remove ${n} and its files from your server. Any plugins or configurations within ${n} will be lost.`})(),warning:(()=>{var e,n,t,r;if(v){if("install"===v.action&&"counterstrikesharp"===v.framework&&(null===(n=null===(e=null==d?void 0:d.metamod)||void 0===e?void 0:e.installed)||void 0===n||!n))return"Metamod:Source is not installed and will be automatically installed first, as CounterStrikeSharp requires it.";if("uninstall"===v.action&&"metamod"===v.framework&&null!==(r=null===(t=null==d?void 0:d.counterstrikesharp)||void 0===t?void 0:t.installed)&&void 0!==r&&r)return"CounterStrikeSharp is still installed and depends on Metamod. Please uninstall CounterStrikeSharp first."}})(),confirmLabel:"install"===(null==v?void 0:v.action)?"Install":"Uninstall",confirmColor:"install"===(null==v?void 0:v.action)?"#22c55e":"#ef4444",loading:null!==m,onConfirm:R,onCancel:I}))};return function(e){const n=function(e){return"id"in e&&!("config"in e)}(e)?{...e,config:{id:e.id}}:e,{config:t,slots:r=[],routes:a=[],onInit:i,onDestroy:l,cssIsolation:s}=n,d=o(),c=d.version;if(c&&"dev"!==c){const e=parseInt(c.split(".")[0],10);isNaN(e)||1===e||console.warn(`[Notur SDK] Bridge major version ${e} may be incompatible with SDK major version 1. Update @notur/sdk or the Notur bridge to match.`)}!function(e,n,t){/^[a-z0-9-]+\/[a-z0-9-]+$/.test(e)||console.warn(`[Notur SDK] Extension id "${e}" does not match vendor/name format (lowercase, hyphen-safe).`);const r=null!=n?n:[],o=null!=t?t:[],a=new Set;for(const n of r)n.slot&&"string"==typeof n.slot||console.warn(`[Notur SDK] Extension ${e} has a slot registration without a valid slot id.`),"function"!=typeof n.component&&console.warn(`[Notur SDK] Extension ${e} slot "${n.slot}" has a non-component value. Expected a React component.`);for(const n of o){n.path.startsWith("/")||console.warn(`[Notur SDK] Extension ${e} route "${n.name}" should start with "/". Received "${n.path}".`),"function"!=typeof n.component&&console.warn(`[Notur SDK] Extension ${e} route "${n.name}" has a non-component value. Expected a React component.`),n.permission&&!n.permission.includes(".")&&console.warn(`[Notur SDK] Extension ${e} route "${n.name}" uses permission "${n.permission}" without namespace (recommended: vendor.action).`);const t=`${n.area}:${n.path}`;a.has(t)?console.warn(`[Notur SDK] Extension ${e} registers duplicate route path "${n.path}" in area "${n.area}".`):a.add(t)}}(t.id,r,a);const u=function(e,n){var t,r,o;const a=null===(t=e.extensions)||void 0===t?void 0:t.find(e=>e.id===n.id),i=null!==(r=n.name)&&void 0!==r?r:null==a?void 0:a.name,l=null!==(o=n.version)&&void 0!==o?o:null==a?void 0:a.version;if(!i||!l)throw new Error(`[Notur] Extension ${n.id} is missing name/version. Provide them in createExtension() or in extension.yaml.`);return{id:n.id,name:i,version:l}}(d,t),m=function(e,n,t){var r,o;if(!0===t)return{mode:"root-class"};if(t&&"object"==typeof t)return t;const a=null===(o=null===(r=e.extensions)||void 0===r?void 0:r.find(e=>e.id===n))||void 0===o?void 0:o.cssIsolation;return a&&"object"==typeof a?"class"in a&&!a.className?{...a,className:a.class}:a:void 0}(d,t.id,s);if(d.registry.registerExtension({id:u.id,name:u.name,version:u.version,slots:r.map(e=>({...e,extensionId:t.id})),routes:a.map(e=>({...e,extensionId:t.id})),cssIsolation:m}),l&&d.registry.registerDestroyCallback(t.id,l),i)try{i()}catch(e){console.error(`[Notur] Extension ${t.id} init error:`,e)}console.log(`[Notur] Extension registered: ${u.id} v${u.version}`)}({id:"notur/cs2-modframework",slots:[{slot:"server.subnav.after",component:()=>{const e=window.location.pathname.match(/\/server\/([a-f0-9-]+)/),n=e?`/server/${e[1]}/mod-frameworks`:"#";return r.createElement("a",{href:n,onClick:t=>{e?(t.preventDefault(),window.location.href=n):t.preventDefault()},style:{display:"flex",alignItems:"center",gap:"0.5rem",padding:"0.5rem 0.75rem",color:"var(--notur-text-secondary, #a0a8b4)",textDecoration:"none",fontSize:"0.875rem",borderRadius:"6px",transition:"color 0.15s, background 0.15s"}},r.createElement("span",{style:{fontSize:"1rem"}},"🧩"),"Mod Frameworks")},order:50,when:{server:!0}}],routes:[{area:"server",path:"/mod-frameworks",name:"Mod Frameworks",component:()=>{r.useEffect(()=>{!function(){if(document.getElementById(A))return;const e=document.createElement("style");e.id=A,e.textContent="@keyframes notur-spin { to { transform: rotate(360deg); } }",document.head.appendChild(e)}()},[]);const e=function(){const[e,n]=(0,r.useState)(null);return(0,r.useEffect)(()=>{const e=document.getElementById("app");if(!e)return;const t=e.dataset.server;if(t)try{n(JSON.parse(t))}catch(e){}const r=window.location.pathname.match(/\/server\/([a-f0-9-]+)/);r&&n(e=>e||{uuid:r[1],name:"",node:"",isOwner:!1,status:null,permissions:[]})},[]),e}();return e?r.createElement(X,{serverUuid:e.uuid}):r.createElement("div",{style:B},"Loading server context...")}}]}),{}})()); \ No newline at end of file diff --git a/extensions/cs2-modframework/resources/frontend/src/api/useModFramework.ts b/extensions/cs2-modframework/resources/frontend/src/api/useModFramework.ts index 48e1ad9e..7e04a19b 100644 --- a/extensions/cs2-modframework/resources/frontend/src/api/useModFramework.ts +++ b/extensions/cs2-modframework/resources/frontend/src/api/useModFramework.ts @@ -6,6 +6,7 @@ const EXTENSION_ID = 'notur/cs2-modframework'; export interface FrameworkStatus { installed: boolean; directory: string | null; + restart_required?: boolean; } export interface StatusResponse { @@ -13,6 +14,10 @@ export interface StatusResponse { counterstrikesharp: FrameworkStatus; metamod: FrameworkStatus; gameinfo_entries: Record; + server?: { + supported: boolean; + reason: string | null; + }; } export interface VersionInfo { @@ -31,6 +36,7 @@ export interface InstallResult { success: boolean; framework: string; version?: string; + backup?: string; message: string; } @@ -78,11 +84,14 @@ export function useModFramework(serverUuid: string) { } }, []); - const installFramework = useCallback(async (framework: FrameworkName): Promise => { + const installFramework = useCallback(async (framework: FrameworkName, version?: string): Promise => { setActionLoading(framework); setError(null); try { - const res = await apiPostRef.current('/install', { framework }) as { data: InstallResult }; + const payload = version && version.trim() !== '' + ? { framework, version: version.trim() } + : { framework }; + const res = await apiPostRef.current('/install', payload) as { data: InstallResult }; await fetchStatus(); return res.data; } catch (e: any) { diff --git a/extensions/cs2-modframework/resources/frontend/src/components/FrameworkCard.tsx b/extensions/cs2-modframework/resources/frontend/src/components/FrameworkCard.tsx index 14e9d20d..d6689858 100644 --- a/extensions/cs2-modframework/resources/frontend/src/components/FrameworkCard.tsx +++ b/extensions/cs2-modframework/resources/frontend/src/components/FrameworkCard.tsx @@ -8,10 +8,13 @@ interface FrameworkCardProps { icon: string; status: FrameworkStatus | null; version: VersionInfo | null; + selectedVersion?: string; gameInfoOk?: boolean; dependency?: string; dependencyInstalled?: boolean; isLoading: boolean; + disabledReason?: string; + onVersionChange?: (version: string) => void; onInstall: () => void; onUninstall: () => void; } @@ -100,6 +103,16 @@ const versionTextStyle: React.CSSProperties = { fontSize: '0.78rem', }; +const versionInputStyle: React.CSSProperties = { + width: '120px', + padding: '0.4rem 0.55rem', + borderRadius: '6px', + border: '1px solid var(--notur-border, rgba(255, 255, 255, 0.12))', + background: 'rgba(15, 23, 42, 0.35)', + color: 'var(--notur-text-primary, #f0f0f0)', + fontSize: '0.78rem', +}; + const footerStyle: React.CSSProperties = { display: 'flex', alignItems: 'center', @@ -156,14 +169,18 @@ export const FrameworkCard: React.FC = ({ icon, status, version, + selectedVersion, gameInfoOk, dependency, dependencyInstalled, isLoading, + disabledReason, + onVersionChange, onInstall, onUninstall, }) => { const installed = status?.installed ?? false; + const disabled = Boolean(disabledReason); const iconStyle: React.CSSProperties = { ...iconContainerStyle, @@ -204,12 +221,34 @@ export const FrameworkCard: React.FC = ({ }, }, 'gameinfo.gi entry missing') : null, + installed && status?.restart_required + ? React.createElement('span', { + style: { + ...baseBadgeStyle, + background: 'rgba(14, 165, 233, 0.1)', + color: '#38bdf8', + border: '1px solid rgba(14, 165, 233, 0.2)', + }, + }, 'Restart required') + : null, ), // Footer: version + action button React.createElement('div', { style: footerStyle }, - React.createElement('span', { style: versionTextStyle }, - version ? `Latest: v${version.version}` : '', + React.createElement('div', { style: { display: 'flex', flexDirection: 'column', gap: '0.35rem' } }, + React.createElement('span', { style: versionTextStyle }, + version ? `Latest: v${version.version}` : 'Latest unavailable', + ), + !installed && version + ? React.createElement('input', { + style: versionInputStyle, + value: selectedVersion ?? version.version, + onChange: (e: React.ChangeEvent) => onVersionChange?.(e.target.value), + disabled: isLoading || disabled, + title: 'Version to install', + 'aria-label': `${label} version to install`, + }) + : null, ), isLoading ? React.createElement('button', { style: disabledBtnStyle, disabled: true }, @@ -226,6 +265,12 @@ export const FrameworkCard: React.FC = ({ }), 'Working...', ) + : disabled + ? React.createElement('button', { + style: disabledBtnStyle, + disabled: true, + title: disabledReason, + }, installed ? 'Unavailable' : 'Install') : installed ? React.createElement('button', { style: uninstallBtnStyle, diff --git a/extensions/cs2-modframework/resources/frontend/src/components/ModFrameworkPage.tsx b/extensions/cs2-modframework/resources/frontend/src/components/ModFrameworkPage.tsx index 224e3f97..67290f96 100644 --- a/extensions/cs2-modframework/resources/frontend/src/components/ModFrameworkPage.tsx +++ b/extensions/cs2-modframework/resources/frontend/src/components/ModFrameworkPage.tsx @@ -113,6 +113,16 @@ const successBoxStyle: React.CSSProperties = { justifyContent: 'space-between', }; +const warningBoxStyle: React.CSSProperties = { + background: 'rgba(245, 158, 11, 0.1)', + border: '1px solid rgba(245, 158, 11, 0.25)', + borderRadius: '10px', + padding: '1rem', + color: '#f59e0b', + fontSize: '0.875rem', + marginBottom: '1rem', +}; + // Global keyframe style (injected once) const STYLE_ID = 'notur-cs2-modframework-styles'; @@ -150,6 +160,9 @@ const ModFrameworkPageInner: React.FC<{ serverUuid: string }> = ({ serverUuid }) const [modal, setModal] = useState(null); const [successMsg, setSuccessMsg] = useState(null); + const [selectedVersions, setSelectedVersions] = useState>>({}); + const serverSupported = status?.server?.supported ?? true; + const unsupportedReason = status?.server?.reason ?? 'This server is not eligible for CS2 mod framework management.'; const openModal = useCallback((action: 'install' | 'uninstall', framework: FrameworkName) => { setSuccessMsg(null); @@ -163,7 +176,7 @@ const ModFrameworkPageInner: React.FC<{ serverUuid: string }> = ({ serverUuid }) let result: InstallResult; if (modal.action === 'install') { - result = await installFramework(modal.framework); + result = await installFramework(modal.framework, selectedVersions[modal.framework]); } else { result = await uninstallFramework(modal.framework); } @@ -172,7 +185,7 @@ const ModFrameworkPageInner: React.FC<{ serverUuid: string }> = ({ serverUuid }) setSuccessMsg(result.message); } closeModal(); - }, [modal, installFramework, uninstallFramework, closeModal]); + }, [modal, selectedVersions, installFramework, uninstallFramework, closeModal]); const getModalTitle = (): string => { if (!modal) return ''; @@ -249,6 +262,8 @@ const ModFrameworkPageInner: React.FC<{ serverUuid: string }> = ({ serverUuid }) }, '\u2715'), ) : null, + !serverSupported ? React.createElement('div', { style: warningBoxStyle }, unsupportedReason) : null, + // Framework cards React.createElement('div', { style: gridStyle }, FRAMEWORKS.map(fw => { @@ -266,10 +281,13 @@ const ModFrameworkPageInner: React.FC<{ serverUuid: string }> = ({ serverUuid }) icon: fw.icon, status: fwStatus, version: fwVersion, + selectedVersion: selectedVersions[fw.key] ?? fwVersion?.version, gameInfoOk, dependency: fw.dependency ? (FRAMEWORKS.find(f => f.key === fw.dependency)?.label) : undefined, dependencyInstalled: depInstalled, isLoading: actionLoading === fw.key || (fw.key === 'metamod' && actionLoading === 'counterstrikesharp'), + disabledReason: serverSupported ? undefined : unsupportedReason, + onVersionChange: (version) => setSelectedVersions(current => ({ ...current, [fw.key]: version })), onInstall: () => openModal('install', fw.key), onUninstall: () => openModal('uninstall', fw.key), }); diff --git a/extensions/cs2-modframework/resources/frontend/src/index.tsx b/extensions/cs2-modframework/resources/frontend/src/index.tsx index cfb36b29..aa70bb95 100644 --- a/extensions/cs2-modframework/resources/frontend/src/index.tsx +++ b/extensions/cs2-modframework/resources/frontend/src/index.tsx @@ -55,11 +55,5 @@ createExtension({ name: 'Mod Frameworks', component: ModFrameworkPage, }, - { - area: 'server', - path: '/server/:id/mod-frameworks', - name: 'Mod Frameworks (compat)', - component: ModFrameworkPage, - }, ], }); diff --git a/extensions/cs2-modframework/src/Controllers/ModFrameworkController.php b/extensions/cs2-modframework/src/Controllers/ModFrameworkController.php index b14bf395..c8128329 100644 --- a/extensions/cs2-modframework/src/Controllers/ModFrameworkController.php +++ b/extensions/cs2-modframework/src/Controllers/ModFrameworkController.php @@ -13,16 +13,18 @@ use Notur\Cs2Modframework\Services\FrameworkInstaller; use Notur\Cs2Modframework\Services\GitHubReleaseResolver; use Notur\Cs2Modframework\Services\GameInfoModifier; +use Notur\Cs2Modframework\Services\ServerEligibility; class ModFrameworkController extends Controller { public function __construct( private readonly DaemonFileRepository $fileRepository, private readonly GitHubReleaseResolver $releaseResolver, + private readonly ServerEligibility $eligibility, ) { } - private function createInstaller(Server $server): FrameworkInstaller + private function createInstaller(Server $server, ?array $eligibility = null): FrameworkInstaller { $repo = clone $this->fileRepository; $repo->setServer($server); @@ -31,6 +33,7 @@ private function createInstaller(Server $server): FrameworkInstaller $repo, $this->releaseResolver, new GameInfoModifier($repo), + $eligibility ?? $this->eligibility->check($server), ); } @@ -52,10 +55,16 @@ public function status(Request $request, string $server): JsonResponse { try { $serverModel = $this->resolveServer($request); - $installer = $this->createInstaller($serverModel); + $eligibility = $this->eligibility->check($serverModel); + $installer = $this->createInstaller($serverModel, $eligibility); return response()->json([ - 'data' => $installer->getStatus(), + 'data' => $installer->getStatus() + [ + 'server' => [ + 'supported' => $eligibility['supported'], + 'reason' => $eligibility['reason'], + ], + ], ]); } catch (\Throwable $e) { Log::error("[Notur cs2-modframework] status error: {$e->getMessage()}", [ @@ -90,13 +99,22 @@ public function install(Request $request, string $server): JsonResponse { $request->validate([ 'framework' => 'required|string|in:swiftly,counterstrikesharp,metamod', + 'version' => 'nullable|string|max:64', ]); try { $serverModel = $this->resolveServer($request); - $installer = $this->createInstaller($serverModel); + $eligibility = $this->eligibility->check($serverModel); + if (!$eligibility['supported']) { + return response()->json([ + 'message' => $eligibility['reason'] ?? 'This server is not eligible for CS2 mod framework installation.', + ], 422); + } - $result = $installer->install($request->input('framework')); + $installer = $this->createInstaller($serverModel, $eligibility); + + $version = $request->filled('version') ? trim((string) $request->input('version')) : null; + $result = $installer->install($request->input('framework'), $version); return response()->json(['data' => $result]); } catch (\Throwable $e) { @@ -120,7 +138,14 @@ public function uninstall(Request $request, string $server): JsonResponse try { $serverModel = $this->resolveServer($request); - $installer = $this->createInstaller($serverModel); + $eligibility = $this->eligibility->check($serverModel); + if (!$eligibility['supported']) { + return response()->json([ + 'message' => $eligibility['reason'] ?? 'This server is not eligible for CS2 mod framework management.', + ], 422); + } + + $installer = $this->createInstaller($serverModel, $eligibility); $result = $installer->uninstall($request->input('framework')); diff --git a/extensions/cs2-modframework/src/Services/FrameworkInstaller.php b/extensions/cs2-modframework/src/Services/FrameworkInstaller.php index 30f857b6..efc5c6dd 100644 --- a/extensions/cs2-modframework/src/Services/FrameworkInstaller.php +++ b/extensions/cs2-modframework/src/Services/FrameworkInstaller.php @@ -34,6 +34,7 @@ public function __construct( private readonly DaemonFileRepository $fileRepository, private readonly GitHubReleaseResolver $releaseResolver, private readonly GameInfoModifier $gameInfoModifier, + private readonly array $serverEligibility = ['supported' => true, 'reason' => null], ) { } @@ -48,19 +49,28 @@ public function getStatus(): array $status[$framework] = [ 'installed' => $installed, 'directory' => $installed ? "game/csgo/addons/{$dir}" : null, + 'restart_required' => $installed, ]; } $status['gameinfo_entries'] = []; foreach (self::GAMEINFO_ENTRIES as $framework => $entry) { - $status['gameinfo_entries'][$framework] = $gameinfoContent !== null && str_contains($gameinfoContent, $entry); + $status['gameinfo_entries'][$framework] = $gameinfoContent !== null && GameInfoModifier::hasEntryInContent($gameinfoContent, $entry); } return $status; } - public function install(string $framework): array + public function install(string $framework, ?string $version = null): array { + if (!$this->isEligible()) { + return [ + 'success' => false, + 'framework' => $framework, + 'message' => $this->serverEligibility['reason'] ?? 'This server is not eligible for CS2 mod framework installation.', + ]; + } + $label = self::FRAMEWORK_LABELS[$framework]; // CSS requires Metamod — auto-install if missing @@ -78,11 +88,19 @@ public function install(string $framework): array } } - return $this->doInstall($framework); + return $this->doInstall($framework, $version); } public function uninstall(string $framework): array { + if (!$this->isEligible()) { + return [ + 'success' => false, + 'framework' => $framework, + 'message' => $this->serverEligibility['reason'] ?? 'This server is not eligible for CS2 mod framework management.', + ]; + } + $label = self::FRAMEWORK_LABELS[$framework]; // Warn if trying to uninstall Metamod while CSS is installed @@ -100,8 +118,16 @@ public function uninstall(string $framework): array $dir = self::FRAMEWORK_DIRS[$framework]; try { - // Delete the framework directory - $this->fileRepository->deleteFiles(self::ADDONS_DIR, [$dir]); + $addons = $this->listAddons(); + if (!$this->dirExistsInList($addons, $dir)) { + return [ + 'success' => true, + 'framework' => $framework, + 'message' => "{$label} is not installed.", + ]; + } + + $backupDir = $this->backupFrameworkDirectory($dir); // Remove gameinfo.gi entry if applicable $entry = self::GAMEINFO_ENTRIES[$framework] ?? null; @@ -117,9 +143,10 @@ public function uninstall(string $framework): array return [ 'success' => true, 'framework' => $framework, - 'message' => "{$label} uninstalled successfully.", + 'backup' => "game/csgo/addons/{$backupDir}", + 'message' => "{$label} uninstalled safely. Existing files were moved to game/csgo/addons/{$backupDir}.", ]; - } catch (DaemonConnectionException $e) { + } catch (\Throwable $e) { Log::error("CS2 ModFramework: Failed to uninstall {$framework}", [ 'error' => $e->getMessage(), ]); @@ -132,22 +159,24 @@ public function uninstall(string $framework): array } } - private function doInstall(string $framework): array + private function doInstall(string $framework, ?string $version = null): array { $label = self::FRAMEWORK_LABELS[$framework]; // Resolve download info $release = match ($framework) { - 'swiftly' => $this->releaseResolver->resolveSwiftly(), - 'counterstrikesharp' => $this->releaseResolver->resolveCounterStrikeSharp(), - 'metamod' => $this->releaseResolver->resolveMetamod(), + 'swiftly' => $this->releaseResolver->resolveSwiftly($version), + 'counterstrikesharp' => $this->releaseResolver->resolveCounterStrikeSharp($version), + 'metamod' => $this->releaseResolver->resolveMetamod($version), }; if ($release === null) { return [ 'success' => false, 'framework' => $framework, - 'message' => "Could not resolve latest {$label} release. Please try again later.", + 'message' => $version + ? "Could not resolve {$label} release {$version}. Check the version and try again." + : "Could not resolve latest {$label} release. Please try again later.", ]; } @@ -180,7 +209,7 @@ private function doInstall(string $framework): array 'version' => $version, 'message' => "{$label} v{$version} installed successfully.", ]; - } catch (DaemonConnectionException $e) { + } catch (\Throwable $e) { Log::error("CS2 ModFramework: Failed to install {$framework}", [ 'error' => $e->getMessage(), 'url' => $downloadUrl, @@ -208,7 +237,7 @@ private function cleanupCssMetamodFiles(): void $metamodDir = $this->fileRepository->getDirectory(self::ADDONS_DIR . '/metamod'); foreach ($metamodDir as $file) { $name = $file['name'] ?? ''; - if (str_contains(strtolower($name), 'counterstrikesharp') && str_ends_with($name, '.vdf')) { + if (strtolower($name) === 'counterstrikesharp.vdf') { $this->fileRepository->deleteFiles(self::ADDONS_DIR . '/metamod', [$name]); } } @@ -245,4 +274,21 @@ private function readGameInfoSafe(): ?string return null; } } + + private function backupFrameworkDirectory(string $dir): string + { + $backupDir = $dir . '.notur-backup-' . gmdate('Ymd-His'); + + $this->fileRepository->renameFiles(self::ADDONS_DIR, [[ + 'from' => $dir, + 'to' => $backupDir, + ]]); + + return $backupDir; + } + + private function isEligible(): bool + { + return (bool) ($this->serverEligibility['supported'] ?? false); + } } diff --git a/extensions/cs2-modframework/src/Services/GameInfoModifier.php b/extensions/cs2-modframework/src/Services/GameInfoModifier.php index fb089d8e..c390a412 100644 --- a/extensions/cs2-modframework/src/Services/GameInfoModifier.php +++ b/extensions/cs2-modframework/src/Services/GameInfoModifier.php @@ -20,75 +20,134 @@ public function hasEntry(string $entry): bool { $content = $this->readGameInfo(); - return str_contains($content, $entry); + return self::hasEntryInContent($content, $entry); } public function addEntry(string $entry): void { $content = $this->readGameInfo(); - // Already present — idempotent - if (str_contains($content, $entry)) { + if (self::hasEntryInContent($content, $entry)) { return; } - // Create backup first $this->fileRepository->putContent(self::GAMEINFO_PATH . '.bak', $content); + $this->fileRepository->putContent(self::GAMEINFO_PATH, self::addEntryToContent($content, $entry)); + } + + public function removeEntry(string $entry): void + { + $content = $this->readGameInfo(); + + if (!self::hasEntryInContent($content, $entry)) { + return; + } + + $this->fileRepository->putContent(self::GAMEINFO_PATH . '.bak', $content); + $this->fileRepository->putContent(self::GAMEINFO_PATH, self::removeEntryFromContent($content, $entry)); + } + + private function readGameInfo(): string + { + return $this->fileRepository->getContent(self::GAMEINFO_PATH); + } + + public static function hasEntryInContent(string $content, string $entry): bool + { + foreach (explode("\n", $content) as $line) { + if (self::lineMatchesEntry($line, $entry)) { + return true; + } + } + + return false; + } + + public static function addEntryToContent(string $content, string $entry): string + { + if (self::hasEntryInContent($content, $entry)) { + return $content; + } - // Find the anchor line and insert after it $lines = explode("\n", $content); - $newLines = []; - $inserted = false; - - foreach ($lines as $line) { - $newLines[] = $line; - - if (!$inserted && str_contains($line, self::ANCHOR_LINE)) { - // Detect indentation from the anchor line - preg_match('/^(\s*)/', $line, $matches); - $indent = $matches[1] ?? "\t\t\t"; - $newLines[] = $indent . $entry; - $inserted = true; + $insertAt = null; + + foreach ($lines as $index => $line) { + if (self::isCommentLine($line)) { + continue; + } + + if (preg_match('/^\s*' . preg_quote(self::ANCHOR_LINE, '/') . '\b/', $line) === 1) { + $insertAt = $index + 1; + break; } } - if (!$inserted) { - // Fallback: append in SearchPaths block if anchor not found - // Try to find any "Game" line and insert after it - $newLines = []; - foreach ($lines as $line) { - $newLines[] = $line; - if (!$inserted && preg_match('/^\s+Game\s+/', $line)) { - preg_match('/^(\s*)/', $line, $matches); - $indent = $matches[1] ?? "\t\t\t"; - $newLines[] = $indent . $entry; - $inserted = true; + if ($insertAt === null) { + foreach ($lines as $index => $line) { + if (!self::isCommentLine($line) && preg_match('/^\s*Game\s+\S+/', $line) === 1) { + $insertAt = $index + 1; + break; } } } - $this->fileRepository->putContent(self::GAMEINFO_PATH, implode("\n", $newLines)); + if ($insertAt === null) { + throw new \RuntimeException('Unable to update gameinfo.gi: no safe SearchPaths insertion point was found.'); + } + + $indent = self::detectIndent($lines[$insertAt - 1] ?? ''); + array_splice($lines, $insertAt, 0, [$indent . self::canonicalEntry($entry)]); + + return implode("\n", $lines); } - public function removeEntry(string $entry): void + public static function removeEntryFromContent(string $content, string $entry): string { - $content = $this->readGameInfo(); + if (!self::hasEntryInContent($content, $entry)) { + return $content; + } - if (!str_contains($content, $entry)) { - return; + $lines = explode("\n", $content); + $newLines = array_filter($lines, fn (string $line): bool => !self::lineMatchesEntry($line, $entry)); + + return implode("\n", array_values($newLines)); + } + + private static function lineMatchesEntry(string $line, string $entry): bool + { + if (self::isCommentLine($line)) { + return false; } - // Create backup first - $this->fileRepository->putContent(self::GAMEINFO_PATH . '.bak', $content); + $path = preg_quote(self::entryPath($entry), '/'); - $lines = explode("\n", $content); - $newLines = array_filter($lines, fn (string $line) => !str_contains($line, $entry)); + return preg_match('/^\s*Game\s+' . $path . '\s*$/i', $line) === 1; + } + + private static function entryPath(string $entry): string + { + if (preg_match('/^\s*Game\s+(\S+)\s*$/i', $entry, $matches) !== 1) { + throw new \InvalidArgumentException("Invalid gameinfo.gi SearchPaths entry: {$entry}"); + } - $this->fileRepository->putContent(self::GAMEINFO_PATH, implode("\n", array_values($newLines))); + return $matches[1]; } - private function readGameInfo(): string + private static function canonicalEntry(string $entry): string { - return $this->fileRepository->getContent(self::GAMEINFO_PATH); + return 'Game ' . self::entryPath($entry); + } + + private static function detectIndent(string $line): string + { + preg_match('/^(\s*)/', $line, $matches); + + return $matches[1] ?? "\t\t\t"; + } + + private static function isCommentLine(string $line): bool + { + return preg_match('/^\s*(\/\/|#)/', $line) === 1; } } diff --git a/extensions/cs2-modframework/src/Services/GitHubReleaseResolver.php b/extensions/cs2-modframework/src/Services/GitHubReleaseResolver.php index cb6faec2..95680899 100644 --- a/extensions/cs2-modframework/src/Services/GitHubReleaseResolver.php +++ b/extensions/cs2-modframework/src/Services/GitHubReleaseResolver.php @@ -43,27 +43,38 @@ public function getLatestVersions(): array }); } - public function resolveSwiftly(): ?array + public function resolveSwiftly(?string $version = null): ?array { return $this->resolveGitHubRelease( self::SWIFTLY_REPO, fn (string $name) => str_contains($name, 'linux') && str_contains($name, 'with-runtimes') && str_ends_with($name, '.zip'), + $version, ); } - public function resolveCounterStrikeSharp(): ?array + public function resolveCounterStrikeSharp(?string $version = null): ?array { return $this->resolveGitHubRelease( self::CSS_REPO, fn (string $name) => str_contains($name, 'with-runtime') && str_contains($name, 'linux') && str_ends_with($name, '.zip'), + $version, ); } - public function resolveMetamod(): ?array + public function resolveMetamod(?string $version = null): ?array { try { - $response = $this->client->get(self::METAMOD_LATEST_URL); - $filename = trim($response->getBody()->__toString()); + if ($version !== null && $version !== '') { + if (preg_match('/^[\d.]+-git\d+$/', $version) !== 1) { + return null; + } + + $filename = "mmsource-{$version}-linux.tar.gz"; + $this->client->head(self::METAMOD_BASE_URL . $filename); + } else { + $response = $this->client->get(self::METAMOD_LATEST_URL); + $filename = trim($response->getBody()->__toString()); + } if (empty($filename)) { return null; @@ -89,10 +100,13 @@ public function resolveMetamod(): ?array } } - private function resolveGitHubRelease(string $repo, callable $assetMatcher): ?array + private function resolveGitHubRelease(string $repo, callable $assetMatcher, ?string $version = null): ?array { try { - $response = $this->client->get("https://api.github.com/repos/{$repo}/releases/latest"); + $endpoint = $version !== null && $version !== '' + ? "https://api.github.com/repos/{$repo}/releases/tags/" . (str_starts_with($version, 'v') ? $version : "v{$version}") + : "https://api.github.com/repos/{$repo}/releases/latest"; + $response = $this->client->get($endpoint); $release = json_decode($response->getBody()->__toString(), true); $tagName = $release['tag_name'] ?? null; diff --git a/extensions/cs2-modframework/src/Services/ServerEligibility.php b/extensions/cs2-modframework/src/Services/ServerEligibility.php new file mode 100644 index 00000000..751f56e3 --- /dev/null +++ b/extensions/cs2-modframework/src/Services/ServerEligibility.php @@ -0,0 +1,71 @@ +} + */ + public function check(Server $server): array + { + $signals = $this->signals($server); + $haystack = strtolower(implode(' ', array_values($signals))); + + $strongPatterns = [ + 'counter-strike 2', + 'counter strike 2', + 'counterstrike2', + 'cs2', + 'csgo', + 'game/csgo', + 'steam_appid 730', + 'app_update 730', + ]; + + foreach ($strongPatterns as $pattern) { + if (str_contains($haystack, $pattern)) { + return [ + 'supported' => true, + 'reason' => null, + 'signals' => $signals, + ]; + } + } + + return [ + 'supported' => false, + 'reason' => 'This server does not look like a Counter-Strike 2 server. Rename the egg or startup metadata to include CS2/csgo if this is a custom CS2 egg.', + 'signals' => $signals, + ]; + } + + /** + * @return array + */ + private function signals(Server $server): array + { + $signals = [ + 'server_name' => (string) ($server->name ?? ''), + 'startup' => (string) ($server->startup ?? ''), + 'image' => (string) ($server->image ?? ''), + ]; + + try { + $egg = $server->relationLoaded('egg') ? $server->getRelation('egg') : $server->egg; + if ($egg !== null) { + $signals['egg_name'] = (string) ($egg->name ?? ''); + $signals['egg_description'] = (string) ($egg->description ?? ''); + $signals['egg_startup'] = (string) ($egg->startup ?? ''); + } + } catch (\Throwable) { + // Eligibility should be conservative if egg metadata is unavailable. + } + + return array_filter($signals, fn (string $value): bool => $value !== ''); + } +} diff --git a/extensions/cs2-modframework/src/routes/api-client.php b/extensions/cs2-modframework/src/routes/api-client.php index 386776d9..f8675681 100644 --- a/extensions/cs2-modframework/src/routes/api-client.php +++ b/extensions/cs2-modframework/src/routes/api-client.php @@ -8,10 +8,12 @@ // Auto-prefixed: /api/client/notur/notur/cs2-modframework/ Route::prefix('servers/{server}') - ->middleware('notur.server-access') + ->middleware(['notur.namespace', 'notur.server-access']) ->group(function () { Route::get('/status', [ModFrameworkController::class, 'status']); Route::get('/versions', [ModFrameworkController::class, 'versions']); - Route::post('/install', [ModFrameworkController::class, 'install']); - Route::post('/uninstall', [ModFrameworkController::class, 'uninstall']); + Route::post('/install', [ModFrameworkController::class, 'install']) + ->middleware('notur.permission:cs2-modframework.manage'); + Route::post('/uninstall', [ModFrameworkController::class, 'uninstall']) + ->middleware('notur.permission:cs2-modframework.manage'); }); diff --git a/package-lock.json b/package-lock.json index 72b63b5f..fe9925ce 100644 --- a/package-lock.json +++ b/package-lock.json @@ -17,6 +17,7 @@ "@testing-library/react": "^12.1.5", "@types/react": "^16.14.0", "@types/react-dom": "^16.9.0", + "@vitest/coverage-v8": "^3.2.4", "jsdom": "^26.1.0", "react": "16.14.0", "react-dom": "16.14.0", @@ -45,6 +46,20 @@ "react-dom": "^16.14.0" } }, + "node_modules/@ampproject/remapping": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.3.0.tgz", + "integrity": "sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" + }, + "engines": { + "node": ">=6.0.0" + } + }, "node_modules/@asamuzakjp/css-color": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/@asamuzakjp/css-color/-/css-color-3.2.0.tgz", @@ -59,13 +74,6 @@ "lru-cache": "^10.4.3" } }, - "node_modules/@asamuzakjp/css-color/node_modules/lru-cache": { - "version": "10.4.3", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", - "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", - "dev": true, - "license": "ISC" - }, "node_modules/@babel/code-frame": { "version": "7.29.0", "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.0.tgz", @@ -81,6 +89,16 @@ "node": ">=6.9.0" } }, + "node_modules/@babel/helper-string-parser": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", + "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, "node_modules/@babel/helper-validator-identifier": { "version": "7.28.5", "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz", @@ -91,6 +109,22 @@ "node": ">=6.9.0" } }, + "node_modules/@babel/parser": { + "version": "7.29.3", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.3.tgz", + "integrity": "sha512-b3ctpQwp+PROvU/cttc4OYl4MzfJUWy6FZg+PMXfzmt/+39iHVF0sDfqay8TQM3JA2EUOyKcFZt75jWriQijsA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.29.0" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, "node_modules/@babel/runtime": { "version": "7.28.6", "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.28.6.tgz", @@ -101,6 +135,30 @@ "node": ">=6.9.0" } }, + "node_modules/@babel/types": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.0.tgz", + "integrity": "sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-string-parser": "^7.27.1", + "@babel/helper-validator-identifier": "^7.28.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@bcoe/v8-coverage": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-1.0.2.tgz", + "integrity": "sha512-6zABk/ECA/QYSCQ1NGiVwwbQerUCZ+TQbp64Q3AgmfNvurHH0j8TtXa1qbShXA6qqkpAj4V5W8pP6mLe1mcMqA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, "node_modules/@csstools/color-helpers": { "version": "5.1.0", "resolved": "https://registry.npmjs.org/@csstools/color-helpers/-/color-helpers-5.1.0.tgz", @@ -668,6 +726,34 @@ "node": ">=18" } }, + "node_modules/@isaacs/cliui": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", + "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", + "dev": true, + "license": "ISC", + "dependencies": { + "string-width": "^5.1.2", + "string-width-cjs": "npm:string-width@^4.2.0", + "strip-ansi": "^7.0.1", + "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", + "wrap-ansi": "^8.1.0", + "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@istanbuljs/schema": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.6.tgz", + "integrity": "sha512-+Sg6GCR/wy1oSmQDFq4LQDAhm3ETKnorxN+y5nbLULOR3P0c14f2Wurzj3/xqPXtasLFfHd5iRFQ7AJt4KH2cw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, "node_modules/@jridgewell/gen-mapping": { "version": "0.3.13", "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", @@ -1077,6 +1163,17 @@ "url": "https://github.com/sponsors/jonschlinkert" } }, + "node_modules/@pkgjs/parseargs": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", + "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==", + "dev": true, + "license": "MIT", + "optional": true, + "engines": { + "node": ">=14" + } + }, "node_modules/@playwright/test": { "version": "1.59.1", "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.59.1.tgz", @@ -1993,6 +2090,40 @@ "dev": true, "license": "MIT" }, + "node_modules/@vitest/coverage-v8": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/@vitest/coverage-v8/-/coverage-v8-3.2.4.tgz", + "integrity": "sha512-EyF9SXU6kS5Ku/U82E259WSnvg6c8KTjppUncuNdm5QHpe17mwREHnjDzozC8x9MZ0xfBUFSaLkRv4TMA75ALQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@ampproject/remapping": "^2.3.0", + "@bcoe/v8-coverage": "^1.0.2", + "ast-v8-to-istanbul": "^0.3.3", + "debug": "^4.4.1", + "istanbul-lib-coverage": "^3.2.2", + "istanbul-lib-report": "^3.0.1", + "istanbul-lib-source-maps": "^5.0.6", + "istanbul-reports": "^3.1.7", + "magic-string": "^0.30.17", + "magicast": "^0.3.5", + "std-env": "^3.9.0", + "test-exclude": "^7.0.1", + "tinyrainbow": "^2.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "@vitest/browser": "3.2.4", + "vitest": "3.2.4" + }, + "peerDependenciesMeta": { + "@vitest/browser": { + "optional": true + } + } + }, "node_modules/@vitest/expect": { "version": "3.2.4", "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-3.2.4.tgz", @@ -2467,6 +2598,25 @@ "node": ">=12" } }, + "node_modules/ast-v8-to-istanbul": { + "version": "0.3.12", + "resolved": "https://registry.npmjs.org/ast-v8-to-istanbul/-/ast-v8-to-istanbul-0.3.12.tgz", + "integrity": "sha512-BRRC8VRZY2R4Z4lFIL35MwNXmwVqBityvOIwETtsCSwvjl0IdgFsy9NhdaA6j74nUdtJJlIypeRhpDam19Wq3g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/trace-mapping": "^0.3.31", + "estree-walker": "^3.0.3", + "js-tokens": "^10.0.0" + } + }, + "node_modules/ast-v8-to-istanbul/node_modules/js-tokens": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-10.0.0.tgz", + "integrity": "sha512-lM/UBzQmfJRo9ABXbPWemivdCW8V2G8FHaHdypQaIy523snUjog0W71ayWXTjiR+ixeMyVHN2XcpnTd/liPg/Q==", + "dev": true, + "license": "MIT" + }, "node_modules/available-typed-arrays": { "version": "1.0.7", "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz", @@ -2483,6 +2633,16 @@ "url": "https://github.com/sponsors/ljharb" } }, + "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/baseline-browser-mapping": { "version": "2.9.19", "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.9.19.tgz", @@ -2493,6 +2653,19 @@ "baseline-browser-mapping": "dist/cli.js" } }, + "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/braces": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", @@ -2910,6 +3083,13 @@ "node": ">= 0.4" } }, + "node_modules/eastasianwidth": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", + "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", + "dev": true, + "license": "MIT" + }, "node_modules/electron-to-chromium": { "version": "1.5.286", "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.286.tgz", @@ -2917,6 +3097,13 @@ "dev": true, "license": "ISC" }, + "node_modules/emoji-regex": { + "version": "9.2.2", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", + "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", + "dev": true, + "license": "MIT" + }, "node_modules/enhanced-resolve": { "version": "5.19.0", "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.19.0.tgz", @@ -3234,6 +3421,23 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/foreground-child": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.1.tgz", + "integrity": "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==", + "dev": true, + "license": "ISC", + "dependencies": { + "cross-spawn": "^7.0.6", + "signal-exit": "^4.0.1" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/fsevents": { "version": "2.3.3", "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", @@ -3308,6 +3512,28 @@ "node": ">= 0.4" } }, + "node_modules/glob": { + "version": "10.5.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.5.0.tgz", + "integrity": "sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg==", + "deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me", + "dev": true, + "license": "ISC", + "dependencies": { + "foreground-child": "^3.1.0", + "jackspeak": "^3.1.2", + "minimatch": "^9.0.4", + "minipass": "^7.1.2", + "package-json-from-dist": "^1.0.0", + "path-scurry": "^1.11.1" + }, + "bin": { + "glob": "dist/esm/bin.mjs" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/glob-to-regexp": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz", @@ -3315,6 +3541,39 @@ "dev": true, "license": "BSD-2-Clause" }, + "node_modules/glob/node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/glob/node_modules/brace-expansion": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.0.tgz", + "integrity": "sha512-TN1kCZAgdgweJhWWpgKYrQaMNHcDULHkWwQIspdtjV4Y5aurRdZpjAqn6yX3FPqTA9ngHCc4hJxMAMgGfve85w==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/glob/node_modules/minimatch": { + "version": "9.0.9", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.9.tgz", + "integrity": "sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.2" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/gopd": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", @@ -3426,6 +3685,13 @@ "node": ">=18" } }, + "node_modules/html-escaper": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", + "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==", + "dev": true, + "license": "MIT" + }, "node_modules/http-proxy-agent": { "version": "7.0.2", "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz", @@ -3636,6 +3902,16 @@ "node": ">=0.10.0" } }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, "node_modules/is-glob": { "version": "4.0.3", "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", @@ -3846,6 +4122,76 @@ "node": ">=0.10.0" } }, + "node_modules/istanbul-lib-coverage": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.2.tgz", + "integrity": "sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=8" + } + }, + "node_modules/istanbul-lib-report": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.1.tgz", + "integrity": "sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "istanbul-lib-coverage": "^3.0.0", + "make-dir": "^4.0.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/istanbul-lib-source-maps": { + "version": "5.0.6", + "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-5.0.6.tgz", + "integrity": "sha512-yg2d+Em4KizZC5niWhQaIomgf5WlL4vOOjZ5xGCmF8SnPE/mDWWXgvRExdcpCgh9lLRRa1/fSYp2ymmbJ1pI+A==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "@jridgewell/trace-mapping": "^0.3.23", + "debug": "^4.1.1", + "istanbul-lib-coverage": "^3.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/istanbul-reports": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.2.0.tgz", + "integrity": "sha512-HGYWWS/ehqTV3xN10i23tkPkpH46MLCIMFNCaaKNavAXTF1RkqxawEPtnjnGZ6XKSInBKkiOA5BKS+aZiY3AvA==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "html-escaper": "^2.0.0", + "istanbul-lib-report": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/jackspeak": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz", + "integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "@isaacs/cliui": "^8.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + }, + "optionalDependencies": { + "@pkgjs/parseargs": "^0.11.0" + } + }, "node_modules/jiti": { "version": "2.6.1", "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.6.1.tgz", @@ -4260,6 +4606,13 @@ "dev": true, "license": "MIT" }, + "node_modules/lru-cache": { + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", + "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", + "dev": true, + "license": "ISC" + }, "node_modules/lz-string": { "version": "1.5.0", "resolved": "https://registry.npmjs.org/lz-string/-/lz-string-1.5.0.tgz", @@ -4280,6 +4633,34 @@ "@jridgewell/sourcemap-codec": "^1.5.5" } }, + "node_modules/magicast": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/magicast/-/magicast-0.3.5.tgz", + "integrity": "sha512-L0WhttDl+2BOsybvEOLK7fW3UA0OQ0IQ2d6Zl2x/a6vVRs3bAY0ECOSHHeL5jD+SbOpOCUEi0y1DgHEn9Qn1AQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.25.4", + "@babel/types": "^7.25.4", + "source-map-js": "^1.2.0" + } + }, + "node_modules/make-dir": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-4.0.0.tgz", + "integrity": "sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==", + "dev": true, + "license": "MIT", + "dependencies": { + "semver": "^7.5.3" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/math-intrinsics": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", @@ -4334,6 +4715,32 @@ "node": ">= 0.6" } }, + "node_modules/minimatch": { + "version": "10.2.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.5.tgz", + "integrity": "sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "brace-expansion": "^5.0.5" + }, + "engines": { + "node": "18 || 20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/minipass": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.3.tgz", + "integrity": "sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A==", + "dev": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, "node_modules/mri": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/mri/-/mri-1.2.0.tgz", @@ -4507,6 +4914,13 @@ "node": ">=6" } }, + "node_modules/package-json-from-dist": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz", + "integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==", + "dev": true, + "license": "BlueOak-1.0.0" + }, "node_modules/parse5": { "version": "7.3.0", "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.3.0.tgz", @@ -4547,6 +4961,23 @@ "dev": true, "license": "MIT" }, + "node_modules/path-scurry": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz", + "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "lru-cache": "^10.2.0", + "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" + }, + "engines": { + "node": ">=16 || 14 >=14.18" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/pathe": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz", @@ -4982,6 +5413,19 @@ "url": "https://opencollective.com/webpack" } }, + "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/set-function-length": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz", @@ -5135,6 +5579,19 @@ "dev": true, "license": "ISC" }, + "node_modules/signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/source-map": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", @@ -5183,6 +5640,103 @@ "node": ">= 0.4" } }, + "node_modules/string-width": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", + "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "eastasianwidth": "^0.2.0", + "emoji-regex": "^9.2.2", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/string-width-cjs": { + "name": "string-width", + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/string-width-cjs/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true, + "license": "MIT" + }, + "node_modules/string-width-cjs/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.2.0.tgz", + "integrity": "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^6.2.2" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/strip-ansi-cjs": { + "name": "strip-ansi", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi/node_modules/ansi-regex": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz", + "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, "node_modules/strip-literal": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/strip-literal/-/strip-literal-3.1.0.tgz", @@ -5352,6 +5906,21 @@ "source-map": "^0.6.0" } }, + "node_modules/test-exclude": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-7.0.2.tgz", + "integrity": "sha512-u9E6A+ZDYdp7a4WnarkXPZOx8Ilz46+kby6p1yZ8zsGTz9gYa6FIS7lj2oezzNKmtdyyJNNmmXDppga5GB7kSw==", + "dev": true, + "license": "ISC", + "dependencies": { + "@istanbuljs/schema": "^0.1.2", + "glob": "^10.4.1", + "minimatch": "^10.2.2" + }, + "engines": { + "node": ">=18" + } + }, "node_modules/tinybench": { "version": "2.9.0", "resolved": "https://registry.npmjs.org/tinybench/-/tinybench-2.9.0.tgz", @@ -5524,19 +6093,6 @@ "webpack": "^5.0.0" } }, - "node_modules/ts-loader/node_modules/semver": { - "version": "7.7.3", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz", - "integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==", - "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, "node_modules/ts-loader/node_modules/source-map": { "version": "0.7.6", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.6.tgz", @@ -6124,6 +6680,91 @@ "dev": true, "license": "MIT" }, + "node_modules/wrap-ansi": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", + "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^6.1.0", + "string-width": "^5.0.1", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrap-ansi-cjs": { + "name": "wrap-ansi", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true, + "license": "MIT" + }, + "node_modules/wrap-ansi-cjs/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/wrap-ansi/node_modules/ansi-styles": { + "version": "6.2.3", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz", + "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, "node_modules/ws": { "version": "8.20.0", "resolved": "https://registry.npmjs.org/ws/-/ws-8.20.0.tgz", diff --git a/package.json b/package.json index bdaf7fd5..c78903fe 100644 --- a/package.json +++ b/package.json @@ -15,6 +15,7 @@ "dev:bridge": "npx webpack-cli --mode development --watch --config bridge/webpack.config.js", "dev:tailwind": "npx @tailwindcss/cli -i resources/tailwind/notur.css -o bridge/dist/tailwind.css --watch", "test:frontend": "vitest run --config tests/Frontend/vitest.config.js", + "test:frontend:coverage": "vitest run --config tests/Frontend/vitest.config.js --coverage", "test:e2e:browser": "npx playwright test --config=playwright.config.ts" }, "devDependencies": { @@ -23,6 +24,7 @@ "@testing-library/react": "^12.1.5", "@types/react": "^16.14.0", "@types/react-dom": "^16.9.0", + "@vitest/coverage-v8": "^3.2.4", "jsdom": "^26.1.0", "react": "16.14.0", "react-dom": "16.14.0", diff --git a/test.txt.rej b/test.txt.rej new file mode 100644 index 00000000..07c83eb6 --- /dev/null +++ b/test.txt.rej @@ -0,0 +1,5 @@ +@@ -1,3 +1,3 @@ + line1 +-changed_line ++line2_patched + line3 diff --git a/tests/E2E/browser/notur-admin.spec.ts b/tests/E2E/browser/notur-admin.spec.ts index e17eb43c..f2b4f00c 100644 --- a/tests/E2E/browser/notur-admin.spec.ts +++ b/tests/E2E/browser/notur-admin.spec.ts @@ -332,6 +332,15 @@ test.describe.serial('Notur admin browser E2E', () => { await expect(page.getByText('Hello World Extension')).toBeVisible({ timeout: 20_000 }); await expect(page.getByText('Hello from Notur!')).toBeVisible(); + + await page.evaluate(() => { + window.history.pushState({}, '', '/hello'); + window.dispatchEvent(new PopStateEvent('popstate')); + }); + + await expect(page).toHaveURL(/\/hello$/); + await expect(page.getByRole('heading', { name: 'Hello World' })).toBeVisible(); + await expect(page.getByText('This is a full page rendered by the notur/hello-world extension.')).toBeVisible(); }); test('extension API rejects guests and responds for authenticated panel users', async ({ browser }) => { @@ -346,7 +355,7 @@ test.describe.serial('Notur admin browser E2E', () => { }, ); - expect(guestResponse.status()).not.toBe(200); + expect([302, 401, 403]).toContain(guestResponse.status()); } finally { await guestContext.close(); } @@ -359,6 +368,24 @@ test.describe.serial('Notur admin browser E2E', () => { expect(response.status()).toBe(200); expect(payload.message).toBe('Hello from Notur!'); expect(payload.extension).toBe(HELLO_WORLD_ID); + + const userContext = await browser.newContext(); + const userPage = await userContext.newPage(); + + try { + await loginAs(userPage, NON_ADMIN_EMAIL, NON_ADMIN_PASSWORD); + + const userResponse = await userPage.request.get('/api/client/notur/notur/hello-world/greet', { + failOnStatusCode: false, + }); + const userPayload = await userResponse.json(); + + expect(userResponse.status()).toBe(200); + expect(userPayload.message).toBe('Hello from Notur!'); + expect(userPayload.extension).toBe(HELLO_WORLD_ID); + } finally { + await userContext.close(); + } }); test('extension details page loads', async () => { @@ -649,6 +676,33 @@ test.describe.serial('Notur admin browser E2E', () => { rmSync(dirname(archivePath), { recursive: true, force: true }); } }); + + test('install extension from registry id via admin UI', async () => { + const page = adminPage; + + await page.locator('input[name="registry_id"]').fill(HELLO_WORLD_ID); + await page.locator('form', { has: page.locator('input[name="registry_id"]') }).getByRole('button', { name: 'Install' }).click(); + + await expectFlashSuccess(page, 'Extension installed successfully.'); + await expect(page.locator('[data-testid="notur-flash-success"]')).toContainText('Registry checksum verified.'); + await expect(extensionRow(page, HELLO_WORLD_ID)).toContainText('Hello World'); + await expect + .poll(() => + mysqlScalar( + `SELECT COUNT(*) FROM notur_extensions WHERE extension_id='${escapeSql(HELLO_WORLD_ID)}';`, + ), + ) + .toBe('1'); + await expect.poll(async () => + extensionRouteStatus(page, '/api/client/notur/notur/hello-world/greet'), + ).toBe(200); + + page.once('dialog', (dialog) => dialog.accept()); + await extensionRow(page, HELLO_WORLD_ID).getByRole('button', { name: `Remove ${HELLO_WORLD_ID}` }).click(); + await expect(page.locator('[data-testid="notur-flash-success"]')).toContainText( + `Extension '${HELLO_WORLD_ID}' has been removed.`, + ); + }); }); test('non-admin cannot access Notur admin UI', async ({ page }) => { diff --git a/tests/Frontend/vitest.config.js b/tests/Frontend/vitest.config.js index b13e92b6..c89c2545 100644 --- a/tests/Frontend/vitest.config.js +++ b/tests/Frontend/vitest.config.js @@ -7,6 +7,13 @@ module.exports = defineConfig({ globals: true, include: ['tests/Frontend/**/*.{test,spec}.{ts,tsx}'], exclude: ['node_modules', 'bridge/dist'], + coverage: { + provider: 'v8', + reporter: ['text', 'html'], + reportsDirectory: 'coverage/frontend', + include: ['bridge/src/**/*.{ts,tsx}', 'sdk/src/**/*.{ts,tsx}'], + exclude: ['**/*.d.ts'], + }, }, resolve: { alias: { diff --git a/tests/Integration/Http/ExtensionApiControllerTest.php b/tests/Integration/Http/ExtensionApiControllerTest.php new file mode 100644 index 00000000..5a2f857e --- /dev/null +++ b/tests/Integration/Http/ExtensionApiControllerTest.php @@ -0,0 +1,127 @@ +set('database.default', 'testing'); + $app['config']->set('database.connections.testing', [ + 'driver' => 'sqlite', + 'database' => ':memory:', + 'prefix' => '', + ]); + } + + protected function setUp(): void + { + parent::setUp(); + $this->loadMigrationsFrom(__DIR__ . '/../../../database/migrations'); + } + + protected function tearDown(): void + { + Mockery::close(); + parent::tearDown(); + } + + public function test_extensions_returns_enabled_extension_metadata_with_slots(): void + { + $extension = new class implements ExtensionInterface { + public function getId(): string { return 'acme/test'; } + public function getName(): string { return 'Acme Test'; } + public function getVersion(): string { return '1.2.3'; } + public function register(): void {} + public function boot(): void {} + public function getBasePath(): string { return '/tmp/acme-test'; } + }; + + $manifest = ExtensionManifest::fromArray([ + 'id' => 'acme/test', + 'name' => 'Acme Test', + 'version' => '1.2.3', + 'description' => 'Public description', + ]); + + $manager = Mockery::mock(ExtensionManager::class); + $manager->shouldReceive('getFrontendSlots')->once()->andReturn([ + 'acme/test' => [ + 'dashboard.widgets' => ['component' => 'Widget'], + ], + ]); + $manager->shouldReceive('all')->once()->andReturn(['acme/test' => $extension]); + $manager->shouldReceive('getManifest')->with('acme/test')->once()->andReturn($manifest); + + $response = (new ExtensionApiController($manager))->extensions(); + $payload = $response->getData(true); + + $this->assertSame(200, $response->getStatusCode()); + $this->assertSame('acme/test', $payload['data'][0]['id']); + $this->assertSame('Acme Test', $payload['data'][0]['name']); + $this->assertSame('1.2.3', $payload['data'][0]['version']); + $this->assertSame('Public description', $payload['data'][0]['description']); + $this->assertSame(['dashboard.widgets' => ['component' => 'Widget']], $payload['data'][0]['slots']); + } + + public function test_settings_returns_only_public_settings_and_defaults(): void + { + ExtensionSetting::setValue('acme/test', 'public_limit', 25); + ExtensionSetting::setValue('acme/test', 'private_secret', 'stored-secret'); + + $manifest = ExtensionManifest::fromArray([ + 'id' => 'acme/test', + 'name' => 'Acme Test', + 'version' => '1.2.3', + 'admin' => [ + 'settings' => [ + 'fields' => [ + ['key' => 'public_limit', 'type' => 'number', 'public' => true, 'default' => 10], + ['key' => 'public_default', 'type' => 'text', 'public' => true, 'default' => 'fallback'], + ['key' => 'private_secret', 'type' => 'text', 'public' => false, 'default' => 'hidden'], + ], + ], + ], + ]); + + $manager = Mockery::mock(ExtensionManager::class); + $manager->shouldReceive('getManifest')->with('acme/test')->once()->andReturn($manifest); + + $response = (new ExtensionApiController($manager))->settings('acme/test'); + $payload = $response->getData(true); + + $this->assertSame(200, $response->getStatusCode()); + $this->assertSame([ + 'public_limit' => 25, + 'public_default' => 'fallback', + ], $payload['data']); + } + + public function test_settings_returns_404_for_unknown_extension(): void + { + $manager = Mockery::mock(ExtensionManager::class); + $manager->shouldReceive('getManifest')->with('acme/missing')->once()->andReturn(null); + + $response = (new ExtensionApiController($manager))->settings('acme/missing'); + $payload = $response->getData(true); + + $this->assertSame(404, $response->getStatusCode()); + $this->assertSame('Extension not found.', $payload['message']); + } +} diff --git a/tests/Integration/Http/Middleware/ExtensionPermissionTest.php b/tests/Integration/Http/Middleware/ExtensionPermissionTest.php new file mode 100644 index 00000000..4c1878fc --- /dev/null +++ b/tests/Integration/Http/Middleware/ExtensionPermissionTest.php @@ -0,0 +1,94 @@ +shouldReceive('extensionDeclares')->with('acme/test', 'view')->once()->andReturn(true); + $broker->shouldReceive('scopePermission')->with('acme/test', 'view')->once()->andReturn('notur.acme/test.view'); + + $request = Request::create('/api/client/notur/acme/test/report', 'GET'); + $request->attributes->set('notur.extension_id', 'acme/test'); + $request->setUserResolver(fn () => new class { + public bool $root_admin = true; + }); + + $response = (new ExtensionPermission($broker))->handle( + $request, + fn () => new Response('ok'), + 'view', + ); + + $this->assertSame('ok', $response->getContent()); + } + + public function test_server_scoped_user_must_have_scoped_permission_or_wildcard(): void + { + $broker = Mockery::mock(PermissionBroker::class); + $broker->shouldReceive('extensionDeclares')->with('acme/test', 'manage')->once()->andReturn(true); + $broker->shouldReceive('scopePermission')->with('acme/test', 'manage')->once()->andReturn('notur.acme/test.manage'); + + $request = Request::create('/api/client/notur/acme/test/server-action', 'POST'); + $request->attributes->set('notur.extension_id', 'acme/test'); + $request->attributes->set('server', (object) ['id' => 123]); + $request->setUserResolver(fn () => new class { + public bool $root_admin = false; + + public function permissions(object $server): array + { + return ['notur.acme/test.manage']; + } + }); + + $response = (new ExtensionPermission($broker))->handle( + $request, + fn () => new Response('ok'), + 'manage', + ); + + $this->assertSame('ok', $response->getContent()); + } + + public function test_undeclared_permission_is_rejected(): void + { + $broker = Mockery::mock(PermissionBroker::class); + $broker->shouldReceive('extensionDeclares')->with('acme/test', 'delete')->once()->andReturn(false); + $broker->shouldNotReceive('scopePermission'); + + $request = Request::create('/api/client/notur/acme/test/delete', 'DELETE'); + $request->attributes->set('notur.extension_id', 'acme/test'); + $request->setUserResolver(fn () => new class { + public bool $root_admin = true; + }); + + $this->expectException(HttpException::class); + $this->expectExceptionMessage("Permission 'delete' is not declared by extension 'acme/test'."); + + (new ExtensionPermission($broker))->handle($request, fn () => new Response('ok'), 'delete'); + } +} diff --git a/tests/Integration/Http/Middleware/VerifyServerAccessTest.php b/tests/Integration/Http/Middleware/VerifyServerAccessTest.php new file mode 100644 index 00000000..697d997f --- /dev/null +++ b/tests/Integration/Http/Middleware/VerifyServerAccessTest.php @@ -0,0 +1,50 @@ + 'application/json', + ]); + + $response = (new VerifyServerAccess())->handle($request, fn () => response('ok')); + $payload = $response->getData(true); + + $this->assertSame(404, $response->getStatusCode()); + $this->assertSame('Server identifier required.', $payload['message']); + } + + public function test_api_request_without_authenticated_user_returns_json_403(): void + { + $request = Request::create('/api/client/notur/acme/test/servers/test-server', 'GET', [], [], [], [ + 'HTTP_ACCEPT' => 'application/json', + ]); + $request->setRouteResolver(fn () => new class { + public function parameter(string $name): ?string + { + return $name === 'server' ? 'test-server' : null; + } + }); + + $response = (new VerifyServerAccess())->handle($request, fn () => response('ok')); + $payload = $response->getData(true); + + $this->assertSame(403, $response->getStatusCode()); + $this->assertSame('Authentication required.', $payload['message']); + } +} diff --git a/tests/Unit/Cs2Modframework/GameInfoModifierTest.php b/tests/Unit/Cs2Modframework/GameInfoModifierTest.php new file mode 100644 index 00000000..a43ab8c3 --- /dev/null +++ b/tests/Unit/Cs2Modframework/GameInfoModifierTest.php @@ -0,0 +1,76 @@ +assertTrue(GameInfoModifier::hasEntryInContent($content, 'Game csgo/addons/metamod')); + $this->assertTrue(GameInfoModifier::hasEntryInContent($content, "Game\tcsgo/addons/metamod")); + } + + public function test_ignores_commented_entries(): void + { + $content = "SearchPaths\n{\n\t\t\t// Game csgo/addons/metamod\n}\n"; + + $this->assertFalse(GameInfoModifier::hasEntryInContent($content, 'Game csgo/addons/metamod')); + } + + public function test_adds_entry_after_low_violence_anchor(): void + { + $content = implode("\n", [ + 'SearchPaths', + '{', + "\t\t\tGame_LowViolence csgo_lv", + "\t\t\tGame csgo", + '}', + ]); + + $updated = GameInfoModifier::addEntryToContent($content, 'Game csgo/addons/metamod'); + + $this->assertStringContainsString("\t\t\tGame_LowViolence csgo_lv\n\t\t\tGame csgo/addons/metamod\n\t\t\tGame csgo", $updated); + } + + public function test_add_is_idempotent_for_equivalent_whitespace(): void + { + $content = "SearchPaths\n{\n\t\t\tGame csgo/addons/metamod\n}\n"; + + $this->assertSame($content, GameInfoModifier::addEntryToContent($content, 'Game csgo/addons/metamod')); + } + + public function test_remove_only_active_matching_entry(): void + { + $content = implode("\n", [ + 'SearchPaths', + '{', + "\t\t\t// Game csgo/addons/metamod", + "\t\t\tGame csgo/addons/metamod", + "\t\t\tGame csgo", + '}', + ]); + + $updated = GameInfoModifier::removeEntryFromContent($content, 'Game csgo/addons/metamod'); + + $this->assertStringContainsString('// Game csgo/addons/metamod', $updated); + $this->assertFalse(GameInfoModifier::hasEntryInContent($updated, 'Game csgo/addons/metamod')); + } + + public function test_add_throws_when_no_safe_insertion_point_exists(): void + { + $this->expectException(RuntimeException::class); + $this->expectExceptionMessage('no safe SearchPaths insertion point'); + + GameInfoModifier::addEntryToContent("SearchPaths\n{\n}\n", 'Game csgo/addons/metamod'); + } +} diff --git a/tests/Unit/Cs2Modframework/RouteDefinitionTest.php b/tests/Unit/Cs2Modframework/RouteDefinitionTest.php new file mode 100644 index 00000000..ef2955b2 --- /dev/null +++ b/tests/Unit/Cs2Modframework/RouteDefinitionTest.php @@ -0,0 +1,19 @@ +assertIsString($routes); + $this->assertStringContainsString("->middleware(['notur.namespace', 'notur.server-access'])", $routes); + $this->assertSame(2, substr_count($routes, "->middleware('notur.permission:cs2-modframework.manage')")); + } +}