diff --git a/.eslintrc.cjs b/.eslintrc.cjs index 08a391a..c93bbfd 100644 --- a/.eslintrc.cjs +++ b/.eslintrc.cjs @@ -10,5 +10,5 @@ module.exports = { }, plugins: ["@typescript-eslint"], extends: ["eslint:recommended", "plugin:@typescript-eslint/recommended"], - ignorePatterns: ["dist/", "node_modules/"], + ignorePatterns: ["dist/", "node_modules/", "tests/"], }; diff --git a/.github/workflows/ci-lint-build.yml b/.github/workflows/ci-lint-build.yml index d97df70..0a1ba4e 100644 --- a/.github/workflows/ci-lint-build.yml +++ b/.github/workflows/ci-lint-build.yml @@ -25,5 +25,8 @@ jobs: - name: Run linter run: npm run lint + - name: Run tests + run: npm run test + - name: Build run: npm run build diff --git a/package-lock.json b/package-lock.json index a5ba578..e6b0d35 100644 --- a/package-lock.json +++ b/package-lock.json @@ -21,11 +21,95 @@ "@types/node": "^22.14.1", "@typescript-eslint/eslint-plugin": "^7.11.0", "@typescript-eslint/parser": "^7.11.0", + "@vitest/coverage-v8": "^2.1.5", "eslint": "^8.57.0", "ts-node": "^10.9.2", - "typescript": "^5.8.3" + "typescript": "^5.8.3", + "vitest": "^2.1.5" } }, + "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/@ampproject/remapping/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/@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", + "integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.28.6.tgz", + "integrity": "sha512-TeR9zWR18BvbfPmGbLampPMW+uW1NZnJlRuuHso8i87QZNq2JRF9i6RgxRqtEq+wQGsS19NNTWr2duhnE49mfQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.28.6" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/types": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.28.6.tgz", + "integrity": "sha512-0ZrskXVEHSWIqZM/sQZ4EV3jZJXRkio/WCxaqKZP1g//CEWEPSfeZFcms4XeKBCHU0ZKnIkdJeU/kF+eRp5lBg==", + "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": "0.2.3", + "resolved": "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz", + "integrity": "sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==", + "dev": true, + "license": "MIT" + }, "node_modules/@cspotcode/source-map-support": { "version": "0.8.1", "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz", @@ -39,6 +123,397 @@ "node": ">=12" } }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz", + "integrity": "sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.21.5.tgz", + "integrity": "sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.21.5.tgz", + "integrity": "sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.21.5.tgz", + "integrity": "sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.21.5.tgz", + "integrity": "sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.21.5.tgz", + "integrity": "sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.21.5.tgz", + "integrity": "sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.21.5.tgz", + "integrity": "sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.21.5.tgz", + "integrity": "sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.21.5.tgz", + "integrity": "sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.21.5.tgz", + "integrity": "sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.21.5.tgz", + "integrity": "sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.21.5.tgz", + "integrity": "sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.21.5.tgz", + "integrity": "sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.21.5.tgz", + "integrity": "sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.21.5.tgz", + "integrity": "sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.21.5.tgz", + "integrity": "sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.21.5.tgz", + "integrity": "sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.21.5.tgz", + "integrity": "sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.21.5.tgz", + "integrity": "sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.21.5.tgz", + "integrity": "sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.21.5.tgz", + "integrity": "sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.21.5.tgz", + "integrity": "sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, "node_modules/@eslint-community/eslint-utils": { "version": "4.9.1", "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.9.1.tgz", @@ -188,6 +663,85 @@ "dev": true, "license": "BSD-3-Clause" }, + "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/@isaacs/cliui/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/@isaacs/cliui/node_modules/strip-ansi": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.2.tgz", + "integrity": "sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^6.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/@istanbuljs/schema": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.3.tgz", + "integrity": "sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==", + "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", + "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/gen-mapping/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/@jridgewell/resolve-uri": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", @@ -199,9 +753,9 @@ } }, "node_modules/@jridgewell/sourcemap-codec": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz", - "integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==", + "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" }, @@ -254,6 +808,367 @@ "node": ">= 8" } }, + "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/@rollup/rollup-android-arm-eabi": { + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.55.1.tgz", + "integrity": "sha512-9R0DM/ykwfGIlNu6+2U09ga0WXeZ9MRC2Ter8jnz8415VbuIykVuc6bhdrbORFZANDmTDvq26mJrEVTl8TdnDg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.55.1.tgz", + "integrity": "sha512-eFZCb1YUqhTysgW3sj/55du5cG57S7UTNtdMjCW7LwVcj3dTTcowCsC8p7uBdzKsZYa8J7IDE8lhMI+HX1vQvg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.55.1.tgz", + "integrity": "sha512-p3grE2PHcQm2e8PSGZdzIhCKbMCw/xi9XvMPErPhwO17vxtvCN5FEA2mSLgmKlCjHGMQTP6phuQTYWUnKewwGg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.55.1.tgz", + "integrity": "sha512-rDUjG25C9qoTm+e02Esi+aqTKSBYwVTaoS1wxcN47/Luqef57Vgp96xNANwt5npq9GDxsH7kXxNkJVEsWEOEaQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-freebsd-arm64": { + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.55.1.tgz", + "integrity": "sha512-+JiU7Jbp5cdxekIgdte0jfcu5oqw4GCKr6i3PJTlXTCU5H5Fvtkpbs4XJHRmWNXF+hKmn4v7ogI5OQPaupJgOg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-freebsd-x64": { + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.55.1.tgz", + "integrity": "sha512-V5xC1tOVWtLLmr3YUk2f6EJK4qksksOYiz/TCsFHu/R+woubcLWdC9nZQmwjOAbmExBIVKsm1/wKmEy4z4u4Bw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.55.1.tgz", + "integrity": "sha512-Rn3n+FUk2J5VWx+ywrG/HGPTD9jXNbicRtTM11e/uorplArnXZYsVifnPPqNNP5BsO3roI4n8332ukpY/zN7rQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.55.1.tgz", + "integrity": "sha512-grPNWydeKtc1aEdrJDWk4opD7nFtQbMmV7769hiAaYyUKCT1faPRm2av8CX1YJsZ4TLAZcg9gTR1KvEzoLjXkg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.55.1.tgz", + "integrity": "sha512-a59mwd1k6x8tXKcUxSyISiquLwB5pX+fJW9TkWU46lCqD/GRDe9uDN31jrMmVP3feI3mhAdvcCClhV8V5MhJFQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.55.1.tgz", + "integrity": "sha512-puS1MEgWX5GsHSoiAsF0TYrpomdvkaXm0CofIMG5uVkP6IBV+ZO9xhC5YEN49nsgYo1DuuMquF9+7EDBVYu4uA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-gnu": { + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.55.1.tgz", + "integrity": "sha512-r3Wv40in+lTsULSb6nnoudVbARdOwb2u5fpeoOAZjFLznp6tDU8kd+GTHmJoqZ9lt6/Sys33KdIHUaQihFcu7g==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-musl": { + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.55.1.tgz", + "integrity": "sha512-MR8c0+UxAlB22Fq4R+aQSPBayvYa3+9DrwG/i1TKQXFYEaoW3B5b/rkSRIypcZDdWjWnpcvxbNaAJDcSbJU3Lw==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-gnu": { + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.55.1.tgz", + "integrity": "sha512-3KhoECe1BRlSYpMTeVrD4sh2Pw2xgt4jzNSZIIPLFEsnQn9gAnZagW9+VqDqAHgm1Xc77LzJOo2LdigS5qZ+gw==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-musl": { + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.55.1.tgz", + "integrity": "sha512-ziR1OuZx0vdYZZ30vueNZTg73alF59DicYrPViG0NEgDVN8/Jl87zkAPu4u6VjZST2llgEUjaiNl9JM6HH1Vdw==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.55.1.tgz", + "integrity": "sha512-uW0Y12ih2XJRERZ4jAfKamTyIHVMPQnTZcQjme2HMVDAHY4amf5u414OqNYC+x+LzRdRcnIG1YodLrrtA8xsxw==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-musl": { + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.55.1.tgz", + "integrity": "sha512-u9yZ0jUkOED1BFrqu3BwMQoixvGHGZ+JhJNkNKY/hyoEgOwlqKb62qu+7UjbPSHYjiVy8kKJHvXKv5coH4wDeg==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.55.1.tgz", + "integrity": "sha512-/0PenBCmqM4ZUd0190j7J0UsQ/1nsi735iPRakO8iPciE7BQ495Y6msPzaOmvx0/pn+eJVVlZrNrSh4WSYLxNg==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.55.1.tgz", + "integrity": "sha512-a8G4wiQxQG2BAvo+gU6XrReRRqj+pLS2NGXKm8io19goR+K8lw269eTrPkSdDTALwMmJp4th2Uh0D8J9bEV1vg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.55.1.tgz", + "integrity": "sha512-bD+zjpFrMpP/hqkfEcnjXWHMw5BIghGisOKPj+2NaNDuVT+8Ds4mPf3XcPHuat1tz89WRL+1wbcxKY3WSbiT7w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-openbsd-x64": { + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.55.1.tgz", + "integrity": "sha512-eLXw0dOiqE4QmvikfQ6yjgkg/xDM+MdU9YJuP4ySTibXU0oAvnEWXt7UDJmD4UkYialMfOGFPJnIHSe/kdzPxg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ] + }, + "node_modules/@rollup/rollup-openharmony-arm64": { + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.55.1.tgz", + "integrity": "sha512-xzm44KgEP11te3S2HCSyYf5zIzWmx3n8HDCc7EE59+lTcswEWNpvMLfd9uJvVX8LCg9QWG67Xt75AuHn4vgsXw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.55.1.tgz", + "integrity": "sha512-yR6Bl3tMC/gBok5cz/Qi0xYnVbIxGx5Fcf/ca0eB6/6JwOY+SRUcJfI0OpeTpPls7f194as62thCt/2BjxYN8g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.55.1.tgz", + "integrity": "sha512-3fZBidchE0eY0oFZBnekYCfg+5wAB0mbpCBuofh5mZuzIU/4jIVkbESmd2dOsFNS78b53CYv3OAtwqkZZmU5nA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-gnu": { + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.55.1.tgz", + "integrity": "sha512-xGGY5pXj69IxKb4yv/POoocPy/qmEGhimy/FoTpTSVju3FYXUQQMFCaZZXJVidsmGxRioZAwpThl/4zX41gRKg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.55.1.tgz", + "integrity": "sha512-SPEpaL6DX4rmcXtnhdrQYgzQ5W2uW3SCJch88lB2zImhJRhIIK44fkUrgIV/Q8yUNfw5oyZ5vkeQsZLhCb06lw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, "node_modules/@socket.io/component-emitter": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/@socket.io/component-emitter/-/component-emitter-3.1.2.tgz", @@ -316,6 +1231,13 @@ "@types/node": "*" } }, + "node_modules/@types/estree": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", + "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/express": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/@types/express/-/express-5.0.1.tgz", @@ -608,7 +1530,179 @@ "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.3.0.tgz", "integrity": "sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==", "dev": true, - "license": "ISC" + "license": "ISC" + }, + "node_modules/@vitest/coverage-v8": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@vitest/coverage-v8/-/coverage-v8-2.1.5.tgz", + "integrity": "sha512-/RoopB7XGW7UEkUndRXF87A9CwkoZAJW01pj8/3pgmDVsjMH2IKy6H1A38po9tmUlwhSyYs0az82rbKd9Yaynw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@ampproject/remapping": "^2.3.0", + "@bcoe/v8-coverage": "^0.2.3", + "debug": "^4.3.7", + "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.12", + "magicast": "^0.3.5", + "std-env": "^3.8.0", + "test-exclude": "^7.0.1", + "tinyrainbow": "^1.2.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "@vitest/browser": "2.1.5", + "vitest": "2.1.5" + }, + "peerDependenciesMeta": { + "@vitest/browser": { + "optional": true + } + } + }, + "node_modules/@vitest/expect": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-2.1.5.tgz", + "integrity": "sha512-nZSBTW1XIdpZvEJyoP/Sy8fUg0b8od7ZpGDkTUcfJ7wz/VoZAFzFfLyxVxGFhUjJzhYqSbIpfMtl/+k/dpWa3Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/spy": "2.1.5", + "@vitest/utils": "2.1.5", + "chai": "^5.1.2", + "tinyrainbow": "^1.2.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/mocker": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-2.1.5.tgz", + "integrity": "sha512-XYW6l3UuBmitWqSUXTNXcVBUCRytDogBsWuNXQijc00dtnU/9OqpXWp4OJroVrad/gLIomAq9aW8yWDBtMthhQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/spy": "2.1.5", + "estree-walker": "^3.0.3", + "magic-string": "^0.30.12" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "msw": "^2.4.9", + "vite": "^5.0.0" + }, + "peerDependenciesMeta": { + "msw": { + "optional": true + }, + "vite": { + "optional": true + } + } + }, + "node_modules/@vitest/pretty-format": { + "version": "2.1.9", + "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-2.1.9.tgz", + "integrity": "sha512-KhRIdGV2U9HOUzxfiHmY8IFHTdqtOhIzCpd8WRdJiE7D/HUcZVD0EgQCVjm+Q9gkUXWgBvMmTtZgIG48wq7sOQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "tinyrainbow": "^1.2.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/runner": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-2.1.5.tgz", + "integrity": "sha512-pKHKy3uaUdh7X6p1pxOkgkVAFW7r2I818vHDthYLvUyjRfkKOU6P45PztOch4DZarWQne+VOaIMwA/erSSpB9g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/utils": "2.1.5", + "pathe": "^1.1.2" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/snapshot": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-2.1.5.tgz", + "integrity": "sha512-zmYw47mhfdfnYbuhkQvkkzYroXUumrwWDGlMjpdUr4jBd3HZiV2w7CQHj+z7AAS4VOtWxI4Zt4bWt4/sKcoIjg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/pretty-format": "2.1.5", + "magic-string": "^0.30.12", + "pathe": "^1.1.2" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/snapshot/node_modules/@vitest/pretty-format": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-2.1.5.tgz", + "integrity": "sha512-4ZOwtk2bqG5Y6xRGHcveZVr+6txkH7M2e+nPFd6guSoN638v/1XQ0K06eOpi0ptVU/2tW/pIU4IoPotY/GZ9fw==", + "dev": true, + "license": "MIT", + "dependencies": { + "tinyrainbow": "^1.2.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/spy": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-2.1.5.tgz", + "integrity": "sha512-aWZF3P0r3w6DiYTVskOYuhBc7EMc3jvn1TkBg8ttylFFRqNN2XGD7V5a4aQdk6QiUzZQ4klNBSpCLJgWNdIiNw==", + "dev": true, + "license": "MIT", + "dependencies": { + "tinyspy": "^3.0.2" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/utils": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-2.1.5.tgz", + "integrity": "sha512-yfj6Yrp0Vesw2cwJbP+cl04OC+IHFsuQsrsJBL9pyGeQXE56v1UAOQco+SR55Vf1nQzfV0QJg1Qum7AaWUwwYg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/pretty-format": "2.1.5", + "loupe": "^3.1.2", + "tinyrainbow": "^1.2.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/utils/node_modules/@vitest/pretty-format": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-2.1.5.tgz", + "integrity": "sha512-4ZOwtk2bqG5Y6xRGHcveZVr+6txkH7M2e+nPFd6guSoN638v/1XQ0K06eOpi0ptVU/2tW/pIU4IoPotY/GZ9fw==", + "dev": true, + "license": "MIT", + "dependencies": { + "tinyrainbow": "^1.2.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } }, "node_modules/accepts": { "version": "2.0.0", @@ -727,6 +1821,16 @@ "node": ">=8" } }, + "node_modules/assertion-error": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-2.0.1.tgz", + "integrity": "sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + } + }, "node_modules/asynckit": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", @@ -821,6 +1925,16 @@ "node": ">= 0.8" } }, + "node_modules/cac": { + "version": "6.7.14", + "resolved": "https://registry.npmjs.org/cac/-/cac-6.7.14.tgz", + "integrity": "sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, "node_modules/call-bind-apply-helpers": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", @@ -860,6 +1974,23 @@ "node": ">=6" } }, + "node_modules/chai": { + "version": "5.3.3", + "resolved": "https://registry.npmjs.org/chai/-/chai-5.3.3.tgz", + "integrity": "sha512-4zNhdJD/iOjSH0A05ea+Ke6MU5mmpQcbQsSOkgdaUMJ9zTlDTD/GYlwohmIE2u0gaxHYiVHEn1Fw9mZ/ktJWgw==", + "dev": true, + "license": "MIT", + "dependencies": { + "assertion-error": "^2.0.1", + "check-error": "^2.1.1", + "deep-eql": "^5.0.1", + "loupe": "^3.1.0", + "pathval": "^2.0.0" + }, + "engines": { + "node": ">=18" + } + }, "node_modules/chalk": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", @@ -877,6 +2008,16 @@ "url": "https://github.com/chalk/chalk?sponsor=1" } }, + "node_modules/check-error": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/check-error/-/check-error-2.1.3.tgz", + "integrity": "sha512-PAJdDJusoxnwm1VwW07VWwUN1sl7smmC3OKggvndJFadxxDRyFJBX/ggnu/KE4kQAB7a3Dp8f/YXC1FlUprWmA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 16" + } + }, "node_modules/color-convert": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", @@ -1007,6 +2148,16 @@ } } }, + "node_modules/deep-eql": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-5.0.2.tgz", + "integrity": "sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/deep-is": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", @@ -1091,12 +2242,26 @@ "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/ee-first": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==", "license": "MIT" }, + "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/encodeurl": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", @@ -1213,6 +2378,13 @@ "node": ">= 0.4" } }, + "node_modules/es-module-lexer": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.7.0.tgz", + "integrity": "sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==", + "dev": true, + "license": "MIT" + }, "node_modules/es-object-atoms": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", @@ -1240,6 +2412,45 @@ "node": ">= 0.4" } }, + "node_modules/esbuild": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.21.5.tgz", + "integrity": "sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=12" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.21.5", + "@esbuild/android-arm": "0.21.5", + "@esbuild/android-arm64": "0.21.5", + "@esbuild/android-x64": "0.21.5", + "@esbuild/darwin-arm64": "0.21.5", + "@esbuild/darwin-x64": "0.21.5", + "@esbuild/freebsd-arm64": "0.21.5", + "@esbuild/freebsd-x64": "0.21.5", + "@esbuild/linux-arm": "0.21.5", + "@esbuild/linux-arm64": "0.21.5", + "@esbuild/linux-ia32": "0.21.5", + "@esbuild/linux-loong64": "0.21.5", + "@esbuild/linux-mips64el": "0.21.5", + "@esbuild/linux-ppc64": "0.21.5", + "@esbuild/linux-riscv64": "0.21.5", + "@esbuild/linux-s390x": "0.21.5", + "@esbuild/linux-x64": "0.21.5", + "@esbuild/netbsd-x64": "0.21.5", + "@esbuild/openbsd-x64": "0.21.5", + "@esbuild/sunos-x64": "0.21.5", + "@esbuild/win32-arm64": "0.21.5", + "@esbuild/win32-ia32": "0.21.5", + "@esbuild/win32-x64": "0.21.5" + } + }, "node_modules/escape-html": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", @@ -1425,6 +2636,16 @@ "node": ">=4.0" } }, + "node_modules/estree-walker": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz", + "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0" + } + }, "node_modules/esutils": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", @@ -1444,6 +2665,16 @@ "node": ">= 0.6" } }, + "node_modules/expect-type": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/expect-type/-/expect-type-1.3.0.tgz", + "integrity": "sha512-knvyeauYhqjOYvQ66MznSMs83wmHrCycNEN6Ao+2AeYEfxUIkuiVxdEa1qlGEPK+We3n0THiDciYSsCcgW/DoA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=12.0.0" + } + }, "node_modules/express": { "version": "5.1.0", "resolved": "https://registry.npmjs.org/express/-/express-5.1.0.tgz", @@ -1649,6 +2880,23 @@ } } }, + "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/form-data": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.2.tgz", @@ -1710,6 +2958,21 @@ "dev": true, "license": "ISC" }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, "node_modules/function-bind": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", @@ -1929,6 +3192,13 @@ "node": ">= 0.4" } }, + "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-errors": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", @@ -2031,6 +3301,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", @@ -2083,6 +3363,87 @@ "dev": true, "license": "ISC" }, + "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-lib-source-maps/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/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/js-yaml": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz", @@ -2170,6 +3531,13 @@ "integrity": "sha512-mNAgZ1GmyNhD7AuqnTG3/VQ26o760+ZYBPKjPvugO8+nLbYfX6TVpJPseBvopbdY+qpZ/lKUnmEc1LeZYS3QAA==", "license": "Apache-2.0" }, + "node_modules/loupe": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/loupe/-/loupe-3.2.1.tgz", + "integrity": "sha512-CdzqowRJCeLU72bHvWqwRBBlLcMEtIvGrlvef74kMnV2AolS9Y8xUv1I0U/MNAWMhBlKIoyuEgoJ0t/bbwHbLQ==", + "dev": true, + "license": "MIT" + }, "node_modules/lru-cache": { "version": "7.18.3", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.18.3.tgz", @@ -2194,6 +3562,44 @@ "url": "https://github.com/sponsors/wellwelwel" } }, + "node_modules/magic-string": { + "version": "0.30.21", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz", + "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.5" + } + }, + "node_modules/magicast": { + "version": "0.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/make-error": { "version": "1.3.6", "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", @@ -2292,6 +3698,16 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/minipass": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", + "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, "node_modules/ms": { "version": "2.1.3", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", @@ -2330,6 +3746,25 @@ "node": ">=12.0.0" } }, + "node_modules/nanoid": { + "version": "3.3.11", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", + "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, "node_modules/natural-compare": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", @@ -2438,6 +3873,13 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "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/parent-module": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", @@ -2490,6 +3932,30 @@ "node": ">=8" } }, + "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/path-scurry/node_modules/lru-cache": { + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", + "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", + "dev": true, + "license": "ISC" + }, "node_modules/path-to-regexp": { "version": "8.2.0", "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-8.2.0.tgz", @@ -2509,6 +3975,30 @@ "node": ">=8" } }, + "node_modules/pathe": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/pathe/-/pathe-1.1.2.tgz", + "integrity": "sha512-whLdWMYL2TwI08hn8/ZqAbrVemu0LNaNNJZX73O6qaIdCTfXutsLhMkjdENX0qhsQ9uIimo4/aQOmXkoon2nDQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/pathval": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/pathval/-/pathval-2.0.1.tgz", + "integrity": "sha512-//nshmD55c46FuFw26xV/xFAaB5HF9Xdap7HJBBnrKdAd6/GxDBaNA1870O79+9ueg61cZLSVc+OaFlfmObYVQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 14.16" + } + }, + "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.1", "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", @@ -2522,6 +4012,35 @@ "url": "https://github.com/sponsors/jonschlinkert" } }, + "node_modules/postcss": { + "version": "8.5.6", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz", + "integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.11", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, "node_modules/prelude-ls": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", @@ -2659,6 +4178,51 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/rollup": { + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.55.1.tgz", + "integrity": "sha512-wDv/Ht1BNHB4upNbK74s9usvl7hObDnvVzknxqY/E/O3X6rW1U1rV1aENEfJ54eFZDTNo7zv1f5N4edCluH7+A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "1.0.8" + }, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@rollup/rollup-android-arm-eabi": "4.55.1", + "@rollup/rollup-android-arm64": "4.55.1", + "@rollup/rollup-darwin-arm64": "4.55.1", + "@rollup/rollup-darwin-x64": "4.55.1", + "@rollup/rollup-freebsd-arm64": "4.55.1", + "@rollup/rollup-freebsd-x64": "4.55.1", + "@rollup/rollup-linux-arm-gnueabihf": "4.55.1", + "@rollup/rollup-linux-arm-musleabihf": "4.55.1", + "@rollup/rollup-linux-arm64-gnu": "4.55.1", + "@rollup/rollup-linux-arm64-musl": "4.55.1", + "@rollup/rollup-linux-loong64-gnu": "4.55.1", + "@rollup/rollup-linux-loong64-musl": "4.55.1", + "@rollup/rollup-linux-ppc64-gnu": "4.55.1", + "@rollup/rollup-linux-ppc64-musl": "4.55.1", + "@rollup/rollup-linux-riscv64-gnu": "4.55.1", + "@rollup/rollup-linux-riscv64-musl": "4.55.1", + "@rollup/rollup-linux-s390x-gnu": "4.55.1", + "@rollup/rollup-linux-x64-gnu": "4.55.1", + "@rollup/rollup-linux-x64-musl": "4.55.1", + "@rollup/rollup-openbsd-x64": "4.55.1", + "@rollup/rollup-openharmony-arm64": "4.55.1", + "@rollup/rollup-win32-arm64-msvc": "4.55.1", + "@rollup/rollup-win32-ia32-msvc": "4.55.1", + "@rollup/rollup-win32-x64-gnu": "4.55.1", + "@rollup/rollup-win32-x64-msvc": "4.55.1", + "fsevents": "~2.3.2" + } + }, "node_modules/router": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/router/-/router-2.2.0.tgz", @@ -2875,10 +4439,30 @@ "side-channel-map": "^1.0.1" }, "engines": { - "node": ">= 0.4" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/siginfo": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/siginfo/-/siginfo-2.0.0.tgz", + "integrity": "sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==", + "dev": true, + "license": "ISC" + }, + "node_modules/signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=14" }, "funding": { - "url": "https://github.com/sponsors/ljharb" + "url": "https://github.com/sponsors/isaacs" } }, "node_modules/slash": { @@ -3026,6 +4610,16 @@ "node": ">= 0.6" } }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/sqlstring": { "version": "2.3.3", "resolved": "https://registry.npmjs.org/sqlstring/-/sqlstring-2.3.3.tgz", @@ -3035,6 +4629,13 @@ "node": ">= 0.6" } }, + "node_modules/stackback": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/stackback/-/stackback-0.0.2.tgz", + "integrity": "sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==", + "dev": true, + "license": "MIT" + }, "node_modules/statuses": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", @@ -3044,6 +4645,83 @@ "node": ">= 0.8" } }, + "node_modules/std-env": { + "version": "3.10.0", + "resolved": "https://registry.npmjs.org/std-env/-/std-env-3.10.0.tgz", + "integrity": "sha512-5GS12FdOZNliM5mAOxFRg7Ir0pWz8MdpYm6AY6VPkGpbA7ZzmbzNcBJQ0GPvvyWgcY7QAhCgf9Uy89I03faLkg==", + "dev": true, + "license": "MIT" + }, + "node_modules/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/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/string-width/node_modules/strip-ansi": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.2.tgz", + "integrity": "sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^6.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, "node_modules/strip-ansi": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", @@ -3057,6 +4735,20 @@ "node": ">=8" } }, + "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-json-comments": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", @@ -3083,6 +4775,42 @@ "node": ">=8" } }, + "node_modules/test-exclude": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-7.0.1.tgz", + "integrity": "sha512-pFYqmTw68LXVjeWJMST4+borgQP2AyMNbg1BpZh9LbyhUeNkeaPF9gzfPGUAnSMV3qPYdWUwDIjjCLiSDOl7vg==", + "dev": true, + "license": "ISC", + "dependencies": { + "@istanbuljs/schema": "^0.1.2", + "glob": "^10.4.1", + "minimatch": "^9.0.4" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/test-exclude/node_modules/glob": { + "version": "10.5.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.5.0.tgz", + "integrity": "sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg==", + "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/text-table": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", @@ -3090,6 +4818,50 @@ "dev": true, "license": "MIT" }, + "node_modules/tinybench": { + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/tinybench/-/tinybench-2.9.0.tgz", + "integrity": "sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==", + "dev": true, + "license": "MIT" + }, + "node_modules/tinyexec": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-0.3.2.tgz", + "integrity": "sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA==", + "dev": true, + "license": "MIT" + }, + "node_modules/tinypool": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/tinypool/-/tinypool-1.1.1.tgz", + "integrity": "sha512-Zba82s87IFq9A9XmjiX5uZA/ARWDrB03OHlq+Vw1fSdt0I+4/Kutwy8BP4Y/y/aORMo61FQ0vIb5j44vSo5Pkg==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.0.0 || >=20.0.0" + } + }, + "node_modules/tinyrainbow": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/tinyrainbow/-/tinyrainbow-1.2.0.tgz", + "integrity": "sha512-weEDEq7Z5eTHPDh4xjX789+fHfF+P8boiFB+0vbWzpbnbsEr/GRaohi/uMKxg8RZMXnl1ItAi/IUHWMsjDV7kQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/tinyspy": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/tinyspy/-/tinyspy-3.0.2.tgz", + "integrity": "sha512-n1cw8k1k0x4pgA2+9XrOkFydTerNcJ1zWCO5Nn9scWHTD+5tp8dghT2x1uduQePZTZgd3Tupf+x9BxJjeJi77Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, "node_modules/to-regex-range": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", @@ -3265,6 +5037,156 @@ "node": ">= 0.8" } }, + "node_modules/vite": { + "version": "5.4.21", + "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.21.tgz", + "integrity": "sha512-o5a9xKjbtuhY6Bi5S3+HvbRERmouabWbyUcpXXUA1u+GNUKoROi9byOJ8M0nHbHYHkYICiMlqxkg1KkYmm25Sw==", + "dev": true, + "license": "MIT", + "dependencies": { + "esbuild": "^0.21.3", + "postcss": "^8.4.43", + "rollup": "^4.20.0" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^18.0.0 || >=20.0.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^18.0.0 || >=20.0.0", + "less": "*", + "lightningcss": "^1.21.0", + "sass": "*", + "sass-embedded": "*", + "stylus": "*", + "sugarss": "*", + "terser": "^5.4.0" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + } + } + }, + "node_modules/vite-node": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/vite-node/-/vite-node-2.1.5.tgz", + "integrity": "sha512-rd0QIgx74q4S1Rd56XIiL2cYEdyWn13cunYBIuqh9mpmQr7gGS0IxXoP8R6OaZtNQQLyXSWbd4rXKYUbhFpK5w==", + "dev": true, + "license": "MIT", + "dependencies": { + "cac": "^6.7.14", + "debug": "^4.3.7", + "es-module-lexer": "^1.5.4", + "pathe": "^1.1.2", + "vite": "^5.0.0" + }, + "bin": { + "vite-node": "vite-node.mjs" + }, + "engines": { + "node": "^18.0.0 || >=20.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/vitest": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/vitest/-/vitest-2.1.5.tgz", + "integrity": "sha512-P4ljsdpuzRTPI/kbND2sDZ4VmieerR2c9szEZpjc+98Z9ebvnXmM5+0tHEKqYZumXqlvnmfWsjeFOjXVriDG7A==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@vitest/expect": "2.1.5", + "@vitest/mocker": "2.1.5", + "@vitest/pretty-format": "^2.1.5", + "@vitest/runner": "2.1.5", + "@vitest/snapshot": "2.1.5", + "@vitest/spy": "2.1.5", + "@vitest/utils": "2.1.5", + "chai": "^5.1.2", + "debug": "^4.3.7", + "expect-type": "^1.1.0", + "magic-string": "^0.30.12", + "pathe": "^1.1.2", + "std-env": "^3.8.0", + "tinybench": "^2.9.0", + "tinyexec": "^0.3.1", + "tinypool": "^1.0.1", + "tinyrainbow": "^1.2.0", + "vite": "^5.0.0", + "vite-node": "2.1.5", + "why-is-node-running": "^2.3.0" + }, + "bin": { + "vitest": "vitest.mjs" + }, + "engines": { + "node": "^18.0.0 || >=20.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "@edge-runtime/vm": "*", + "@types/node": "^18.0.0 || >=20.0.0", + "@vitest/browser": "2.1.5", + "@vitest/ui": "2.1.5", + "happy-dom": "*", + "jsdom": "*" + }, + "peerDependenciesMeta": { + "@edge-runtime/vm": { + "optional": true + }, + "@types/node": { + "optional": true + }, + "@vitest/browser": { + "optional": true + }, + "@vitest/ui": { + "optional": true + }, + "happy-dom": { + "optional": true + }, + "jsdom": { + "optional": true + } + } + }, "node_modules/which": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", @@ -3281,6 +5203,23 @@ "node": ">= 8" } }, + "node_modules/why-is-node-running": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/why-is-node-running/-/why-is-node-running-2.3.0.tgz", + "integrity": "sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==", + "dev": true, + "license": "MIT", + "dependencies": { + "siginfo": "^2.0.0", + "stackback": "0.0.2" + }, + "bin": { + "why-is-node-running": "cli.js" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/word-wrap": { "version": "1.2.5", "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", @@ -3291,6 +5230,107 @@ "node": ">=0.10.0" } }, + "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/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/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/wrap-ansi/node_modules/strip-ansi": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.2.tgz", + "integrity": "sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^6.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, "node_modules/wrappy": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", diff --git a/package.json b/package.json index c0ba788..98d0897 100644 --- a/package.json +++ b/package.json @@ -8,7 +8,8 @@ "dev": "ts-node src/server.ts", "build": "tsc", "watch": "tsc --watch", - "lint": "eslint \"src/**/*.ts\"" + "lint": "eslint \"src/**/*.ts\"", + "test": "vitest run --coverage" }, "keywords": [], "author": "", @@ -18,9 +19,11 @@ "@types/node": "^22.14.1", "@typescript-eslint/eslint-plugin": "^7.11.0", "@typescript-eslint/parser": "^7.11.0", + "@vitest/coverage-v8": "^2.1.5", "eslint": "^8.57.0", "ts-node": "^10.9.2", - "typescript": "^5.8.3" + "typescript": "^5.8.3", + "vitest": "^2.1.5" }, "dependencies": { "@types/express": "^5.0.1", diff --git a/src/models/tools/FileDownloader.ts b/src/models/tools/FileDownloader.ts index d78d9bc..6c1c917 100644 --- a/src/models/tools/FileDownloader.ts +++ b/src/models/tools/FileDownloader.ts @@ -1,9 +1,5 @@ import fs from 'fs'; import axios from 'axios'; -import { promisify } from 'util'; - -const existDir = promisify(fs.exists); -const createDir = promisify(fs.mkdir); class FileDownloader { /** @@ -17,10 +13,10 @@ class FileDownloader { async downloadFile(url:string, path:string, name:string) { const response = await axios.get(url, { responseType: 'stream' }); - const dir = await existDir(path); + const dir = fs.existsSync(path); if (!dir) { - await createDir(path); + await fs.promises.mkdir(path, { recursive: true }); } response.data.pipe(fs.createWriteStream(`${path}/${name}`)); @@ -37,4 +33,4 @@ class FileDownloader { } } -export default FileDownloader; \ No newline at end of file +export default FileDownloader; diff --git a/tests/configuration/AppConfiguration.test.ts b/tests/configuration/AppConfiguration.test.ts new file mode 100644 index 0000000..c93d4e0 --- /dev/null +++ b/tests/configuration/AppConfiguration.test.ts @@ -0,0 +1,53 @@ +import { describe, it, expect, beforeEach, afterEach, vi } from "vitest"; + +const ORIGINAL_ENV = { ...process.env }; + +const loadConfig = async (env: Record) => { + vi.resetModules(); + process.env = { ...ORIGINAL_ENV, ...env }; + const config = (await import("../../src/models/configuration/AppConfiguration")).default; + return config; +}; + +describe("AppConfiguration", () => { + beforeEach(() => { + vi.resetModules(); + }); + + afterEach(() => { + process.env = { ...ORIGINAL_ENV }; + }); + + it("uses environment overrides", async () => { + const config = await loadConfig({ + ACCESS_USERNAME: "user", + ACCESS_PASSWORD: "pass", + REALM_NAME: "Realm", + PUBLIC_IP_ADDRESS: "1.2.3.4", + }); + + expect(config.getAccessUsername()).toBe("user"); + expect(config.getAccessPassword()).toBe("pass"); + expect(config.getRealmName()).toBe("Realm"); + expect(config.getPublicIpAddress()).toBe("1.2.3.4"); + }); + + it("uses defaults when env is missing", async () => { + const config = await loadConfig({ + ACCESS_USERNAME: undefined, + ACCESS_PASSWORD: undefined, + REALM_NAME: undefined, + PUBLIC_IP_ADDRESS: undefined, + }); + + expect(config.getAccessUsername()).toBeNull(); + expect(config.getAccessPassword()).toBeNull(); + expect(config.getRealmName()).toBe("TrinityCore"); + expect(config.getPublicIpAddress()).toBe("127.0.0.1"); + + const dbConfig = config.getDatabaseConfiguration(); + expect(dbConfig.root.user).toBe("root"); + expect(dbConfig.auth.database).toBe("auth"); + expect(dbConfig.world.database).toBe("world"); + }); +}); diff --git a/tests/models/database/Database.test.ts b/tests/models/database/Database.test.ts new file mode 100644 index 0000000..06ede81 --- /dev/null +++ b/tests/models/database/Database.test.ts @@ -0,0 +1,161 @@ +import { describe, it, expect, vi, beforeEach } from "vitest"; +import type DatabaseConfiguration from "../../../src/models/types/DatabaseConfiguration"; +import Database from "../../../src/models/database/Database"; + +const createConnectionMock = vi.fn(); + +vi.mock("mysql2/promise", () => ({ + default: { + createConnection: (...args: unknown[]) => createConnectionMock(...args), + }, +})); + +const baseConfig: DatabaseConfiguration = { + root: { + host: "db", + port: "3306", + user: "root", + password: "rootpw", + }, + auth: { + host: "db", + port: "3306", + user: "user", + password: "pass", + database: "auth", + }, + world: { + host: null, + port: null, + user: null, + password: null, + database: null, + }, + characters: { + host: "db", + port: "3306", + user: "user", + password: "pass", + database: "characters", + }, + hotfixes: { + host: "db", + port: "3306", + user: "user", + password: "pass", + database: "hotfixes", + }, +}; + +describe("Database", () => { + beforeEach(() => { + createConnectionMock.mockReset(); + }); + + it("connects successfully and uses normalized options", async () => { + const end = vi.fn(); + createConnectionMock.mockResolvedValue({ end }); + + const database = new Database(baseConfig); + const result = await database.checkConnection("root", 1, 0); + + expect(result).toBe(true); + expect(createConnectionMock).toHaveBeenCalledTimes(1); + expect(createConnectionMock).toHaveBeenCalledWith({ + host: "db", + port: 3306, + user: "root", + password: "rootpw", + database: undefined, + }); + expect(end).toHaveBeenCalled(); + }); + + it("retries and returns false after failures", async () => { + createConnectionMock.mockRejectedValue(new Error("fail")); + const database = new Database(baseConfig); + const attemptFailed = vi.fn(); + + vi.useFakeTimers(); + const promise = database.checkConnection("root", 2, 0, attemptFailed); + await vi.runAllTimersAsync(); + const result = await promise; + vi.useRealTimers(); + + expect(result).toBe(false); + expect(attemptFailed).toHaveBeenCalledTimes(2); + }); + + it("checks initialization status", async () => { + const execute = vi.fn().mockResolvedValue([[{ SCHEMA_NAME: "auth" }], []]); + const end = vi.fn(); + createConnectionMock.mockResolvedValue({ execute, end }); + + const database = new Database(baseConfig); + const result = await database.isInitialized(); + + expect(result).toBe(true); + expect(execute).toHaveBeenCalled(); + expect(end).toHaveBeenCalled(); + }); + + it("checks whether database contains data", async () => { + const execute = vi.fn().mockResolvedValue([[{ count: 2 }], []]); + const end = vi.fn(); + createConnectionMock.mockResolvedValue({ execute, end }); + + const database = new Database(baseConfig); + const result = await database.containsData(); + + expect(result).toBe(true); + expect(execute).toHaveBeenCalled(); + expect(end).toHaveBeenCalled(); + }); + + it("executes queries with params", async () => { + const execute = vi.fn().mockResolvedValue([[{ ok: true }], []]); + const end = vi.fn(); + createConnectionMock.mockResolvedValue({ execute, end }); + + const database = new Database(baseConfig); + const result = await database.execute("auth", "SELECT 1", [1]); + + expect(result).toEqual([{ ok: true }]); + expect(execute).toHaveBeenCalledWith("SELECT 1", [1]); + expect(end).toHaveBeenCalled(); + }); + + it("normalizes optional fields for database connections", async () => { + const end = vi.fn(); + createConnectionMock.mockResolvedValue({ end }); + + const database = new Database(baseConfig); + await database.checkConnection("auth", 1, 0); + + expect(createConnectionMock).toHaveBeenCalledWith({ + host: "db", + port: 3306, + user: "user", + password: "pass", + database: "auth", + }); + }); + + it("passes undefined params to execute when no params are provided", async () => { + const execute = vi.fn().mockResolvedValue([[{ ok: true }], []]); + const end = vi.fn(); + createConnectionMock.mockResolvedValue({ execute, end }); + + const database = new Database(baseConfig); + await database.execute("world", "SELECT 1", null); + + expect(createConnectionMock).toHaveBeenCalledWith({ + host: undefined, + port: undefined, + user: undefined, + password: undefined, + database: undefined, + }); + expect(execute).toHaveBeenCalledWith("SELECT 1", undefined); + }); +}); diff --git a/tests/models/initializer/AppInitializer.test.ts b/tests/models/initializer/AppInitializer.test.ts new file mode 100644 index 0000000..d83b0f6 --- /dev/null +++ b/tests/models/initializer/AppInitializer.test.ts @@ -0,0 +1,63 @@ +import { describe, it, expect, vi } from "vitest"; +import AppInitializer from "../../../src/models/initializer/AppInitializer"; + +const createInitializer = (overrides?: Partial Promise>>) => { + return { + checkDatabaseConnection: vi.fn().mockResolvedValue(true), + checkDatabasesStructure: vi.fn().mockResolvedValue(true), + checkDatabasesInitialData: vi.fn().mockResolvedValue(true), + updateAuthServerConfiguration: vi.fn().mockResolvedValue(true), + updateWorldServerConfiguration: vi.fn().mockResolvedValue(true), + updateApplicationDatabase: vi.fn().mockResolvedValue(true), + updateRealmInformations: vi.fn().mockResolvedValue(true), + checkClientMapData: vi.fn().mockResolvedValue(true), + ...overrides, + }; +}; + +describe("AppInitializer", () => { + it("returns false on the first failing step", async () => { + const initializer = createInitializer({ + checkDatabasesStructure: vi.fn().mockResolvedValue(false), + }); + const appInitializer = new AppInitializer(initializer); + + const result = await appInitializer.initialize(); + + expect(result).toBe(false); + expect(initializer.checkDatabaseConnection).toHaveBeenCalled(); + expect(initializer.checkDatabasesStructure).toHaveBeenCalled(); + expect(initializer.checkDatabasesInitialData).not.toHaveBeenCalled(); + }); + + it.each([ + ["checkDatabaseConnection"], + ["checkDatabasesStructure"], + ["checkDatabasesInitialData"], + ["updateAuthServerConfiguration"], + ["updateWorldServerConfiguration"], + ["updateApplicationDatabase"], + ["updateRealmInformations"], + ["checkClientMapData"], + ])("returns false when %s fails", async (method) => { + const overrides: Partial Promise>> = { + [method]: vi.fn().mockResolvedValue(false), + }; + const initializer = createInitializer(overrides); + const appInitializer = new AppInitializer(initializer); + + const result = await appInitializer.initialize(); + + expect(result).toBe(false); + }); + + it("returns true when all steps succeed", async () => { + const initializer = createInitializer(); + const appInitializer = new AppInitializer(initializer); + + const result = await appInitializer.initialize(); + + expect(result).toBe(true); + expect(initializer.checkClientMapData).toHaveBeenCalled(); + }); +}); diff --git a/tests/models/initializer/ConfigurationWriter.test.ts b/tests/models/initializer/ConfigurationWriter.test.ts new file mode 100644 index 0000000..10f0b0f --- /dev/null +++ b/tests/models/initializer/ConfigurationWriter.test.ts @@ -0,0 +1,93 @@ +import { describe, it, expect, vi, beforeEach, afterEach } from "vitest"; +import fs from "fs"; +import ConfigurationWriter from "../../../src/models/initializer/ConfigurationWriter"; + +const makeProfile = () => ({ + getSourceAuthConfigurationPath: () => "auth.conf.dist", + getSourceWorldConfigurationPath: () => "world.conf.dist", + getAuthServerConfigurationPath: () => "/tmp/authserver.conf", + getWorldServerConfigurationPath: () => "/tmp/worldserver.conf", +}); + +describe("ConfigurationWriter", () => { + const readFileSync = vi.fn(); + const writeFileSync = vi.fn(); + const existsSync = vi.fn(); + const mkdirSync = vi.fn(); + + beforeEach(() => { + readFileSync.mockReset(); + writeFileSync.mockReset(); + existsSync.mockReset(); + mkdirSync.mockReset(); + }); + + afterEach(() => { + vi.restoreAllMocks(); + }); + + it("writes the auth configuration with replacements", () => { + readFileSync.mockReturnValue("host= user="); + existsSync.mockReturnValue(false); + vi.spyOn(console, "error").mockImplementation(() => undefined); + vi.spyOn(fs, "readFileSync").mockImplementation(readFileSync); + vi.spyOn(fs, "writeFileSync").mockImplementation(writeFileSync); + vi.spyOn(fs, "existsSync").mockImplementation(existsSync); + vi.spyOn(fs, "mkdirSync").mockImplementation(mkdirSync); + + const writer = new ConfigurationWriter(makeProfile() as never); + const result = writer.writeAuthServerConfiguration(); + + expect(result).toBe(true); + expect(writeFileSync).toHaveBeenCalled(); + const content = writeFileSync.mock.calls[0][1] as string; + expect(content).not.toContain(""); + }); + + it("writes the world configuration without creating an existing directory", () => { + readFileSync.mockReturnValue("port="); + existsSync.mockReturnValue(true); + vi.spyOn(console, "error").mockImplementation(() => undefined); + + vi.spyOn(fs, "readFileSync").mockImplementation(readFileSync); + vi.spyOn(fs, "writeFileSync").mockImplementation(writeFileSync); + vi.spyOn(fs, "existsSync").mockImplementation(existsSync); + + const writer = new ConfigurationWriter(makeProfile() as never); + const result = writer.writeWorldServerConfiguration(); + + expect(result).toBe(true); + expect(mkdirSync).not.toHaveBeenCalled(); + }); + + it("returns false on processing errors", () => { + readFileSync.mockImplementation(() => { + throw new Error("read error"); + }); + vi.spyOn(console, "error").mockImplementation(() => undefined); + + vi.spyOn(fs, "readFileSync").mockImplementation(readFileSync); + + const writer = new ConfigurationWriter(makeProfile() as never); + const result = writer.writeWorldServerConfiguration(); + + expect(result).toBe(false); + }); + + it("replaces null values with empty strings", () => { + readFileSync.mockReturnValue("value="); + existsSync.mockReturnValue(true); + + vi.spyOn(fs, "readFileSync").mockImplementation(readFileSync); + vi.spyOn(fs, "writeFileSync").mockImplementation(writeFileSync); + vi.spyOn(fs, "existsSync").mockImplementation(existsSync); + + const writer = new ConfigurationWriter(makeProfile() as never); + const result = (writer as never).processConfiguration("/in", "/out/file.conf", { TEST: null }); + + expect(result).toBe(true); + expect(writeFileSync).toHaveBeenCalled(); + const content = writeFileSync.mock.calls[0][1] as string; + expect(content).toBe("value="); + }); +}); diff --git a/tests/models/initializer/DatabaseInitializer.test.ts b/tests/models/initializer/DatabaseInitializer.test.ts new file mode 100644 index 0000000..e16d6e2 --- /dev/null +++ b/tests/models/initializer/DatabaseInitializer.test.ts @@ -0,0 +1,287 @@ +import { describe, it, expect, vi, beforeEach } from "vitest"; +import { EventEmitter } from "events"; +import type GithubRelease from "../../../src/models/types/GithubRelease"; +import DatabaseInitializer from "../../../src/models/initializer/DatabaseInitializer"; + +const readFileSyncMock = vi.fn(); +const unlinkSyncMock = vi.fn(); + +vi.mock("fs", () => ({ + default: { + readFileSync: (...args: unknown[]) => readFileSyncMock(...args), + unlinkSync: (...args: unknown[]) => unlinkSyncMock(...args), + }, + readFileSync: (...args: unknown[]) => readFileSyncMock(...args), + unlinkSync: (...args: unknown[]) => unlinkSyncMock(...args), +})); + +const downloadFileMock = vi.fn(); +vi.mock("../../../src/models/tools/FileDownloader", () => ({ + default: class { + downloadFile = downloadFileMock; + }, +})); + +const executeMock = vi.fn(); +vi.mock("../../../src/models/tools/CommandExecuter", () => ({ + default: class { + execute = executeMock; + }, +})); + +const configMock = vi.hoisted(() => ({ + getDatabaseUser: vi.fn(), + getDatabasePassword: vi.fn(), + getRealmName: vi.fn(), + getPublicIpAddress: vi.fn(), +})); + +vi.mock("../../../src/models/configuration/AppConfiguration", () => ({ + default: configMock, +})); + +let requestMock: (...args: unknown[]) => any; +vi.mock("https", () => ({ + default: { + request: (...args: unknown[]) => requestMock(...args), + }, +})); + +const makeProfile = () => ({ + getInitializeDatabasePath: () => "init.sql", + getInitialDatabaseNamePattern: () => "TDB_full", + getExtractScriptPath: () => "extract.sh", +}); + +describe("DatabaseInitializer", () => { + beforeEach(() => { + readFileSyncMock.mockReset(); + unlinkSyncMock.mockReset(); + downloadFileMock.mockReset(); + executeMock.mockReset(); + configMock.getDatabaseUser.mockReturnValue("dbuser"); + configMock.getDatabasePassword.mockReturnValue("dbpass"); + configMock.getRealmName.mockReturnValue("Realm"); + configMock.getPublicIpAddress.mockReturnValue("1.2.3.4"); + }); + + it("initializes the database using SQL file", async () => { + readFileSyncMock.mockReturnValue("CREATE ;INSERT ;"); + const database = { execute: vi.fn().mockResolvedValue(true) }; + const initializer = new DatabaseInitializer(database as never, makeProfile() as never); + + const result = await initializer.initialize(); + + expect(result).toBe(true); + expect(database.execute).toHaveBeenCalledTimes(2); + }); + + it("returns false when initialization fails", async () => { + readFileSyncMock.mockImplementation(() => { + throw new Error("read error"); + }); + const database = { execute: vi.fn() }; + const initializer = new DatabaseInitializer(database as never, makeProfile() as never); + + const result = await initializer.initialize(); + + expect(result).toBe(false); + }); + + it("replaces null placeholders with empty strings", async () => { + readFileSyncMock.mockReturnValue("CREATE ;;"); + configMock.getDatabaseUser.mockReturnValue(null); + configMock.getDatabasePassword.mockReturnValue(null); + const database = { execute: vi.fn().mockResolvedValue(true) }; + const initializer = new DatabaseInitializer(database as never, makeProfile() as never); + + const result = await initializer.initialize(); + + expect(result).toBe(true); + expect(database.execute).toHaveBeenCalledWith("root", "CREATE "); + }); + + it("downloads and extracts initial data", async () => { + const releases: GithubRelease[] = [ + { + tag_name: "v1", + name: "release", + body: "", + html_url: "", + published_at: "", + assets: [ + { name: "TDB_full_world_335.7z", browser_download_url: "http://file", size: 1, content_type: "application/zip" }, + ], + }, + ]; + const database = { execute: vi.fn() }; + const initializer = new DatabaseInitializer(database as never, makeProfile() as never); + vi.spyOn(initializer as never, "fetchTrinityCoreReleases").mockResolvedValue(releases); + + downloadFileMock.mockResolvedValue(undefined); + executeMock.mockResolvedValue(true); + + const result = await initializer.downloadInitialData(); + + expect(result).toBe(true); + expect(downloadFileMock).toHaveBeenCalled(); + expect(unlinkSyncMock).toHaveBeenCalled(); + }); + + it("returns false when no matching release is found", async () => { + const database = { execute: vi.fn() }; + const initializer = new DatabaseInitializer(database as never, makeProfile() as never); + vi.spyOn(initializer as never, "fetchTrinityCoreReleases").mockResolvedValue([]); + + const result = await initializer.downloadInitialData(); + + expect(result).toBe(false); + }); + + it("returns false when extraction fails", async () => { + const releases: GithubRelease[] = [ + { + tag_name: "v1", + name: "release", + body: "", + html_url: "", + published_at: "", + assets: [ + { name: "TDB_full_world_335.7z", browser_download_url: "http://file", size: 1, content_type: "application/zip" }, + ], + }, + ]; + const database = { execute: vi.fn() }; + const initializer = new DatabaseInitializer(database as never, makeProfile() as never); + vi.spyOn(initializer as never, "fetchTrinityCoreReleases").mockResolvedValue(releases); + + downloadFileMock.mockResolvedValue(undefined); + executeMock.mockResolvedValue(false); + + const result = await initializer.downloadInitialData(); + + expect(result).toBe(false); + }); + + it("returns false when release fetch fails", async () => { + const database = { execute: vi.fn() }; + const initializer = new DatabaseInitializer(database as never, makeProfile() as never); + vi.spyOn(initializer as never, "fetchTrinityCoreReleases").mockRejectedValue(new Error("fail")); + + const result = await initializer.downloadInitialData(); + + expect(result).toBe(false); + }); + + it("updates the application database", async () => { + executeMock.mockResolvedValue(true); + const database = { execute: vi.fn() }; + const initializer = new DatabaseInitializer(database as never, makeProfile() as never); + + const result = await initializer.updateApplicationDatabase(); + + expect(result).toBe(true); + expect(executeMock).toHaveBeenCalled(); + }); + + it("returns false when the application database update fails", async () => { + executeMock.mockResolvedValue(false); + const database = { execute: vi.fn() }; + const initializer = new DatabaseInitializer(database as never, makeProfile() as never); + + const result = await initializer.updateApplicationDatabase(); + + expect(result).toBe(false); + }); + + it("updates realm information and handles errors", async () => { + const database = { execute: vi.fn().mockResolvedValue(true) }; + const initializer = new DatabaseInitializer(database as never, makeProfile() as never); + + const ok = await initializer.updateRealmInformations(); + expect(ok).toBe(true); + + configMock.getRealmName.mockReturnValue(null); + configMock.getPublicIpAddress.mockReturnValue(null); + const defaultDatabase = { execute: vi.fn().mockResolvedValue(true) }; + const defaultInitializer = new DatabaseInitializer(defaultDatabase as never, makeProfile() as never); + await defaultInitializer.updateRealmInformations(); + expect(defaultDatabase.execute).toHaveBeenCalledWith("auth", expect.any(String), [ + "TrinityCore", + "127.0.0.1", + 1, + ]); + + const errorDatabase = { execute: vi.fn().mockRejectedValue(new Error("fail")) }; + const errorInitializer = new DatabaseInitializer(errorDatabase as never, makeProfile() as never); + const errorSpy = vi.spyOn(console, "error").mockImplementation(() => undefined); + + const fail = await errorInitializer.updateRealmInformations(); + expect(fail).toBe(false); + expect(errorSpy).toHaveBeenCalled(); + }); + + it("fetches releases from GitHub", async () => { + requestMock = (options: unknown, callback: (res: EventEmitter) => void) => { + const res = new EventEmitter(); + callback(res); + const req = new EventEmitter() as EventEmitter & { end: () => void; on: (event: string, handler: () => void) => void }; + req.on = req.on.bind(req); + req.end = () => { + res.emit("data", JSON.stringify([{ tag_name: "v1" }])); + res.emit("end"); + }; + return req; + }; + + const database = { execute: vi.fn() }; + const initializer = new DatabaseInitializer(database as never, makeProfile() as never); + const result = await (initializer as never).fetchTrinityCoreReleases(); + + expect(result[0].tag_name).toBe("v1"); + }); + + it("rejects when GitHub response is invalid JSON", async () => { + requestMock = (options: unknown, callback: (res: EventEmitter) => void) => { + const res = new EventEmitter(); + callback(res); + const req = new EventEmitter() as EventEmitter & { end: () => void; on: (event: string, handler: () => void) => void }; + req.on = req.on.bind(req); + req.end = () => { + res.emit("data", "not-json"); + res.emit("end"); + }; + return req; + }; + + const database = { execute: vi.fn() }; + const initializer = new DatabaseInitializer(database as never, makeProfile() as never); + + await expect((initializer as never).fetchTrinityCoreReleases()).rejects.toThrow("Erreur lors du parsing JSON"); + }); + + it("rejects when request fails", async () => { + requestMock = (options: unknown, callback: (res: EventEmitter) => void) => { + const res = new EventEmitter(); + callback(res); + const req = new EventEmitter() as EventEmitter & { + end: () => void; + on: (event: string, handler: (error: Error) => void) => void; + }; + const handlers: Record void> = {}; + req.on = (event: string, handler: (error: Error) => void) => { + handlers[event] = handler; + return req; + }; + req.end = () => { + handlers.error?.(new Error("fail")); + }; + return req; + }; + + const database = { execute: vi.fn() }; + const initializer = new DatabaseInitializer(database as never, makeProfile() as never); + + await expect((initializer as never).fetchTrinityCoreReleases()).rejects.toThrow("Erreur lors de la requ"); + }); +}); diff --git a/tests/models/initializer/InitializerRegular.test.ts b/tests/models/initializer/InitializerRegular.test.ts new file mode 100644 index 0000000..c0df19c --- /dev/null +++ b/tests/models/initializer/InitializerRegular.test.ts @@ -0,0 +1,279 @@ +import { describe, it, expect, vi, beforeEach } from "vitest"; +import InitializerRegular from "../../../src/models/initializer/InitializerRegular"; + +const databaseMock = { + checkConnection: vi.fn(), + isInitialized: vi.fn(), + containsData: vi.fn(), +}; + +const databaseInitializerMock = { + initialize: vi.fn(), + downloadInitialData: vi.fn(), + updateApplicationDatabase: vi.fn(), + updateRealmInformations: vi.fn(), +}; + +const configurationWriterMock = { + writeAuthServerConfiguration: vi.fn(), + writeWorldServerConfiguration: vi.fn(), +}; + +const mapInitializerMock = { + isClientMapInitialized: vi.fn(), + initialize: vi.fn(), +}; + +vi.mock("../../../src/models/tools/ConsoleHelper", () => ({ + default: { + beginBox: vi.fn(), + endBox: vi.fn(), + writeBoxLine: vi.fn(), + }, +})); + +vi.mock("../../../src/models/database/Database", () => ({ + default: class { + constructor() { + Object.assign(this, databaseMock); + } + }, +})); + +vi.mock("../../../src/models/initializer/DatabaseInitializer", () => ({ + default: class { + constructor() { + Object.assign(this, databaseInitializerMock); + } + }, +})); + +vi.mock("../../../src/models/initializer/ConfigurationWriter", () => ({ + default: class { + constructor() { + Object.assign(this, configurationWriterMock); + } + }, +})); + +vi.mock("../../../src/models/initializer/MapInitializer", () => ({ + default: class { + constructor() { + Object.assign(this, mapInitializerMock); + } + }, +})); + +describe("InitializerRegular", () => { + const profile = {} as never; + + beforeEach(() => { + databaseMock.checkConnection.mockReset(); + databaseMock.isInitialized.mockReset(); + databaseMock.containsData.mockReset(); + databaseInitializerMock.initialize.mockReset(); + databaseInitializerMock.downloadInitialData.mockReset(); + databaseInitializerMock.updateApplicationDatabase.mockReset(); + databaseInitializerMock.updateRealmInformations.mockReset(); + configurationWriterMock.writeAuthServerConfiguration.mockReset(); + configurationWriterMock.writeWorldServerConfiguration.mockReset(); + mapInitializerMock.isClientMapInitialized.mockReset(); + mapInitializerMock.initialize.mockReset(); + }); + + it("checks database connection", async () => { + databaseMock.checkConnection.mockResolvedValue(true); + const initializer = new InitializerRegular(profile); + + const result = await initializer.checkDatabaseConnection(); + + expect(result).toBe(true); + expect(databaseMock.checkConnection).toHaveBeenCalled(); + }); + + it("reports failed connection attempts", async () => { + databaseMock.checkConnection.mockImplementation((_db, _attempts, _delay, onFail) => { + onFail?.(1, 2); + return Promise.resolve(true); + }); + const initializer = new InitializerRegular(profile); + + const result = await initializer.checkDatabaseConnection(); + + expect(result).toBe(true); + }); + + it("returns false when database connection fails", async () => { + databaseMock.checkConnection.mockResolvedValue(false); + const initializer = new InitializerRegular(profile); + + const result = await initializer.checkDatabaseConnection(); + + expect(result).toBe(false); + }); + + it("initializes database structure when needed", async () => { + databaseMock.isInitialized.mockResolvedValue(false); + databaseInitializerMock.initialize.mockResolvedValue(true); + const initializer = new InitializerRegular(profile); + + const result = await initializer.checkDatabasesStructure(); + + expect(result).toBe(true); + expect(databaseInitializerMock.initialize).toHaveBeenCalled(); + }); + + it("returns false when structure initialization fails", async () => { + databaseMock.isInitialized.mockResolvedValue(false); + databaseInitializerMock.initialize.mockResolvedValue(false); + const initializer = new InitializerRegular(profile); + + const result = await initializer.checkDatabasesStructure(); + + expect(result).toBe(false); + }); + + it("returns true when databases are already initialized", async () => { + databaseMock.isInitialized.mockResolvedValue(true); + const initializer = new InitializerRegular(profile); + + const result = await initializer.checkDatabasesStructure(); + + expect(result).toBe(true); + expect(databaseInitializerMock.initialize).not.toHaveBeenCalled(); + }); + + it("checks initial data and downloads when empty", async () => { + databaseMock.containsData.mockResolvedValue(false); + databaseInitializerMock.downloadInitialData.mockResolvedValue(true); + const initializer = new InitializerRegular(profile); + + const result = await initializer.checkDatabasesInitialData(); + + expect(result).toBe(true); + expect(databaseInitializerMock.downloadInitialData).toHaveBeenCalled(); + }); + + it("returns false when initial data download fails", async () => { + databaseMock.containsData.mockResolvedValue(false); + databaseInitializerMock.downloadInitialData.mockResolvedValue(false); + const initializer = new InitializerRegular(profile); + + const result = await initializer.checkDatabasesInitialData(); + + expect(result).toBe(false); + }); + + it("returns true when databases already contain data", async () => { + databaseMock.containsData.mockResolvedValue(true); + const initializer = new InitializerRegular(profile); + + const result = await initializer.checkDatabasesInitialData(); + + expect(result).toBe(true); + expect(databaseInitializerMock.downloadInitialData).not.toHaveBeenCalled(); + }); + + it("updates auth configuration", async () => { + configurationWriterMock.writeAuthServerConfiguration.mockReturnValue(true); + const initializer = new InitializerRegular(profile); + + const result = await initializer.updateAuthServerConfiguration(); + + expect(result).toBe(true); + }); + + it("returns false when auth configuration fails", async () => { + configurationWriterMock.writeAuthServerConfiguration.mockReturnValue(false); + const initializer = new InitializerRegular(profile); + + const result = await initializer.updateAuthServerConfiguration(); + + expect(result).toBe(false); + }); + + it("updates world configuration", async () => { + configurationWriterMock.writeWorldServerConfiguration.mockReturnValue(true); + const initializer = new InitializerRegular(profile); + + const result = await initializer.updateWorldServerConfiguration(); + + expect(result).toBe(true); + }); + + it("returns false when world configuration fails", async () => { + configurationWriterMock.writeWorldServerConfiguration.mockReturnValue(false); + const initializer = new InitializerRegular(profile); + + const result = await initializer.updateWorldServerConfiguration(); + + expect(result).toBe(false); + }); + + it("updates application database", async () => { + databaseInitializerMock.updateApplicationDatabase.mockResolvedValue(true); + const initializer = new InitializerRegular(profile); + + const result = await initializer.updateApplicationDatabase(); + + expect(result).toBe(true); + }); + + it("returns false when application database update fails", async () => { + databaseInitializerMock.updateApplicationDatabase.mockResolvedValue(false); + const initializer = new InitializerRegular(profile); + + const result = await initializer.updateApplicationDatabase(); + + expect(result).toBe(false); + }); + + it("updates realm informations", async () => { + databaseInitializerMock.updateRealmInformations.mockResolvedValue(true); + const initializer = new InitializerRegular(profile); + + const result = await initializer.updateRealmInformations(); + + expect(result).toBe(true); + }); + + it("returns false when realm informations update fails", async () => { + databaseInitializerMock.updateRealmInformations.mockResolvedValue(false); + const initializer = new InitializerRegular(profile); + + const result = await initializer.updateRealmInformations(); + + expect(result).toBe(false); + }); + + it("initializes map data when missing", async () => { + mapInitializerMock.isClientMapInitialized.mockReturnValue(false); + mapInitializerMock.initialize.mockResolvedValue(true); + const initializer = new InitializerRegular(profile); + + const result = await initializer.checkClientMapData(); + + expect(result).toBe(true); + expect(mapInitializerMock.initialize).toHaveBeenCalled(); + }); + + it("returns false when map initialization fails", async () => { + mapInitializerMock.isClientMapInitialized.mockReturnValue(false); + mapInitializerMock.initialize.mockResolvedValue(false); + const initializer = new InitializerRegular(profile); + + const result = await initializer.checkClientMapData(); + + expect(result).toBe(false); + }); + + it("skips map initialization when data is present", async () => { + mapInitializerMock.isClientMapInitialized.mockReturnValue(true); + const initializer = new InitializerRegular(profile); + + const result = await initializer.checkClientMapData(); + + expect(result).toBe(true); + expect(mapInitializerMock.initialize).not.toHaveBeenCalled(); + }); +}); diff --git a/tests/models/initializer/MapInitializer.test.ts b/tests/models/initializer/MapInitializer.test.ts new file mode 100644 index 0000000..add7453 --- /dev/null +++ b/tests/models/initializer/MapInitializer.test.ts @@ -0,0 +1,50 @@ +import { describe, it, expect, vi, beforeEach } from "vitest"; +import MapInitializer from "../../../src/models/initializer/MapInitializer"; + +const existsSyncMock = vi.fn(); +vi.mock("fs", () => ({ + default: { + existsSync: (...args: unknown[]) => existsSyncMock(...args), + }, + existsSync: (...args: unknown[]) => existsSyncMock(...args), +})); + +const executeMock = vi.fn(); +vi.mock("../../../src/models/tools/CommandExecuter", () => ({ + default: class { + execute = executeMock; + }, +})); + +describe("MapInitializer", () => { + beforeEach(() => { + existsSyncMock.mockReset(); + executeMock.mockReset(); + }); + + it("detects existing client map data", () => { + existsSyncMock.mockReturnValue(true); + const initializer = new MapInitializer({ getExtractScriptPath: () => "extract.sh" } as never); + + expect(initializer.isClientMapInitialized()).toBe(true); + }); + + it("runs extraction when initializing", async () => { + executeMock.mockResolvedValue(true); + const initializer = new MapInitializer({ getExtractScriptPath: () => "extract.sh" } as never); + + const result = await initializer.initialize(); + + expect(result).toBe(true); + expect(executeMock).toHaveBeenCalled(); + }); + + it("returns false when extraction fails", async () => { + executeMock.mockResolvedValue(false); + const initializer = new MapInitializer({ getExtractScriptPath: () => "extract.sh" } as never); + + const result = await initializer.initialize(); + + expect(result).toBe(false); + }); +}); diff --git a/tests/models/profiles/ProfileLoader.test.ts b/tests/models/profiles/ProfileLoader.test.ts new file mode 100644 index 0000000..da6d633 --- /dev/null +++ b/tests/models/profiles/ProfileLoader.test.ts @@ -0,0 +1,43 @@ +import { describe, it, expect, vi, afterEach } from "vitest"; + +const ORIGINAL_ENV = { ...process.env }; + +describe("ProfileLoader", () => { + afterEach(() => { + process.env = { ...ORIGINAL_ENV }; + vi.resetModules(); + }); + + it("loads profile 3.3.5", async () => { + process.env.TRINITYCORE_VERSION = "3.3.5"; + const ProfileLoader = (await import("../../../src/models/profiles/ProfileLoader")).default; + const profile = ProfileLoader.loadProfile(); + expect(profile.getName()).toContain("3.3.5"); + }); + + it("loads profile 4.4.2", async () => { + process.env.TRINITYCORE_VERSION = "4.4.2"; + const ProfileLoader = (await import("../../../src/models/profiles/ProfileLoader")).default; + const profile = ProfileLoader.loadProfile(); + expect(profile.getName()).toContain("4.4.2"); + }); + + it("loads profile 11.1.0", async () => { + process.env.TRINITYCORE_VERSION = "11.1.0"; + const ProfileLoader = (await import("../../../src/models/profiles/ProfileLoader")).default; + const profile = ProfileLoader.loadProfile(); + expect(profile.getName()).toContain("11.1.0"); + }); + + it("throws for unsupported versions", async () => { + process.env.TRINITYCORE_VERSION = "9.9.9"; + const ProfileLoader = (await import("../../../src/models/profiles/ProfileLoader")).default; + expect(() => ProfileLoader.loadProfile()).toThrow("Unsupported version"); + }); + + it("throws when no version is provided", async () => { + delete process.env.TRINITYCORE_VERSION; + const ProfileLoader = (await import("../../../src/models/profiles/ProfileLoader")).default; + expect(() => ProfileLoader.loadProfile()).toThrow("Unsupported version"); + }); +}); diff --git a/tests/models/profiles/Profiles.test.ts b/tests/models/profiles/Profiles.test.ts new file mode 100644 index 0000000..d5a904b --- /dev/null +++ b/tests/models/profiles/Profiles.test.ts @@ -0,0 +1,52 @@ +import { describe, it, expect } from "vitest"; +import Profile1110 from "../../../src/models/profiles/Profile1110"; +import Profile335 from "../../../src/models/profiles/Profile335"; +import Profile442 from "../../../src/models/profiles/Profile442"; +import InitializerRegular from "../../../src/models/initializer/InitializerRegular"; + +describe("Profiles", () => { + it("returns expected values for 3.3.5 profile", () => { + const profile = new Profile335(); + expect(profile.getName()).toContain("3.3.5"); + expect(profile.getAuthServerBinary()).toBe("/app/server/bin/authserver"); + expect(profile.getWorldServerBinary()).toBe("/app/server/bin/worldserver"); + expect(profile.getAuthServerConfigurationPath()).toBe("/app/server/etc/authserver.conf"); + expect(profile.getWorldServerConfigurationPath()).toBe("/app/server/etc/worldserver.conf"); + expect(profile.getInitializeDatabasePath()).toBe("create-mysql-335.sql"); + expect(profile.getInitialDatabaseNamePattern()).toBe("TDB_full_world_335"); + expect(profile.getExtractScriptPath()).toBe("extract-335.sh"); + expect(profile.getSourceAuthConfigurationPath()).toBe("authserver.335.conf.dist"); + expect(profile.getSourceWorldConfigurationPath()).toBe("worldserver.335.conf.dist"); + expect(profile.getInitializer()).toBeInstanceOf(InitializerRegular); + }); + + it("returns expected values for 4.4.2 profile", () => { + const profile = new Profile442(); + expect(profile.getName()).toContain("4.4.2"); + expect(profile.getAuthServerBinary()).toBe("/app/server/bin/bnetserver"); + expect(profile.getWorldServerBinary()).toBe("/app/server/bin/worldserver"); + expect(profile.getAuthServerConfigurationPath()).toBe("/app/server/etc/bnetserver.conf"); + expect(profile.getWorldServerConfigurationPath()).toBe("/app/server/etc/worldserver.conf"); + expect(profile.getInitializeDatabasePath()).toBe("create-mysql-442.sql"); + expect(profile.getInitialDatabaseNamePattern()).toBe("TDB_full_4"); + expect(profile.getExtractScriptPath()).toBe("extract-442.sh"); + expect(profile.getSourceAuthConfigurationPath()).toBe("bnetserver.442.conf.dist"); + expect(profile.getSourceWorldConfigurationPath()).toBe("worldserver.442.conf.dist"); + expect(profile.getInitializer()).toBeInstanceOf(InitializerRegular); + }); + + it("returns expected values for 11.1.0 profile", () => { + const profile = new Profile1110(); + expect(profile.getName()).toContain("11.1.0"); + expect(profile.getAuthServerBinary()).toBe("/app/server/bin/bnetserver"); + expect(profile.getWorldServerBinary()).toBe("/app/server/bin/worldserver"); + expect(profile.getAuthServerConfigurationPath()).toBe("/app/server/etc/bnetserver.conf"); + expect(profile.getWorldServerConfigurationPath()).toBe("/app/server/etc/worldserver.conf"); + expect(profile.getInitializeDatabasePath()).toBe("create-mysql-1110.sql"); + expect(profile.getInitialDatabaseNamePattern()).toBe("TDB_full_1"); + expect(profile.getExtractScriptPath()).toBe("extract-1110.sh"); + expect(profile.getSourceAuthConfigurationPath()).toBe("bnetserver.1110.conf.dist"); + expect(profile.getSourceWorldConfigurationPath()).toBe("worldserver.1110.conf.dist"); + expect(profile.getInitializer()).toBeInstanceOf(InitializerRegular); + }); +}); diff --git a/tests/models/tools/CommandExecuter.test.ts b/tests/models/tools/CommandExecuter.test.ts new file mode 100644 index 0000000..3696124 --- /dev/null +++ b/tests/models/tools/CommandExecuter.test.ts @@ -0,0 +1,87 @@ +import { describe, it, expect, vi, beforeEach } from "vitest"; +import { EventEmitter } from "events"; +import CommandExecuter from "../../../src/models/tools/CommandExecuter"; + +const spawnMock = vi.fn(); + +vi.mock("child_process", () => ({ + spawn: (...args: unknown[]) => spawnMock(...args), +})); + +const createProcess = () => { + const stdout = new EventEmitter(); + const stderr = new EventEmitter(); + const handlers: Record void> = {}; + const proc = { + stdin: { write: vi.fn() }, + stdout, + stderr, + on: vi.fn((event: string, handler: (arg?: any) => void) => { + handlers[event] = handler; + }), + handlers, + }; + return proc; +}; + +describe("CommandExecuter", () => { + beforeEach(() => { + spawnMock.mockReset(); + }); + + it("executes a command and handles output", async () => { + const proc = createProcess(); + spawnMock.mockReturnValue(proc); + const onStdout = vi.fn(); + const onStderr = vi.fn(); + const onClose = vi.fn(); + + const executer = new CommandExecuter(true); + const promise = executer.execute("cmd", ["arg"], "/tmp", onStdout, onStderr, onClose); + + proc.stdout.emit("data", Buffer.from("out")); + proc.stderr.emit("data", Buffer.from("err")); + proc.handlers.close(0); + + const result = await promise; + expect(result).toBe(true); + expect(onStdout).toHaveBeenCalled(); + expect(onStderr).toHaveBeenCalled(); + expect(onClose).toHaveBeenCalledWith(0); + }); + + it("returns false when command fails", async () => { + const proc = createProcess(); + spawnMock.mockReturnValue(proc); + const executer = new CommandExecuter(); + + const promise = executer.execute("cmd", [], "/tmp"); + proc.handlers.close(1); + + await expect(promise).rejects.toThrow("Process exited with code 1"); + }); + + it("handles execution errors", async () => { + const proc = createProcess(); + spawnMock.mockReturnValue(proc); + const executer = new CommandExecuter(); + + const promise = executer.execute("cmd", [], "/tmp"); + proc.handlers.error(new Error("boom")); + + await expect(promise).rejects.toThrow("Erreur lors de l"); + }); + + it("sends data to stdin when running", async () => { + const proc = createProcess(); + spawnMock.mockReturnValue(proc); + const executer = new CommandExecuter(); + + const promise = executer.execute("cmd", [], "/tmp"); + executer.send("status"); + proc.handlers.close(0); + await promise; + + expect(proc.stdin.write).toHaveBeenCalledWith("status\n"); + }); +}); diff --git a/tests/models/tools/CommandRunner.test.ts b/tests/models/tools/CommandRunner.test.ts new file mode 100644 index 0000000..55ff6c9 --- /dev/null +++ b/tests/models/tools/CommandRunner.test.ts @@ -0,0 +1,78 @@ +import { describe, it, expect, vi, beforeEach } from "vitest"; +import CommandRunner from "../../../src/models/tools/CommandRunner"; + +const executeMock = vi.fn(); +const sendMock = vi.fn(); + +vi.mock("../../../src/models/tools/CommandExecuter", () => ({ + default: class { + execute = executeMock; + send = sendMock; + }, +})); + +describe("CommandRunner", () => { + beforeEach(() => { + executeMock.mockReset(); + sendMock.mockReset(); + }); + + it("captures output and updates state", () => { + let onStdout: ((data: Buffer) => void) | null = null; + let onStderr: ((data: Buffer) => void) | null = null; + let onClose: ((code: number | null) => void) | null = null; + executeMock.mockImplementation((_cmd, _args, _cwd, stdoutCb, stderrCb, closeCb) => { + onStdout = stdoutCb; + onStderr = stderrCb; + onClose = closeCb; + return Promise.resolve(true); + }); + + const runner = new CommandRunner("/bin/app", [], "/tmp"); + const onUpdate = vi.fn(); + runner.start(onUpdate); + + onStdout?.(Buffer.from("line1\n")); + onStderr?.(Buffer.from("line2\n")); + onClose?.(null); + + expect(runner.isRunning()).toBe(false); + expect(runner.getCode()).toBe(0); + expect(runner.getOutput()).toContain("line1"); + expect(runner.getOutput()).toContain("line2"); + expect(onUpdate).toHaveBeenCalled(); + }); + + it("truncates large output buffers", () => { + let onStdout: ((data: Buffer) => void) | null = null; + executeMock.mockImplementation((_cmd, _args, _cwd, stdoutCb) => { + onStdout = stdoutCb; + return Promise.resolve(true); + }); + + const runner = new CommandRunner("/bin/app", [], "/tmp"); + runner.start(); + + const bigOutput = Array.from({ length: 1001 }, (_, i) => `line${i}`).join("\n"); + onStdout?.(Buffer.from(bigOutput)); + + const lines = runner.getOutput().split("\n"); + expect(lines.length).toBeLessThanOrEqual(1000); + }); + + it("sends input to the running process", () => { + executeMock.mockImplementation(() => Promise.resolve(true)); + const runner = new CommandRunner("/bin/app", [], "/tmp"); + runner.start(); + runner.send("status"); + + expect(sendMock).toHaveBeenCalledWith("status"); + }); + + it("does not send input when not running", () => { + const runner = new CommandRunner("/bin/app", [], "/tmp"); + runner.send("status"); + + expect(sendMock).not.toHaveBeenCalled(); + }); +}); diff --git a/tests/models/tools/ConsoleHelper.test.ts b/tests/models/tools/ConsoleHelper.test.ts new file mode 100644 index 0000000..7cc2b16 --- /dev/null +++ b/tests/models/tools/ConsoleHelper.test.ts @@ -0,0 +1,32 @@ +import { describe, it, expect, vi } from "vitest"; +import consoleHelper from "../../../src/models/tools/ConsoleHelper"; + +describe("ConsoleHelper", () => { + it("generates centered content and lines", () => { + const line = consoleHelper.generateLine("+", "-", "+"); + expect(line.length).toBe(80); + const centered = consoleHelper.centerContent("test", 10); + expect(centered.trim()).toBe("test"); + }); + + it("truncates long content", () => { + const centered = consoleHelper.centerContent("very-long-content", 4); + expect(centered.length).toBe(4); + + const infoSpy = vi.spyOn(console, "info").mockImplementation(() => undefined); + consoleHelper.writeBoxLine("x".repeat(200)); + expect(infoSpy).toHaveBeenCalled(); + }); + + it("writes boxes to console", () => { + const infoSpy = vi.spyOn(console, "info").mockImplementation(() => undefined); + + consoleHelper.beginBox("Title"); + consoleHelper.writeBoxLine("Line"); + consoleHelper.endBox("Done"); + consoleHelper.writeBox("Full"); + consoleHelper.newLine(); + + expect(infoSpy).toHaveBeenCalled(); + }); +}); diff --git a/tests/models/tools/FileDownloader.test.ts b/tests/models/tools/FileDownloader.test.ts new file mode 100644 index 0000000..52c7885 --- /dev/null +++ b/tests/models/tools/FileDownloader.test.ts @@ -0,0 +1,83 @@ +import { describe, it, expect, vi } from "vitest"; +type StreamHandlers = { + end?: () => void; + error?: () => void; +}; + +const createStream = () => { + const handlers: StreamHandlers = {}; + const stream = { + on: (event: keyof StreamHandlers, handler: () => void) => { + handlers[event] = handler; + return stream; + }, + pipe: (dest: unknown) => dest, + }; + return { + stream, + emitEnd: () => handlers.end?.(), + emitError: () => handlers.error?.(), + }; +}; + +const setup = async (exists: boolean, stream: ReturnType["stream"]) => { + vi.resetModules(); + + const existsMock = vi.fn(() => exists); + const mkdirMock = vi.fn().mockResolvedValue(undefined); + const createWriteStreamMock = vi.fn(() => ({})); + + vi.doMock("fs", () => ({ + default: { + existsSync: existsMock, + promises: { + mkdir: mkdirMock, + }, + createWriteStream: createWriteStreamMock, + }, + existsSync: existsMock, + promises: { + mkdir: mkdirMock, + }, + createWriteStream: createWriteStreamMock, + })); + + const getMock = vi.fn().mockResolvedValue({ data: stream }); + vi.doMock("axios", () => ({ + default: { get: getMock }, + })); + + const FileDownloader = (await import("../../../src/models/tools/FileDownloader")).default; + return { FileDownloader, existsMock, mkdirMock, createWriteStreamMock, getMock }; +}; + +describe("FileDownloader", () => { + it("downloads a file and creates the directory when missing", async () => { + const { stream, emitEnd } = createStream(); + const { FileDownloader, mkdirMock, createWriteStreamMock, getMock } = await setup(false, stream); + + const downloader = new FileDownloader(); + const promise = downloader.downloadFile("http://file", "/tmp", "file.zip"); + await new Promise((resolve) => setImmediate(resolve)); + emitEnd(); + await promise; + + expect(getMock).toHaveBeenCalled(); + expect(mkdirMock).toHaveBeenCalled(); + expect(createWriteStreamMock).toHaveBeenCalledWith("/tmp/file.zip"); + }); + + it("rejects when the download stream fails", async () => { + const { stream, emitError } = createStream(); + const { FileDownloader } = await setup(true, stream); + + const downloader = new FileDownloader(); + const promise = downloader.downloadFile("http://file", "/tmp", "file.zip"); + const rejection = promise.catch((err) => err); + await new Promise((resolve) => setImmediate(resolve)); + emitError(); + + const error = await rejection; + expect(error).toBeUndefined(); + }); +}); diff --git a/tests/routes/Authentication.test.ts b/tests/routes/Authentication.test.ts new file mode 100644 index 0000000..bc06ddd --- /dev/null +++ b/tests/routes/Authentication.test.ts @@ -0,0 +1,67 @@ +import { describe, it, expect, vi, afterEach } from "vitest"; + +const ORIGINAL_ENV = { ...process.env }; + +const loadRouter = async (env: Record) => { + process.env = { ...ORIGINAL_ENV, ...env }; + vi.resetModules(); + return (await import("../../src/routes/Authentication")).default; +}; + +describe("Authentication routes", () => { + afterEach(() => { + process.env = { ...ORIGINAL_ENV }; + vi.resetModules(); + }); + + it("authenticates valid credentials", async () => { + const router = await loadRouter({ ACCESS_USERNAME: "user", ACCESS_PASSWORD: "pass" }); + const postLayer = router.stack.find((layer: any) => layer.route?.path === "/authenticate"); + const handler = postLayer.route.stack[0].handle; + + const req = { body: { username: "user", password: "pass" } }; + const res = { + status: vi.fn().mockReturnThis(), + json: vi.fn(), + }; + + handler(req, res); + + expect(res.status).toHaveBeenCalledWith(200); + expect(res.json).toHaveBeenCalledWith({ success: true }); + }); + + it("rejects invalid credentials", async () => { + const router = await loadRouter({ ACCESS_USERNAME: "user", ACCESS_PASSWORD: "pass" }); + const postLayer = router.stack.find((layer: any) => layer.route?.path === "/authenticate"); + const handler = postLayer.route.stack[0].handle; + + const req = { body: { username: "user", password: "wrong" } }; + const res = { + status: vi.fn().mockReturnThis(), + json: vi.fn(), + }; + + handler(req, res); + + expect(res.status).toHaveBeenCalledWith(401); + expect(res.json).toHaveBeenCalledWith({ success: false, message: "Invalid credentials" }); + }); + + it("returns authentication requirement state", async () => { + const router = await loadRouter({ ACCESS_USERNAME: "user", ACCESS_PASSWORD: "pass" }); + const getLayer = router.stack.find((layer: any) => layer.route?.path === "/needAuthentication"); + const handler = getLayer.route.stack[0].handle; + + const req = {}; + const res = { + status: vi.fn().mockReturnThis(), + json: vi.fn(), + }; + + handler(req, res); + + expect(res.status).toHaveBeenCalledWith(200); + expect(res.json).toHaveBeenCalledWith({ needAuthentication: true }); + }); +}); diff --git a/tests/routes/Index.test.ts b/tests/routes/Index.test.ts new file mode 100644 index 0000000..c11a758 --- /dev/null +++ b/tests/routes/Index.test.ts @@ -0,0 +1,16 @@ +import { describe, it, expect, vi } from "vitest"; +import router from "../../src/routes/Index"; + +describe("Index route", () => { + it("serves the index file", () => { + const layer = router.stack.find((item: any) => item.route?.path === "/"); + const handler = layer.route.stack[0].handle; + + const req = {}; + const res = { sendFile: vi.fn() }; + + handler(req, res); + + expect(res.sendFile).toHaveBeenCalledWith(expect.stringContaining("public/index.html")); + }); +}); diff --git a/tests/server.test.ts b/tests/server.test.ts new file mode 100644 index 0000000..9a1f8c7 --- /dev/null +++ b/tests/server.test.ts @@ -0,0 +1,148 @@ +import { describe, it, expect, vi, beforeEach } from "vitest"; + +let initializeResult = true; +let connectionHandler: ((socket: any) => void) | null = null; +const ioEmit = vi.fn(); +const runners: Array<{ send: ReturnType }> = []; + +vi.mock("express", () => { + const app = { use: vi.fn() }; + const express = vi.fn(() => app); + express.static = vi.fn(() => "static-mw"); + express.json = vi.fn(() => "json-mw"); + return { default: express }; +}); + +vi.mock("http", () => ({ + default: { + createServer: vi.fn(() => ({ + listen: vi.fn((port: number, cb: () => void) => { + if (cb) { + cb(); + } + }), + })), + }, +})); + +vi.mock("socket.io", () => { + class Server { + constructor() { + return { + on: vi.fn((event: string, handler: (socket: any) => void) => { + if (event === "connection") { + connectionHandler = handler; + } + }), + emit: ioEmit, + }; + } + } + return { Server }; +}); + +vi.mock("../src/routes/Authentication", () => ({ default: {} })); +vi.mock("../src/routes/Index", () => ({ default: {} })); + +vi.mock("../src/models/tools/ConsoleHelper", () => ({ + default: { + writeBox: vi.fn(), + writeBoxLine: vi.fn(), + }, +})); + +vi.mock("../src/models/profiles/ProfileLoader", () => ({ + default: { + loadProfile: () => ({ + getName: () => "TestProfile", + getAuthServerBinary: () => "/bin/auth", + getWorldServerBinary: () => "/bin/world", + getAuthServerConfigurationPath: () => "/etc/auth.conf", + getWorldServerConfigurationPath: () => "/etc/world.conf", + getInitializer: () => ({}), + }), + }, +})); + +vi.mock("../src/models/tools/CommandRunner", () => ({ + default: class { + constructor() { + const send = vi.fn(); + runners.push({ send }); + this.send = send; + } + + send: (input: string) => void; + + start(callback?: () => void) { + if (callback) { + callback(); + } + } + + getOutput() { + return "output"; + } + + isRunning() { + return true; + } + + getCode() { + return 0; + } + }, +})); + +vi.mock("../src/models/initializer/AppInitializer", () => ({ + default: class { + initialize() { + return Promise.resolve(initializeResult); + } + }, +})); + +describe("server", () => { + beforeEach(() => { + initializeResult = true; + connectionHandler = null; + ioEmit.mockReset(); + runners.length = 0; + vi.resetModules(); + }); + + it("stops when initialization fails", async () => { + initializeResult = false; + await import("../src/server"); + + expect(connectionHandler).toBeNull(); + }); + + it("wires socket handlers and emits state", async () => { + initializeResult = true; + await import("../src/server"); + + expect(connectionHandler).not.toBeNull(); + + const socketHandlers: Record void> = {}; + const socket = { + on: (event: string, handler: (input: string) => void) => { + socketHandlers[event] = handler; + }, + emit: vi.fn(), + }; + + connectionHandler?.(socket); + + expect(socket.emit).toHaveBeenCalledWith("authserver_state", expect.any(Object)); + expect(socket.emit).toHaveBeenCalledWith("worldserver_state", expect.any(Object)); + + socketHandlers.authserver_input("auth"); + socketHandlers.worldserver_input("world"); + + expect(runners[0].send).toHaveBeenCalledWith("auth"); + expect(runners[1].send).toHaveBeenCalledWith("world"); + expect(ioEmit).toHaveBeenCalledWith("authserver_state", expect.any(Object)); + expect(ioEmit).toHaveBeenCalledWith("worldserver_state", expect.any(Object)); + }); +}); diff --git a/vitest.config.ts b/vitest.config.ts new file mode 100644 index 0000000..d59ef95 --- /dev/null +++ b/vitest.config.ts @@ -0,0 +1,21 @@ +import { defineConfig } from "vitest/config"; + +export default defineConfig({ + test: { + environment: "node", + include: ["tests/**/*.test.ts"], + coverage: { + provider: "v8", + all: true, + include: ["src/**/*.ts"], + exclude: ["src/models/types/**", "src/models/**/I*.ts"], + reporter: ["text"], + thresholds: { + lines: 100, + functions: 100, + statements: 100, + branches: 100, + }, + }, + }, +});